diff --git a/packages/store-sync/package.json b/packages/store-sync/package.json index 17a41ceb44..daf2a74bc8 100644 --- a/packages/store-sync/package.json +++ b/packages/store-sync/package.json @@ -62,6 +62,7 @@ "test:ci": "vitest --run" }, "dependencies": { + "@arktype/util": "0.0.40", "@latticexyz/block-logs-stream": "workspace:*", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", diff --git a/packages/store-sync/src/zustand/tablesByLabel.test.ts b/packages/store-sync/src/zustand/configToTables.test.ts similarity index 63% rename from packages/store-sync/src/zustand/tablesByLabel.test.ts rename to packages/store-sync/src/zustand/configToTables.test.ts index 8082b9992c..3104083307 100644 --- a/packages/store-sync/src/zustand/tablesByLabel.test.ts +++ b/packages/store-sync/src/zustand/configToTables.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; -import { tablesByLabel } from "./tablesByLabel"; +import { configToTables } from "./configToTables"; import { defineWorld } from "@latticexyz/world"; import { resourceToHex } from "@latticexyz/common"; -describe("tablesByLabel", () => { - it("maps table label to component name", async () => { +describe("configToTables", () => { + it("flattens tables from single namespace", async () => { const config = defineWorld({ namespace: "app", tables: { @@ -12,7 +12,7 @@ describe("tablesByLabel", () => { }, }); - const tables = tablesByLabel(config.tables); + const tables = configToTables(config); expect(tables.ExceedsResourceNameSizeLimit.tableId).toBe( resourceToHex({ type: "table", @@ -23,4 +23,7 @@ describe("tablesByLabel", () => { expect(tables.ExceedsResourceNameSizeLimit.label).toBe("ExceedsResourceNameSizeLimit"); expect(tables.ExceedsResourceNameSizeLimit.name).toBe("ExceedsResourceN"); }); + + // TODO: add test with multiple namespaces + // TODO: add test where the label is the same for two tables in different namespaces to make sure TS + runtime agree on which takes precedence }); diff --git a/packages/store-sync/src/zustand/configToTables.ts b/packages/store-sync/src/zustand/configToTables.ts new file mode 100644 index 0000000000..8b7af9a514 --- /dev/null +++ b/packages/store-sync/src/zustand/configToTables.ts @@ -0,0 +1,30 @@ +import { satisfy, show } from "@arktype/util"; +import { Tables } from "@latticexyz/config"; +import { Store } from "@latticexyz/store"; + +type flattenedTables = config extends { readonly namespaces: infer namespaces } + ? { + [namespaceLabel in keyof namespaces]: namespaces[namespaceLabel] extends { readonly tables: infer tables } + ? `${namespaceLabel & string}__${keyof tables & string}` + : never; + }[keyof namespaces] + : never; + +// TODO: figure out how TS handles overlapping table labels so we can make runtime match +// TODO: move satisfy to type test + +export type configToTables = satisfy< + Tables, + { + readonly [key in flattenedTables as key extends `${string}__${infer tableLabel}` + ? tableLabel + : never]: key extends `${infer namespaceLabel}__${infer tableLabel}` + ? config["namespaces"][namespaceLabel]["tables"][tableLabel] + : never; + } +>; + +export function configToTables(config: config): show> { + const tables = Object.values(config.namespaces).flatMap((namespace) => Object.values(namespace.tables)); + return Object.fromEntries(tables.map((table) => [table.label, table])) as never; +} diff --git a/packages/store-sync/src/zustand/getAllTables.ts b/packages/store-sync/src/zustand/getAllTables.ts index d050127f3b..31c0e30b0a 100644 --- a/packages/store-sync/src/zustand/getAllTables.ts +++ b/packages/store-sync/src/zustand/getAllTables.ts @@ -1,28 +1,31 @@ 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"; +import { configToTables } from "./configToTables"; +import { satisfy, show } from "@arktype/util"; -const storeTables = storeConfig.tables; -type storeTables = typeof storeTables; +type mudTables = mergeRight, configToTables>; +const mudTables = { + ...configToTables(storeConfig), + ...configToTables(worldConfig), +}; -const worldTables = worldConfig.tables; -type worldTables = typeof worldTables; - -export type getAllTables = tablesByLabel< - mergeRight>> +// TODO: validate that extraTables keys correspond to table labels? +// TODO: move satisfy to type test +export type getAllTables = satisfy< + Tables, + mergeRight, mergeRight> >; export function getAllTables( config: config, extraTables: extraTables, -): getAllTables { - return tablesByLabel({ - ...config.tables, +): show> { + return { + ...configToTables(config), ...extraTables, - ...storeTables, - ...worldTables, - }) as never; + ...mudTables, + } as never; } diff --git a/packages/store-sync/src/zustand/tablesByLabel.ts b/packages/store-sync/src/zustand/tablesByLabel.ts deleted file mode 100644 index d247ef3223..0000000000 --- a/packages/store-sync/src/zustand/tablesByLabel.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Tables } from "@latticexyz/config"; - -export type tablesByLabel = { - readonly [key in string & keyof tables as tables[key]["label"]]: tables[key]; -}; - -export function tablesByLabel(tables: tables): tablesByLabel { - return Object.fromEntries(Object.entries(tables).map(([, table]) => [table.label, table])) as never; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6733c1e6c4..b7824886e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -874,6 +874,9 @@ importers: packages/store-sync: dependencies: + '@arktype/util': + specifier: 0.0.40 + version: 0.0.40 '@latticexyz/block-logs-stream': specifier: workspace:* version: link:../block-logs-stream