From 7146b66831aaff8f0fd6ac3a4713ade1aa5252ca Mon Sep 17 00:00:00 2001 From: Andrew Haines Date: Fri, 22 Mar 2024 07:13:13 +0000 Subject: [PATCH 1/2] Optimise deep equality check and refactor `useCerbosRequest` Signed-off-by: Andrew Haines --- docs/react.asyncresult.data.md | 11 -- docs/react.asyncresult.error.md | 11 -- docs/react.asyncresult.isloading.md | 11 -- docs/react.asyncresult.md | 92 +++-------------- docs/react.md | 21 +++- packages/react/.eslintrc.yaml | 3 - packages/react/package.json | 3 +- packages/react/src/cerbos-provider.tsx | 9 +- packages/react/src/deep-equal.ts | 55 ++++++++++ packages/react/src/index.ts | 5 +- packages/react/src/is-equal.ts | 40 -------- packages/react/src/use-cerbos-request.ts | 120 ++++++++++------------ packages/react/src/use-deep-equal-memo.ts | 40 ++++++++ packages/react/tsconfig.json | 4 +- pnpm-lock.yaml | 11 +- 15 files changed, 198 insertions(+), 238 deletions(-) delete mode 100644 docs/react.asyncresult.data.md delete mode 100644 docs/react.asyncresult.error.md delete mode 100644 docs/react.asyncresult.isloading.md create mode 100644 packages/react/src/deep-equal.ts delete mode 100644 packages/react/src/is-equal.ts create mode 100644 packages/react/src/use-deep-equal-memo.ts diff --git a/docs/react.asyncresult.data.md b/docs/react.asyncresult.data.md deleted file mode 100644 index 6c11d252..00000000 --- a/docs/react.asyncresult.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@cerbos/react](./react.md) > [AsyncResult](./react.asyncresult.md) > [data](./react.asyncresult.data.md) - -## AsyncResult.data property - -**Signature:** - -```typescript -data: T | undefined; -``` diff --git a/docs/react.asyncresult.error.md b/docs/react.asyncresult.error.md deleted file mode 100644 index 8aeaf297..00000000 --- a/docs/react.asyncresult.error.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@cerbos/react](./react.md) > [AsyncResult](./react.asyncresult.md) > [error](./react.asyncresult.error.md) - -## AsyncResult.error property - -**Signature:** - -```typescript -error: Error | undefined; -``` diff --git a/docs/react.asyncresult.isloading.md b/docs/react.asyncresult.isloading.md deleted file mode 100644 index 7af54a41..00000000 --- a/docs/react.asyncresult.isloading.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@cerbos/react](./react.md) > [AsyncResult](./react.asyncresult.md) > [isLoading](./react.asyncresult.isloading.md) - -## AsyncResult.isLoading property - -**Signature:** - -```typescript -isLoading: boolean; -``` diff --git a/docs/react.asyncresult.md b/docs/react.asyncresult.md index 61c9b89b..caf3112b 100644 --- a/docs/react.asyncresult.md +++ b/docs/react.asyncresult.md @@ -2,87 +2,23 @@ [Home](./index.md) > [@cerbos/react](./react.md) > [AsyncResult](./react.asyncresult.md) -## AsyncResult interface +## AsyncResult type **Signature:** ```typescript -export interface AsyncResult +export type AsyncResult = { + isLoading: true; + data: undefined; + error: undefined; +} | { + isLoading: false; + data: T; + error: undefined; +} | { + isLoading: false; + data: undefined; + error: Error; +}; ``` - -## Properties - - - - - -
- -Property - - - - -Modifiers - - - - -Type - - - - -Description - - -
- -[data](./react.asyncresult.data.md) - - - - - - - -T \| undefined - - - - - -
- -[error](./react.asyncresult.error.md) - - - - - - - -Error \| undefined - - - - - -
- -[isLoading](./react.asyncresult.isloading.md) - - - - - - - -boolean - - - - - -
diff --git a/docs/react.md b/docs/react.md index 1906cda1..499adf8c 100644 --- a/docs/react.md +++ b/docs/react.md @@ -89,7 +89,7 @@ Description -[AsyncResult](./react.asyncresult.md) +[CerbosProviderProps](./react.cerbosproviderprops.md) @@ -97,9 +97,24 @@ Description - + -[CerbosProviderProps](./react.cerbosproviderprops.md) +## Type Aliases + + +
+ +Type Alias + + + + +Description + + +
+ +[AsyncResult](./react.asyncresult.md) diff --git a/packages/react/.eslintrc.yaml b/packages/react/.eslintrc.yaml index e4179aa6..df818c41 100644 --- a/packages/react/.eslintrc.yaml +++ b/packages/react/.eslintrc.yaml @@ -1,5 +1,2 @@ -plugins: - - react-hooks - extends: - plugin:react-hooks/recommended diff --git a/packages/react/package.json b/packages/react/package.json index 7ee716ea..11413871 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -53,8 +53,7 @@ }, "devDependencies": { "@types/react": "18.2.67", - "eslint-plugin-react-hooks": "4.6.0", - "react": "18.2.0" + "eslint-plugin-react-hooks": "4.6.0" }, "publishConfig": { "access": "public", diff --git a/packages/react/src/cerbos-provider.tsx b/packages/react/src/cerbos-provider.tsx index 56badf4f..6deaef20 100644 --- a/packages/react/src/cerbos-provider.tsx +++ b/packages/react/src/cerbos-provider.tsx @@ -7,6 +7,8 @@ import type { import type { ReactElement, ReactNode } from "react"; import { createContext, useMemo } from "react"; +import { useDeepEqualMemo } from "./use-deep-equal-memo"; + export const CerbosContext = createContext( undefined, ); @@ -63,9 +65,12 @@ export function CerbosProvider({ principal, auxData, }: CerbosProviderProps): ReactElement { + const principalMemo = useDeepEqualMemo(principal); + const auxDataMemo = useDeepEqualMemo(auxData); + const value = useMemo( - () => client.withPrincipal(principal, auxData), - [auxData, client, principal], + () => client.withPrincipal(principalMemo, auxDataMemo), + [client, principalMemo, auxDataMemo], ); return ( diff --git a/packages/react/src/deep-equal.ts b/packages/react/src/deep-equal.ts new file mode 100644 index 00000000..4fd426e5 --- /dev/null +++ b/packages/react/src/deep-equal.ts @@ -0,0 +1,55 @@ +export function deepEqual(a: unknown, b: unknown): boolean { + if (a === b) { + return true; + } + + if ( + typeof a !== "object" || + a === null || + typeof b !== "object" || + b === null + ) { + // one of them is a primitive or null, so if they weren't strictly equal above, then they aren't equal + return false; + } + + // they are both non-null objects (arrays are objects too) + + if (Array.isArray(a)) { + if (!Array.isArray(b)) { + return false; + } + + // they are both arrays + + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + + return true; + } + + // they are both objects - ignore more complex types like Map, Symbol, Date etc... + + const keys = Object.keys(a); + if (keys.length !== Object.keys(b).length) { + return false; + } + + for (const key of keys) { + const valueA = (a as Record)[key]; + const valueB = (b as Record)[key]; + + if (!deepEqual(valueA, valueB)) { + return false; + } + } + + return true; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index cf9f8224..34b65551 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,9 +1,8 @@ -export { CerbosProvider } from "./cerbos-provider"; -export type { CerbosProviderProps } from "./cerbos-provider"; +export { type CerbosProviderProps, CerbosProvider } from "./cerbos-provider"; export { useCerbos } from "./use-cerbos"; export { + type AsyncResult, useCheckResource, useCheckResources, useIsAllowed, } from "./use-cerbos-request"; -export type { AsyncResult } from "./use-cerbos-request"; diff --git a/packages/react/src/is-equal.ts b/packages/react/src/is-equal.ts deleted file mode 100644 index ab484206..00000000 --- a/packages/react/src/is-equal.ts +++ /dev/null @@ -1,40 +0,0 @@ -export function isEqual( - a: T1 | undefined | null, - b: T2 | undefined | null, -): boolean { - if (typeof a !== typeof b) { - return false; - } - - // they are of the same type - if (typeof a !== "object" || typeof b !== "object") { - // they are both primitives - return a === b; - } - - // ignore more complex types like Map, Symbol, Date etc... - - // they are both objects - if (a === null || b === null) { - // one of them is null - return a === b; - } - - // they are both non null objects (arrays are objects too) - const entriesA = Object.entries(a); - const entriesB = Object.entries(b); - - if (entriesA.length !== entriesB.length) { - return false; - } - - for (const [key, valueA] of entriesA) { - // @ts-expect-error -- typescript complains that b can be an array and string cannot be used to index it but that's fine because we are checking for equality - const valueB = b[key] as unknown; - if (!isEqual(valueA, valueB)) { - return false; - } - } - - return true; -} diff --git a/packages/react/src/use-cerbos-request.ts b/packages/react/src/use-cerbos-request.ts index 4c9a08fe..74728ee5 100644 --- a/packages/react/src/use-cerbos-request.ts +++ b/packages/react/src/use-cerbos-request.ts @@ -7,86 +7,74 @@ import type { IsAllowedRequest, RequestOptions, } from "@cerbos/core"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; -import { isEqual } from "./is-equal"; import { useCerbos } from "./use-cerbos"; +import { useDeepEqualMemo } from "./use-deep-equal-memo"; /** * @public */ -export interface AsyncResult { - isLoading: boolean; - data: T | undefined; - error: Error | undefined; -} +export type AsyncResult = + | { isLoading: true; data: undefined; error: undefined } + | { isLoading: false; data: T; error: undefined } + | { isLoading: false; data: undefined; error: Error }; -function useCerbosRequest< - TRequest extends keyof Pick< - ClientWithPrincipal, - "checkResource" | "checkResources" | "isAllowed" - >, - TRequestParams extends Parameters[0], ->( - request: TRequest, - requestParams: TRequestParams, - options?: RequestOptions, -): AsyncResult>> { - const client = useCerbos(); - const [data, setData] = - useState>>(); - const [error, setError] = useState(); - const [isLoading, setIsLoading] = useState(true); +type Methods = "checkResource" | "checkResources" | "isAllowed"; - const previousRequest = useRef(); - const previousOptions = useRef(); +type Result = Awaited< + ReturnType +>; - const checkResource = useCallback( - async ( - request: TRequest, - requestParams: TRequestParams, - options?: RequestOptions, - ) => { - setIsLoading(true); - setError(undefined); - setData(undefined); +function useCerbosRequest( + method: Method, + ...params: Parameters +): AsyncResult> { + const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState>(); + const [error, setError] = useState(); + + const client = useCerbos(); + const paramsMemo = useDeepEqualMemo(params); - try { - const result = await (client[request]( - //@ts-expect-error --- this is strange because the generic types narrow nicely when this hook is called but not here :( - requestParams, - options, - ) as ReturnType); - setData(result); - } catch (error) { - setError( - error instanceof Error - ? error - : new Error("An unexpected error occurred", { cause: error }), - ); - } finally { - setIsLoading(false); - } - }, - [client], + const load = useCallback<() => Promise>>( + // @ts-expect-error -- https://github.com/microsoft/TypeScript/issues/30581 + async () => await client[method](...paramsMemo), + [client, method, paramsMemo], ); useEffect(() => { - if ( - !isEqual(requestParams, previousRequest.current) || - !isEqual(options, previousOptions.current) - ) { - previousRequest.current = requestParams; - previousOptions.current = options; - void checkResource(request, requestParams, options); - } - }, [checkResource, options, request, requestParams]); + let aborted = false; + setIsLoading(true); + setData(undefined); + setError(undefined); + + load() + .then((data) => { + if (!aborted) { + setIsLoading(false); + setData(data); + setError(undefined); + } + }) + .catch((error: unknown) => { + if (!aborted) { + setIsLoading(false); + setData(undefined); + setError( + error instanceof Error + ? error + : new Error("An unexpected error occurred", { cause: error }), + ); + } + }); + + return () => { + aborted = true; + }; + }, [load]); - return { - data, - isLoading, - error, - }; + return { isLoading, data, error } as AsyncResult>; } /** diff --git a/packages/react/src/use-deep-equal-memo.ts b/packages/react/src/use-deep-equal-memo.ts new file mode 100644 index 00000000..e0cd4299 --- /dev/null +++ b/packages/react/src/use-deep-equal-memo.ts @@ -0,0 +1,40 @@ +// Based on https://github.com/kentcdodds/use-deep-compare-effect/blob/ec3c204ae3a29be7db8e30fc7ca29dd38d1c9441/src/index.ts#L28-L43 + +/* +The MIT License (MIT) +Copyright (c) 2020 Kent C. Dodds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import { useMemo, useRef } from "react"; + +import { deepEqual } from "./deep-equal"; + +export function useDeepEqualMemo(value: T): T { + const ref = useRef(value); + const signalRef = useRef(0); + + if (!deepEqual(value, ref.current)) { + ref.current = value; + signalRef.current++; + } + + return useMemo(() => ref.current, [signalRef.current]); // eslint-disable-line react-hooks/exhaustive-deps +} diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 705f7e28..dc4cdbda 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -3,9 +3,9 @@ "include": ["src"], "references": [], "compilerOptions": { + "jsx": "react-jsx", "outDir": "lib", "rootDir": "src", - "tsBuildInfoFile": "lib/react.tsbuildinfo", - "jsx": "react-jsx" + "tsBuildInfoFile": "lib/react.tsbuildinfo" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 562c8e69..0a98237a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,9 @@ importers: '@cerbos/core': specifier: ^0.16.0 version: link:../core + react: + specifier: '>=16.8.0' + version: 18.2.0 devDependencies: '@types/react': specifier: 18.2.67 @@ -140,9 +143,6 @@ importers: eslint-plugin-react-hooks: specifier: 4.6.0 version: 4.6.0(eslint@8.57.0) - react: - specifier: 18.2.0 - version: 18.2.0 packages/test: devDependencies: @@ -2669,7 +2669,6 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-tokens@8.0.3: resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} @@ -2803,7 +2802,7 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: true + dev: false /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -3173,7 +3172,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: true + dev: false /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} From ff0b9cc4c47f1a43c6d74394dffdc1c047759db0 Mon Sep 17 00:00:00 2001 From: Andrew Haines Date: Fri, 22 Mar 2024 07:12:12 +0000 Subject: [PATCH 2/2] Just add the dependency... Signed-off-by: Andrew Haines --- packages/react/package.json | 5 ++- packages/react/src/cerbos-provider.tsx | 7 ++- packages/react/src/deep-equal.ts | 55 ----------------------- packages/react/src/use-cerbos-request.ts | 4 +- packages/react/src/use-deep-equal-memo.ts | 40 ----------------- pnpm-lock.yaml | 21 ++++++++- 6 files changed, 27 insertions(+), 105 deletions(-) delete mode 100644 packages/react/src/deep-equal.ts delete mode 100644 packages/react/src/use-deep-equal-memo.ts diff --git a/packages/react/package.json b/packages/react/package.json index 11413871..29bd0af4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -46,10 +46,11 @@ "React" ], "peerDependencies": { - "react": ">=16.8.0" + "react": ">=16.13.0" }, "dependencies": { - "@cerbos/core": "^0.16.0" + "@cerbos/core": "^0.16.0", + "use-deep-compare-effect": "^1.8.1" }, "devDependencies": { "@types/react": "18.2.67", diff --git a/packages/react/src/cerbos-provider.tsx b/packages/react/src/cerbos-provider.tsx index 6deaef20..0e2cc4e6 100644 --- a/packages/react/src/cerbos-provider.tsx +++ b/packages/react/src/cerbos-provider.tsx @@ -6,8 +6,7 @@ import type { } from "@cerbos/core"; import type { ReactElement, ReactNode } from "react"; import { createContext, useMemo } from "react"; - -import { useDeepEqualMemo } from "./use-deep-equal-memo"; +import { useDeepCompareMemoize } from "use-deep-compare-effect"; export const CerbosContext = createContext( undefined, @@ -65,8 +64,8 @@ export function CerbosProvider({ principal, auxData, }: CerbosProviderProps): ReactElement { - const principalMemo = useDeepEqualMemo(principal); - const auxDataMemo = useDeepEqualMemo(auxData); + const principalMemo = useDeepCompareMemoize(principal); + const auxDataMemo = useDeepCompareMemoize(auxData); const value = useMemo( () => client.withPrincipal(principalMemo, auxDataMemo), diff --git a/packages/react/src/deep-equal.ts b/packages/react/src/deep-equal.ts deleted file mode 100644 index 4fd426e5..00000000 --- a/packages/react/src/deep-equal.ts +++ /dev/null @@ -1,55 +0,0 @@ -export function deepEqual(a: unknown, b: unknown): boolean { - if (a === b) { - return true; - } - - if ( - typeof a !== "object" || - a === null || - typeof b !== "object" || - b === null - ) { - // one of them is a primitive or null, so if they weren't strictly equal above, then they aren't equal - return false; - } - - // they are both non-null objects (arrays are objects too) - - if (Array.isArray(a)) { - if (!Array.isArray(b)) { - return false; - } - - // they are both arrays - - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (!deepEqual(a[i], b[i])) { - return false; - } - } - - return true; - } - - // they are both objects - ignore more complex types like Map, Symbol, Date etc... - - const keys = Object.keys(a); - if (keys.length !== Object.keys(b).length) { - return false; - } - - for (const key of keys) { - const valueA = (a as Record)[key]; - const valueB = (b as Record)[key]; - - if (!deepEqual(valueA, valueB)) { - return false; - } - } - - return true; -} diff --git a/packages/react/src/use-cerbos-request.ts b/packages/react/src/use-cerbos-request.ts index 74728ee5..0732a118 100644 --- a/packages/react/src/use-cerbos-request.ts +++ b/packages/react/src/use-cerbos-request.ts @@ -8,9 +8,9 @@ import type { RequestOptions, } from "@cerbos/core"; import { useCallback, useEffect, useState } from "react"; +import { useDeepCompareMemoize } from "use-deep-compare-effect"; import { useCerbos } from "./use-cerbos"; -import { useDeepEqualMemo } from "./use-deep-equal-memo"; /** * @public @@ -35,7 +35,7 @@ function useCerbosRequest( const [error, setError] = useState(); const client = useCerbos(); - const paramsMemo = useDeepEqualMemo(params); + const paramsMemo = useDeepCompareMemoize(params); const load = useCallback<() => Promise>>( // @ts-expect-error -- https://github.com/microsoft/TypeScript/issues/30581 diff --git a/packages/react/src/use-deep-equal-memo.ts b/packages/react/src/use-deep-equal-memo.ts deleted file mode 100644 index e0cd4299..00000000 --- a/packages/react/src/use-deep-equal-memo.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Based on https://github.com/kentcdodds/use-deep-compare-effect/blob/ec3c204ae3a29be7db8e30fc7ca29dd38d1c9441/src/index.ts#L28-L43 - -/* -The MIT License (MIT) -Copyright (c) 2020 Kent C. Dodds - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -import { useMemo, useRef } from "react"; - -import { deepEqual } from "./deep-equal"; - -export function useDeepEqualMemo(value: T): T { - const ref = useRef(value); - const signalRef = useRef(0); - - if (!deepEqual(value, ref.current)) { - ref.current = value; - signalRef.current++; - } - - return useMemo(() => ref.current, [signalRef.current]); // eslint-disable-line react-hooks/exhaustive-deps -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a98237a..d1b24a01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,6 +136,9 @@ importers: react: specifier: '>=16.8.0' version: 18.2.0 + use-deep-compare-effect: + specifier: ^1.8.1 + version: 1.8.1(react@18.2.0) devDependencies: '@types/react': specifier: 18.2.67 @@ -285,7 +288,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: true /@babel/template@7.23.9: resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} @@ -1750,6 +1752,11 @@ packages: resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + /detect-file@1.0.0: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} @@ -3183,7 +3190,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true /regexp.prototype.flags@1.5.1: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} @@ -3689,6 +3695,17 @@ packages: punycode: 2.3.1 dev: true + /use-deep-compare-effect@1.8.1(react@18.2.0): + resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13' + dependencies: + '@babel/runtime': 7.23.8 + dequal: 2.0.3 + react: 18.2.0 + dev: false + /uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true