From 2b917462f66c1c71c50dbd3b2f7733d3e3646efe Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 4 Jul 2024 15:10:20 +0100 Subject: [PATCH 01/10] chore: bump pnpm setup action (#2927) --- .github/actions/setup-prerequisites/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-prerequisites/action.yml b/.github/actions/setup-prerequisites/action.yml index 8d9abe9b1f..b4d6534721 100644 --- a/.github/actions/setup-prerequisites/action.yml +++ b/.github/actions/setup-prerequisites/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 - name: Setup node uses: actions/setup-node@v3 From 5a8987e1e2fc15e268b09c959065e43430795a7d Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Fri, 5 Jul 2024 11:47:22 +0100 Subject: [PATCH 02/10] refactor(store,world): rename TS keys of resource labels (#2928) --- packages/store/ts/config/v2/input.ts | 8 ++++---- packages/store/ts/config/v2/output.ts | 2 +- packages/store/ts/config/v2/tables.ts | 10 +++++----- packages/world/ts/config/v2/input.ts | 2 +- packages/world/ts/config/v2/namespaces.ts | 2 +- packages/world/ts/config/v2/output.ts | 2 +- packages/world/ts/config/v2/systems.ts | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/store/ts/config/v2/input.ts b/packages/store/ts/config/v2/input.ts index 60c6b2dbad..dde3083d04 100644 --- a/packages/store/ts/config/v2/input.ts +++ b/packages/store/ts/config/v2/input.ts @@ -8,11 +8,11 @@ export type EnumsInput = { }; export type SchemaInput = { - readonly [key: string]: string; + readonly [fieldName: string]: string; }; export type ScopedSchemaInput = { - readonly [key: string]: keyof scope["types"]; + readonly [fieldName: string]: keyof scope["types"]; }; export type TableCodegenInput = Partial; @@ -30,7 +30,7 @@ export type TableInput = { }; export type TablesInput = { - readonly [key: string]: Omit; + readonly [label: string]: Omit; }; export type CodegenInput = Partial; @@ -55,7 +55,7 @@ export type StoreInput = { export type TableShorthandInput = SchemaInput | string; export type TablesWithShorthandsInput = { - readonly [key: string]: TableInput | TableShorthandInput; + readonly [label: string]: TableInput | TableShorthandInput; }; export type StoreWithShorthandsInput = evaluate & { tables: TablesWithShorthandsInput }>; diff --git a/packages/store/ts/config/v2/output.ts b/packages/store/ts/config/v2/output.ts index 9db233f7d6..91ea668e84 100644 --- a/packages/store/ts/config/v2/output.ts +++ b/packages/store/ts/config/v2/output.ts @@ -71,7 +71,7 @@ export type Store = { */ readonly sourceDirectory: string; readonly tables: { - readonly [namespacedTableName: string]: Table; + readonly [label: string]: Table; }; readonly userTypes: UserTypes; readonly enums: EnumsInput; diff --git a/packages/store/ts/config/v2/tables.ts b/packages/store/ts/config/v2/tables.ts index 3e25809b91..bd54bd98a7 100644 --- a/packages/store/ts/config/v2/tables.ts +++ b/packages/store/ts/config/v2/tables.ts @@ -5,8 +5,8 @@ import { Scope, AbiTypeScope } from "./scope"; import { validateTable, resolveTable } from "./table"; export type validateTables = { - [key in keyof tables]: tables[key] extends object - ? validateTable + [label in keyof tables]: tables[label] extends object + ? validateTable : ErrorMessage<`Expected full table config.`>; }; @@ -24,7 +24,7 @@ export function validateTables( } export type resolveTables = evaluate<{ - readonly [key in keyof tables]: resolveTable, scope>; + readonly [label in keyof tables]: resolveTable, scope>; }>; export function resolveTables( @@ -36,8 +36,8 @@ export function resolveTables { - return [key, resolveTable(mergeIfUndefined(table, { name: key }), scope)]; + Object.entries(tables).map(([label, table]) => { + return [label, resolveTable(mergeIfUndefined(table, { name: label }), scope)]; }), ) as never; } diff --git a/packages/world/ts/config/v2/input.ts b/packages/world/ts/config/v2/input.ts index f8e8927047..8879265d05 100644 --- a/packages/world/ts/config/v2/input.ts +++ b/packages/world/ts/config/v2/input.ts @@ -101,7 +101,7 @@ export type WorldInput = evaluate< } >; -export type NamespacesInput = { [key: string]: NamespaceInput }; +export type NamespacesInput = { [label: string]: NamespaceInput }; export type NamespaceInput = Pick; diff --git a/packages/world/ts/config/v2/namespaces.ts b/packages/world/ts/config/v2/namespaces.ts index 9050a727c9..f8b1e08853 100644 --- a/packages/world/ts/config/v2/namespaces.ts +++ b/packages/world/ts/config/v2/namespaces.ts @@ -38,7 +38,7 @@ export function validateNamespace( } export type validateNamespaces = { - [namespace in keyof namespaces]: validateNamespace; + [label in keyof namespaces]: validateNamespace; }; export function validateNamespaces( diff --git a/packages/world/ts/config/v2/output.ts b/packages/world/ts/config/v2/output.ts index 72f2354019..bbe2e3a92e 100644 --- a/packages/world/ts/config/v2/output.ts +++ b/packages/world/ts/config/v2/output.ts @@ -40,7 +40,7 @@ export type System = { readonly accessList: readonly string[]; }; -export type Systems = { readonly [key: string]: System }; +export type Systems = { readonly [label: string]: System }; export type Deploy = { /** The name of a custom World contract to deploy. If no name is provided, a default MUD World is deployed */ diff --git a/packages/world/ts/config/v2/systems.ts b/packages/world/ts/config/v2/systems.ts index 9440b0a5de..ce963486f2 100644 --- a/packages/world/ts/config/v2/systems.ts +++ b/packages/world/ts/config/v2/systems.ts @@ -4,7 +4,7 @@ import { SystemsInput } from "./input"; import { mergeIfUndefined } from "@latticexyz/store/config/v2"; export type resolveSystems = { - [system in keyof systems]: mergeIfUndefined; + [label in keyof systems]: mergeIfUndefined; }; export function resolveSystems(systems: systems): resolveSystems { From b1791a90b8acf5032c025ccb928d1689cf36a2ea Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 10 Jul 2024 14:18:13 +0100 Subject: [PATCH 03/10] build: remove npm tag by name (#2931) --- .github/workflows/remove-npm-tag.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/remove-npm-tag.yml b/.github/workflows/remove-npm-tag.yml index bbf4ed5ab4..6a0d554b15 100644 --- a/.github/workflows/remove-npm-tag.yml +++ b/.github/workflows/remove-npm-tag.yml @@ -2,6 +2,11 @@ name: Remove npm tag on: workflow_dispatch: + inputs: + tag: + description: "Tag name in npm" + type: string + required: true jobs: remove-npm-tag: @@ -21,4 +26,4 @@ jobs: - name: Remove npm dist tag run: pnpm dist-tag-rm - env: TAG=${{ github.ref_name }} + env: TAG=${{ github.event.inputs.tag }} From 0cd0e564bebf21422677b3f8c2dbb249d3a9db32 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 10 Jul 2024 14:20:46 +0100 Subject: [PATCH 04/10] build: fix env in rm npm tag action (#2932) --- .github/workflows/remove-npm-tag.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/remove-npm-tag.yml b/.github/workflows/remove-npm-tag.yml index 6a0d554b15..3943a72357 100644 --- a/.github/workflows/remove-npm-tag.yml +++ b/.github/workflows/remove-npm-tag.yml @@ -26,4 +26,5 @@ jobs: - name: Remove npm dist tag run: pnpm dist-tag-rm - env: TAG=${{ github.event.inputs.tag }} + env: + TAG: ${{ github.event.inputs.tag }} From bde67add68738fe412d2eb0fbd73cb93738a9d5b Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 10 Jul 2024 14:24:29 +0100 Subject: [PATCH 05/10] build: fix rm npm tag setup (#2933) --- .github/workflows/remove-npm-tag.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/remove-npm-tag.yml b/.github/workflows/remove-npm-tag.yml index 3943a72357..01f0620cc0 100644 --- a/.github/workflows/remove-npm-tag.yml +++ b/.github/workflows/remove-npm-tag.yml @@ -19,12 +19,15 @@ jobs: submodules: recursive fetch-depth: 0 + - name: Setup + uses: ./.github/actions/setup + - name: Set deployment token run: npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Remove npm dist tag - run: pnpm dist-tag-rm + run: pnpm run dist-tag-rm env: TAG: ${{ github.event.inputs.tag }} From 7129a16057a7fcc7195015a916bdf74e0809f3a2 Mon Sep 17 00:00:00 2001 From: David Blass Date: Thu, 11 Jul 2024 08:12:27 -0400 Subject: [PATCH 06/10] fix: preserve JsDoc on defineWorld output, bump @arktype/util (#2815) Co-authored-by: Kevin Ingersoll --- .changeset/good-rice-bathe.md | 8 +++++ .changeset/ninety-mirrors-pretend.md | 5 +++ package.json | 2 -- packages/common/src/type-utils/evaluate.ts | 2 -- packages/common/src/type-utils/index.ts | 2 -- packages/common/src/type-utils/satisfy.ts | 1 - packages/config/package.json | 1 + packages/config/src/common.ts | 2 +- packages/query/package.json | 1 + packages/query/src/api.ts | 2 +- packages/store/package.json | 3 +- packages/store/ts/config/v2/input.ts | 4 +-- packages/store/ts/config/v2/output.ts | 4 +-- packages/store/ts/config/v2/schema.ts | 4 +-- packages/store/ts/config/v2/scope.ts | 6 ++-- packages/store/ts/config/v2/store.ts | 6 ++-- packages/store/ts/config/v2/table.ts | 4 +-- packages/store/ts/config/v2/tables.ts | 4 +-- packages/world/package.json | 3 +- packages/world/ts/config/v2/input.ts | 4 +-- packages/world/ts/config/v2/world.ts | 14 ++++---- pnpm-lock.yaml | 41 +++++++++++----------- 22 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 .changeset/good-rice-bathe.md create mode 100644 .changeset/ninety-mirrors-pretend.md delete mode 100644 packages/common/src/type-utils/evaluate.ts delete mode 100644 packages/common/src/type-utils/satisfy.ts diff --git a/.changeset/good-rice-bathe.md b/.changeset/good-rice-bathe.md new file mode 100644 index 0000000000..1016ac7f1b --- /dev/null +++ b/.changeset/good-rice-bathe.md @@ -0,0 +1,8 @@ +--- +"@latticexyz/config": patch +"@latticexyz/query": patch +"@latticexyz/store": patch +"@latticexyz/world": patch +--- + +Bumped `@arktype/util` and moved `evaluate`/`satisfy` usages to its `show`/`satisfy` helpers. diff --git a/.changeset/ninety-mirrors-pretend.md b/.changeset/ninety-mirrors-pretend.md new file mode 100644 index 0000000000..c19441bf20 --- /dev/null +++ b/.changeset/ninety-mirrors-pretend.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/common": patch +--- + +Removed `evaluate` and `satisfy` type utils in favor of `show` and `satisfy` from `@arktype/util`. diff --git a/package.json b/package.json index b6350d9599..dc0100ac15 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,6 @@ "package.json": "pnpm sort-package-json" }, "devDependencies": { - "@arktype/attest": "0.7.5", - "@arktype/util": "0.0.43", "@changesets/cli": "^2.26.1", "@types/node": "^18.15.11", "@typescript-eslint/eslint-plugin": "7.1.1", diff --git a/packages/common/src/type-utils/evaluate.ts b/packages/common/src/type-utils/evaluate.ts deleted file mode 100644 index 14f9b55283..0000000000 --- a/packages/common/src/type-utils/evaluate.ts +++ /dev/null @@ -1,2 +0,0 @@ -// https://github.com/arktypeio/arktype/blob/93e79fa3d28567b7547e8e2df9a84e3c5b86e8e1/src/utils/generics.ts#L23 -export type evaluate = { [k in keyof t]: t[k] } & unknown; diff --git a/packages/common/src/type-utils/index.ts b/packages/common/src/type-utils/index.ts index 1cd21a40c7..866ee2904c 100644 --- a/packages/common/src/type-utils/index.ts +++ b/packages/common/src/type-utils/index.ts @@ -1,4 +1,2 @@ export * from "./abi"; export * from "./common"; -export * from "./evaluate"; -export * from "./satisfy"; diff --git a/packages/common/src/type-utils/satisfy.ts b/packages/common/src/type-utils/satisfy.ts deleted file mode 100644 index 61f7863418..0000000000 --- a/packages/common/src/type-utils/satisfy.ts +++ /dev/null @@ -1 +0,0 @@ -export type satisfy = t; diff --git a/packages/config/package.json b/packages/config/package.json index 804f98e76d..d6b98f3d98 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -48,6 +48,7 @@ "test:ci": "pnpm run test" }, "dependencies": { + "@arktype/util": "0.0.40", "@latticexyz/common": "workspace:*", "@latticexyz/schema-type": "workspace:*", "esbuild": "^0.17.15", diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index 037a910698..e08c34634a 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -1,7 +1,7 @@ import { Hex } from "viem"; import { DynamicAbiType, StaticAbiType } from "@latticexyz/schema-type/internal"; import { ResourceType } from "@latticexyz/common"; -import { satisfy } from "@latticexyz/common/type-utils"; +import { satisfy } from "@arktype/util"; /** * Common output types of a MUD config. We use these types as inputs for libraries. diff --git a/packages/query/package.json b/packages/query/package.json index 0593440c54..5855652bec 100644 --- a/packages/query/package.json +++ b/packages/query/package.json @@ -34,6 +34,7 @@ "test:ci": "pnpm run test" }, "dependencies": { + "@arktype/util": "0.0.40", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/schema-type": "workspace:*", diff --git a/packages/query/src/api.ts b/packages/query/src/api.ts index 48479801cd..27108ff30a 100644 --- a/packages/query/src/api.ts +++ b/packages/query/src/api.ts @@ -1,6 +1,6 @@ import { Hex } from "viem"; +import { satisfy } from "@arktype/util"; import { StaticPrimitiveType, DynamicPrimitiveType, SchemaAbiType } from "@latticexyz/schema-type/internal"; -import { satisfy } from "@latticexyz/common/type-utils"; import { SchemaToPrimitives } from "@latticexyz/store/internal"; import { Table } from "@latticexyz/config"; diff --git a/packages/store/package.json b/packages/store/package.json index a787d9e127..6d0df9bd52 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -67,7 +67,7 @@ "test:ci": "pnpm run test" }, "dependencies": { - "@arktype/util": "0.0.29", + "@arktype/util": "0.0.40", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/protocol-parser": "workspace:*", @@ -78,6 +78,7 @@ "zod": "^3.22.2" }, "devDependencies": { + "@arktype/attest": "0.7.5", "@latticexyz/abi-ts": "workspace:*", "@latticexyz/gas-report": "workspace:*", "@types/ejs": "^3.1.1", diff --git a/packages/store/ts/config/v2/input.ts b/packages/store/ts/config/v2/input.ts index dde3083d04..7f15491ca1 100644 --- a/packages/store/ts/config/v2/input.ts +++ b/packages/store/ts/config/v2/input.ts @@ -1,7 +1,7 @@ import { Hex } from "viem"; import { Codegen, TableCodegen, TableDeploy, UserTypes } from "./output"; import { Scope } from "./scope"; -import { evaluate } from "@arktype/util"; +import { show } from "@arktype/util"; export type EnumsInput = { readonly [enumName: string]: readonly [string, ...string[]]; @@ -58,4 +58,4 @@ export type TablesWithShorthandsInput = { readonly [label: string]: TableInput | TableShorthandInput; }; -export type StoreWithShorthandsInput = evaluate & { tables: TablesWithShorthandsInput }>; +export type StoreWithShorthandsInput = show & { tables: TablesWithShorthandsInput }>; diff --git a/packages/store/ts/config/v2/output.ts b/packages/store/ts/config/v2/output.ts index 91ea668e84..ddc4c63e45 100644 --- a/packages/store/ts/config/v2/output.ts +++ b/packages/store/ts/config/v2/output.ts @@ -1,4 +1,4 @@ -import { evaluate } from "@arktype/util"; +import { show } from "@arktype/util"; import { AbiType, Schema, Table as BaseTable } from "@latticexyz/config"; import { EnumsInput } from "./input"; @@ -33,7 +33,7 @@ export type TableDeploy = { readonly disabled: boolean; }; -export type Table = evaluate< +export type Table = show< BaseTable & { readonly codegen: TableCodegen; readonly deploy: TableDeploy; diff --git a/packages/store/ts/config/v2/schema.ts b/packages/store/ts/config/v2/schema.ts index 099d2fd820..e053a67b9c 100644 --- a/packages/store/ts/config/v2/schema.ts +++ b/packages/store/ts/config/v2/schema.ts @@ -1,4 +1,4 @@ -import { conform, evaluate } from "@arktype/util"; +import { conform, show } from "@arktype/util"; import { AbiTypeScope, Scope } from "./scope"; import { hasOwnKey, isObject } from "./generics"; import { SchemaInput } from "./input"; @@ -27,7 +27,7 @@ export function validateSchema( } } -export type resolveSchema = evaluate<{ +export type resolveSchema = show<{ readonly [key in keyof schema]: { /** the Solidity primitive ABI type */ readonly type: schema[key] extends FixedArrayAbiType diff --git a/packages/store/ts/config/v2/scope.ts b/packages/store/ts/config/v2/scope.ts index a5f70e6f5d..7c033f23cb 100644 --- a/packages/store/ts/config/v2/scope.ts +++ b/packages/store/ts/config/v2/scope.ts @@ -1,4 +1,4 @@ -import { Dict, evaluate } from "@arktype/util"; +import { Dict, show } from "@arktype/util"; import { SchemaInput } from "./input"; import { StaticAbiType, schemaAbiTypes } from "@latticexyz/schema-type/internal"; import { AbiType } from "./output"; @@ -24,8 +24,8 @@ export type getStaticAbiTypeKeys< [key in keyof schema]: scope["types"] extends { [_ in schema[key]]: StaticAbiType } ? key : never; }[keyof schema]; -export type extendScope> = evaluate< - ScopeOptions> +export type extendScope> = show< + ScopeOptions> >; export function extendScope>( diff --git a/packages/store/ts/config/v2/store.ts b/packages/store/ts/config/v2/store.ts index 1337a38286..7f7fe8cff8 100644 --- a/packages/store/ts/config/v2/store.ts +++ b/packages/store/ts/config/v2/store.ts @@ -1,4 +1,4 @@ -import { ErrorMessage, evaluate, flatMorph, narrow } from "@arktype/util"; +import { ErrorMessage, show, flatMorph, narrow } from "@arktype/util"; import { get, hasOwnKey, mergeIfUndefined } from "./generics"; import { UserTypes } from "./output"; import { CONFIG_DEFAULTS } from "./defaults"; @@ -59,8 +59,8 @@ export type resolveStore = { > : {}; readonly userTypes: "userTypes" extends keyof store ? store["userTypes"] : {}; - readonly enums: "enums" extends keyof store ? evaluate> : {}; - readonly enumValues: "enums" extends keyof store ? evaluate> : {}; + readonly enums: "enums" extends keyof store ? show> : {}; + readonly enumValues: "enums" extends keyof store ? show> : {}; readonly namespace: "namespace" extends keyof store ? store["namespace"] : CONFIG_DEFAULTS["namespace"]; readonly codegen: "codegen" extends keyof store ? resolveCodegen : resolveCodegen<{}>; }; diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index e07ef4863d..e44bd7c918 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -1,4 +1,4 @@ -import { ErrorMessage, conform, evaluate, narrow, requiredKeyOf } from "@arktype/util"; +import { ErrorMessage, conform, show, narrow, requiredKeyOf } from "@arktype/util"; import { isStaticAbiType } from "@latticexyz/schema-type/internal"; import { Hex } from "viem"; import { get, hasOwnKey, mergeIfUndefined } from "./generics"; @@ -101,7 +101,7 @@ export function validateTable( } } -export type resolveTableCodegen = evaluate<{ +export type resolveTableCodegen = show<{ [key in keyof TableCodegen]-?: key extends keyof input["codegen"] ? undefined extends input["codegen"][key] ? key extends "dataStruct" diff --git a/packages/store/ts/config/v2/tables.ts b/packages/store/ts/config/v2/tables.ts index bd54bd98a7..4d76aecbd9 100644 --- a/packages/store/ts/config/v2/tables.ts +++ b/packages/store/ts/config/v2/tables.ts @@ -1,4 +1,4 @@ -import { ErrorMessage, evaluate } from "@arktype/util"; +import { ErrorMessage, show } from "@arktype/util"; import { isObject, mergeIfUndefined } from "./generics"; import { TablesInput } from "./input"; import { Scope, AbiTypeScope } from "./scope"; @@ -23,7 +23,7 @@ export function validateTables( throw new Error(`Expected store config, received ${JSON.stringify(input)}`); } -export type resolveTables = evaluate<{ +export type resolveTables = show<{ readonly [label in keyof tables]: resolveTable, scope>; }>; diff --git a/packages/world/package.json b/packages/world/package.json index dc8a4885a5..0b7ce19f61 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -63,7 +63,7 @@ "test:ci": "pnpm run test" }, "dependencies": { - "@arktype/util": "0.0.29", + "@arktype/util": "0.0.40", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/protocol-parser": "workspace:*", @@ -75,6 +75,7 @@ "zod": "^3.22.2" }, "devDependencies": { + "@arktype/attest": "0.7.5", "@latticexyz/abi-ts": "workspace:*", "@latticexyz/gas-report": "workspace:*", "@types/ejs": "^3.1.1", diff --git a/packages/world/ts/config/v2/input.ts b/packages/world/ts/config/v2/input.ts index 8879265d05..2967703ded 100644 --- a/packages/world/ts/config/v2/input.ts +++ b/packages/world/ts/config/v2/input.ts @@ -1,4 +1,4 @@ -import { evaluate } from "@arktype/util"; +import { show } from "@arktype/util"; import { StoreInput, StoreWithShorthandsInput } from "@latticexyz/store/config/v2"; import { DynamicResolution, ValueWithType } from "./dynamicResolution"; @@ -79,7 +79,7 @@ export type CodegenInput = { worldImportPath?: string; }; -export type WorldInput = evaluate< +export type WorldInput = show< StoreInput & { namespaces?: NamespacesInput; /** diff --git a/packages/world/ts/config/v2/world.ts b/packages/world/ts/config/v2/world.ts index a04d90ed42..5fc6257b4f 100644 --- a/packages/world/ts/config/v2/world.ts +++ b/packages/world/ts/config/v2/world.ts @@ -1,4 +1,4 @@ -import { ErrorMessage, conform, evaluate, narrow } from "@arktype/util"; +import { ErrorMessage, conform, narrow, type withJsDoc } from "@arktype/util"; import { UserTypes, extendedScope, @@ -19,6 +19,7 @@ import { resolveSystems } from "./systems"; import { resolveNamespacedTables, validateNamespaces } from "./namespaces"; import { resolveCodegen } from "./codegen"; import { resolveDeploy } from "./deploy"; +import type { World } from "./output.js"; export type validateWorld = { readonly [key in keyof world]: key extends "tables" @@ -44,7 +45,7 @@ export function validateWorld(world: unknown): asserts world is WorldInput { } } -export type resolveWorld = evaluate< +export type resolveWorld = withJsDoc< resolveStore & mergeIfUndefined< { tables: resolveNamespacedTables } & Omit< @@ -60,7 +61,8 @@ export type resolveWorld = evaluate< "namespaces" | keyof Store >, CONFIG_DEFAULTS - > + >, + World >; export function resolveWorld(world: world): resolveWorld { @@ -99,7 +101,7 @@ export function resolveWorld(world: world): reso ) as never; } -export function defineWorld(world: validateWorld): resolveWorld { - validateWorld(world); - return resolveWorld(world) as never; +export function defineWorld(input: validateWorld): resolveWorld { + validateWorld(input); + return resolveWorld(input) as never; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99d0e672ae..38b03accc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,6 @@ importers: .: devDependencies: - '@arktype/attest': - specifier: 0.7.5 - version: 0.7.5(typescript@5.4.2) - '@arktype/util': - specifier: 0.0.43 - version: 0.0.43 '@changesets/cli': specifier: ^2.26.1 version: 2.26.1 @@ -321,6 +315,9 @@ importers: packages/config: dependencies: + '@arktype/util': + specifier: 0.0.40 + version: 0.0.40 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -555,6 +552,9 @@ importers: packages/query: dependencies: + '@arktype/util': + specifier: 0.0.40 + version: 0.0.40 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -711,8 +711,8 @@ importers: packages/store: dependencies: '@arktype/util': - specifier: 0.0.29 - version: 0.0.29 + specifier: 0.0.40 + version: 0.0.40 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -738,6 +738,9 @@ importers: specifier: ^3.22.2 version: 3.23.7 devDependencies: + '@arktype/attest': + specifier: 0.7.5 + version: 0.7.5(typescript@5.4.2) '@latticexyz/abi-ts': specifier: workspace:* version: link:../abi-ts @@ -1009,8 +1012,8 @@ importers: packages/world: dependencies: '@arktype/util': - specifier: 0.0.29 - version: 0.0.29 + specifier: 0.0.40 + version: 0.0.40 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -1039,6 +1042,9 @@ importers: specifier: ^3.22.2 version: 3.23.7 devDependencies: + '@arktype/attest': + specifier: 0.7.5 + version: 0.7.5(typescript@5.4.2) '@latticexyz/abi-ts': specifier: workspace:* version: link:../abi-ts @@ -1202,18 +1208,15 @@ packages: '@arktype/schema@0.1.2': resolution: {integrity: sha512-ggvxs5P0toqd/4/XK76URQrtyOYpbYcLhirEZeTso6FxkloPa0lT+whPg7DNQj5qi2OQXLUHBYKMx9DOb13ViQ==} - '@arktype/util@0.0.29': - resolution: {integrity: sha512-fDTBSVzxLj9k1ZjinkawmaQdcXFKMBVK8c+vqMPxwoa94mPMZxBo84yQcqyFVcIcWIkg6qQQmH1ozyT4nqFT/g==} - '@arktype/util@0.0.38': resolution: {integrity: sha512-IvYMGnkUASJllRk3mdBVgckomKx2LNsDTrWCxz04EBK1OuU+4fJ/smSjxgZVWfopNXZds9sHNxZgTJOIw7GvJw==} + '@arktype/util@0.0.40': + resolution: {integrity: sha512-dwC3xZh9Bz6LWSJq71AUoh06zB0qM65N4zS/NNogbumhbO55yot7yqDlv0qeBMNOWXj/gX7l7l58v0EqEaXN2w==} + '@arktype/util@0.0.41': resolution: {integrity: sha512-0YURzJ42v+lhlP1t5Dj90YezETRTCdFU0oM4xMVpYsmPx/DHJzr9n7AX1QPAlYWH4wY7hYY3gwai3O+8VntPgw==} - '@arktype/util@0.0.43': - resolution: {integrity: sha512-tVrvIoghAI/OY6Oc8wo8dpgiZc/a+C8M7NnpSDvfkagAasW9CzLtEbybP1xAALf3A6y5cuJOV6cCcIi9NKQRzA==} - '@aws-crypto/ie11-detection@3.0.0': resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} @@ -6911,13 +6914,11 @@ snapshots: dependencies: '@arktype/util': 0.0.38 - '@arktype/util@0.0.29': {} - '@arktype/util@0.0.38': {} - '@arktype/util@0.0.41': {} + '@arktype/util@0.0.40': {} - '@arktype/util@0.0.43': {} + '@arktype/util@0.0.41': {} '@aws-crypto/ie11-detection@3.0.0': dependencies: From f8363c247256cb3f0377cb36bbcd6e9029ad3dda Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 11 Jul 2024 13:25:12 +0100 Subject: [PATCH 07/10] refactor: build step cleanup (#2926) --- package.json | 2 +- packages/cli/scripts/generate-test-tables.ts | 3 +- packages/cli/src/build.ts | 10 ++--- packages/cli/src/commands/build.ts | 4 +- packages/cli/src/commands/tablegen.ts | 3 +- packages/cli/src/runDeploy.ts | 2 +- .../config/src/deprecated/node/loadConfig.ts | 2 +- packages/store/package.json | 2 +- packages/store/ts/codegen/tablegen.ts | 9 ++-- packages/store/ts/config/v2/output.ts | 2 +- .../ts/scripts/{tablegen.ts => build.ts} | 7 +--- .../store/ts/scripts/generate-test-tables.ts | 3 +- packages/world-modules/ts/scripts/tablegen.ts | 9 ++-- packages/world/package.json | 2 +- packages/world/ts/node/findSolidityFiles.ts | 14 +++++++ packages/world/ts/scripts/build.ts | 41 +++++++++++++++++++ .../world/ts/scripts/generate-test-tables.ts | 3 +- packages/world/ts/scripts/tablegen.ts | 14 ------- packages/world/ts/scripts/worldgen.ts | 35 ---------------- 19 files changed, 87 insertions(+), 80 deletions(-) rename packages/store/ts/scripts/{tablegen.ts => build.ts} (78%) create mode 100644 packages/world/ts/node/findSolidityFiles.ts create mode 100644 packages/world/ts/scripts/build.ts delete mode 100644 packages/world/ts/scripts/tablegen.ts delete mode 100644 packages/world/ts/scripts/worldgen.ts diff --git a/package.json b/package.json index dc0100ac15..df2157f242 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/latticexyz/mud.git" }, "scripts": { - "all-codegen": "for dir in packages/store packages/world packages/world-modules packages/cli test/mock-game-contracts e2e/packages/contracts examples/*/packages/contracts templates/*/packages/contracts; do (cd \"$dir\" && pwd && pnpm build); done", + "all-build": "for dir in packages/store packages/world packages/world-modules packages/cli test/mock-game-contracts e2e/packages/contracts examples/*/packages/contracts templates/*/packages/contracts; do (cd \"$dir\" && pwd && pnpm build); done", "all-install": "for dir in . docs e2e examples/* templates/*; do (cd \"$dir\" && pwd && pnpm install); done", "bench": "pnpm run --recursive bench", "build": "turbo run build", diff --git a/packages/cli/scripts/generate-test-tables.ts b/packages/cli/scripts/generate-test-tables.ts index 8274f805e9..a564fdfdf1 100644 --- a/packages/cli/scripts/generate-test-tables.ts +++ b/packages/cli/scripts/generate-test-tables.ts @@ -2,6 +2,7 @@ import { tablegen } from "@latticexyz/store/codegen"; import { defineStore } from "@latticexyz/store"; import { getRemappings } from "@latticexyz/common/foundry"; import { fileURLToPath } from "node:url"; +import path from "node:path"; const configPath = fileURLToPath(import.meta.url); @@ -97,4 +98,4 @@ const config = defineStore({ const remappings = await getRemappings(); -await tablegen({ configPath, config, remappings }); +await tablegen({ rootDir: path.dirname(configPath), config, remappings }); diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 9b8fda5f1c..8d90517bd2 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -10,16 +10,16 @@ type BuildOptions = { foundryProfile?: string; srcDir: string; /** - * Path to `mud.config.ts`. We use this as the "project root" to resolve other relative paths. + * MUD project root directory where all other relative paths are resolved from. * - * Defaults to finding the nearest `mud.config.ts`, looking in `process.cwd()` and moving up the directory tree. + * Defaults to the directory of the nearest `mud.config.ts`, looking in `process.cwd()` and moving up the directory tree. */ - configPath: string; + rootDir: string; config: WorldConfig; }; export async function build({ - configPath, + rootDir, config, srcDir, foundryProfile = process.env.FOUNDRY_PROFILE, @@ -28,7 +28,7 @@ export async function build({ const remappings = await getRemappings(foundryProfile); await Promise.all([ - tablegen({ configPath, config, remappings }), + tablegen({ rootDir, config, remappings }), worldgen(config, getExistingContracts(srcDir), outPath), ]); diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index abc37486f4..fba961ee84 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -1,9 +1,9 @@ import type { CommandModule } from "yargs"; import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; import { World as WorldConfig } from "@latticexyz/world"; - import { getSrcDirectory } from "@latticexyz/common/foundry"; import { build } from "../build"; +import path from "node:path"; type Options = { configPath?: string; @@ -27,7 +27,7 @@ const commandModule: CommandModule = { const config = (await loadConfig(configPath)) as WorldConfig; const srcDir = await getSrcDirectory(); - await build({ configPath, config, srcDir, foundryProfile: opts.profile }); + await build({ rootDir: path.dirname(configPath), config, srcDir, foundryProfile: opts.profile }); process.exit(0); }, diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 210b2885f2..cc406dc1bd 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -3,6 +3,7 @@ import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; import { Store as StoreConfig } from "@latticexyz/store"; import { tablegen } from "@latticexyz/store/codegen"; import { getRemappings } from "@latticexyz/common/foundry"; +import path from "node:path"; type Options = { configPath?: string; @@ -24,7 +25,7 @@ const commandModule: CommandModule = { const config = (await loadConfig(configPath)) as StoreConfig; const remappings = await getRemappings(); - await tablegen({ configPath, config, remappings }); + await tablegen({ rootDir: path.dirname(configPath), config, remappings }); process.exit(0); }, diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index ce9f48fb75..c8c9c3fa09 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -83,7 +83,7 @@ export async function runDeploy(opts: DeployOptions): Promise { // Run build if (!opts.skipBuild) { - await build({ configPath, config: configV2, srcDir, foundryProfile: profile }); + await build({ rootDir: path.dirname(configPath), config: configV2, srcDir, foundryProfile: profile }); } const resolvedConfig = resolveConfig({ config, forgeSourceDir: srcDir, forgeOutDir: outDir }); diff --git a/packages/config/src/deprecated/node/loadConfig.ts b/packages/config/src/deprecated/node/loadConfig.ts index 9d4a52f338..b117bc7ca6 100644 --- a/packages/config/src/deprecated/node/loadConfig.ts +++ b/packages/config/src/deprecated/node/loadConfig.ts @@ -40,7 +40,7 @@ export async function loadConfig(configPath?: string): Promise { } /** @deprecated */ -export async function resolveConfigPath(configPath: string | undefined, toFileURL?: boolean) { +export async function resolveConfigPath(configPath?: string, toFileURL?: boolean) { if (configPath === undefined) { configPath = await getUserConfigPath(); } else { diff --git a/packages/store/package.json b/packages/store/package.json index 6d0df9bd52..ce2c3e4876 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -54,7 +54,7 @@ "build:abi": "forge build", "build:abi-ts": "abi-ts", "build:js": "tsup", - "build:mud": "tsx ./ts/scripts/tablegen.ts && tsx ./ts/scripts/generate-test-tables.ts", + "build:mud": "tsx ./ts/scripts/build.ts && tsx ./ts/scripts/generate-test-tables.ts", "build:tightcoder": "tsx ./ts/scripts/generate-tightcoder.ts", "clean": "pnpm run clean:abi && pnpm run clean:js && pnpm run clean:mud", "clean:abi": "forge clean", diff --git a/packages/store/ts/codegen/tablegen.ts b/packages/store/ts/codegen/tablegen.ts index faf65aaa61..fe547032c3 100644 --- a/packages/store/ts/codegen/tablegen.ts +++ b/packages/store/ts/codegen/tablegen.ts @@ -9,13 +9,16 @@ import { Store as StoreConfig } from "../config/v2/output"; import { storeToV1 } from "../config/v2/compat"; export type TablegenOptions = { - configPath: string; + /** + * MUD project root directory where all other relative paths are resolved from. + */ + rootDir: string; config: StoreConfig; remappings: [string, string][]; }; -export async function tablegen({ configPath, config, remappings }: TablegenOptions) { - const outputDirectory = path.join(path.dirname(configPath), config.sourceDirectory, config.codegen.outputDirectory); +export async function tablegen({ rootDir, config, remappings }: TablegenOptions) { + const outputDirectory = path.join(rootDir, config.sourceDirectory, config.codegen.outputDirectory); const configV1 = storeToV1(config); const solidityUserTypes = loadAndExtractUserTypes(configV1.userTypes, outputDirectory, remappings); const allTableOptions = getTableOptions(config, solidityUserTypes); diff --git a/packages/store/ts/config/v2/output.ts b/packages/store/ts/config/v2/output.ts index ddc4c63e45..d744750ca9 100644 --- a/packages/store/ts/config/v2/output.ts +++ b/packages/store/ts/config/v2/output.ts @@ -64,7 +64,7 @@ export type Codegen = { export type Store = { /** - * Directory of contracts source (i.e. Solidity) relative to the MUD config. + * Directory of Solidity source relative to the MUD config. * This is used to resolve other paths in the config, like codegen and user types. * * Defaults to `src` to match `foundry.toml`'s default. If you change this from the default, you may also need to configure foundry with the same source directory. diff --git a/packages/store/ts/scripts/tablegen.ts b/packages/store/ts/scripts/build.ts similarity index 78% rename from packages/store/ts/scripts/tablegen.ts rename to packages/store/ts/scripts/build.ts index 7a255b21f1..b207284106 100644 --- a/packages/store/ts/scripts/tablegen.ts +++ b/packages/store/ts/scripts/build.ts @@ -2,13 +2,10 @@ import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; import { getRemappings } from "@latticexyz/common/foundry"; import { tablegen } from "../codegen"; import { Store as StoreConfig } from "../config/v2/output"; +import path from "node:path"; const configPath = await resolveConfigPath(undefined); const config = (await loadConfig(configPath)) as StoreConfig; const remappings = await getRemappings(); -await tablegen({ - configPath, - config, - remappings, -}); +await tablegen({ rootDir: path.dirname(configPath), config, remappings }); diff --git a/packages/store/ts/scripts/generate-test-tables.ts b/packages/store/ts/scripts/generate-test-tables.ts index 5d7683d71f..0a61b15d24 100644 --- a/packages/store/ts/scripts/generate-test-tables.ts +++ b/packages/store/ts/scripts/generate-test-tables.ts @@ -2,6 +2,7 @@ import { getRemappings } from "@latticexyz/common/foundry"; import { tablegen } from "../codegen"; import { defineStore } from "../config/v2/store"; import { fileURLToPath } from "node:url"; +import path from "node:path"; const configPath = fileURLToPath(import.meta.url); @@ -53,4 +54,4 @@ const config = defineStore({ const remappings = await getRemappings(); -await tablegen({ configPath, config, remappings }); +await tablegen({ rootDir: path.dirname(configPath), config, remappings }); diff --git a/packages/world-modules/ts/scripts/tablegen.ts b/packages/world-modules/ts/scripts/tablegen.ts index bac85728d6..f97cc26648 100644 --- a/packages/world-modules/ts/scripts/tablegen.ts +++ b/packages/world-modules/ts/scripts/tablegen.ts @@ -2,13 +2,10 @@ import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; import { getRemappings } from "@latticexyz/common/foundry"; import { Store as StoreConfig } from "@latticexyz/store"; import { tablegen } from "@latticexyz/store/codegen"; +import path from "node:path"; -const configPath = await resolveConfigPath(undefined); +const configPath = await resolveConfigPath(); const config = (await loadConfig(configPath)) as StoreConfig; const remappings = await getRemappings(); -await tablegen({ - configPath, - config, - remappings, -}); +await tablegen({ rootDir: path.dirname(configPath), config, remappings }); diff --git a/packages/world/package.json b/packages/world/package.json index 0b7ce19f61..7adaadad73 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -51,7 +51,7 @@ "build:abi": "forge build", "build:abi-ts": "abi-ts", "build:js": "tsup", - "build:mud": "tsx ./ts/scripts/tablegen.ts && tsx ./ts/scripts/worldgen.ts && tsx ./ts/scripts/generate-test-tables.ts", + "build:mud": "tsx ./ts/scripts/build.ts && tsx ./ts/scripts/generate-test-tables.ts", "clean": "pnpm run clean:abi && pnpm run clean:js && pnpm run clean:mud", "clean:abi": "forge clean", "clean:js": "rimraf dist", diff --git a/packages/world/ts/node/findSolidityFiles.ts b/packages/world/ts/node/findSolidityFiles.ts new file mode 100644 index 0000000000..6e61125fbd --- /dev/null +++ b/packages/world/ts/node/findSolidityFiles.ts @@ -0,0 +1,14 @@ +import path from "node:path"; +import { glob } from "glob"; +import { World } from "../config/v2/output"; + +export async function findSolidityFiles({ rootDir, config }: { rootDir: string; config: World }) { + const files = await glob(path.join(config.sourceDirectory, "**", "*.sol"), { + cwd: rootDir, + }); + + return files.sort().map((filename) => ({ + filename, + basename: path.basename(filename, ".sol"), + })); +} diff --git a/packages/world/ts/scripts/build.ts b/packages/world/ts/scripts/build.ts new file mode 100644 index 0000000000..6d9401776e --- /dev/null +++ b/packages/world/ts/scripts/build.ts @@ -0,0 +1,41 @@ +import path from "node:path"; +import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; +import { getRemappings } from "@latticexyz/common/foundry"; +import { tablegen } from "@latticexyz/store/codegen"; +import { findSolidityFiles } from "../node/findSolidityFiles"; +import { World } from "../config/v2"; +import { worldgen } from "../node"; + +/** + * To avoid circular dependencies, we run a very similar `build` step as `cli` package here. + */ + +// TODO: turn this into something we can run from CLI, then we can import/use it via CLI and here rather than duplicating. +// TODO: do the same for store build too + +const configPath = await resolveConfigPath(); +const rootDir = path.dirname(configPath); +const config = (await loadConfig(configPath)) as World; +const remappings = await getRemappings(); + +// TODO: move this into worldgen +const existingContracts = (await findSolidityFiles({ rootDir, config })).map((file) => ({ + path: file.filename, + basename: file.basename, +})); +const codegenDirectory = path.join(config.sourceDirectory, config.codegen.outputDirectory); + +// TODO: clean + +await Promise.all([ + tablegen({ rootDir, config, remappings }), + worldgen( + { + ...config, + // override the namespace to be the root namespace for generating the core system interface + namespace: "", + }, + existingContracts, + codegenDirectory, + ), +]); diff --git a/packages/world/ts/scripts/generate-test-tables.ts b/packages/world/ts/scripts/generate-test-tables.ts index 4f310140a6..392596fd96 100644 --- a/packages/world/ts/scripts/generate-test-tables.ts +++ b/packages/world/ts/scripts/generate-test-tables.ts @@ -2,6 +2,7 @@ import { getRemappings } from "@latticexyz/common/foundry"; import { tablegen } from "@latticexyz/store/codegen"; import { defineWorld } from "../config/v2/world"; import { fileURLToPath } from "node:url"; +import path from "node:path"; const configPath = fileURLToPath(import.meta.url); @@ -42,4 +43,4 @@ const config = defineWorld({ const remappings = await getRemappings(); -await tablegen({ configPath, config, remappings }); +await tablegen({ rootDir: path.dirname(configPath), config, remappings }); diff --git a/packages/world/ts/scripts/tablegen.ts b/packages/world/ts/scripts/tablegen.ts deleted file mode 100644 index bac85728d6..0000000000 --- a/packages/world/ts/scripts/tablegen.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; -import { getRemappings } from "@latticexyz/common/foundry"; -import { Store as StoreConfig } from "@latticexyz/store"; -import { tablegen } from "@latticexyz/store/codegen"; - -const configPath = await resolveConfigPath(undefined); -const config = (await loadConfig(configPath)) as StoreConfig; -const remappings = await getRemappings(); - -await tablegen({ - configPath, - config, - remappings, -}); diff --git a/packages/world/ts/scripts/worldgen.ts b/packages/world/ts/scripts/worldgen.ts deleted file mode 100644 index 366db3fae8..0000000000 --- a/packages/world/ts/scripts/worldgen.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { globSync } from "glob"; -import path, { basename } from "path"; -import { rmSync } from "fs"; -import { loadConfig } from "@latticexyz/config/node"; -import { getSrcDirectory } from "@latticexyz/common/foundry"; -import { worldgen } from "../node"; -import { World as WorldConfig } from "../config/v2/output"; - -// TODO dedupe this and cli's worldgen command -const configPath = undefined; -const clean = false; -const srcDir = await getSrcDirectory(); - -// Get a list of all contract names -const existingContracts = globSync(`${srcDir}/**/*.sol`) - .sort() - .map((path) => ({ - path, - basename: basename(path, ".sol"), - })); - -// Load and resolve the config -const mudConfig = (await loadConfig(configPath)) as WorldConfig; - -const outputBaseDirectory = path.join(srcDir, mudConfig.codegen.outputDirectory); - -// clear the worldgen directory -if (clean) { - rmSync(path.join(outputBaseDirectory, mudConfig.codegen.worldgenDirectory), { recursive: true, force: true }); -} - -// generate new interfaces -// override the namespace to be the root namespace for generating the core system interface -const rootMudConfig = { ...mudConfig, namespace: "" }; -await worldgen(rootMudConfig, existingContracts, outputBaseDirectory); From bc947685c0b9cfe00f3c473a8eed6aa41f8e6ee0 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 11 Jul 2024 17:16:26 +0100 Subject: [PATCH 08/10] refactor(store-sync): update recs sync to use new config (#2539) --- .changeset/real-pigs-work.md | 5 ++++ ...e.test.ts => createStorageAdapter.test.ts} | 12 ++++------ ...recsStorage.ts => createStorageAdapter.ts} | 15 ++++++------ .../src/recs/defineInternalComponents.ts | 2 ++ packages/store-sync/src/recs/index.ts | 2 +- packages/store-sync/src/recs/syncToRecs.ts | 13 +++++----- .../store-sync/src/recs/tableToComponent.ts | 24 ++++++++----------- .../src/recs/tablesToComponents.test.ts | 21 ++++++++++++++++ .../store-sync/src/recs/tablesToComponents.ts | 10 ++++---- 9 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 .changeset/real-pigs-work.md rename packages/store-sync/src/recs/{recsStorage.test.ts => createStorageAdapter.test.ts} (86%) rename packages/store-sync/src/recs/{recsStorage.ts => createStorageAdapter.ts} (92%) create mode 100644 packages/store-sync/src/recs/tablesToComponents.test.ts diff --git a/.changeset/real-pigs-work.md b/.changeset/real-pigs-work.md new file mode 100644 index 0000000000..50c5f5426e --- /dev/null +++ b/.changeset/real-pigs-work.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/store-sync": patch +--- + +Refactored `syncToRecs` to use new Store config under the hood, removing compatibility layers and improving performance. diff --git a/packages/store-sync/src/recs/recsStorage.test.ts b/packages/store-sync/src/recs/createStorageAdapter.test.ts similarity index 86% rename from packages/store-sync/src/recs/recsStorage.test.ts rename to packages/store-sync/src/recs/createStorageAdapter.test.ts index fbe22bce3a..f768db2f46 100644 --- a/packages/store-sync/src/recs/recsStorage.test.ts +++ b/packages/store-sync/src/recs/createStorageAdapter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { recsStorage } from "./recsStorage"; +import { createStorageAdapter } from "./createStorageAdapter"; import { createWorld, getComponentEntities, getComponentValue } from "@latticexyz/recs"; import mudConfig from "../../../../e2e/packages/contracts/mud.config"; import worldRpcLogs from "../../../../test-data/world-logs.json"; @@ -8,10 +8,8 @@ import { StoreEventsLog } from "../common"; import { singletonEntity } from "./singletonEntity"; import { RpcLog, formatLog, decodeEventLog, Hex } from "viem"; import { storeEventsAbi } from "@latticexyz/store"; -import { resolveConfig } from "@latticexyz/store/internal"; -import { storeToV1 } from "@latticexyz/store/config/v2"; -const tables = resolveConfig(storeToV1(mudConfig)).tables; +const tables = mudConfig.tables; // TODO: make test-data a proper package and export this const blocks = groupLogsByBlockNumber( @@ -27,10 +25,10 @@ const blocks = groupLogsByBlockNumber( }), ); -describe("recsStorage", () => { +describe("createStorageAdapter", () => { it("creates components", async () => { const world = createWorld(); - const { components } = recsStorage({ world, tables }); + const { components } = createStorageAdapter({ world, tables }); expect(components.NumberList.id).toMatchInlineSnapshot( '"0x746200000000000000000000000000004e756d6265724c697374000000000000"', ); @@ -38,7 +36,7 @@ describe("recsStorage", () => { it("sets component values from logs", async () => { const world = createWorld(); - const { storageAdapter, components } = recsStorage({ world, tables }); + const { storageAdapter, components } = createStorageAdapter({ world, tables }); for (const block of blocks) { await storageAdapter(block); diff --git a/packages/store-sync/src/recs/recsStorage.ts b/packages/store-sync/src/recs/createStorageAdapter.ts similarity index 92% rename from packages/store-sync/src/recs/recsStorage.ts rename to packages/store-sync/src/recs/createStorageAdapter.ts index 0604cd72f0..dd15532f96 100644 --- a/packages/store-sync/src/recs/recsStorage.ts +++ b/packages/store-sync/src/recs/createStorageAdapter.ts @@ -1,4 +1,4 @@ -import { Table, resolveConfig } from "@latticexyz/store/internal"; +import { Table } from "@latticexyz/config"; import { debug } from "./debug"; import { World as RecsWorld, getComponentValue, hasComponent, removeComponent, setComponent } from "@latticexyz/recs"; import { defineInternalComponents } from "./defineInternalComponents"; @@ -14,18 +14,17 @@ import { singletonEntity } from "./singletonEntity"; import storeConfig from "@latticexyz/store/mud.config"; import worldConfig from "@latticexyz/world/mud.config"; import { TablesToComponents, tablesToComponents } from "./tablesToComponents"; -import { storeToV1 } from "@latticexyz/store/config/v2"; -const storeTables = resolveConfig(storeToV1(storeConfig)).tables; -const worldTables = resolveConfig(storeToV1(worldConfig)).tables; +const storeTables = storeConfig.tables; +const worldTables = worldConfig.tables; -export type RecsStorageOptions> = { +export type CreateStorageAdapterOptions> = { world: RecsWorld; tables: tables; shouldSkipUpdateStream?: () => boolean; }; -export type RecsStorageAdapter> = { +export type CreateStorageAdapterResult> = { storageAdapter: StorageAdapter; components: TablesToComponents & TablesToComponents & @@ -33,11 +32,11 @@ export type RecsStorageAdapter> = { ReturnType; }; -export function recsStorage>({ +export function createStorageAdapter>({ world, tables, shouldSkipUpdateStream, -}: RecsStorageOptions): RecsStorageAdapter { +}: CreateStorageAdapterOptions): CreateStorageAdapterResult { world.registerEntity({ id: singletonEntity }); const components = { diff --git a/packages/store-sync/src/recs/defineInternalComponents.ts b/packages/store-sync/src/recs/defineInternalComponents.ts index 5b974cbd65..24794d97c6 100644 --- a/packages/store-sync/src/recs/defineInternalComponents.ts +++ b/packages/store-sync/src/recs/defineInternalComponents.ts @@ -1,6 +1,8 @@ import { World, defineComponent, Type, Component, Schema, Metadata } from "@latticexyz/recs"; import { Table } from "../common"; +export type InternalComponents = ReturnType; + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function defineInternalComponents(world: World) { return { diff --git a/packages/store-sync/src/recs/index.ts b/packages/store-sync/src/recs/index.ts index d7df9e00b3..f6cffb18c2 100644 --- a/packages/store-sync/src/recs/index.ts +++ b/packages/store-sync/src/recs/index.ts @@ -4,6 +4,6 @@ export * from "./encodeEntity"; export * from "./entityToHexKeyTuple"; export * from "./hexKeyTupleToEntity"; export * from "./isStoreComponent"; -export * from "./recsStorage"; +export * from "./createStorageAdapter"; export * from "./singletonEntity"; export * from "./syncToRecs"; diff --git a/packages/store-sync/src/recs/syncToRecs.ts b/packages/store-sync/src/recs/syncToRecs.ts index 2643cfaebf..c845cd2878 100644 --- a/packages/store-sync/src/recs/syncToRecs.ts +++ b/packages/store-sync/src/recs/syncToRecs.ts @@ -1,9 +1,8 @@ -import { Table, ResolvedStoreConfig, resolveConfig } from "@latticexyz/store/internal"; +import { Table } from "@latticexyz/config"; import { Store as StoreConfig } from "@latticexyz/store"; -import { storeToV1 } from "@latticexyz/store/config/v2"; import { Component as RecsComponent, World as RecsWorld, getComponentValue, setComponent } from "@latticexyz/recs"; import { SyncOptions, SyncResult } from "../common"; -import { RecsStorageAdapter, recsStorage } from "./recsStorage"; +import { CreateStorageAdapterResult, createStorageAdapter } from "./createStorageAdapter"; import { createStoreSync } from "../createStoreSync"; import { singletonEntity } from "./singletonEntity"; import { SyncStep } from "../SyncStep"; @@ -19,7 +18,7 @@ type SyncToRecsOptions> = SyncResult & { - components: RecsStorageAdapter>["tables"] & extraTables>["components"]; + components: CreateStorageAdapterResult["components"]; stopSync: () => void; }; @@ -31,11 +30,11 @@ export async function syncToRecs): Promise> { const tables = { - ...resolveConfig(storeToV1(config as StoreConfig)).tables, + ...config.tables, ...extraTables, - } as ResolvedStoreConfig>["tables"] & extraTables; + } as config["tables"] & extraTables; - const { storageAdapter, components } = recsStorage({ + const { storageAdapter, components } = createStorageAdapter({ world, tables, shouldSkipUpdateStream: (): boolean => diff --git a/packages/store-sync/src/recs/tableToComponent.ts b/packages/store-sync/src/recs/tableToComponent.ts index 0634fa171a..b195671935 100644 --- a/packages/store-sync/src/recs/tableToComponent.ts +++ b/packages/store-sync/src/recs/tableToComponent.ts @@ -1,10 +1,10 @@ import { Component, Type, World, defineComponent } from "@latticexyz/recs"; import { StoreComponentMetadata } from "./common"; import { SchemaAbiTypeToRecsType, schemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType"; -import { SchemaAbiType } from "@latticexyz/schema-type/internal"; -import { Table } from "@latticexyz/store/internal"; +import { Table } from "@latticexyz/config"; import { mapObject } from "@latticexyz/common/utils"; import { ResourceLabel, resourceToLabel } from "@latticexyz/common"; +import { getKeySchema, getSchemaTypes, getValueSchema } from "@latticexyz/protocol-parser/internal"; export type TableToComponent = Component< { @@ -12,27 +12,23 @@ export type TableToComponent
= Component< __encodedLengths: Type.OptionalString; __dynamicData: Type.OptionalString; } & { - [fieldName in keyof table["valueSchema"] & string]: Type & - SchemaAbiTypeToRecsType; + [fieldName in keyof getValueSchema
]: Type & SchemaAbiTypeToRecsType; }, StoreComponentMetadata & { componentName: table["name"]; tableName: ResourceLabel; - keySchema: { [name in keyof table["keySchema"] & string]: table["keySchema"][name]["type"] }; - valueSchema: { [name in keyof table["valueSchema"] & string]: table["valueSchema"][name]["type"] }; + keySchema: getSchemaTypes>; + valueSchema: getSchemaTypes>; } >; export function tableToComponent
(world: World, table: table): TableToComponent
{ + const keySchema = getSchemaTypes(getKeySchema(table)); + const valueSchema = getSchemaTypes(getValueSchema(table)); return defineComponent( world, { - ...Object.fromEntries( - Object.entries(table.valueSchema).map(([fieldName, { type: schemaAbiType }]) => [ - fieldName, - schemaAbiTypeToRecsType[schemaAbiType as SchemaAbiType], - ]), - ), + ...mapObject(valueSchema, (type) => schemaAbiTypeToRecsType[type]), __staticData: Type.OptionalString, __encodedLengths: Type.OptionalString, __dynamicData: Type.OptionalString, @@ -42,8 +38,8 @@ export function tableToComponent
(world: World, table: table metadata: { componentName: table.name, tableName: resourceToLabel(table), - keySchema: mapObject(table.keySchema, ({ type }) => type), - valueSchema: mapObject(table.valueSchema, ({ type }) => type), + keySchema, + valueSchema, }, }, ) as TableToComponent
; diff --git a/packages/store-sync/src/recs/tablesToComponents.test.ts b/packages/store-sync/src/recs/tablesToComponents.test.ts new file mode 100644 index 0000000000..60376b2636 --- /dev/null +++ b/packages/store-sync/src/recs/tablesToComponents.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { createWorld } from "@latticexyz/recs"; +import { tablesToComponents } from "./tablesToComponents"; +import { defineWorld } from "@latticexyz/world"; + +describe("tablesToComponents", () => { + // Eventually, config output will truncate table names and the label will move to a `label` key. + // This test ensures we still have components named by their labels so we don't forget to update this code path. + it("maps table label to component name", async () => { + const world = createWorld(); + const config = defineWorld({ + namespace: "app", + tables: { + ExceedsResourceNameSizeLimit: "bytes32", + }, + }); + + const components = tablesToComponents(world, config.tables); + expect(components.ExceedsResourceNameSizeLimit.metadata.componentName).toBe("ExceedsResourceNameSizeLimit"); + }); +}); diff --git a/packages/store-sync/src/recs/tablesToComponents.ts b/packages/store-sync/src/recs/tablesToComponents.ts index 8806dd43fc..926fe34fb7 100644 --- a/packages/store-sync/src/recs/tablesToComponents.ts +++ b/packages/store-sync/src/recs/tablesToComponents.ts @@ -1,15 +1,17 @@ -import { Table } from "@latticexyz/store/internal"; +import { Table } from "@latticexyz/config"; import { TableToComponent, tableToComponent } from "./tableToComponent"; -import { mapObject } from "@latticexyz/common/utils"; import { World } from "@latticexyz/recs"; export type TablesToComponents> = { - [tableName in keyof tables]: TableToComponent; + // TODO: update this to table label once available in the config output + [label in keyof tables as tables[label]["name"]]: TableToComponent; }; export function tablesToComponents>( world: World, tables: tables, ): TablesToComponents { - return mapObject(tables, (table) => tableToComponent(world, table)); + return Object.fromEntries( + Object.entries(tables).map(([, table]) => [table.name, tableToComponent(world, table)]), + ) as never; } From 811049a072d7ca88b123fed59674e017a06a4576 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 11 Jul 2024 17:38:51 +0100 Subject: [PATCH 09/10] chore: remove unused store-sync/query-cache (#2937) --- packages/store-sync/src/query-cache/common.ts | 55 - .../query-cache/createStorageAdapter.test.ts | 76 - .../src/query-cache/createStorageAdapter.ts | 140 -- .../store-sync/src/query-cache/createStore.ts | 51 - packages/store-sync/src/query-cache/debug.ts | 10 - .../store-sync/src/query-cache/query.test.ts | 438 ---- packages/store-sync/src/query-cache/query.ts | 27 - .../store-sync/src/query-cache/queryToWire.ts | 38 - .../src/query-cache/subjectsToWire.ts | 20 - .../src/query-cache/subscribeToQuery.test.ts | 1873 ----------------- .../src/query-cache/subscribeToQuery.ts | 124 -- .../src/query-cache/syncToQueryCache.ts | 42 - .../query-cache/test/createHydratedStore.ts | 44 - .../src/query-cache/test/minePending.ts | 9 - .../query-cache/test/waitForTransaction.ts | 13 - 15 files changed, 2960 deletions(-) delete mode 100644 packages/store-sync/src/query-cache/common.ts delete mode 100644 packages/store-sync/src/query-cache/createStorageAdapter.test.ts delete mode 100644 packages/store-sync/src/query-cache/createStorageAdapter.ts delete mode 100644 packages/store-sync/src/query-cache/createStore.ts delete mode 100644 packages/store-sync/src/query-cache/debug.ts delete mode 100644 packages/store-sync/src/query-cache/query.test.ts delete mode 100644 packages/store-sync/src/query-cache/query.ts delete mode 100644 packages/store-sync/src/query-cache/queryToWire.ts delete mode 100644 packages/store-sync/src/query-cache/subjectsToWire.ts delete mode 100644 packages/store-sync/src/query-cache/subscribeToQuery.test.ts delete mode 100644 packages/store-sync/src/query-cache/subscribeToQuery.ts delete mode 100644 packages/store-sync/src/query-cache/syncToQueryCache.ts delete mode 100644 packages/store-sync/src/query-cache/test/createHydratedStore.ts delete mode 100644 packages/store-sync/src/query-cache/test/minePending.ts delete mode 100644 packages/store-sync/src/query-cache/test/waitForTransaction.ts diff --git a/packages/store-sync/src/query-cache/common.ts b/packages/store-sync/src/query-cache/common.ts deleted file mode 100644 index 2352e4ec12..0000000000 --- a/packages/store-sync/src/query-cache/common.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Store } from "@latticexyz/store"; -import { Table } from "@latticexyz/config"; -import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-type/internal"; -import { ComparisonCondition, InCondition } from "@latticexyz/query"; - -export type mapTuple = { - [key in keyof tuple]: tuple[key] extends keyof mapping ? mapping[tuple[key]] : never; -}; - -export type subjectSchemaToPrimitive = { - [key in keyof tuple]: tuple[key] extends SchemaAbiType ? SchemaAbiTypeToPrimitiveType : never; -}; - -export type Tables = Store["tables"]; - -export type TableSubjectItem
= keyof table["schema"]; - -export type TableSubject
= readonly [ - TableSubjectItem
, - ...TableSubjectItem
[], -]; - -export type schemaAbiTypes> = { - [key in keyof schema]: schema[key]["type"]; -}; - -type tableConditions = { - [field in keyof table["schema"]]: - | [ - `${tableName}.${field & string}`, - ComparisonCondition["op"], - SchemaAbiTypeToPrimitiveType, - ] - | [ - `${tableName}.${field & string}`, - InCondition["op"], - readonly SchemaAbiTypeToPrimitiveType[], - ]; -}[keyof table["schema"]]; - -type queryConditions = { - [tableName in keyof tables]: tableConditions; -}[keyof tables]; - -export type Query = { - readonly from: { - readonly [k in keyof tables]?: readonly [keyof tables[k]["schema"], ...(keyof tables[k]["schema"])[]]; - }; - readonly except?: { - readonly [k in keyof tables]?: readonly [keyof tables[k]["schema"], ...(keyof tables[k]["schema"])[]]; - }; - readonly where?: readonly queryConditions[]; -}; - -export type extractTables = T extends Query ? tables : never; diff --git a/packages/store-sync/src/query-cache/createStorageAdapter.test.ts b/packages/store-sync/src/query-cache/createStorageAdapter.test.ts deleted file mode 100644 index 8df44cfe11..0000000000 --- a/packages/store-sync/src/query-cache/createStorageAdapter.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { storeEventsAbi } from "@latticexyz/store"; -import { createStorageAdapter } from "./createStorageAdapter"; -import { createStore } from "./createStore"; -import { configV2 as config, deployMockGame } from "../../test/mockGame"; -import { fetchAndStoreLogs } from "../fetchAndStoreLogs"; -import { testClient } from "../../test/common"; -import { getBlockNumber } from "viem/actions"; -import { Address } from "viem"; - -describe.skip("createStorageAdapter", async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let worldAddress: Address; - beforeAll(async () => { - worldAddress = await deployMockGame(); - }); - - it("sets component values from logs", async () => { - const useStore = createStore({ tables: config.tables }); - const storageAdapter = createStorageAdapter({ store: useStore }); - - console.log("fetching blocks"); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const block of fetchAndStoreLogs({ - storageAdapter, - publicClient: testClient, - events: storeEventsAbi, - fromBlock: 0n, - toBlock: await getBlockNumber(testClient), - })) { - // console.log("got block", block.blockNumber); - } - - expect(useStore.getState().records.map((record) => record.fields)).toMatchInlineSnapshot(` - [ - { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - { - "health": 5n, - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - }, - { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - { - "health": 5n, - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - }, - { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - { - "health": 0n, - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - }, - { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - { - "terrainType": 2, - "x": 3, - "y": 5, - }, - ] - `); - }); -}); diff --git a/packages/store-sync/src/query-cache/createStorageAdapter.ts b/packages/store-sync/src/query-cache/createStorageAdapter.ts deleted file mode 100644 index 4b990b3899..0000000000 --- a/packages/store-sync/src/query-cache/createStorageAdapter.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { StorageAdapter } from "../common"; -import { QueryCacheStore, RawTableRecord, TableRecord } from "./createStore"; -import { hexToResource, resourceToLabel, spliceHex } from "@latticexyz/common"; -import { Hex, concatHex, size } from "viem"; -import { - KeySchema, - decodeKey, - decodeValueArgs, - getKeySchema, - getValueSchema, -} from "@latticexyz/protocol-parser/internal"; -import { flattenSchema } from "../flattenSchema"; -import debug from "debug"; -import { Tables } from "./common"; - -function getRecordId({ tableId, keyTuple }: { tableId: Hex; keyTuple: readonly Hex[] }): string { - return `${tableId}:${concatHex(keyTuple)}`; -} - -export type CreateStorageAdapterOptions = { - store: store; -}; - -// TS isn't happy when we use the strongly typed store for the function definition so we -// overload the strongly typed variant here and allow the more generic version in the function. -export function createStorageAdapter({ - store, -}: CreateStorageAdapterOptions): StorageAdapter; - -export function createStorageAdapter({ store }: CreateStorageAdapterOptions>): StorageAdapter { - return async function queryCacheStorageAdapter({ logs }) { - const touchedIds = new Set(); - - const { tables, rawRecords: previousRawRecords, records: previousRecords } = store.getState(); - const tableList = Object.values(tables); - const updatedRawRecords: { [id: string]: RawTableRecord } = {}; - - for (const log of logs) { - const table = tableList.find((table) => table.tableId === log.args.tableId); - if (!table) { - const { namespace, name } = hexToResource(log.args.tableId); - debug( - `skipping update for unknown table: ${resourceToLabel({ namespace, name })} (${log.args.tableId}) at ${ - log.address - }`, - ); - continue; - } - - const id = getRecordId(log.args); - - if (log.eventName === "Store_SetRecord") { - // debug("setting record", { namespace: table.namespace, name: table.name, id, log }); - updatedRawRecords[id] = { - id, - table, - keyTuple: log.args.keyTuple, - staticData: log.args.staticData, - encodedLengths: log.args.encodedLengths, - dynamicData: log.args.dynamicData, - }; - touchedIds.add(id); - } else if (log.eventName === "Store_SpliceStaticData") { - // debug("splicing static data", { namespace: table.namespace, name: table.name, id, log }); - const previousRecord = - previousRawRecords.find((record) => record.id === id) ?? - ({ - id, - table, - keyTuple: log.args.keyTuple, - staticData: "0x", - encodedLengths: "0x", - dynamicData: "0x", - } satisfies RawTableRecord); - const staticData = spliceHex(previousRecord.staticData, log.args.start, size(log.args.data), log.args.data); - updatedRawRecords[id] = { - ...previousRecord, - staticData, - }; - touchedIds.add(id); - } else if (log.eventName === "Store_SpliceDynamicData") { - // debug("splicing dynamic data", { namespace: table.namespace, name: table.name, id, log }); - const previousRecord = - previousRawRecords.find((record) => record.id === id) ?? - ({ - id, - table, - keyTuple: log.args.keyTuple, - staticData: "0x", - encodedLengths: "0x", - dynamicData: "0x", - } satisfies RawTableRecord); - const encodedLengths = log.args.encodedLengths; - const dynamicData = spliceHex(previousRecord.dynamicData, log.args.start, log.args.deleteCount, log.args.data); - updatedRawRecords[id] = { - ...previousRecord, - encodedLengths, - dynamicData, - }; - touchedIds.add(id); - } else if (log.eventName === "Store_DeleteRecord") { - // debug("deleting record", { namespace: table.namespace, name: table.name, id, log }); - delete updatedRawRecords[id]; - touchedIds.add(id); - } - } - - if (!touchedIds.size) return; - - const rawRecords: readonly RawTableRecord[] = [ - ...previousRawRecords.filter((record) => !touchedIds.has(record.id)), - ...Object.values(updatedRawRecords), - ]; - - const records: readonly TableRecord[] = [ - ...previousRecords.filter((record) => !touchedIds.has(record.id)), - ...Object.values(updatedRawRecords).map((rawRecord): TableRecord => { - const keySchema = flattenSchema(getKeySchema(rawRecord.table)); - const key = decodeKey(keySchema as KeySchema, rawRecord.keyTuple); - const value = decodeValueArgs(flattenSchema(getValueSchema(rawRecord.table)), rawRecord); - - return { - table: rawRecord.table, - id: rawRecord.id, - keyTuple: rawRecord.keyTuple, - // TODO: do something to make sure this stays ordered? - primaryKey: Object.values(key), - key, - value, - fields: { ...key, ...value }, - }; - }), - ]; - - store.setState({ - rawRecords, - records, - }); - }; -} diff --git a/packages/store-sync/src/query-cache/createStore.ts b/packages/store-sync/src/query-cache/createStore.ts deleted file mode 100644 index 8bff3d18a8..0000000000 --- a/packages/store-sync/src/query-cache/createStore.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { StoreApi, UseBoundStore, create } from "zustand"; -import { Table } from "@latticexyz/config"; -import { Tables } from "./common"; -import { Hex } from "viem"; -import { StaticPrimitiveType } from "@latticexyz/schema-type/internal"; -import { SchemaToPrimitives } from "@latticexyz/store/internal"; -import { getKeySchema, getValueSchema } from "@latticexyz/protocol-parser/internal"; - -export type RawTableRecord
= { - readonly table: table; - /** @internal Internal unique ID */ - readonly id: string; - readonly keyTuple: readonly Hex[]; - readonly staticData: Hex; - readonly encodedLengths: Hex; - readonly dynamicData: Hex; -}; - -export type TableRecord
= { - readonly table: table; - /** @internal Internal unique ID */ - readonly id: string; - readonly keyTuple: readonly Hex[]; - readonly primaryKey: readonly StaticPrimitiveType[]; - readonly key: SchemaToPrimitives>; - readonly value: SchemaToPrimitives>; - readonly fields: SchemaToPrimitives; -}; - -export type QueryCacheState = { - readonly tables: tables; - readonly rawRecords: readonly RawTableRecord[]; - readonly records: readonly TableRecord[]; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type QueryCacheStore = UseBoundStore>>; - -export type extractTables = T extends QueryCacheStore ? tables : never; - -export type CreateStoreOptions = { - tables: tables; -}; - -export function createStore({ tables }: CreateStoreOptions): QueryCacheStore { - return create>(() => ({ - tables, - rawRecords: [], - records: [], - })); -} diff --git a/packages/store-sync/src/query-cache/debug.ts b/packages/store-sync/src/query-cache/debug.ts deleted file mode 100644 index b74e4d9bf0..0000000000 --- a/packages/store-sync/src/query-cache/debug.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { debug as parentDebug } from "../debug"; - -export const debug = parentDebug.extend("query-cache"); -export const error = parentDebug.extend("query-cache"); - -// Pipe debug output to stdout instead of stderr -debug.log = console.debug.bind(console); - -// Pipe error output to stderr -error.log = console.error.bind(console); diff --git a/packages/store-sync/src/query-cache/query.test.ts b/packages/store-sync/src/query-cache/query.test.ts deleted file mode 100644 index 46178132c5..0000000000 --- a/packages/store-sync/src/query-cache/query.test.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { createHydratedStore } from "./test/createHydratedStore"; -import { query } from "./query"; -import { deployMockGame } from "../../test/mockGame"; -import { Address } from "viem"; - -describe.skip("query", async () => { - let worldAddress: Address; - beforeAll(async () => { - worldAddress = await deployMockGame(); - }); - - it("can get players with a position", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Position: ["player"], - }, - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - }, - ], - } - `); - }); - - it("can get players at position (3, 5)", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", "=", 3], - ["Position.y", "=", 5], - ], - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - } - `); - }); - - it("can get players within the bounds of (-5, -5) and (5, 5)", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", ">=", -5], - ["Position.x", "<=", 5], - ["Position.y", ">=", -5], - ["Position.y", "<=", 5], - ], - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - } - `); - }); - - it("can get players that are still alive", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Position: ["player"], - Health: ["player"], - }, - where: [["Health.health", "!=", 0n]], - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "health": 5n, - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - ], - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "health": 5n, - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - ], - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - ], - } - `); - }); - - it("can get all players in grassland", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Terrain: ["x", "y"], - }, - where: [["Terrain.terrainType", "=", 2]], - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "terrainType": 2, - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000005", - ], - "primaryKey": [ - 3, - 5, - ], - "tableId": "0x746200000000000000000000000000005465727261696e000000000000000000", - }, - ], - "subject": [ - 3, - 5, - ], - "subjectSchema": [ - "int32", - "int32", - ], - }, - ], - } - `); - }); - - it("can get all players without health (e.g. spectator)", async () => { - const { store } = await createHydratedStore(worldAddress); - const result = await query(store, { - from: { - Position: ["player"], - }, - except: { - Health: ["player"], - }, - }); - - expect(result).toMatchInlineSnapshot(` - { - "subjects": [ - { - "records": [ - { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - }, - ], - } - `); - }); -}); diff --git a/packages/store-sync/src/query-cache/query.ts b/packages/store-sync/src/query-cache/query.ts deleted file mode 100644 index 9aaf12de56..0000000000 --- a/packages/store-sync/src/query-cache/query.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Query } from "./common"; -import { QueryCacheStore, extractTables } from "./createStore"; -import { SubjectRecords } from "@latticexyz/query"; -import { findSubjects } from "@latticexyz/query/internal"; -import { queryToWire } from "./queryToWire"; - -// TODO: take in query input type so we can narrow result types - -export type QueryResult = { - subjects: readonly SubjectRecords[]; -}; - -export async function query>>( - store: store, - query: query, -): Promise { - const { tables, records } = store.getState(); - - const subjects = findSubjects({ - records, - query: queryToWire(tables, query), - }); - - return { - subjects, - }; -} diff --git a/packages/store-sync/src/query-cache/queryToWire.ts b/packages/store-sync/src/query-cache/queryToWire.ts deleted file mode 100644 index 5ac31030c6..0000000000 --- a/packages/store-sync/src/query-cache/queryToWire.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Query, Tables } from "./common"; -import { QueryCondition as WireQueryCondition, Query as WireQuery } from "@latticexyz/query"; -import { subjectsToWire } from "./subjectsToWire"; - -// TODO: validate query -// - one subject per table -// - underlying subject field types match -// - only keys as subjects for now? -// - subjects and conditions all have valid fields -// - all subjects match -// - can only compare like types? -// - `where` tables are in `from` - -export function queryToWire>( - tables: tables, - query: query, -): WireQuery { - // TODO: move out validation to its own thing - // TODO: validate that all query subjects match in underlying abi types - // TODO: do other validations - - const where = (query.where ?? []).map(([leftTableField, op, right]): WireQueryCondition => { - // TODO: translate table field - const left = leftTableField; - const [tableName, fieldName] = left.split("."); - const table = tables[tableName]; - if (op === "in") { - return { left: { tableId: table.tableId, field: fieldName }, op, right }; - } - return { left: { tableId: table.tableId, field: fieldName }, op, right }; - }); - - return { - from: subjectsToWire(tables, query.from), - except: subjectsToWire(tables, query.except ?? {}), - where, - }; -} diff --git a/packages/store-sync/src/query-cache/subjectsToWire.ts b/packages/store-sync/src/query-cache/subjectsToWire.ts deleted file mode 100644 index c076313e50..0000000000 --- a/packages/store-sync/src/query-cache/subjectsToWire.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TableSubject, Tables } from "./common"; -import { QuerySubject } from "@latticexyz/query"; - -// TODO: validate -// - all subject types match -// - only keys as subjects for now? - -export function subjectsToWire( - tables: Tables, - subjects: { - [tableName in keyof tables]?: TableSubject; - }, -): readonly QuerySubject[] { - // TODO: validate `tables` contains all tables used `subjects` map - // TODO: validate that subject field names exist in table schema - return Object.entries(subjects).map(([tableName, subject]) => ({ - tableId: tables[tableName].tableId, - subject, - })); -} diff --git a/packages/store-sync/src/query-cache/subscribeToQuery.test.ts b/packages/store-sync/src/query-cache/subscribeToQuery.test.ts deleted file mode 100644 index a1ea43e1df..0000000000 --- a/packages/store-sync/src/query-cache/subscribeToQuery.test.ts +++ /dev/null @@ -1,1873 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { createHydratedStore } from "./test/createHydratedStore"; -import { subscribeToQuery } from "./subscribeToQuery"; -import { deployMockGame, worldAbi } from "../../test/mockGame"; -import { writeContract } from "viem/actions"; -import { Address, keccak256, parseEther, stringToHex } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { testClient } from "../../test/common"; -import { combineLatest, filter, firstValueFrom, map, scan, shareReplay } from "rxjs"; -import { waitForTransaction } from "./test/waitForTransaction"; -import { SubjectEvent, SubjectRecord } from "@latticexyz/query"; - -const henryAccount = privateKeyToAccount(keccak256(stringToHex("henry"))); - -describe.skip("subscribeToQuery", async () => { - let worldAddress: Address; - beforeAll(async () => { - await testClient.setBalance({ address: henryAccount.address, value: parseEther("1") }); - worldAddress = await deployMockGame(); - }); - - it("can get players with a position", async () => { - const { store, fetchLatestLogs } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [1, 2], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 1, - "y": 2, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 1, - "y": 2, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("can get players at position (3, 5)", async () => { - const { store, fetchLatestLogs } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", "=", 3], - ["Position.y", "=", 5], - ], - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [3, 5], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [2, 4], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "exit", - }, - ], - }, - "subjects$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("can get players within the bounds of (-5, -5) and (5, 5)", async () => { - const { store, fetchLatestLogs } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", ">=", -5], - ["Position.x", "<=", 5], - ["Position.y", ">=", -5], - ["Position.y", "<=", 5], - ], - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [3, 5], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [100, 100], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "exit", - }, - ], - }, - "subjects$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - "x": 1, - "y": -1, - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("can get players that are still alive", async () => { - const { store } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - Health: ["player"], - }, - where: [["Health.health", "!=", 0n]], - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "health": 5n, - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - { - "record": { - "fields": { - "health": 5n, - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "health": 5n, - "player": "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - }, - "keyTuple": [ - "0x0000000000000000000000001d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - ], - "primaryKey": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - "subject": [ - "0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "health": 5n, - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x746200000000000000000000000000004865616c746800000000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("can get all players in grassland", async () => { - const { store } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Terrain: ["x", "y"], - }, - where: [["Terrain.terrainType", "=", 2]], - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "terrainType": 2, - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000005", - ], - "primaryKey": [ - 3, - 5, - ], - "tableId": "0x746200000000000000000000000000005465727261696e000000000000000000", - }, - "subject": [ - 3, - 5, - ], - "subjectSchema": [ - "int32", - "int32", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "terrainType": 2, - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000005", - ], - "primaryKey": [ - 3, - 5, - ], - "tableId": "0x746200000000000000000000000000005465727261696e000000000000000000", - }, - "subject": [ - 3, - 5, - ], - "subjectSchema": [ - "int32", - "int32", - ], - }, - ], - }, - } - `); - }); - - it("can get all players without health (e.g. spectator)", async () => { - const { store } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - except: { - Health: ["player"], - }, - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 1, - "value": [ - { - "record": { - "fields": { - "player": "0xdBa86119a787422C593ceF119E40887f396024E2", - "x": 100, - "y": 100, - }, - "keyTuple": [ - "0x000000000000000000000000dba86119a787422c593cef119e40887f396024e2", - ], - "primaryKey": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0xdBa86119a787422C593ceF119E40887f396024E2", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("emits new subjects when initial matching set is empty", async () => { - const { store, fetchLatestLogs } = await createHydratedStore(worldAddress); - - const { subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", "=", 999], - ["Position.y", "=", 999], - ], - }); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 1, - "value": [], - }, - "subjects$": { - "count": 1, - "value": [], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [999, 999], - }), - ); - await fetchLatestLogs(); - - expect(await firstValueFrom(latest$)).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 999, - "y": 999, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 999, - "y": 999, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); - - it("emits changed subjects when subscribing some time after initial query", async () => { - const { store, fetchLatestLogs } = await createHydratedStore(worldAddress); - - const { subjects, subjects$, subjectEvents$ } = subscribeToQuery(store, { - from: { - Position: ["player"], - }, - where: [ - ["Position.x", "=", 3], - ["Position.y", "=", 5], - ], - }); - - expect(await subjects).toMatchInlineSnapshot(` - [ - { - "records": [ - { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "records": [ - { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - ], - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ] - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [3, 5], - }), - ); - await fetchLatestLogs(); - - const latest$ = combineLatest({ - subjects$: subjects$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectRecord[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - subjectEvents$: subjectEvents$.pipe( - scan((values, value) => [...values, value], [] as readonly (readonly SubjectEvent[])[]), - map((values) => ({ count: values.length, value: values.at(-1) })), - ), - }).pipe(shareReplay(1)); - - // we expect two emissions for by this point: initial subjects + subjects changed since starting the subscriptions - expect( - await firstValueFrom( - latest$.pipe(filter((latest) => latest.subjects$.count === 2 && latest.subjectEvents$.count === 2)), - ), - ).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "enter", - }, - ], - }, - "subjects$": { - "count": 2, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - - waitForTransaction( - await writeContract(testClient, { - account: henryAccount, - chain: null, - address: worldAddress, - abi: worldAbi, - functionName: "move", - args: [2, 4], - }), - ); - await fetchLatestLogs(); - - expect( - await firstValueFrom( - latest$.pipe(filter((latest) => latest.subjects$.count === 3 && latest.subjectEvents$.count === 3)), - ), - ).toMatchInlineSnapshot(` - { - "subjectEvents$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x0000000000000000000000005f2cc8fb10299751348e1b10f5f1ba47820b1cb8", - ], - "primaryKey": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x5f2cC8fb10299751348e1b10f5F1Ba47820B1cB8", - ], - "subjectSchema": [ - "address", - ], - "type": "exit", - }, - ], - }, - "subjects$": { - "count": 3, - "value": [ - { - "record": { - "fields": { - "player": "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000328809bc894f92807417d2dad6b7c998c1afdac6", - ], - "primaryKey": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", - ], - "subjectSchema": [ - "address", - ], - }, - { - "record": { - "fields": { - "player": "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - "x": 3, - "y": 5, - }, - "keyTuple": [ - "0x000000000000000000000000078cf0753dd50f7c56f20b3ae02719ea199be2eb", - ], - "primaryKey": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "tableId": "0x74620000000000000000000000000000506f736974696f6e0000000000000000", - }, - "subject": [ - "0x078cf0753dd50f7C56F20B3Ae02719EA199BE2eb", - ], - "subjectSchema": [ - "address", - ], - }, - ], - }, - } - `); - }); -}); diff --git a/packages/store-sync/src/query-cache/subscribeToQuery.ts b/packages/store-sync/src/query-cache/subscribeToQuery.ts deleted file mode 100644 index 78b891894c..0000000000 --- a/packages/store-sync/src/query-cache/subscribeToQuery.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Observable, map, scan } from "rxjs"; -import { SubjectEvent, SubjectRecord, SubjectRecords } from "@latticexyz/query"; -import { findSubjects } from "@latticexyz/query/internal"; -import { Query } from "./common"; -import { QueryCacheStore, extractTables } from "./createStore"; -import { queryToWire } from "./queryToWire"; - -function getId({ subject, record }: SubjectRecord): string { - // TODO: memoize - return JSON.stringify([subject, record.primaryKey]); -} - -function flattenSubjectRecords(subjects: readonly SubjectRecords[]): readonly SubjectRecord[] { - return subjects.flatMap((subject) => - subject.records.map((record) => ({ - subject: subject.subject, - subjectSchema: subject.subjectSchema, - record, - })), - ); -} - -function subjectEvents(prev: readonly SubjectRecord[], next: readonly SubjectRecord[]): readonly SubjectEvent[] { - const prevSet = new Set(prev.map((record) => getId(record))); - const nextSet = new Set(next.map((record) => getId(record))); - - const enters = next.filter((record) => !prevSet.has(getId(record))); - const exits = prev.filter((record) => !nextSet.has(getId(record))); - const changes = next.filter((nextRecord) => { - const prevRecord = prev.find((record) => getId(record) === getId(nextRecord)); - // TODO: improve this so we're not dependent on field order - return prevRecord && JSON.stringify(prevRecord.record.fields) !== JSON.stringify(nextRecord.record.fields); - }); - - return [ - ...enters.map((record) => ({ ...record, type: "enter" as const })), - ...exits.map((record) => ({ ...record, type: "exit" as const })), - ...changes.map((record) => ({ ...record, type: "change" as const })), - ]; -} - -// TODO: decide if this whole thing is returned in a promise or just `subjects` -// TODO: return matching records alongside subjects? because the record subset may be smaller than what querying for records with matching subjects -// TODO: stronger types -export type SubscribeToQueryResult = { - /** - * Set of initial matching subjects for query. - */ - subjects: Promise; - /** - * Stream of matching subjects for query. - * First emission has the same data as `subjects`, flattened per record. - */ - subjects$: Observable; - /** - * Stream of subject changes for query. - * First emission will be an `enter` for each item in `subjects`, or an empty array if no matches. - * Each emission after that will only be the subjects that have changed (entered/exited the result set, or the underlying record changed). - */ - subjectEvents$: Observable; -}; - -export function subscribeToQuery>>( - store: store, - query: query, -): SubscribeToQueryResult { - const { tables, records: initialTableRecords } = store.getState(); - const wireQuery = queryToWire(tables, query); - const initialSubjects = findSubjects({ - records: Object.values(initialTableRecords), - query: wireQuery, - }); - - function createSubjectStream(): Observable { - return new Observable(function subscribe(subscriber) { - // return initial results immediately - const initialRecords = flattenSubjectRecords(initialSubjects); - subscriber.next(initialRecords); - - // if records have changed between query and subscription, reevaluate - const { records: tableRecords } = store.getState(); - if (tableRecords !== initialTableRecords) { - const nextSubjectRecords = flattenSubjectRecords( - findSubjects({ - records: Object.values(tableRecords), - query: wireQuery, - }), - ); - subscriber.next(nextSubjectRecords); - } - - // then listen for changes to records and reevaluate - const unsub = store.subscribe((state, prevState) => { - if (state.records !== prevState.records) { - const nextSubjectRecords = flattenSubjectRecords( - findSubjects({ - records: Object.values(state.records), - query: wireQuery, - }), - ); - subscriber.next(nextSubjectRecords); - } - }); - - return () => void unsub(); - }); - } - - const subjects$ = createSubjectStream(); - - const subjectEvents$ = createSubjectStream().pipe( - scan( - (acc, next) => ({ prev: acc.next, next }), - { prev: [], next: [] }, - ), - map(({ prev, next }) => subjectEvents(prev, next)), - ); - - return { - subjects: new Promise((resolve) => resolve(initialSubjects)), - subjects$, - subjectEvents$, - }; -} diff --git a/packages/store-sync/src/query-cache/syncToQueryCache.ts b/packages/store-sync/src/query-cache/syncToQueryCache.ts deleted file mode 100644 index 84f77f5c80..0000000000 --- a/packages/store-sync/src/query-cache/syncToQueryCache.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { SyncOptions, SyncResult } from "../common"; -import { createStoreSync } from "../createStoreSync"; -import { Address } from "viem"; -import { Store } from "@latticexyz/store"; -import { createStore } from "./createStore"; -import { createStorageAdapter } from "./createStorageAdapter"; - -type SyncToQueryCacheOptions = Omit & { - // require address for now to keep the data model + retrieval simpler - address: Address; - config: config; - startSync?: boolean; -}; - -type SyncToQueryCacheResult = SyncResult & { - stopSync: () => void; -}; - -export async function syncToQueryCache({ - config, - startSync = true, - ...syncOptions -}: SyncToQueryCacheOptions): Promise { - const useStore = createStore({ tables: config.tables }); - const storageAdapter = createStorageAdapter({ store: useStore }); - - const storeSync = await createStoreSync({ - storageAdapter, - ...syncOptions, - // TODO: sync progress - }); - - const sub = startSync ? storeSync.storedBlockLogs$.subscribe() : null; - const stopSync = (): void => { - sub?.unsubscribe(); - }; - - return { - ...storeSync, - stopSync, - }; -} diff --git a/packages/store-sync/src/query-cache/test/createHydratedStore.ts b/packages/store-sync/src/query-cache/test/createHydratedStore.ts deleted file mode 100644 index 42e7621b51..0000000000 --- a/packages/store-sync/src/query-cache/test/createHydratedStore.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { storeEventsAbi } from "@latticexyz/store"; -import { configV2 as config } from "../../../test/mockGame"; -import { fetchAndStoreLogs } from "../../fetchAndStoreLogs"; -import { testClient } from "../../../test/common"; -import { Address } from "viem"; -import { getBlock, getBlockNumber } from "viem/actions"; -import { QueryCacheStore, createStore } from "../createStore"; -import { createStorageAdapter } from "../createStorageAdapter"; - -export { config }; - -export async function createHydratedStore(worldAddress: Address): Promise<{ - store: QueryCacheStore<(typeof config)["tables"]>; - fetchLatestLogs: () => Promise; -}> { - const store = createStore({ tables: config.tables }); - const storageAdapter = createStorageAdapter({ store }); - - let lastBlockProcessed = (await getBlock(testClient, { blockTag: "earliest" })).number - 1n; - async function fetchLatestLogs(): Promise { - const toBlock = await getBlockNumber(testClient); - if (toBlock > lastBlockProcessed) { - const fromBlock = lastBlockProcessed + 1n; - // console.log("fetching blocks", fromBlock, "to", toBlock); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const block of fetchAndStoreLogs({ - storageAdapter, - publicClient: testClient, - address: worldAddress, - events: storeEventsAbi, - fromBlock, - toBlock, - })) { - // console.log("got block logs", block.blockNumber, block.logs.length); - } - lastBlockProcessed = toBlock; - } - return toBlock; - } - - await fetchLatestLogs(); - - return { store, fetchLatestLogs }; -} diff --git a/packages/store-sync/src/query-cache/test/minePending.ts b/packages/store-sync/src/query-cache/test/minePending.ts deleted file mode 100644 index eb0d1524ee..0000000000 --- a/packages/store-sync/src/query-cache/test/minePending.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { testClient } from "../../../test/common"; - -export async function minePending(): Promise { - const content = await testClient.getTxpoolContent(); - if (!Object.keys(content.pending).length) return; - - await testClient.mine({ blocks: 1 }); - await minePending(); -} diff --git a/packages/store-sync/src/query-cache/test/waitForTransaction.ts b/packages/store-sync/src/query-cache/test/waitForTransaction.ts deleted file mode 100644 index 8f5de8e9b6..0000000000 --- a/packages/store-sync/src/query-cache/test/waitForTransaction.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Hex } from "viem"; -import { getTransactionReceipt } from "viem/actions"; -import { testClient } from "../../../test/common"; -import { minePending } from "./minePending"; - -export async function waitForTransaction(hash: Hex): Promise { - await minePending(); - const receipt = await getTransactionReceipt(testClient, { hash }); - if (receipt.status === "reverted") { - // TODO: better error - throw new Error(`Transaction reverted (${hash})`); - } -} From 9e05278de6730517647ae33fd9d46f2687ea5f93 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 11 Jul 2024 18:09:13 +0100 Subject: [PATCH 10/10] refactor(store-sync): move syncToZustand to new config (#2936) --- .changeset/flat-swans-turn.md | 5 + .changeset/orange-beans-allow.md | 5 + packages/config/src/common.ts | 4 + packages/config/src/exports/index.ts | 2 +- .../dev-tools/src/zustand/TableDataTable.tsx | 18 +-- packages/dev-tools/src/zustand/useRecords.ts | 3 +- packages/dev-tools/src/zustand/useTables.ts | 2 +- packages/protocol-parser/src/common.ts | 1 + packages/store-sync/src/zustand/common.ts | 10 +- .../src/zustand/createStorageAdapter.test.ts | 114 ++++++++++++++---- .../src/zustand/createStorageAdapter.ts | 23 +++- .../store-sync/src/zustand/createStore.ts | 19 ++- .../store-sync/src/zustand/getAllTables.ts | 28 +++++ packages/store-sync/src/zustand/mergeRight.ts | 7 ++ .../store-sync/src/zustand/syncToZustand.ts | 41 +++---- .../store-sync/src/zustand/tablesByLabel.ts | 15 +++ 16 files changed, 208 insertions(+), 89 deletions(-) create mode 100644 .changeset/flat-swans-turn.md create mode 100644 .changeset/orange-beans-allow.md create mode 100644 packages/store-sync/src/zustand/getAllTables.ts create mode 100644 packages/store-sync/src/zustand/mergeRight.ts create mode 100644 packages/store-sync/src/zustand/tablesByLabel.ts 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; +}