diff --git a/.changeset/flat-swans-turn.md b/.changeset/flat-swans-turn.md new file mode 100644 index 0000000000..b84f3e8e32 --- /dev/null +++ b/.changeset/flat-swans-turn.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/store-sync": patch +--- + +Refactored `syncToZustand` to use new Store config under the hood, removing compatibility layers and improving performance. diff --git a/.changeset/orange-beans-allow.md b/.changeset/orange-beans-allow.md new file mode 100644 index 0000000000..a886edba0c --- /dev/null +++ b/.changeset/orange-beans-allow.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/dev-tools": patch +--- + +Updated Zustand components after changes to `syncToZustand`. diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index e08c34634a..f8807d3bde 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -31,3 +31,7 @@ export type Table = { readonly schema: Schema; readonly key: readonly string[]; }; + +export type Tables = { + readonly [label: string]: Table; +}; diff --git a/packages/config/src/exports/index.ts b/packages/config/src/exports/index.ts index d6df7e297e..8805ded492 100644 --- a/packages/config/src/exports/index.ts +++ b/packages/config/src/exports/index.ts @@ -4,4 +4,4 @@ * Be sure we're ready to commit to these being supported and changes made backward compatible! */ -export type { AbiType, StaticAbiType, DynamicAbiType, Schema, Table } from "../common"; +export type { AbiType, StaticAbiType, DynamicAbiType, Schema, Table, Tables } from "../common"; diff --git a/packages/dev-tools/src/zustand/TableDataTable.tsx b/packages/dev-tools/src/zustand/TableDataTable.tsx index 36d29922e3..064d60ea2f 100644 --- a/packages/dev-tools/src/zustand/TableDataTable.tsx +++ b/packages/dev-tools/src/zustand/TableDataTable.tsx @@ -1,6 +1,6 @@ -import { Table } from "@latticexyz/store/internal"; import { useRecords } from "./useRecords"; import { FieldValue } from "./FieldValue"; +import { Table } from "@latticexyz/store-sync/zustand"; // TODO: use react-table or similar for better perf with lots of logs @@ -15,12 +15,7 @@ export function TableDataTable({ table }: Props) { - {Object.keys(table.keySchema).map((name) => ( - - ))} - {Object.keys(table.valueSchema).map((name) => ( + {Object.keys(table.schema).map((name) => ( @@ -31,14 +26,9 @@ export function TableDataTable({ table }: Props) { {records.map((record) => { return ( - {Object.keys(table.keySchema).map((name) => ( - - ))} - {Object.keys(table.valueSchema).map((name) => ( + {Object.keys(table.schema).map((name) => ( ))} diff --git a/packages/dev-tools/src/zustand/useRecords.ts b/packages/dev-tools/src/zustand/useRecords.ts index 27d4b7f9fc..3d2941ed4e 100644 --- a/packages/dev-tools/src/zustand/useRecords.ts +++ b/packages/dev-tools/src/zustand/useRecords.ts @@ -1,7 +1,6 @@ -import { Table } from "@latticexyz/store/internal"; import { useDevToolsContext } from "../DevToolsContext"; import { useEffect, useState } from "react"; -import { TableRecord } from "@latticexyz/store-sync/zustand"; +import { Table, TableRecord } from "@latticexyz/store-sync/zustand"; export function useRecords
- {name} - {name}
- - - +
(table: table): TableRecord
[] { const { useStore } = useDevToolsContext(); diff --git a/packages/dev-tools/src/zustand/useTables.ts b/packages/dev-tools/src/zustand/useTables.ts index d9fbd55ea3..0732383788 100644 --- a/packages/dev-tools/src/zustand/useTables.ts +++ b/packages/dev-tools/src/zustand/useTables.ts @@ -1,6 +1,6 @@ -import { Table } from "@latticexyz/store/internal"; import { useDevToolsContext } from "../DevToolsContext"; import { useEffect, useState } from "react"; +import { Table } from "@latticexyz/store-sync/zustand"; export function useTables(): Table[] { const { useStore } = useDevToolsContext(); diff --git a/packages/protocol-parser/src/common.ts b/packages/protocol-parser/src/common.ts index 8922bbac9c..904a646ef1 100644 --- a/packages/protocol-parser/src/common.ts +++ b/packages/protocol-parser/src/common.ts @@ -30,6 +30,7 @@ export type KeySchema = Rec string, userTypes extends UserTypes ? StaticAbiType | keyof userTypes : StaticAbiType >; + export type ValueSchema = Record< string, userTypes extends UserTypes ? SchemaAbiType | keyof userTypes : SchemaAbiType diff --git a/packages/store-sync/src/zustand/common.ts b/packages/store-sync/src/zustand/common.ts index d86b1ad654..5f5a64edc4 100644 --- a/packages/store-sync/src/zustand/common.ts +++ b/packages/store-sync/src/zustand/common.ts @@ -1,4 +1,5 @@ -import { Table, SchemaToPrimitives } from "@latticexyz/store/internal"; +import { Table } from "@latticexyz/config"; +import { SchemaToPrimitives, getKeySchema, getSchemaTypes, getValueSchema } from "@latticexyz/protocol-parser/internal"; import { Hex } from "viem"; export type RawRecord = { @@ -16,6 +17,9 @@ export type TableRecord
= { readonly id: string; readonly table: table; readonly keyTuple: readonly Hex[]; - readonly key: SchemaToPrimitives; - readonly value: SchemaToPrimitives; + readonly key: SchemaToPrimitives>>; + readonly value: SchemaToPrimitives>>; + readonly fields: SchemaToPrimitives>; }; + +export type { Table }; diff --git a/packages/store-sync/src/zustand/createStorageAdapter.test.ts b/packages/store-sync/src/zustand/createStorageAdapter.test.ts index c26b7df3f1..63cf544712 100644 --- a/packages/store-sync/src/zustand/createStorageAdapter.test.ts +++ b/packages/store-sync/src/zustand/createStorageAdapter.test.ts @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from "vitest"; import { storeEventsAbi } from "@latticexyz/store"; import { createStorageAdapter } from "./createStorageAdapter"; import { createStore } from "./createStore"; -import { config, deployMockGame } from "../../test/mockGame"; +import { configV2 as config, deployMockGame } from "../../test/mockGame"; import { fetchAndStoreLogs } from "../fetchAndStoreLogs"; import { testClient } from "../../test/common"; import { getBlockNumber } from "viem/actions"; @@ -34,6 +34,11 @@ describe("createStorageAdapter", async () => { expect(useStore.getState().getRecords(config.tables.Position)).toMatchInlineSnapshot(` { "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb": { + "fields": { + "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", + "x": 3, + "y": 5, + }, "id": "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", "key": { "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", @@ -42,16 +47,25 @@ describe("createStorageAdapter", async () => { "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", ], "table": { - "keySchema": { + "codegen": { + "dataStruct": true, + "outputDirectory": "tables", + "storeArgument": false, + "tableIdArgument": false, + }, + "deploy": { + "disabled": false, + }, + "key": [ + "player", + ], + "name": "Position", + "namespace": "", + "schema": { "player": { "internalType": "address", "type": "address", }, - }, - "name": "Position", - "namespace": "", - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - "valueSchema": { "x": { "internalType": "int32", "type": "int32", @@ -61,6 +75,8 @@ describe("createStorageAdapter", async () => { "type": "int32", }, }, + "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", + "type": "table", }, "value": { "x": 3, @@ -68,6 +84,11 @@ describe("createStorageAdapter", async () => { }, }, "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e": { + "fields": { + "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", + "x": 1, + "y": -1, + }, "id": "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", "key": { "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", @@ -76,16 +97,25 @@ describe("createStorageAdapter", async () => { "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", ], "table": { - "keySchema": { + "codegen": { + "dataStruct": true, + "outputDirectory": "tables", + "storeArgument": false, + "tableIdArgument": false, + }, + "deploy": { + "disabled": false, + }, + "key": [ + "player", + ], + "name": "Position", + "namespace": "", + "schema": { "player": { "internalType": "address", "type": "address", }, - }, - "name": "Position", - "namespace": "", - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - "valueSchema": { "x": { "internalType": "int32", "type": "int32", @@ -95,6 +125,8 @@ describe("createStorageAdapter", async () => { "type": "int32", }, }, + "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", + "type": "table", }, "value": { "x": 1, @@ -102,6 +134,11 @@ describe("createStorageAdapter", async () => { }, }, "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6": { + "fields": { + "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", + "x": 3, + "y": 5, + }, "id": "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", "key": { "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", @@ -110,16 +147,25 @@ describe("createStorageAdapter", async () => { "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", ], "table": { - "keySchema": { + "codegen": { + "dataStruct": true, + "outputDirectory": "tables", + "storeArgument": false, + "tableIdArgument": false, + }, + "deploy": { + "disabled": false, + }, + "key": [ + "player", + ], + "name": "Position", + "namespace": "", + "schema": { "player": { "internalType": "address", "type": "address", }, - }, - "name": "Position", - "namespace": "", - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - "valueSchema": { "x": { "internalType": "int32", "type": "int32", @@ -129,6 +175,8 @@ describe("createStorageAdapter", async () => { "type": "int32", }, }, + "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", + "type": "table", }, "value": { "x": 3, @@ -136,6 +184,11 @@ describe("createStorageAdapter", async () => { }, }, "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2": { + "fields": { + "player": "0xdBa86119a787422C593ceF119E40887f396024E2", + "x": 100, + "y": 100, + }, "id": "0x74620000000000000000000000000000506f736974696f6e0000000000000000:0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", "key": { "player": "0xdBa86119a787422C593ceF119E40887f396024E2", @@ -144,16 +197,25 @@ describe("createStorageAdapter", async () => { "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", ], "table": { - "keySchema": { + "codegen": { + "dataStruct": true, + "outputDirectory": "tables", + "storeArgument": false, + "tableIdArgument": false, + }, + "deploy": { + "disabled": false, + }, + "key": [ + "player", + ], + "name": "Position", + "namespace": "", + "schema": { "player": { "internalType": "address", "type": "address", }, - }, - "name": "Position", - "namespace": "", - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - "valueSchema": { "x": { "internalType": "int32", "type": "int32", @@ -163,6 +225,8 @@ describe("createStorageAdapter", async () => { "type": "int32", }, }, + "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", + "type": "table", }, "value": { "x": 100, diff --git a/packages/store-sync/src/zustand/createStorageAdapter.ts b/packages/store-sync/src/zustand/createStorageAdapter.ts index aefa93bae7..f55c1424c0 100644 --- a/packages/store-sync/src/zustand/createStorageAdapter.ts +++ b/packages/store-sync/src/zustand/createStorageAdapter.ts @@ -1,4 +1,3 @@ -import { Tables } from "@latticexyz/store/internal"; import { StorageAdapter } from "../common"; import { RawRecord, TableRecord } from "./common"; import { ZustandStore } from "./createStore"; @@ -6,9 +5,16 @@ import { hexToResource, resourceToLabel, spliceHex } from "@latticexyz/common"; import { debug } from "./debug"; import { getId } from "./getId"; import { size } from "viem"; -import { decodeKey, decodeValueArgs } from "@latticexyz/protocol-parser/internal"; -import { flattenSchema } from "../flattenSchema"; +import { + KeySchema, + decodeKey, + decodeValueArgs, + getKeySchema, + getSchemaTypes, + getValueSchema, +} from "@latticexyz/protocol-parser/internal"; import { isDefined } from "@latticexyz/common/utils"; +import { Tables } from "@latticexyz/config"; export type CreateStorageAdapterOptions = { store: ZustandStore; @@ -131,14 +137,21 @@ export function createStorageAdapter({ return; } // TODO: warn if no table + + // TODO: update decodeKey to use more recent types + const key = decodeKey(getSchemaTypes(getKeySchema(table)) as KeySchema, rawRecord.keyTuple); + // TODO: update decodeValueArgs to use more recent types + const value = decodeValueArgs(getSchemaTypes(getValueSchema(table)), rawRecord); + return [ id, { id, table: store.getState().tables[rawRecord.tableId], keyTuple: rawRecord.keyTuple, - key: decodeKey(flattenSchema(table.keySchema), rawRecord.keyTuple), - value: decodeValueArgs(flattenSchema(table.valueSchema), rawRecord), + key, + value, + fields: { ...key, ...value }, } satisfies TableRecord, ]; }) diff --git a/packages/store-sync/src/zustand/createStore.ts b/packages/store-sync/src/zustand/createStore.ts index 67810bb8f3..e515b4faab 100644 --- a/packages/store-sync/src/zustand/createStore.ts +++ b/packages/store-sync/src/zustand/createStore.ts @@ -1,11 +1,10 @@ -import { SchemaToPrimitives, Table, Tables } from "@latticexyz/store/internal"; import { StoreApi, UseBoundStore, create } from "zustand"; import { RawRecord, TableRecord } from "./common"; import { Hex } from "viem"; -import { encodeKey } from "@latticexyz/protocol-parser/internal"; -import { flattenSchema } from "../flattenSchema"; +import { encodeKey, getKeySchema, getSchemaTypes } from "@latticexyz/protocol-parser/internal"; import { getId } from "./getId"; import { SyncStep } from "../SyncStep"; +import { Table, Tables } from "@latticexyz/config"; type TableRecords
= { readonly [id: string]: TableRecord
; @@ -36,11 +35,11 @@ export type ZustandState = { readonly getRecords:
(table: table) => TableRecords
; readonly getRecord:
( table: table, - key: SchemaToPrimitives, + key: TableRecord
["key"], ) => TableRecord
| undefined; readonly getValue:
( table: table, - key: SchemaToPrimitives, + key: TableRecord
["key"], ) => TableRecord
["value"] | undefined; }; @@ -69,17 +68,15 @@ export function createStore(opts: CreateStoreOptions record.table.tableId === table.tableId), ) as unknown as TableRecords
; }, - getRecord:
( - table: table, - key: SchemaToPrimitives, - ): TableRecord
| undefined => { - const keyTuple = encodeKey(flattenSchema(table.keySchema), key); + getRecord:
(table: table, key: TableRecord
["key"]): TableRecord
| undefined => { + // TODO: update encodeKey to use more recent types + const keyTuple = encodeKey(getSchemaTypes(getKeySchema(table)) as never, key as never); const id = getId({ tableId: table.tableId, keyTuple }); return get().records[id] as unknown as TableRecord
| undefined; }, getValue:
( table: table, - key: SchemaToPrimitives, + key: TableRecord
["key"], ): TableRecord
["value"] | undefined => { return get().getRecord(table, key)?.value; }, diff --git a/packages/store-sync/src/zustand/getAllTables.ts b/packages/store-sync/src/zustand/getAllTables.ts new file mode 100644 index 0000000000..d050127f3b --- /dev/null +++ b/packages/store-sync/src/zustand/getAllTables.ts @@ -0,0 +1,28 @@ +import { Store as StoreConfig } from "@latticexyz/store"; +import { Tables } from "@latticexyz/config"; +import { tablesByLabel } from "./tablesByLabel"; +import { mergeRight } from "./mergeRight"; +import storeConfig from "@latticexyz/store/mud.config"; +import worldConfig from "@latticexyz/world/mud.config"; + +const storeTables = storeConfig.tables; +type storeTables = typeof storeTables; + +const worldTables = worldConfig.tables; +type worldTables = typeof worldTables; + +export type getAllTables = tablesByLabel< + mergeRight>> +>; + +export function getAllTables( + config: config, + extraTables: extraTables, +): getAllTables { + return tablesByLabel({ + ...config.tables, + ...extraTables, + ...storeTables, + ...worldTables, + }) as never; +} diff --git a/packages/store-sync/src/zustand/mergeRight.ts b/packages/store-sync/src/zustand/mergeRight.ts new file mode 100644 index 0000000000..b45ef5e54a --- /dev/null +++ b/packages/store-sync/src/zustand/mergeRight.ts @@ -0,0 +1,7 @@ +export type mergeRight = { + readonly [key in keyof left | keyof right]: key extends keyof right + ? right[key] + : key extends keyof left + ? left[key] + : never; +}; diff --git a/packages/store-sync/src/zustand/syncToZustand.ts b/packages/store-sync/src/zustand/syncToZustand.ts index 38c4caaa19..61e75fd2ed 100644 --- a/packages/store-sync/src/zustand/syncToZustand.ts +++ b/packages/store-sync/src/zustand/syncToZustand.ts @@ -1,5 +1,4 @@ -import { ResolvedStoreConfig, Tables, resolveConfig } from "@latticexyz/store/internal"; -import { SyncOptions, SyncResult, storeTables, worldTables } from "../common"; +import { SyncOptions, SyncResult } from "../common"; import { createStoreSync } from "../createStoreSync"; import { ZustandStore } from "./createStore"; import { createStore } from "./createStore"; @@ -7,47 +6,35 @@ import { createStorageAdapter } from "./createStorageAdapter"; import { Address } from "viem"; import { SyncStep } from "../SyncStep"; import { Store as StoreConfig } from "@latticexyz/store"; -import { storeToV1 } from "@latticexyz/store/config/v2"; +import { Tables } from "@latticexyz/config"; +import { getAllTables } from "./getAllTables"; -type AllTables = ResolvedStoreConfig< - storeToV1 ->["tables"] & - (extraTables extends Tables ? extraTables : Record) & - typeof storeTables & - typeof worldTables; - -type SyncToZustandOptions = SyncOptions & { +type SyncToZustandOptions = Omit< + SyncOptions, + "address" | "config" +> & { // require address for now to keep the data model + retrieval simpler address: Address; config: config; tables?: extraTables; - store?: ZustandStore>; + store?: ZustandStore>; startSync?: boolean; }; -type SyncToZustandResult = SyncResult & { - tables: AllTables; - useStore: ZustandStore>; +type SyncToZustandResult = SyncResult & { + tables: getAllTables; + useStore: ZustandStore>; stopSync: () => void; }; -export async function syncToZustand({ +export async function syncToZustand({ config, - tables: extraTables, + tables: extraTables = {} as extraTables, store, startSync = true, ...syncOptions }: SyncToZustandOptions): Promise> { - // TODO: migrate this once we redo config to return fully resolved tables (https://github.com/latticexyz/mud/issues/1668) - // TODO: move store/world tables into `resolveConfig` - const resolvedConfig = resolveConfig(storeToV1(config as StoreConfig)); - const tables = { - ...resolvedConfig.tables, - ...extraTables, - ...storeTables, - ...worldTables, - } as unknown as AllTables; - + const tables = getAllTables(config, extraTables); const useStore = store ?? createStore({ tables }); const storageAdapter = createStorageAdapter({ store: useStore }); diff --git a/packages/store-sync/src/zustand/tablesByLabel.ts b/packages/store-sync/src/zustand/tablesByLabel.ts new file mode 100644 index 0000000000..2f18dc9376 --- /dev/null +++ b/packages/store-sync/src/zustand/tablesByLabel.ts @@ -0,0 +1,15 @@ +import { Tables } from "@latticexyz/config"; + +export type tablesByLabel = { + // TODO: switch from name to label once its available + readonly [key in string & keyof tables as tables[key]["name"]]: tables[key]; +}; + +export function tablesByLabel(tables: tables): tablesByLabel { + return Object.fromEntries( + Object.entries(tables).map(([, table]) => + // TODO: switch from name to label once its available + [table.name, table], + ), + ) as never; +}