From 93d0e763cca0facaaa20d7bde861c98c298f08ad Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 24 Oct 2024 22:23:49 +0100 Subject: [PATCH] feat(stash): add useStash and improve other helpers (#3320) --- .changeset/twenty-boats-burn.md | 32 ++ packages/common/tsconfig.base.json | 2 + packages/stash/.eslintrc.json | 7 + packages/stash/package.json | 18 +- packages/stash/src/actions/decodeKey.ts | 10 +- packages/stash/src/actions/deleteRecord.ts | 6 +- packages/stash/src/actions/getKeys.ts | 7 +- packages/stash/src/actions/getRecord.test.ts | 216 +++++++--- packages/stash/src/actions/getRecord.ts | 24 +- packages/stash/src/actions/getRecords.test.ts | 88 ++-- packages/stash/src/actions/getRecords.ts | 33 +- packages/stash/src/actions/getTable.ts | 10 +- packages/stash/src/actions/getTableConfig.ts | 4 +- packages/stash/src/actions/getTables.ts | 5 +- packages/stash/src/actions/index.ts | 2 +- packages/stash/src/actions/registerTable.ts | 9 +- packages/stash/src/actions/runQuery.ts | 3 +- packages/stash/src/actions/setRecords.ts | 6 +- packages/stash/src/actions/subscribeQuery.ts | 16 +- ...beStore.test.ts => subscribeStash.test.ts} | 6 +- .../{subscribeStore.ts => subscribeStash.ts} | 8 +- packages/stash/src/actions/subscribeTable.ts | 9 +- packages/stash/src/apiEquality.test.ts | 2 +- packages/stash/src/common.ts | 16 +- packages/stash/src/createStash.test.ts | 10 +- packages/stash/src/createStash.ts | 9 +- .../src/decorators/defaultActions.test.ts | 6 +- .../stash/src/decorators/defaultActions.ts | 16 +- packages/stash/src/react/isEqual.ts | 3 + packages/stash/src/react/memoize.ts | 12 + packages/stash/src/react/useStash.test.ts | 162 ++++++++ packages/stash/src/react/useStash.ts | 31 ++ packages/stash/tsconfig.json | 3 +- packages/stash/tsconfig.test.json | 8 + packages/stash/vitest.config.ts | 7 - packages/stash/vitestSetup.ts | 1 - pnpm-lock.yaml | 381 +++--------------- 37 files changed, 669 insertions(+), 519 deletions(-) create mode 100644 .changeset/twenty-boats-burn.md create mode 100644 packages/stash/.eslintrc.json rename packages/stash/src/actions/{subscribeStore.test.ts => subscribeStash.test.ts} (94%) rename packages/stash/src/actions/{subscribeStore.ts => subscribeStash.ts} (60%) create mode 100644 packages/stash/src/react/isEqual.ts create mode 100644 packages/stash/src/react/memoize.ts create mode 100644 packages/stash/src/react/useStash.test.ts create mode 100644 packages/stash/src/react/useStash.ts create mode 100644 packages/stash/tsconfig.test.json delete mode 100644 packages/stash/vitest.config.ts delete mode 100644 packages/stash/vitestSetup.ts diff --git a/.changeset/twenty-boats-burn.md b/.changeset/twenty-boats-burn.md new file mode 100644 index 0000000000..d485e124bd --- /dev/null +++ b/.changeset/twenty-boats-burn.md @@ -0,0 +1,32 @@ +--- +"@latticexyz/stash": patch +--- + +Added `useStash` React hook. It's heavily inspired by Zustand's `useStore` and accepts a stash, a state selector, an an optional equality function to avoid unnecessary re-render cycles when returning unstable values. + +Also updated `getRecord` and `getRecords` to each take either a `stash` or `state` object for more ergonomic use with `useStash`. + +```ts +import { useStash } from "@latticexyz/stash/react"; +import { getRecord } from "@latticexyz/stash"; +import config from "../mud.config"; + +const tables = config.namespaces.app.tables; + +export function PlayerName({ playerId }) { + const record = useStash(stash, (state) => getRecord({ state, table: tables.Player, key: { playerId } })); + ... +} +``` + +```ts +import isEqual from "fast-deep-equal"; +import { useStash } from "@latticexyz/stash/react"; +import { getRecords } from "@latticexyz/stash"; +import config from "../mud.config"; + +export function PlayerNames() { + const record = useStash(stash, (state) => getRecords({ state, table: tables.Player }), { isEqual }); + ... +} +``` diff --git a/packages/common/tsconfig.base.json b/packages/common/tsconfig.base.json index 1c1554782e..a07f03a706 100644 --- a/packages/common/tsconfig.base.json +++ b/packages/common/tsconfig.base.json @@ -18,6 +18,8 @@ "noErrorTruncation": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, + // TODO: enable this + // "noUncheckedIndexedAccess": true, "sourceMap": true } } diff --git a/packages/stash/.eslintrc.json b/packages/stash/.eslintrc.json new file mode 100644 index 0000000000..930af95967 --- /dev/null +++ b/packages/stash/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["../../.eslintrc", "plugin:react/recommended", "plugin:react-hooks/recommended"], + "plugins": ["react", "react-hooks"], + "rules": { + "react/react-in-jsx-scope": "off" + } +} diff --git a/packages/stash/package.json b/packages/stash/package.json index 28b85edbc8..c504d77116 100644 --- a/packages/stash/package.json +++ b/packages/stash/package.json @@ -31,26 +31,36 @@ "build": "tsup", "clean": "shx rm -rf dist", "dev": "tsup --watch", - "test": "vitest typecheck --run --passWithNoTests && vitest --run --passWithNoTests", + "test": "tsc -p tsconfig.test.json --noEmit && vitest --run", "test:ci": "pnpm run test" }, "dependencies": { + "@ark/util": "catalog:", + "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/protocol-parser": "workspace:*", "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", - "react": "^18.2.0", "viem": "catalog:" }, "devDependencies": { "@testing-library/react": "^16.0.0", "@testing-library/react-hooks": "^8.0.1", "@types/react": "18.2.22", + "eslint-plugin-react": "7.31.11", + "eslint-plugin-react-hooks": "4.6.0", + "fast-deep-equal": "^3.1.3", + "react": "^18.2.0", "react-dom": "^18.2.0", - "tsup": "^6.7.0", - "vitest": "0.34.6" + "tsup": "^6.7.0" + }, + "peerDependencies": { + "react": "18.x" }, "publishConfig": { "access": "public" + }, + "optionalPeerDependencies": { + "react": "18.x" } } diff --git a/packages/stash/src/actions/decodeKey.ts b/packages/stash/src/actions/decodeKey.ts index 6f6767eb77..e71b38cd4c 100644 --- a/packages/stash/src/actions/decodeKey.ts +++ b/packages/stash/src/actions/decodeKey.ts @@ -1,5 +1,6 @@ import { Table } from "@latticexyz/config"; import { Key, Stash } from "../common"; +import { getKey } from "@latticexyz/protocol-parser/internal"; export type DecodeKeyArgs = { stash: Stash; @@ -14,9 +15,8 @@ export function decodeKey
({ table, encodedKey, }: DecodeKeyArgs
): DecodeKeyResult
{ - const { namespaceLabel, label, key } = table; - const record = stash.get().records[namespaceLabel][label][encodedKey]; - - // Typecast needed because record values could be arrays, but we know they are not if they are key fields - return Object.fromEntries(Object.entries(record).filter(([field]) => key.includes(field))) as never; + const { namespaceLabel, label } = table; + const record = stash.get().records[namespaceLabel]?.[label]?.[encodedKey]; + if (!record) throw new Error(`No record found for key "${encodedKey}".`); + return getKey(table, record); } diff --git a/packages/stash/src/actions/deleteRecord.ts b/packages/stash/src/actions/deleteRecord.ts index ff8a04e475..df8125d213 100644 --- a/packages/stash/src/actions/deleteRecord.ts +++ b/packages/stash/src/actions/deleteRecord.ts @@ -19,17 +19,17 @@ export function deleteRecord
({ stash, table, key }: DeleteR } const encodedKey = encodeKey({ table, key }); - const prevRecord = stash.get().records[namespaceLabel][label][encodedKey]; + const prevRecord = stash.get().records[namespaceLabel]?.[label]?.[encodedKey]; // Early return if this record doesn't exist if (prevRecord == null) return; // Delete record - delete stash._.state.records[namespaceLabel][label][encodedKey]; + delete stash._.state.records[namespaceLabel]?.[label]?.[encodedKey]; // Notify table subscribers const updates = { [encodedKey]: { prev: prevRecord && { ...prevRecord }, current: undefined } }; - stash._.tableSubscribers[namespaceLabel][label].forEach((subscriber) => subscriber(updates)); + stash._.tableSubscribers[namespaceLabel]?.[label]?.forEach((subscriber) => subscriber(updates)); // Notify stash subscribers const storeUpdate = { config: {}, records: { [namespaceLabel]: { [label]: updates } } }; diff --git a/packages/stash/src/actions/getKeys.ts b/packages/stash/src/actions/getKeys.ts index eee1f31c5d..5d5946e120 100644 --- a/packages/stash/src/actions/getKeys.ts +++ b/packages/stash/src/actions/getKeys.ts @@ -1,6 +1,6 @@ import { Table } from "@latticexyz/config"; +import { getKey } from "@latticexyz/protocol-parser/internal"; import { Stash, Keys } from "../common"; -import { decodeKey } from "./decodeKey"; export type GetKeysArgs
= { stash: Stash; @@ -11,11 +11,10 @@ export type GetKeysResult
= Keys
; export function getKeys
({ stash, table }: GetKeysArgs
): GetKeysResult
{ const { namespaceLabel, label } = table; - return Object.fromEntries( - Object.keys(stash.get().records[namespaceLabel][label]).map((encodedKey) => [ + Object.entries(stash.get().records[namespaceLabel]?.[label] ?? {}).map(([encodedKey, record]) => [ encodedKey, - decodeKey({ stash, table, encodedKey }), + getKey(table, record), ]), ); } diff --git a/packages/stash/src/actions/getRecord.test.ts b/packages/stash/src/actions/getRecord.test.ts index 29a8a482b4..d9efc3d1c5 100644 --- a/packages/stash/src/actions/getRecord.test.ts +++ b/packages/stash/src/actions/getRecord.test.ts @@ -6,91 +6,183 @@ import { setRecord } from "./setRecord"; import { getRecord } from "./getRecord"; describe("getRecord", () => { - it("should get a record by key from the table", () => { - const config = defineStore({ - namespace: "namespace1", - tables: { - table1: { - schema: { - field1: "string", - field2: "uint32", - field3: "int32", + describe("with stash", () => { + it("should get a record by key from the table", () => { + const config = defineStore({ + namespace: "namespace1", + tables: { + table1: { + schema: { + field1: "string", + field2: "uint32", + field3: "int32", + }, + key: ["field2", "field3"], }, - key: ["field2", "field3"], }, - }, - }); - - const table = config.namespaces.namespace1.tables.table1; + }); - const stash = createStash(config); - - setRecord({ - stash, - table, - key: { field2: 1, field3: 2 }, - value: { field1: "hello" }, - }); + const table = config.namespaces.namespace1.tables.table1; - setRecord({ - stash, - table, - key: { field2: 2, field3: 1 }, - value: { field1: "world" }, - }); + const stash = createStash(config); - attest( - getRecord({ + setRecord({ stash, table, key: { field2: 1, field3: 2 }, - }), - ).snap({ field1: "hello", field2: 1, field3: 2 }); + value: { field1: "hello" }, + }); - attest<{ field1: string; field2: number; field3: number }>( - getRecord({ + setRecord({ stash, table, key: { field2: 2, field3: 1 }, - }), - ).snap({ field1: "world", field2: 2, field3: 1 }); - }); + value: { field1: "world" }, + }); - it("should throw a type error if the key type doesn't match", () => { - const config = defineStore({ - namespace: "namespace1", - tables: { - table1: { - schema: { - field1: "string", - field2: "uint32", - field3: "int32", + attest( + getRecord({ + stash, + table, + key: { field2: 1, field3: 2 }, + }), + ).snap({ field1: "hello", field2: 1, field3: 2 }); + + attest<{ field1: string; field2: number; field3: number } | undefined>( + getRecord({ + stash, + table, + key: { field2: 2, field3: 1 }, + }), + ).snap({ field1: "world", field2: 2, field3: 1 }); + }); + + it("should throw a type error if the key type doesn't match", () => { + const config = defineStore({ + namespace: "namespace1", + tables: { + table1: { + schema: { + field1: "string", + field2: "uint32", + field3: "int32", + }, + key: ["field2", "field3"], }, - key: ["field2", "field3"], }, - }, + }); + + const table = config.namespaces.namespace1.tables.table1; + + const stash = createStash(config); + + attest(() => + getRecord({ + stash, + table, + // @ts-expect-error Property 'field3' is missing in type '{ field2: number; }' + key: { field2: 1 }, + }), + ).type.errors(`Property 'field3' is missing in type '{ field2: number; }'`); + + attest(() => + getRecord({ + stash, + table, + // @ts-expect-error Type 'string' is not assignable to type 'number' + key: { field2: 1, field3: "invalid" }, + }), + ).type.errors(`Type 'string' is not assignable to type 'number'`); }); + }); + + describe("with state", () => { + it("should get a record by key from the table", () => { + const config = defineStore({ + namespace: "namespace1", + tables: { + table1: { + schema: { + field1: "string", + field2: "uint32", + field3: "int32", + }, + key: ["field2", "field3"], + }, + }, + }); - const table = config.namespaces.namespace1.tables.table1; + const table = config.namespaces.namespace1.tables.table1; - const stash = createStash(config); + const stash = createStash(config); - attest(() => - getRecord({ + setRecord({ stash, table, - // @ts-expect-error Property 'field3' is missing in type '{ field2: number; }' - key: { field2: 1 }, - }), - ).type.errors(`Property 'field3' is missing in type '{ field2: number; }'`); + key: { field2: 1, field3: 2 }, + value: { field1: "hello" }, + }); - attest(() => - getRecord({ + setRecord({ stash, table, - // @ts-expect-error Type 'string' is not assignable to type 'number' - key: { field2: 1, field3: "invalid" }, - }), - ).type.errors(`Type 'string' is not assignable to type 'number'`); + key: { field2: 2, field3: 1 }, + value: { field1: "world" }, + }); + + attest( + getRecord({ + state: stash.get(), + table, + key: { field2: 1, field3: 2 }, + }), + ).snap({ field1: "hello", field2: 1, field3: 2 }); + + attest<{ field1: string; field2: number; field3: number } | undefined>( + getRecord({ + state: stash.get(), + table, + key: { field2: 2, field3: 1 }, + }), + ).snap({ field1: "world", field2: 2, field3: 1 }); + }); + + it("should throw a type error if the key type doesn't match", () => { + const config = defineStore({ + namespace: "namespace1", + tables: { + table1: { + schema: { + field1: "string", + field2: "uint32", + field3: "int32", + }, + key: ["field2", "field3"], + }, + }, + }); + + const table = config.namespaces.namespace1.tables.table1; + + const stash = createStash(config); + + attest(() => + getRecord({ + state: stash.get(), + table, + // @ts-expect-error Property 'field3' is missing in type '{ field2: number; }' + key: { field2: 1 }, + }), + ).type.errors(`Property 'field3' is missing in type '{ field2: number; }'`); + + attest(() => + getRecord({ + state: stash.get(), + table, + // @ts-expect-error Type 'string' is not assignable to type 'number' + key: { field2: 1, field3: "invalid" }, + }), + ).type.errors(`Type 'string' is not assignable to type 'number'`); + }); }); }); diff --git a/packages/stash/src/actions/getRecord.ts b/packages/stash/src/actions/getRecord.ts index ebdb141a2c..1183d2652d 100644 --- a/packages/stash/src/actions/getRecord.ts +++ b/packages/stash/src/actions/getRecord.ts @@ -1,16 +1,28 @@ +import { propwiseXor } from "@ark/util"; import { Table } from "@latticexyz/config"; -import { Key, Stash, TableRecord } from "../common"; +import { Key, Stash, State, TableRecord } from "../common"; import { encodeKey } from "./encodeKey"; -export type GetRecordArgs
= { - stash: Stash; +export type GetRecordArgs< + table extends Table = Table, + defaultValue extends Omit, keyof Key
> | undefined = undefined, +> = propwiseXor<{ stash: Stash }, { state: State }> & { table: table; key: Key
; + defaultValue?: defaultValue; }; -export type GetRecordResult
= TableRecord
; +export type GetRecordResult< + table extends Table = Table, + defaultValue extends Omit, keyof Key
> | undefined = undefined, +> = defaultValue extends undefined ? TableRecord
| undefined : TableRecord
; -export function getRecord
({ stash, table, key }: GetRecordArgs
): GetRecordResult
{ +export function getRecord< + const table extends Table, + const defaultValue extends Omit, keyof Key
> | undefined = undefined, +>(args: GetRecordArgs): GetRecordResult { + const state = args.state ?? args.stash.get(); + const { table, key, defaultValue } = args; const { namespaceLabel, label } = table; - return stash.get().records[namespaceLabel][label][encodeKey({ table, key })]; + return (state.records[namespaceLabel]?.[label]?.[encodeKey({ table, key })] ?? defaultValue) as never; } diff --git a/packages/stash/src/actions/getRecords.test.ts b/packages/stash/src/actions/getRecords.test.ts index ff020ec345..3f1b7c00fd 100644 --- a/packages/stash/src/actions/getRecords.test.ts +++ b/packages/stash/src/actions/getRecords.test.ts @@ -6,37 +6,75 @@ import { attest } from "@ark/attest"; import { getRecords } from "./getRecords"; describe("getRecords", () => { - it("should get all records from a table", () => { - const config = defineStore({ - tables: { - test: { - schema: { - player: "int32", - match: "int32", - x: "uint256", - y: "uint256", + describe("with stash", () => { + it("should get all records from a table", () => { + const config = defineStore({ + tables: { + test: { + schema: { + player: "int32", + match: "int32", + x: "uint256", + y: "uint256", + }, + key: ["player", "match"], }, - key: ["player", "match"], }, - }, - }); - const table = config.tables.test; - const stash = createStash(config); + }); + const table = config.tables.test; + const stash = createStash(config); + + setRecord({ stash, table, key: { player: 1, match: 2 }, value: { x: 3n, y: 4n } }); + setRecord({ stash, table, key: { player: 5, match: 6 }, value: { x: 7n, y: 8n } }); - setRecord({ stash, table, key: { player: 1, match: 2 }, value: { x: 3n, y: 4n } }); - setRecord({ stash, table, key: { player: 5, match: 6 }, value: { x: 7n, y: 8n } }); + attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( + getRecords({ stash, table }), + ).equals({ + "1|2": { player: 1, match: 2, x: 3n, y: 4n }, + "5|6": { player: 5, match: 6, x: 7n, y: 8n }, + }); - attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( - getRecords({ stash, table }), - ).equals({ - "1|2": { player: 1, match: 2, x: 3n, y: 4n }, - "5|6": { player: 5, match: 6, x: 7n, y: 8n }, + attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( + getRecords({ stash, table, keys: [{ player: 1, match: 2 }] }), + ).equals({ + "1|2": { player: 1, match: 2, x: 3n, y: 4n }, + }); }); + }); + + describe("with state", () => { + it("should get all records from a table", () => { + const config = defineStore({ + tables: { + test: { + schema: { + player: "int32", + match: "int32", + x: "uint256", + y: "uint256", + }, + key: ["player", "match"], + }, + }, + }); + const table = config.tables.test; + const stash = createStash(config); + + setRecord({ stash, table, key: { player: 1, match: 2 }, value: { x: 3n, y: 4n } }); + setRecord({ stash, table, key: { player: 5, match: 6 }, value: { x: 7n, y: 8n } }); + + attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( + getRecords({ state: stash.get(), table }), + ).equals({ + "1|2": { player: 1, match: 2, x: 3n, y: 4n }, + "5|6": { player: 5, match: 6, x: 7n, y: 8n }, + }); - attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( - getRecords({ stash, table, keys: [{ player: 1, match: 2 }] }), - ).equals({ - "1|2": { player: 1, match: 2, x: 3n, y: 4n }, + attest<{ [encodedKey: string]: { player: number; match: number; x: bigint; y: bigint } }>( + getRecords({ state: stash.get(), table, keys: [{ player: 1, match: 2 }] }), + ).equals({ + "1|2": { player: 1, match: 2, x: 3n, y: 4n }, + }); }); }); }); diff --git a/packages/stash/src/actions/getRecords.ts b/packages/stash/src/actions/getRecords.ts index 9470cca33c..98b24e0275 100644 --- a/packages/stash/src/actions/getRecords.ts +++ b/packages/stash/src/actions/getRecords.ts @@ -1,31 +1,34 @@ +import { propwiseXor } from "@ark/util"; import { Table } from "@latticexyz/config"; -import { Key, Stash, TableRecords } from "../common"; +import { isDefined } from "@latticexyz/common/utils"; +import { Key, Stash, State, TableRecords } from "../common"; import { encodeKey } from "./encodeKey"; -export type GetRecordsArgs
= { - stash: Stash; +export type GetRecordsArgs
= propwiseXor<{ stash: Stash }, { state: State }> & { table: table; - keys?: Key
[]; + keys?: readonly Key
[]; }; export type GetRecordsResult
= TableRecords
; -export function getRecords
({ - stash, - table, - keys, -}: GetRecordsArgs
): GetRecordsResult
{ +export function getRecords
(args: GetRecordsArgs
): GetRecordsResult
{ + const state = args.state ?? args.stash.get(); + const { table, keys } = args; const { namespaceLabel, label } = table; - const records = stash.get().records[namespaceLabel][label]; - if (!keys) { + const records = state.records[namespaceLabel]?.[label] ?? {}; + + if (!keys || !keys.length) { return records; } return Object.fromEntries( - keys.map((key) => { - const encodedKey = encodeKey({ table, key }); - return [encodedKey, records[encodedKey]]; - }), + keys + .map((key) => { + const encodedKey = encodeKey({ table, key }); + const record = records[encodedKey]; + return record != null ? [encodedKey, record] : undefined; + }) + .filter(isDefined), ); } diff --git a/packages/stash/src/actions/getTable.ts b/packages/stash/src/actions/getTable.ts index 9188756bd3..3338b69ee5 100644 --- a/packages/stash/src/actions/getTable.ts +++ b/packages/stash/src/actions/getTable.ts @@ -15,8 +15,14 @@ import { registerTable } from "./registerTable"; export type TableBoundDecodeKeyArgs
= Omit, "stash" | "table">; export type TableBoundDeleteRecordArgs
= Omit, "stash" | "table">; export type TableBoundEncodeKeyArgs
= Omit, "stash" | "table">; -export type TableBoundGetRecordArgs
= Omit, "stash" | "table">; -export type TableBoundGetRecordsArgs
= Omit, "stash" | "table">; +export type TableBoundGetRecordArgs
= Omit< + GetRecordArgs
, + "stash" | "state" | "table" +>; +export type TableBoundGetRecordsArgs
= Omit< + GetRecordsArgs
, + "stash" | "state" | "table" +>; export type TableBoundSetRecordArgs
= Omit, "stash" | "table">; export type TableBoundSetRecordsArgs
= Omit, "stash" | "table">; export type TableBoundSubscribeTableArgs
= Omit< diff --git a/packages/stash/src/actions/getTableConfig.ts b/packages/stash/src/actions/getTableConfig.ts index 9a01ac8e4b..982f0716ff 100644 --- a/packages/stash/src/actions/getTableConfig.ts +++ b/packages/stash/src/actions/getTableConfig.ts @@ -10,5 +10,7 @@ export type GetTableConfigResult
= table; export function getTableConfig({ stash, table }: GetTableConfigArgs): GetTableConfigResult
{ const { namespaceLabel, label } = table; - return stash.get().config[namespaceLabel ?? ""][label]; + const tableConfig = stash.get().config[namespaceLabel ?? ""]?.[label]; + if (tableConfig == null) throw new Error("No table found."); + return tableConfig; } diff --git a/packages/stash/src/actions/getTables.ts b/packages/stash/src/actions/getTables.ts index f226264bf3..c62598c829 100644 --- a/packages/stash/src/actions/getTables.ts +++ b/packages/stash/src/actions/getTables.ts @@ -23,9 +23,8 @@ export function getTables({ stash }: GetTablesArgs({ const tableConfig = { namespace, namespaceLabel, name, label, key, schema, type, tableId }; // Set config for table - stash._.state.config[namespaceLabel] ??= {}; - stash._.state.config[namespaceLabel][label] = tableConfig; + (stash._.state.config[namespaceLabel] ??= {})[label] = tableConfig; // Init records map for table - stash._.state.records[namespaceLabel] ??= {}; - stash._.state.records[namespaceLabel][label] ??= {}; + (stash._.state.records[namespaceLabel] ??= {})[label] ??= {}; // Init subscribers set for table - stash._.tableSubscribers[namespaceLabel] ??= {}; - stash._.tableSubscribers[namespaceLabel][label] ??= new Set(); + (stash._.tableSubscribers[namespaceLabel] ??= {})[label] ??= new Set(); // Notify stash subscribers const storeUpdate = { diff --git a/packages/stash/src/actions/runQuery.ts b/packages/stash/src/actions/runQuery.ts index 4ea11068fb..fb74a7de24 100644 --- a/packages/stash/src/actions/runQuery.ts +++ b/packages/stash/src/actions/runQuery.ts @@ -76,9 +76,8 @@ export function runQuery({ const records: MutableStoreRecords = {}; for (const { table } of query) { const { namespaceLabel, label } = table; - records[namespaceLabel] ??= {}; const tableRecords = getRecords({ stash, table, keys: Object.values(keys) }); - records[namespaceLabel][label] ??= tableRecords; + (records[namespaceLabel] ??= {})[label] ??= tableRecords; } return { keys, records } as never; } diff --git a/packages/stash/src/actions/setRecords.ts b/packages/stash/src/actions/setRecords.ts index 8e8e958d6a..110f8332a9 100644 --- a/packages/stash/src/actions/setRecords.ts +++ b/packages/stash/src/actions/setRecords.ts @@ -23,7 +23,7 @@ export function setRecords
({ stash, table, records }: SetRe const updates: TableUpdates = {}; for (const record of records) { const encodedKey = encodeKey({ table, key: record as never }); - const prevRecord = stash.get().records[namespaceLabel][label][encodedKey]; + const prevRecord = stash.get().records[namespaceLabel]?.[label]?.[encodedKey]; const newRecord = Object.fromEntries( Object.keys(schema).map((fieldName) => [ fieldName, @@ -38,11 +38,11 @@ export function setRecords
({ stash, table, records }: SetRe // Update records for (const [encodedKey, { current }] of Object.entries(updates)) { - stash._.state.records[namespaceLabel][label][encodedKey] = current as never; + ((stash._.state.records[namespaceLabel] ??= {})[label] ??= {})[encodedKey] = current as never; } // Notify table subscribers - stash._.tableSubscribers[namespaceLabel][label].forEach((subscriber) => subscriber(updates)); + stash._.tableSubscribers[namespaceLabel]?.[label]?.forEach((subscriber) => subscriber(updates)); // Notify stash subscribers const storeUpdate = { config: {}, records: { [namespaceLabel]: { [label]: updates } } }; diff --git a/packages/stash/src/actions/subscribeQuery.ts b/packages/stash/src/actions/subscribeQuery.ts index d20426e095..9405fdd51a 100644 --- a/packages/stash/src/actions/subscribeQuery.ts +++ b/packages/stash/src/actions/subscribeQuery.ts @@ -24,7 +24,7 @@ export type SubscribeQueryOptions = Co initialSubscribers?: QuerySubscriber[]; }; -type QueryTableUpdates = { +export type QueryTableUpdates = { [namespace in getNamespaces]: { [table in getNamespaceTables]: TableUpdates>; }; @@ -33,10 +33,12 @@ type QueryTableUpdates = { export type QueryUpdate = { records: QueryTableUpdates; keys: Keys; - types: { [key: string]: "enter" | "update" | "exit" }; + types: { + [key: string]: "enter" | "update" | "exit"; + }; }; -type QuerySubscriber = (update: QueryUpdate) => void; +export type QuerySubscriber = (update: QueryUpdate) => void; export type SubscribeQueryArgs = { stash: Stash; @@ -90,8 +92,9 @@ export function subscribeQuery({ }; for (const key of Object.keys(tableUpdates)) { - if (key in matching) { - update.keys[key] = matching[key]; + const matchedKey = matching[key]; + if (matchedKey != null) { + update.keys[key] = matchedKey; // If the key matched before, check if the relevant fragments (accessing this table) still match const relevantFragments = query.filter((f) => f.table.namespace === namespaceLabel && f.table.label === label); const match = relevantFragments.every((f) => f.pass(stash, key)); @@ -135,8 +138,7 @@ export function subscribeQuery({ const records: QueryTableUpdates = {}; for (const namespace of Object.keys(initialRun.records)) { for (const table of Object.keys(initialRun.records[namespace])) { - records[namespace] ??= {}; - records[namespace][table] = Object.fromEntries( + (records[namespace] ??= {})[table] = Object.fromEntries( Object.entries(initialRun.records[namespace][table]).map(([key, record]) => [ key, { prev: undefined, current: record }, diff --git a/packages/stash/src/actions/subscribeStore.test.ts b/packages/stash/src/actions/subscribeStash.test.ts similarity index 94% rename from packages/stash/src/actions/subscribeStore.test.ts rename to packages/stash/src/actions/subscribeStash.test.ts index c2aeffdb4e..887ca1dbc6 100644 --- a/packages/stash/src/actions/subscribeStore.test.ts +++ b/packages/stash/src/actions/subscribeStash.test.ts @@ -1,10 +1,10 @@ import { defineStore } from "@latticexyz/store"; import { describe, expect, it, vi } from "vitest"; import { createStash } from "../createStash"; -import { subscribeStore } from "./subscribeStore"; +import { subscribeStash } from "./subscribeStash"; import { setRecord } from "./setRecord"; -describe("subscribeStore", () => { +describe("subscribeStash", () => { it("should notify subscriber of any stash change", () => { const config = defineStore({ namespaces: { @@ -30,7 +30,7 @@ describe("subscribeStore", () => { const stash = createStash(config); const subscriber = vi.fn(); - subscribeStore({ stash, subscriber }); + subscribeStash({ stash, subscriber }); setRecord({ stash, table: config.tables.namespace1__table1, key: { a: "0x00" }, value: { b: 1n, c: 2 } }); diff --git a/packages/stash/src/actions/subscribeStore.ts b/packages/stash/src/actions/subscribeStash.ts similarity index 60% rename from packages/stash/src/actions/subscribeStore.ts rename to packages/stash/src/actions/subscribeStash.ts index f0e6ed119f..95dd16daa8 100644 --- a/packages/stash/src/actions/subscribeStore.ts +++ b/packages/stash/src/actions/subscribeStash.ts @@ -1,16 +1,16 @@ import { Stash, StoreConfig, StoreUpdatesSubscriber, Unsubscribe } from "../common"; -export type SubscribeStoreArgs = { +export type SubscribeStashArgs = { stash: Stash; subscriber: StoreUpdatesSubscriber; }; -export type SubscribeStoreResult = Unsubscribe; +export type SubscribeStashResult = Unsubscribe; -export function subscribeStore({ +export function subscribeStash({ stash, subscriber, -}: SubscribeStoreArgs): SubscribeStoreResult { +}: SubscribeStashArgs): SubscribeStashResult { stash._.storeSubscribers.add(subscriber as StoreUpdatesSubscriber); return () => stash._.storeSubscribers.delete(subscriber as StoreUpdatesSubscriber); } diff --git a/packages/stash/src/actions/subscribeTable.ts b/packages/stash/src/actions/subscribeTable.ts index f129ecea1a..2a8cba3bea 100644 --- a/packages/stash/src/actions/subscribeTable.ts +++ b/packages/stash/src/actions/subscribeTable.ts @@ -1,5 +1,6 @@ import { Table } from "@latticexyz/config"; import { Stash, TableUpdatesSubscriber, Unsubscribe } from "../common"; +import { registerTable } from "./registerTable"; export type SubscribeTableArgs
= { stash: Stash; @@ -16,6 +17,10 @@ export function subscribeTable
({ }: SubscribeTableArgs
): SubscribeTableResult { const { namespaceLabel, label } = table; - stash._.tableSubscribers[namespaceLabel][label].add(subscriber); - return () => stash._.tableSubscribers[namespaceLabel][label].delete(subscriber); + if (stash.get().config[namespaceLabel]?.[label] == null) { + registerTable({ stash, table }); + } + + stash._.tableSubscribers[namespaceLabel]?.[label]?.add(subscriber); + return () => stash._.tableSubscribers[namespaceLabel]?.[label]?.delete(subscriber); } diff --git a/packages/stash/src/apiEquality.test.ts b/packages/stash/src/apiEquality.test.ts index bb74cec13f..5948293f40 100644 --- a/packages/stash/src/apiEquality.test.ts +++ b/packages/stash/src/apiEquality.test.ts @@ -22,7 +22,7 @@ describe("stash actions, bound table", () => { "getTables", "runQuery", "subscribeQuery", - "subscribeStore", + "subscribeStash", "subscribeTable", // renamed to subscribe in table API "_", "get", diff --git a/packages/stash/src/common.ts b/packages/stash/src/common.ts index fb28474766..ed5c09d7e9 100644 --- a/packages/stash/src/common.ts +++ b/packages/stash/src/common.ts @@ -1,6 +1,6 @@ -import { QueryFragment } from "./queryFragments"; -import { Table } from "@latticexyz/config"; import { getKeySchema, getSchemaPrimitives } from "@latticexyz/protocol-parser/internal"; +import { Table } from "@latticexyz/config"; +import { QueryFragment } from "./queryFragments"; export type StoreConfig = { namespaces: { @@ -33,7 +33,9 @@ export type Key
= getSchemaPrimitives = { [encodedKey: string]: Key
}; +export type Keys
= { + [encodedKey: string]: Key
; +}; export type CommonQueryResult = { /** @@ -75,9 +77,13 @@ export type TableLabel = { readonly [key: string]: TableRecord
}; +export type TableRecords
= { + readonly [key: string]: TableRecord
; +}; -export type MutableTableRecords
= { [key: string]: TableRecord
}; +export type MutableTableRecords
= { + [key: string]: TableRecord
; +}; export type StoreRecords = { readonly [namespace in getNamespaces]: { diff --git a/packages/stash/src/createStash.test.ts b/packages/stash/src/createStash.test.ts index 51e860d333..e92c361839 100644 --- a/packages/stash/src/createStash.test.ts +++ b/packages/stash/src/createStash.test.ts @@ -208,7 +208,7 @@ describe("createStash", () => { }); }); - describe("subscribeStore", () => { + describe("subscribeStash", () => { it("should notify listeners on stash updates", () => { const config = defineStore({ namespace: "namespace1", @@ -229,7 +229,7 @@ describe("createStash", () => { const subscriber = vi.fn(); - stash.subscribeStore({ subscriber }); + stash.subscribeStash({ subscriber }); stash.setRecord({ table, @@ -350,7 +350,7 @@ describe("createStash", () => { const subscriber = vi.fn(); - const unsubscribe = stash.subscribeStore({ + const unsubscribe = stash.subscribeStash({ subscriber, }); @@ -407,8 +407,8 @@ describe("createStash", () => { }); const tables = stash.getTables(); - expect(tables.namespace1.table1).toBeDefined(); - expect(tables.namespace2.table2).toBeDefined(); + expect(tables.namespace1?.table1).toBeDefined(); + expect(tables.namespace2?.table2).toBeDefined(); }); }); }); diff --git a/packages/stash/src/createStash.ts b/packages/stash/src/createStash.ts index 1cda3ed13d..316ae6aa61 100644 --- a/packages/stash/src/createStash.ts +++ b/packages/stash/src/createStash.ts @@ -28,16 +28,13 @@ export function createStash(storeConfig?: config): Create const { deploy, codegen, ...tableConfig } = { ...(fullTableConfig as Table) }; // Set config for tables - state.config[namespace] ??= {}; - state.config[namespace][table] = tableConfig; + (state.config[namespace] ??= {})[table] = tableConfig; // Init records map for tables - state.records[namespace] ??= {}; - state.records[namespace][table] = {}; + (state.records[namespace] ??= {})[table] = {}; // Init subscribers set for tables - tableSubscribers[namespace] ??= {}; - tableSubscribers[namespace][table] ??= new Set(); + (tableSubscribers[namespace] ??= {})[table] ??= new Set(); } } } diff --git a/packages/stash/src/decorators/defaultActions.test.ts b/packages/stash/src/decorators/defaultActions.test.ts index c70f61c48c..e3a128c612 100644 --- a/packages/stash/src/decorators/defaultActions.test.ts +++ b/packages/stash/src/decorators/defaultActions.test.ts @@ -183,7 +183,7 @@ describe("stash with default actions", () => { value: { field1: "world" }, }); - attest<{ field1: string; field2: number; field3: number }>( + attest<{ field1: string; field2: number; field3: number } | undefined>( stash.getRecord({ table, key: { field2: 2, field3: 1 }, @@ -460,7 +460,7 @@ describe("stash with default actions", () => { }); }); - describe("subscribeStore", () => { + describe("subscribeStash", () => { it("should notify subscriber of any stash change", () => { const config = defineStore({ namespaces: { @@ -478,7 +478,7 @@ describe("stash with default actions", () => { const stash = createStash(config); const subscriber = vi.fn(); - stash.subscribeStore({ subscriber }); + stash.subscribeStash({ subscriber }); stash.setRecord({ table: config.tables.namespace1__table1, key: { a: "0x00" }, value: { b: 1n, c: 2 } }); diff --git a/packages/stash/src/decorators/defaultActions.ts b/packages/stash/src/decorators/defaultActions.ts index 70ae53f9a5..207b605c6f 100644 --- a/packages/stash/src/decorators/defaultActions.ts +++ b/packages/stash/src/decorators/defaultActions.ts @@ -13,7 +13,7 @@ import { RunQueryArgs, RunQueryOptions, RunQueryResult, runQuery } from "../acti import { SetRecordArgs, SetRecordResult, setRecord } from "../actions/setRecord"; import { SetRecordsArgs, SetRecordsResult, setRecords } from "../actions/setRecords"; import { SubscribeQueryArgs, SubscribeQueryResult, subscribeQuery } from "../actions/subscribeQuery"; -import { SubscribeStoreArgs, SubscribeStoreResult, subscribeStore } from "../actions/subscribeStore"; +import { SubscribeStashArgs, SubscribeStashResult, subscribeStash } from "../actions/subscribeStash"; import { SubscribeTableArgs, SubscribeTableResult, subscribeTable } from "../actions/subscribeTable"; import { Table } from "@latticexyz/config"; @@ -22,8 +22,8 @@ export type StashBoundDeleteRecordArgs
= Omit = EncodeKeyArgs
; export type StashBoundGetTableConfigArgs = Omit; export type StashBoundGetKeysArgs
= Omit, "stash">; -export type StashBoundGetRecordArgs
= Omit, "stash">; -export type StashBoundGetRecordsArgs
= Omit, "stash">; +export type StashBoundGetRecordArgs
= Omit, "stash" | "state">; +export type StashBoundGetRecordsArgs
= Omit, "stash" | "state">; export type StashBoundGetTableArgs
= Omit, "stash">; export type StashBoundRegisterTableArgs
= Omit, "stash">; export type StashBoundRunQueryArgs< @@ -33,8 +33,8 @@ export type StashBoundRunQueryArgs< export type StashBoundSetRecordArgs
= Omit, "stash">; export type StashBoundSetRecordsArgs
= Omit, "stash">; export type StashBoundSubscribeQueryArgs = Omit, "stash">; -export type StashBoundSubscribeStoreArgs = Omit< - SubscribeStoreArgs, +export type StashBoundSubscribeStashArgs = Omit< + SubscribeStashArgs, "stash" >; export type StashBoundSubscribeTableArgs
= Omit, "stash">; @@ -56,7 +56,7 @@ export type DefaultActions = { setRecord:
(args: StashBoundSetRecordArgs
) => SetRecordResult; setRecords:
(args: StashBoundSetRecordsArgs
) => SetRecordsResult; subscribeQuery: (args: StashBoundSubscribeQueryArgs) => SubscribeQueryResult; - subscribeStore: (args: StashBoundSubscribeStoreArgs) => SubscribeStoreResult; + subscribeStash: (args: StashBoundSubscribeStashArgs) => SubscribeStashResult; subscribeTable:
(args: StashBoundSubscribeTableArgs
) => SubscribeTableResult; }; @@ -78,8 +78,8 @@ export function defaultActions(stash: Stash) setRecords:
(args: StashBoundSetRecordsArgs
) => setRecords({ stash, ...args }), subscribeQuery: (args: StashBoundSubscribeQueryArgs) => subscribeQuery({ stash, ...args }), - subscribeStore: (args: StashBoundSubscribeStoreArgs) => - subscribeStore({ stash, ...args }), + subscribeStash: (args: StashBoundSubscribeStashArgs) => + subscribeStash({ stash, ...args }), subscribeTable:
(args: StashBoundSubscribeTableArgs
) => subscribeTable({ stash, ...args }), }; diff --git a/packages/stash/src/react/isEqual.ts b/packages/stash/src/react/isEqual.ts new file mode 100644 index 0000000000..3efa1b22aa --- /dev/null +++ b/packages/stash/src/react/isEqual.ts @@ -0,0 +1,3 @@ +export function isEqual(a: unknown, b: unknown): boolean { + return a === b; +} diff --git a/packages/stash/src/react/memoize.ts b/packages/stash/src/react/memoize.ts new file mode 100644 index 0000000000..317e1ebf3e --- /dev/null +++ b/packages/stash/src/react/memoize.ts @@ -0,0 +1,12 @@ +export function memoize(fn: () => T, isEqual: (a: T, b: T) => boolean): () => T { + let ref: { current: T } | null = null; + return () => { + const current = fn(); + if (ref == null) { + ref = { current }; + } else if (!isEqual(ref.current, current)) { + ref.current = current; + } + return ref.current; + }; +} diff --git a/packages/stash/src/react/useStash.test.ts b/packages/stash/src/react/useStash.test.ts new file mode 100644 index 0000000000..944b770799 --- /dev/null +++ b/packages/stash/src/react/useStash.test.ts @@ -0,0 +1,162 @@ +import { describe, it, expect } from "vitest"; +import { renderHook, act } from "@testing-library/react-hooks"; +import { useStash } from "./useStash"; +import { defineStore } from "@latticexyz/store/config/v2"; +import { createStash } from "../createStash"; +import isEqual from "fast-deep-equal"; +import { getRecord, getRecords } from "../actions"; +import { Hex } from "viem"; + +// TODO: migrate to ark/attest snapshots for better formatting + typechecking + +describe("useStash", () => { + it("returns a single record", async () => { + const config = defineStore({ + namespaces: { + game: { + tables: { + Position: { + schema: { player: "address", x: "uint32", y: "uint32" }, + key: ["player"], + }, + }, + }, + }, + }); + const Position = config.namespaces.game.tables.Position; + const stash = createStash(config); + const player: Hex = "0x00"; + + const { result, rerender } = renderHook( + ({ player }) => useStash(stash, (state) => getRecord({ state, table: Position, key: { player } })), + { initialProps: { player } }, + ); + expect(result.all.length).toBe(1); + expect(result.current).toMatchInlineSnapshot(`undefined`); + + act(() => { + stash.setRecord({ table: Position, key: { player }, value: { x: 1, y: 2 } }); + }); + // Expect update to have triggered rerender + expect(result.all.length).toBe(2); + expect(result.current).toMatchInlineSnapshot(` + { + "player": "0x00", + "x": 1, + "y": 2, + } + `); + + act(() => { + stash.setRecord({ table: Position, key: { player: "0x01" }, value: { x: 1, y: 2 } }); + }); + // Expect unrelated update to not have triggered rerender + expect(result.all.length).toBe(2); + + // Expect update to have triggered rerender + act(() => { + stash.setRecord({ table: Position, key: { player }, value: { x: 1, y: 3 } }); + }); + expect(result.all.length).toBe(3); + expect(result.current).toMatchInlineSnapshot(` + { + "player": "0x00", + "x": 1, + "y": 3, + } + `); + + // Expect a change in args to trigger a rerender + rerender({ player: "0x01" }); + expect(result.all.length).toBe(4); + expect(result.current).toMatchInlineSnapshot(` + { + "player": "0x01", + "x": 1, + "y": 2, + } + `); + + act(() => { + stash.setRecord({ table: Position, key: { player: "0x0" }, value: { x: 5, y: 0 } }); + }); + // Expect unrelated update to not have triggered rerender + expect(result.all.length).toBe(4); + }); + + it("returns records of a table using equality function", async () => { + const config = defineStore({ + namespaces: { + game: { + tables: { + Position: { + schema: { player: "address", x: "uint32", y: "uint32" }, + key: ["player"], + }, + }, + }, + }, + }); + const Position = config.namespaces.game.tables.Position; + const stash = createStash(config); + const player: Hex = "0x00"; + + const { result } = renderHook(() => + useStash(stash, (state) => Object.values(getRecords({ state, table: Position })), { isEqual }), + ); + expect(result.all.length).toBe(1); + expect(result.current).toMatchInlineSnapshot(`[]`); + + act(() => { + stash.setRecord({ table: Position, key: { player }, value: { x: 1, y: 2 } }); + }); + expect(result.all.length).toBe(2); + expect(result.current).toMatchInlineSnapshot(` + [ + { + "player": "0x00", + "x": 1, + "y": 2, + }, + ] + `); + + act(() => { + stash.setRecord({ table: Position, key: { player: "0x01" }, value: { x: 1, y: 2 } }); + }); + expect(result.all.length).toBe(3); + expect(result.current).toMatchInlineSnapshot(` + [ + { + "player": "0x00", + "x": 1, + "y": 2, + }, + { + "player": "0x01", + "x": 1, + "y": 2, + }, + ] + `); + + act(() => { + stash.setRecord({ table: Position, key: { player }, value: { x: 1, y: 3 } }); + }); + expect(result.all.length).toBe(4); + expect(result.current).toMatchInlineSnapshot(` + [ + { + "player": "0x00", + "x": 1, + "y": 3, + }, + { + "player": "0x01", + "x": 1, + "y": 2, + }, + ] + `); + }); +}); diff --git a/packages/stash/src/react/useStash.ts b/packages/stash/src/react/useStash.ts new file mode 100644 index 0000000000..700b6cbb47 --- /dev/null +++ b/packages/stash/src/react/useStash.ts @@ -0,0 +1,31 @@ +import { useDebugValue, useSyncExternalStore } from "react"; +import { subscribeStash } from "../actions"; +import { StoreConfig, Stash, State } from "../common"; +import { isEqual } from "./isEqual"; +import { memoize } from "./memoize"; + +export type UseStashOptions = { + /** + * Optional equality function. + * Must be a stable function, otherwise you may end up with this hook rerendering infinitely. + * @default (a, b) => a === b + */ + isEqual?: (a: T, b: T) => boolean; +}; + +export function useStash( + stash: Stash, + /** + * Selector to pick values from state. + * Be aware of the stability of both the `selector` and the return value, otherwise you may end up with unnecessary re-renders. + */ + selector: (state: State) => T, + opts: UseStashOptions = {}, +): T { + const slice = useSyncExternalStore( + (subscriber) => subscribeStash({ stash, subscriber }), + memoize(() => selector(stash.get()), opts.isEqual ?? isEqual), + ); + useDebugValue(slice); + return slice; +} diff --git a/packages/stash/tsconfig.json b/packages/stash/tsconfig.json index 039e0b4d16..65b4611165 100644 --- a/packages/stash/tsconfig.json +++ b/packages/stash/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "noUncheckedIndexedAccess": true }, "include": ["src"] } diff --git a/packages/stash/tsconfig.test.json b/packages/stash/tsconfig.test.json new file mode 100644 index 0000000000..f056ce67bd --- /dev/null +++ b/packages/stash/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + // disable paths so we don't check deps using `noUncheckedIndexedAccess` + // TODO: remove this file once all packages pass `noUncheckedIndexedAccess` + "paths": {} + } +} diff --git a/packages/stash/vitest.config.ts b/packages/stash/vitest.config.ts deleted file mode 100644 index b6a66f2a98..0000000000 --- a/packages/stash/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globalSetup: "vitestSetup.ts", - }, -}); diff --git a/packages/stash/vitestSetup.ts b/packages/stash/vitestSetup.ts deleted file mode 100644 index dddf730b02..0000000000 --- a/packages/stash/vitestSetup.ts +++ /dev/null @@ -1 +0,0 @@ -export { setup, teardown } from "@ark/attest"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5340428860..7c622f31c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -890,6 +890,12 @@ importers: packages/stash: dependencies: + '@ark/util': + specifier: 'catalog:' + version: 0.2.2 + '@latticexyz/common': + specifier: workspace:* + version: link:../common '@latticexyz/config': specifier: workspace:* version: link:../config @@ -902,9 +908,6 @@ importers: '@latticexyz/store': specifier: workspace:* version: link:../store - react: - specifier: ^18.2.0 - version: 18.2.0 viem: specifier: 'catalog:' version: 2.21.19(bufferutil@4.0.8)(typescript@5.4.2)(utf-8-validate@5.0.10)(zod@3.23.8) @@ -918,15 +921,24 @@ importers: '@types/react': specifier: 18.2.22 version: 18.2.22 + eslint-plugin-react: + specifier: 7.31.11 + version: 7.31.11(eslint@8.57.0) + eslint-plugin-react-hooks: + specifier: 4.6.0 + version: 4.6.0(eslint@8.57.0) + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 + react: + specifier: ^18.2.0 + version: 18.2.0 react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) - vitest: - specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) packages/store: dependencies: @@ -5831,10 +5843,6 @@ packages: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} - array-includes@3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} - engines: {node: '>= 0.4'} - array-includes@3.1.8: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} @@ -5855,17 +5863,10 @@ packages: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} - array.prototype.flatmap@1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} - engines: {node: '>= 0.4'} - array.prototype.flatmap@1.3.2: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} - array.prototype.tosorted@1.1.1: - resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} - array.prototype.tosorted@1.1.4: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} @@ -6116,9 +6117,6 @@ packages: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} - call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -6648,10 +6646,6 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - define-properties@1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} - engines: {node: '>= 0.4'} - define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -6944,10 +6938,6 @@ packages: resolution: {integrity: sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==} engines: {node: '>= 0.8'} - es-abstract@1.20.5: - resolution: {integrity: sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==} - engines: {node: '>= 0.4'} - es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} @@ -6975,9 +6965,6 @@ packages: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} - es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} @@ -7506,16 +7493,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} @@ -7550,9 +7530,6 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.1.3: - resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -7588,10 +7565,6 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -7688,9 +7661,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -7896,10 +7866,6 @@ packages: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} - internal-slot@1.0.3: - resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} - engines: {node: '>= 0.4'} - internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -8035,10 +8001,6 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -8077,9 +8039,6 @@ packages: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} @@ -8483,10 +8442,6 @@ packages: engines: {node: '>=10'} hasBin: true - jsx-ast-utils@3.3.3: - resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} - engines: {node: '>=4.0'} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -9191,9 +9146,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.12.2: - resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} @@ -9206,26 +9158,14 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - object.assign@4.1.5: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} - object.entries@1.1.6: - resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} - engines: {node: '>= 0.4'} - object.entries@1.1.8: resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} engines: {node: '>= 0.4'} - object.fromentries@2.0.6: - resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} - engines: {node: '>= 0.4'} - object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} @@ -9237,10 +9177,6 @@ packages: object.hasown@1.1.2: resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} - object.values@1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} - engines: {node: '>= 0.4'} - object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} @@ -10086,10 +10022,6 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.4.3: - resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} - engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} @@ -10151,10 +10083,6 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - resolve@2.0.0-next.4: - resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} - hasBin: true - resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true @@ -10258,9 +10186,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} @@ -10389,9 +10314,6 @@ packages: engines: {node: '>=6'} hasBin: true - side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -10634,9 +10556,6 @@ packages: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.8: - resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} - string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} @@ -10644,15 +10563,9 @@ packages: resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} - string.prototype.trimend@1.0.8: resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} - string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} @@ -17474,14 +17387,6 @@ snapshots: call-bind: 1.0.7 is-array-buffer: 3.0.4 - array-includes@3.1.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - get-intrinsic: 1.1.3 - is-string: 1.0.7 - array-includes@3.1.8: dependencies: call-bind: 1.0.7 @@ -17513,32 +17418,17 @@ snapshots: array.prototype.flat@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 - es-shim-unscopables: 1.0.0 - - array.prototype.flatmap@1.3.1: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - es-shim-unscopables: 1.0.0 + es-shim-unscopables: 1.0.2 array.prototype.flatmap@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 - es-shim-unscopables: 1.0.0 - - array.prototype.tosorted@1.1.1: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.1.3 + es-shim-unscopables: 1.0.2 array.prototype.tosorted@1.1.4: dependencies: @@ -17896,11 +17786,6 @@ snapshots: mime-types: 2.1.35 ylru: 1.3.2 - call-bind@1.0.2: - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.3 - call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -18415,13 +18300,13 @@ snapshots: is-array-buffer: 3.0.4 is-date-object: 1.0.5 is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 + is-shared-array-buffer: 1.0.3 isarray: 2.0.5 object-is: 1.1.6 object-keys: 1.1.1 - object.assign: 4.1.4 + object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - side-channel: 1.0.4 + side-channel: 1.0.6 which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 @@ -18446,15 +18331,10 @@ snapshots: define-lazy-prop@2.0.0: {} - define-properties@1.1.4: - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - define-properties@1.2.1: dependencies: define-data-property: 1.1.4 - has-property-descriptors: 1.0.0 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 defu@6.1.4: {} @@ -18663,34 +18543,6 @@ snapshots: accepts: 1.3.8 escape-html: 1.0.3 - es-abstract@1.20.5: - dependencies: - call-bind: 1.0.2 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.1.3 - get-symbol-description: 1.0.0 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-weakref: 1.0.2 - object-inspect: 1.12.2 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 - safe-regex-test: 1.0.0 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - unbox-primitive: 1.0.2 - es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -18785,10 +18637,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.0.0: - dependencies: - has: 1.0.3 - es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.2 @@ -18998,7 +18846,7 @@ snapshots: eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 - is-core-module: 2.12.0 + is-core-module: 2.15.0 is-glob: 4.0.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -19070,22 +18918,22 @@ snapshots: eslint-plugin-react@7.31.11(eslint@8.57.0): dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 eslint: 8.57.0 estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 + jsx-ast-utils: 3.3.5 minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 + object.entries: 1.1.8 + object.fromentries: 2.0.8 object.hasown: 1.1.2 - object.values: 1.1.6 + object.values: 1.2.0 prop-types: 15.8.1 - resolve: 2.0.0-next.4 + resolve: 2.0.0-next.5 semver: 6.3.1 - string.prototype.matchall: 4.0.8 + string.prototype.matchall: 4.0.11 eslint-plugin-react@7.35.0(eslint@8.57.0): dependencies: @@ -19098,7 +18946,7 @@ snapshots: eslint: 8.57.0 estraverse: 5.3.0 hasown: 2.0.2 - jsx-ast-utils: 3.3.3 + jsx-ast-utils: 3.3.5 minimatch: 3.1.2 object.entries: 1.1.8 object.fromentries: 2.0.8 @@ -19646,17 +19494,8 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.1: {} - function-bind@1.1.2: {} - function.prototype.name@1.1.5: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - functions-have-names: 1.2.3 - function.prototype.name@1.1.6: dependencies: call-bind: 1.0.7 @@ -19689,12 +19528,6 @@ snapshots: get-func-name@2.0.2: {} - get-intrinsic@1.1.3: - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -19722,11 +19555,6 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-symbol-description@1.0.0: - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.3 - get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 @@ -19832,7 +19660,7 @@ snapshots: gopd@1.0.1: dependencies: - get-intrinsic: 1.1.3 + get-intrinsic: 1.2.4 graceful-fs@4.2.11: {} @@ -19868,10 +19696,6 @@ snapshots: has-flag@4.0.0: {} - has-property-descriptors@1.0.0: - dependencies: - get-intrinsic: 1.1.3 - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 @@ -19892,7 +19716,7 @@ snapshots: has@1.0.3: dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 hasbin@1.2.3: dependencies: @@ -20095,12 +19919,6 @@ snapshots: strip-ansi: 6.0.1 through: 2.3.8 - internal-slot@1.0.3: - dependencies: - get-intrinsic: 1.1.3 - has: 1.0.3 - side-channel: 1.0.4 - internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -20150,8 +19968,8 @@ snapshots: is-boolean-object@1.1.2: dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 is-callable@1.2.7: {} @@ -20169,7 +19987,7 @@ snapshots: is-date-object@1.0.5: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-directory@0.3.1: {} @@ -20213,13 +20031,11 @@ snapshots: is-map@2.0.3: {} - is-negative-zero@2.0.2: {} - is-negative-zero@2.0.3: {} is-number-object@1.0.7: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -20237,15 +20053,11 @@ snapshots: is-regex@1.1.4: dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 is-set@2.0.3: {} - is-shared-array-buffer@1.0.2: - dependencies: - call-bind: 1.0.2 - is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 @@ -20258,7 +20070,7 @@ snapshots: is-string@1.0.7: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-subdir@1.2.0: dependencies: @@ -20282,7 +20094,7 @@ snapshots: is-weakref@1.0.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 is-weakset@2.0.3: dependencies: @@ -20647,7 +20459,7 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@29.5.0) jest-util: 29.5.0 jest-validate: 29.5.0 - resolve: 1.22.2 + resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 @@ -20937,17 +20749,12 @@ snapshots: jsonparse: 1.3.1 through2: 4.0.2 - jsx-ast-utils@3.3.3: - dependencies: - array-includes: 3.1.6 - object.assign: 4.1.4 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.2 - object.assign: 4.1.4 - object.values: 1.1.6 + object.assign: 4.1.5 + object.values: 1.2.0 keccak@3.0.4: dependencies: @@ -21807,8 +21614,6 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.12.2: {} - object-inspect@1.13.2: {} object-is@1.1.6: @@ -21818,13 +21623,6 @@ snapshots: object-keys@1.1.1: {} - object.assign@4.1.4: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - has-symbols: 1.0.3 - object-keys: 1.1.1 - object.assign@4.1.5: dependencies: call-bind: 1.0.7 @@ -21832,24 +21630,12 @@ snapshots: has-symbols: 1.0.3 object-keys: 1.1.1 - object.entries@1.1.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - object.entries@1.1.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 - object.fromentries@2.0.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - object.fromentries@2.0.8: dependencies: call-bind: 1.0.7 @@ -21865,14 +21651,8 @@ snapshots: object.hasown@1.1.2: dependencies: - define-properties: 1.1.4 - es-abstract: 1.20.5 - - object.values@1.1.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 + define-properties: 1.2.1 + es-abstract: 1.23.3 object.values@1.2.0: dependencies: @@ -22722,12 +22502,6 @@ snapshots: dependencies: '@babel/runtime': 7.25.6 - regexp.prototype.flags@1.4.3: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - functions-have-names: 1.2.3 - regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 @@ -22784,12 +22558,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.4: - dependencies: - is-core-module: 2.12.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: dependencies: is-core-module: 2.15.0 @@ -22901,12 +22669,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-regex-test@1.0.0: - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.3 - is-regex: 1.1.4 - safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 @@ -23047,12 +22809,6 @@ snapshots: minimist: 1.2.8 shelljs: 0.8.5 - side-channel@1.0.4: - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.3 - object-inspect: 1.12.2 - side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -23312,8 +23068,8 @@ snapshots: string.prototype.includes@2.0.0: dependencies: - define-properties: 1.1.4 - es-abstract: 1.20.5 + define-properties: 1.2.1 + es-abstract: 1.23.3 string.prototype.matchall@4.0.11: dependencies: @@ -23330,21 +23086,10 @@ snapshots: set-function-name: 2.0.2 side-channel: 1.0.6 - string.prototype.matchall@4.0.8: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - get-intrinsic: 1.1.3 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - regexp.prototype.flags: 1.4.3 - side-channel: 1.0.4 - string.prototype.repeat@1.0.0: dependencies: - define-properties: 1.1.4 - es-abstract: 1.20.5 + define-properties: 1.2.1 + es-abstract: 1.23.3 string.prototype.trim@1.2.9: dependencies: @@ -23353,24 +23098,12 @@ snapshots: es-abstract: 1.23.3 es-object-atoms: 1.0.0 - string.prototype.trimend@1.0.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 - string.prototype.trimstart@1.0.6: - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.5 - string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.7 @@ -23908,7 +23641,7 @@ snapshots: unbox-primitive@1.0.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2