From 11a3d2daf80c6ed00b322b581324f8db3918c0a4 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 17:24:19 +0100 Subject: [PATCH 01/10] add namespaceLabel to system, clarify things with comments --- packages/config/src/common.ts | 28 ++++++++++++++++++++++++++- packages/world/ts/config/v2/output.ts | 6 +++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index d56e487acd..56ac0ea92a 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -24,13 +24,39 @@ export type Schema = { }; export type Table = { + /** + * Human-readable label for this table's namespace. Used for namespace config keys and directory names. + */ + readonly namespaceLabel: string; + /** + * Human-readable label for this table. Used as config keys, library names, and filenames. + * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. + */ readonly label: string; + /** + * Table type used in table's resource ID and determines how storage and events are used by this table. + */ readonly type: satisfy; + /** + * Table namespace used in table's resource ID and determines access control. + */ readonly namespace: string; - readonly namespaceLabel: string; + /** + * Table name used in system's resource ID. + */ readonly name: string; + /** + * Table's resource ID. + */ readonly tableId: Hex; + /** + * Schema definition for this table's records. + */ readonly schema: Schema; + /** + * Primary key for records of this table. An array of zero or more schema field names. + * Using an empty array acts like a singleton, where only one record can exist for this table. + */ readonly key: readonly string[]; }; diff --git a/packages/world/ts/config/v2/output.ts b/packages/world/ts/config/v2/output.ts index 00e78c83b5..dedfbe9e91 100644 --- a/packages/world/ts/config/v2/output.ts +++ b/packages/world/ts/config/v2/output.ts @@ -41,7 +41,11 @@ export type SystemDeploy = { export type System = { /** - * Human-readable system label. Used as config keys, interface names, and filenames. + * Human-readable label for this system's namespace. Used for namespace config keys and directory names. + */ + readonly namespaceLabel: string; + /** + * Human-readable label for this system. Used as config keys, interface names, and filenames. * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. */ readonly label: string; From b9415f95784c3f6b57a0d37e31c526f4d8bb7cf8 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 17:39:45 +0100 Subject: [PATCH 02/10] validate table namespaceLabel --- packages/store/ts/config/v2/store.test.ts | 24 +++++++++++++++-------- packages/store/ts/config/v2/table.ts | 15 +++++++++----- packages/world/ts/config/v2/world.test.ts | 24 +++++++++++++++-------- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index d5efe6e04e..8796de37b3 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -651,28 +651,32 @@ describe("defineStore", () => { namespace: "CustomNS", tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" label: "NotAllowed", schema: { id: "address" }, key: ["id"], }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineStore({ namespace: "CustomNS", tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" namespace: "NotAllowed", schema: { id: "address" }, key: ["id"], }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineStore({ @@ -680,7 +684,7 @@ describe("defineStore", () => { CustomNS: { tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" label: "NotAllowed", schema: { id: "address" }, key: ["id"], @@ -689,7 +693,9 @@ describe("defineStore", () => { }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineStore({ @@ -697,7 +703,7 @@ describe("defineStore", () => { CustomNS: { tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" namespace: "NotAllowed", schema: { id: "address" }, key: ["id"], @@ -706,7 +712,9 @@ describe("defineStore", () => { }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); }); it("should allow const enum as input", () => { diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index 2287cd81ec..9149dad58d 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -47,7 +47,7 @@ export type ValidateTableOptions = { inStoreContext: boolean }; export type requiredTableKey = Exclude< requiredKeyOf, - inStoreContext extends true ? "label" | "namespace" : "" + inStoreContext extends true ? "label" | "namespaceLabel" | "namespace" : never >; export type validateTable< @@ -59,9 +59,9 @@ export type validateTable< ? validateKeys, SchemaInput>, scope>, get> : key extends "schema" ? validateSchema, scope> - : key extends "label" | "namespace" + : key extends "label" | "namespaceLabel" | "namespace" ? options["inStoreContext"] extends true - ? ErrorMessage<"Overrides of `label` and `namespace` are not allowed for tables in this context"> + ? ErrorMessage<"Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context"> : key extends keyof input ? narrow : never @@ -115,8 +115,13 @@ export function validateTable( throw new Error(`Table \`name\` must fit into a \`bytes16\`, but "${input.name}" is too long.`); } - if (options.inStoreContext && (hasOwnKey(input, "label") || hasOwnKey(input, "namespace"))) { - throw new Error("Overrides of `label` and `namespace` are not allowed for tables in this context."); + if ( + options.inStoreContext && + (hasOwnKey(input, "label") || hasOwnKey(input, "namespaceLabel") || hasOwnKey(input, "namespace")) + ) { + throw new Error( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context.", + ); } } diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index 222fc2438f..9f9871e12a 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -517,28 +517,32 @@ describe("defineWorld", () => { namespace: "CustomNS", tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" label: "NotAllowed", schema: { id: "address" }, key: ["id"], }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineWorld({ namespace: "CustomNS", tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" namespace: "NotAllowed", schema: { id: "address" }, key: ["id"], }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineWorld({ @@ -546,7 +550,7 @@ describe("defineWorld", () => { CustomNS: { tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" label: "NotAllowed", schema: { id: "address" }, key: ["id"], @@ -555,7 +559,9 @@ describe("defineWorld", () => { }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); attest(() => defineWorld({ @@ -563,7 +569,7 @@ describe("defineWorld", () => { CustomNS: { tables: { Example: { - // @ts-expect-error "Overrides of `label` and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" namespace: "NotAllowed", schema: { id: "address" }, key: ["id"], @@ -572,7 +578,9 @@ describe("defineWorld", () => { }, }, }), - ).throwsAndHasTypeError("Overrides of `label` and `namespace` are not allowed for tables in this context"); + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); }); it("should expand systems config", () => { From 8e319b33cccbe134c6173398b2fbec156476f75e Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 18:20:55 +0100 Subject: [PATCH 03/10] add system namespace label --- packages/config/src/common.ts | 10 +++---- packages/store/ts/config/v2/input.ts | 22 ++++++++++----- packages/store/ts/config/v2/table.ts | 4 ++- packages/world/ts/config/v2/input.ts | 7 ++++- packages/world/ts/config/v2/namespace.ts | 4 +-- packages/world/ts/config/v2/output.ts | 8 +++--- packages/world/ts/config/v2/system.ts | 35 ++++++++++++++++++++---- packages/world/ts/config/v2/systems.ts | 15 ++++++---- 8 files changed, 73 insertions(+), 32 deletions(-) diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index 56ac0ea92a..0e63a6069c 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -24,15 +24,15 @@ export type Schema = { }; export type Table = { - /** - * Human-readable label for this table's namespace. Used for namespace config keys and directory names. - */ - readonly namespaceLabel: string; /** * Human-readable label for this table. Used as config keys, library names, and filenames. * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. */ readonly label: string; + /** + * Human-readable label for this table's namespace. Used for namespace config keys and directory names. + */ + readonly namespaceLabel: string; /** * Table type used in table's resource ID and determines how storage and events are used by this table. */ @@ -42,7 +42,7 @@ export type Table = { */ readonly namespace: string; /** - * Table name used in system's resource ID. + * Table name used in table's resource ID. */ readonly name: string; /** diff --git a/packages/store/ts/config/v2/input.ts b/packages/store/ts/config/v2/input.ts index ca103761bf..081e9671cc 100644 --- a/packages/store/ts/config/v2/input.ts +++ b/packages/store/ts/config/v2/input.ts @@ -18,11 +18,17 @@ export type TableDeployInput = Partial; export type TableInput = { /** - * Human-readable table label. Used as config keys, table library names, and filenames. + * Human-readable label for this table. Used as config keys, library names, and filenames. * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. */ readonly label: string; /** + * Human-readable label for this table's namespace. Used for namespace config keys and directory names. + * Defaults to the nearest namespace in the config or root namespace if not set. + */ + readonly namespaceLabel?: string; + /** + * Table type used in table's resource ID and determines how storage and events are used by this table. * Defaults to `table` if not set. */ readonly type?: "table" | "offchainTable"; @@ -31,17 +37,19 @@ export type TableInput = { * Defaults to the nearest namespace in the config or root namespace if not set. */ readonly namespace?: string; - /** - * Human-readable namespace label. - * Defaults to the nearest namespace in the config or root namespace if not set. - */ - readonly namespaceLabel?: string; /** * Table name used in table's resource ID. * Defaults to the first 16 characters of `label` if not set. */ readonly name?: string; + /** + * Schema definition for this table's records. + */ readonly schema: SchemaInput; + /** + * Primary key for records of this table. An array of zero or more schema field names. + * Using an empty array acts like a singleton, where only one record can exist for this table. + */ readonly key: readonly string[]; readonly codegen?: TableCodegenInput; readonly deploy?: TableDeployInput; @@ -52,7 +60,7 @@ export type TableShorthandInput = SchemaInput | string; export type TablesInput = { // remove label and namespace as these are set contextually // and allow defining a table using shorthand - readonly [label: string]: Omit | TableShorthandInput; + readonly [label: string]: Omit | TableShorthandInput; }; export type CodegenInput = Partial; diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index 9149dad58d..a836b59cd8 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -156,10 +156,10 @@ export function resolveTableCodegen(input: input): res export type resolveTable = input extends TableInput ? { readonly label: input["label"]; - readonly type: undefined extends input["type"] ? typeof TABLE_DEFAULTS.type : input["type"]; readonly namespaceLabel: undefined extends input["namespaceLabel"] ? typeof TABLE_DEFAULTS.namespace : input["namespaceLabel"]; + readonly type: undefined extends input["type"] ? typeof TABLE_DEFAULTS.type : input["type"]; readonly namespace: string; readonly name: string; readonly tableId: Hex; @@ -177,7 +177,9 @@ export function resolveTable { const namespaceLabel = input.namespaceLabel ?? TABLE_DEFAULTS.namespace; + // validate ensures this is length constrained const namespace = input.namespace ?? namespaceLabel; + const label = input.label; const name = input.name ?? label.slice(0, 16); const type = input.type ?? TABLE_DEFAULTS.type; diff --git a/packages/world/ts/config/v2/input.ts b/packages/world/ts/config/v2/input.ts index 9af5b30884..e18f10458c 100644 --- a/packages/world/ts/config/v2/input.ts +++ b/packages/world/ts/config/v2/input.ts @@ -10,6 +10,11 @@ export type SystemInput = { * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. */ readonly label: string; + /** + * Human-readable label for this system's namespace. Used for namespace config keys and directory names. + * Defaults to the nearest namespace in the config or root namespace if not set. + */ + readonly namespaceLabel?: string; /** * System namespace used in systems's resource ID and determines access control. * Defaults to the nearest namespace in the config or root namespace if not set. @@ -28,7 +33,7 @@ export type SystemInput = { }; export type SystemsInput = { - readonly [label: string]: Omit; + readonly [label: string]: Omit; }; export type NamespaceInput = StoreNamespaceInput & { diff --git a/packages/world/ts/config/v2/namespace.ts b/packages/world/ts/config/v2/namespace.ts index 36100fc7e4..760b9a80bd 100644 --- a/packages/world/ts/config/v2/namespace.ts +++ b/packages/world/ts/config/v2/namespace.ts @@ -27,7 +27,7 @@ export type resolveNamespace = input ? show< resolveStoreNamespace & { readonly systems: input["systems"] extends SystemsInput - ? show["namespace"]>> + ? show["label"]>> : {}; } > @@ -38,7 +38,7 @@ export function resolveNamespace { const namespace = resolveStoreNamespace(input, scope); - const systems = resolveSystems(input.systems ?? {}, namespace.namespace); + const systems = resolveSystems(input.systems ?? {}, namespace.label, namespace.namespace); return { ...namespace, systems, diff --git a/packages/world/ts/config/v2/output.ts b/packages/world/ts/config/v2/output.ts index dedfbe9e91..1bf8cb6427 100644 --- a/packages/world/ts/config/v2/output.ts +++ b/packages/world/ts/config/v2/output.ts @@ -40,15 +40,15 @@ export type SystemDeploy = { }; export type System = { - /** - * Human-readable label for this system's namespace. Used for namespace config keys and directory names. - */ - readonly namespaceLabel: string; /** * Human-readable label for this system. Used as config keys, interface names, and filenames. * Labels are not length constrained like resource names, but special characters should be avoided to be compatible with the filesystem, Solidity compiler, etc. */ readonly label: string; + /** + * Human-readable label for this system's namespace. Used for namespace config keys and directory names. + */ + readonly namespaceLabel: string; /** * System namespace used in system's resource ID and determines access control. */ diff --git a/packages/world/ts/config/v2/system.ts b/packages/world/ts/config/v2/system.ts index cc201078a4..7bbcbb9ea3 100644 --- a/packages/world/ts/config/v2/system.ts +++ b/packages/world/ts/config/v2/system.ts @@ -9,12 +9,12 @@ export type ValidateSystemOptions = { readonly inNamespace?: true }; export type requiredSystemKey = Exclude< requiredKeyOf, - inNamespace extends true ? "label" | "namespace" : never + inNamespace extends true ? "label" | "namespaceLabel" | "namespace" : never >; export type validateSystem = { [key in keyof input | requiredSystemKey]: key extends keyof SystemInput - ? key extends "label" | "namespace" + ? key extends "label" | "namespaceLabel" | "namespace" ? options["inNamespace"] extends true ? ErrorMessage<"Overrides of `label` and `namespace` are not allowed for systems in this context."> : key extends keyof input @@ -32,8 +32,24 @@ export function validateSystem( throw new Error(`Expected full system config, got \`${JSON.stringify(input)}\``); } - if (options.inNamespace && (hasOwnKey(input, "label") || hasOwnKey(input, "namespace"))) { - throw new Error("Overrides of `label` and `namespace` are not allowed for systems in this context."); + if ( + options.inNamespace && + (hasOwnKey(input, "label") || hasOwnKey(input, "namespaceLabel") || hasOwnKey(input, "namespace")) + ) { + throw new Error( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context.", + ); + } + + if ( + hasOwnKey(input, "namespaceLabel") && + typeof input.namespaceLabel === "string" && + (!hasOwnKey(input, "namespace") || typeof input.namespace !== "string") && + input.namespaceLabel.length > 14 + ) { + throw new Error( + `System \`namespace\` defaults to \`namespaceLabel\`, but must fit into a \`bytes14\` and "${input.namespaceLabel}" is too long. Provide explicit \`namespace\` override.`, + ); } if (hasOwnKey(input, "namespace") && typeof input.namespace === "string" && input.namespace.length > 14) { @@ -47,7 +63,10 @@ export function validateSystem( export type resolveSystem = input extends SystemInput ? { readonly label: input["label"]; - readonly namespace: undefined extends input["namespace"] ? SYSTEM_DEFAULTS["namespace"] : input["namespace"]; + readonly namespaceLabel: undefined extends input["namespaceLabel"] + ? typeof SYSTEM_DEFAULTS.namespace + : input["namespaceLabel"]; + readonly namespace: string; readonly name: string; readonly systemId: Hex; readonly openAccess: undefined extends input["openAccess"] ? SYSTEM_DEFAULTS["openAccess"] : input["openAccess"]; @@ -59,8 +78,11 @@ export type resolveSystem = input extends SystemInput : never; export function resolveSystem(input: input): resolveSystem { + const namespaceLabel = input.namespaceLabel ?? SYSTEM_DEFAULTS.namespace; + // validate ensures this is length constrained + const namespace = input.namespace ?? namespaceLabel; + const label = input.label; - const namespace = input.namespace ?? SYSTEM_DEFAULTS.namespace; const name = input.name ?? label.slice(0, 16); const systemId = resourceToHex({ type: "system", namespace, name }); @@ -68,6 +90,7 @@ export function resolveSystem(input: input): resolveS { ...input, label, + namespaceLabel, namespace, name, systemId, diff --git a/packages/world/ts/config/v2/systems.ts b/packages/world/ts/config/v2/systems.ts index d223708b52..8bf6fe40fc 100644 --- a/packages/world/ts/config/v2/systems.ts +++ b/packages/world/ts/config/v2/systems.ts @@ -21,17 +21,20 @@ export function validateSystems(input: unknown): asserts input is SystemsInput { throw new Error(`Expected system config, received ${JSON.stringify(input)}`); } -export type resolveSystems = { - [label in keyof systems]: resolveSystem; +export type resolveSystems = { + [label in keyof systems]: resolveSystem< + systems[label] & { label: label; namespaceLabel: namespaceLabel; namespace: string } + >; }; -export function resolveSystems( +export function resolveSystems( systems: systems, - namespace: namespace, -): resolveSystems { + namespaceLabel: namespaceLabel, + namespace: string, +): resolveSystems { return Object.fromEntries( Object.entries(systems).map(([label, system]) => { - return [label, resolveSystem({ ...system, label, namespace })]; + return [label, resolveSystem({ ...system, label, namespaceLabel, namespace })]; }), ) as never; } From d5d63fc8412cd626247c69de51158dc174b282fd Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 18:52:44 +0100 Subject: [PATCH 04/10] fixes, thanks tests --- packages/store/ts/config/v2/defaults.ts | 4 ++-- packages/store/ts/config/v2/table.ts | 4 ++-- packages/store/vitestSetup.ts | 4 +++- packages/world/ts/config/v2/defaults.ts | 4 ++-- packages/world/ts/config/v2/system.test.ts | 6 +++--- packages/world/ts/config/v2/system.ts | 6 +++--- packages/world/ts/config/v2/world.test.ts | 10 ++++++---- packages/world/ts/config/v2/world.ts | 2 +- packages/world/vitestSetup.ts | 4 +++- 9 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/store/ts/config/v2/defaults.ts b/packages/store/ts/config/v2/defaults.ts index 13bf194559..fb2abaf6ef 100644 --- a/packages/store/ts/config/v2/defaults.ts +++ b/packages/store/ts/config/v2/defaults.ts @@ -24,9 +24,9 @@ export const TABLE_DEPLOY_DEFAULTS = { export type TABLE_DEPLOY_DEFAULTS = typeof TABLE_DEPLOY_DEFAULTS; export const TABLE_DEFAULTS = { - namespace: "", + namespaceLabel: "", type: "table", -} as const satisfies Pick; +} as const satisfies Pick; export type TABLE_DEFAULTS = typeof TABLE_DEFAULTS; diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index a836b59cd8..3c95eb484d 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -157,7 +157,7 @@ export type resolveTable = input extends Tab ? { readonly label: input["label"]; readonly namespaceLabel: undefined extends input["namespaceLabel"] - ? typeof TABLE_DEFAULTS.namespace + ? typeof TABLE_DEFAULTS.namespaceLabel : input["namespaceLabel"]; readonly type: undefined extends input["type"] ? typeof TABLE_DEFAULTS.type : input["type"]; readonly namespace: string; @@ -176,7 +176,7 @@ export function resolveTable { - const namespaceLabel = input.namespaceLabel ?? TABLE_DEFAULTS.namespace; + const namespaceLabel = input.namespaceLabel ?? TABLE_DEFAULTS.namespaceLabel; // validate ensures this is length constrained const namespace = input.namespace ?? namespaceLabel; diff --git a/packages/store/vitestSetup.ts b/packages/store/vitestSetup.ts index 533675030d..61f38808c3 100644 --- a/packages/store/vitestSetup.ts +++ b/packages/store/vitestSetup.ts @@ -1 +1,3 @@ -export { setup, cleanup as teardown } from "@ark/attest"; +import { setup } from "@ark/attest"; + +export default () => setup({ updateSnapshots: true }); diff --git a/packages/world/ts/config/v2/defaults.ts b/packages/world/ts/config/v2/defaults.ts index 13f77b09fb..8a965d6d80 100644 --- a/packages/world/ts/config/v2/defaults.ts +++ b/packages/world/ts/config/v2/defaults.ts @@ -8,10 +8,10 @@ export const SYSTEM_DEPLOY_DEFAULTS = { export type SYSTEM_DEPLOY_DEFAULTS = typeof SYSTEM_DEPLOY_DEFAULTS; export const SYSTEM_DEFAULTS = { - namespace: "", + namespaceLabel: "", openAccess: true, accessList: [], -} as const satisfies Omit, "label" | "name" | "deploy">; +} as const satisfies Omit, "label" | "namespace" | "name" | "deploy">; export type SYSTEM_DEFAULTS = typeof SYSTEM_DEFAULTS; diff --git a/packages/world/ts/config/v2/system.test.ts b/packages/world/ts/config/v2/system.test.ts index a1a11b08fe..b8935f95e6 100644 --- a/packages/world/ts/config/v2/system.test.ts +++ b/packages/world/ts/config/v2/system.test.ts @@ -13,7 +13,7 @@ describe("resolveSystem", () => { const expected = { ...SYSTEM_DEFAULTS, label: "ExampleSystem", - namespace: "", + namespace: "" as string, name: "ExampleSystem" as string, systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }), deploy: { @@ -34,7 +34,7 @@ describe("resolveSystem", () => { const expected = { ...SYSTEM_DEFAULTS, label: "ExampleSystem", - namespace: "", + namespace: "" as string, name: "ExampleSystem" as string, systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }), deploy: { @@ -55,7 +55,7 @@ describe("resolveSystem", () => { const expected = { ...SYSTEM_DEFAULTS, label: "ExampleSystem", - namespace: "", + namespace: "" as string, name: "ExampleSystem" as string, systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }), openAccess: false, diff --git a/packages/world/ts/config/v2/system.ts b/packages/world/ts/config/v2/system.ts index 7bbcbb9ea3..ea74a6f256 100644 --- a/packages/world/ts/config/v2/system.ts +++ b/packages/world/ts/config/v2/system.ts @@ -16,7 +16,7 @@ export type validateSystem = [key in keyof input | requiredSystemKey]: key extends keyof SystemInput ? key extends "label" | "namespaceLabel" | "namespace" ? options["inNamespace"] extends true - ? ErrorMessage<"Overrides of `label` and `namespace` are not allowed for systems in this context."> + ? ErrorMessage<"Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context."> : key extends keyof input ? narrow : never @@ -64,7 +64,7 @@ export type resolveSystem = input extends SystemInput ? { readonly label: input["label"]; readonly namespaceLabel: undefined extends input["namespaceLabel"] - ? typeof SYSTEM_DEFAULTS.namespace + ? typeof SYSTEM_DEFAULTS.namespaceLabel : input["namespaceLabel"]; readonly namespace: string; readonly name: string; @@ -78,7 +78,7 @@ export type resolveSystem = input extends SystemInput : never; export function resolveSystem(input: input): resolveSystem { - const namespaceLabel = input.namespaceLabel ?? SYSTEM_DEFAULTS.namespace; + const namespaceLabel = input.namespaceLabel ?? SYSTEM_DEFAULTS.namespaceLabel; // validate ensures this is length constrained const namespace = input.namespace ?? namespaceLabel; diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index 9f9871e12a..5ba528013a 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -206,8 +206,8 @@ describe("defineWorld", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -257,8 +257,8 @@ describe("defineWorld", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -391,8 +391,8 @@ describe("defineWorld", () => { readonly tables: { readonly root__Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "root" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -442,8 +442,8 @@ describe("defineWorld", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "root" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -594,6 +594,7 @@ describe("defineWorld", () => { attest(config.systems).snap({ Example: { label: "Example", + namespaceLabel: "app", namespace: "app", name: "Example", systemId: "0x737961707000000000000000000000004578616d706c65000000000000000000", @@ -604,6 +605,7 @@ describe("defineWorld", () => { }).type.toString.snap(`{ readonly Example: { readonly label: "Example" + readonly namespaceLabel: string readonly namespace: string readonly name: string readonly systemId: \`0x\${string}\` diff --git a/packages/world/ts/config/v2/world.ts b/packages/world/ts/config/v2/world.ts index 2ac7fadb5b..92743fd2d5 100644 --- a/packages/world/ts/config/v2/world.ts +++ b/packages/world/ts/config/v2/world.ts @@ -112,7 +112,7 @@ export function resolveWorld(input: input): reso // TODO: flatten systems from namespaces systems: !store.multipleNamespaces && input.systems - ? resolveSystems(input.systems, store.namespace) + ? resolveSystems(input.systems, store.namespace, store.namespace) : CONFIG_DEFAULTS.systems, excludeSystems: get(input, "excludeSystems"), codegen: mergeIfUndefined(store.codegen, resolveCodegen(input.codegen)), diff --git a/packages/world/vitestSetup.ts b/packages/world/vitestSetup.ts index 533675030d..61f38808c3 100644 --- a/packages/world/vitestSetup.ts +++ b/packages/world/vitestSetup.ts @@ -1 +1,3 @@ -export { setup, cleanup as teardown } from "@ark/attest"; +import { setup } from "@ark/attest"; + +export default () => setup({ updateSnapshots: true }); From 65ecfb1451eda3d17610527dab330bed728995ec Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 18:58:55 +0100 Subject: [PATCH 05/10] more tests --- packages/store/ts/config/v2/store.test.ts | 35 ++++++ packages/world/ts/config/v2/world.test.ts | 127 ++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index 8796de37b3..09fa26b390 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -662,6 +662,22 @@ describe("defineStore", () => { "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", ); + attest(() => + defineStore({ + namespace: "CustomNS", + tables: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "NotAllowed", + schema: { id: "address" }, + key: ["id"], + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + attest(() => defineStore({ namespace: "CustomNS", @@ -697,6 +713,25 @@ describe("defineStore", () => { "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", ); + attest(() => + defineStore({ + namespaces: { + CustomNS: { + tables: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "NotAllowed", + schema: { id: "address" }, + key: ["id"], + }, + }, + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + attest(() => defineStore({ namespaces: { diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index 5ba528013a..d8f21db3c3 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -528,6 +528,22 @@ describe("defineWorld", () => { "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", ); + attest(() => + defineWorld({ + namespace: "CustomNS", + tables: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "NotAllowed", + schema: { id: "address" }, + key: ["id"], + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + attest(() => defineWorld({ namespace: "CustomNS", @@ -563,6 +579,25 @@ describe("defineWorld", () => { "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", ); + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + tables: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "NotAllowed", + schema: { id: "address" }, + key: ["id"], + }, + }, + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + attest(() => defineWorld({ namespaces: { @@ -631,6 +666,98 @@ describe("defineWorld", () => { attest(config.systems.Example.openAccess).equals(false); }); + it("should throw if system label/namespace is overridden in namespace context", () => { + attest(() => + defineWorld({ + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + label: "", + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + + attest(() => + defineWorld({ + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "", + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + + attest(() => + defineWorld({ + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespace: "", + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + label: "", + }, + }, + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespaceLabel: "", + }, + }, + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + namespace: "", + }, + }, + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + ); + }); + it("should allow a const config as input", () => { const config = { tables: { From 543dbb0bce02734a7a6926db17f877170b7a45c6 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 19:01:49 +0100 Subject: [PATCH 06/10] add to cli --- packages/cli/src/deploy/common.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 97a94dd174..08c696eb49 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -82,19 +82,24 @@ export type Library = DeterministicContract & { }; export type System = DeterministicContract & { + // labels readonly label: string; + readonly namespaceLabel: string; + // resource ID readonly namespace: string; readonly name: string; readonly systemId: Hex; + // access readonly allowAll: boolean; readonly allowedAddresses: readonly Hex[]; readonly allowedSystemIds: readonly Hex[]; + // world registration readonly worldFunctions: readonly WorldFunction[]; }; export type DeployedSystem = Omit< System, - "label" | "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds" + "label" | "namespaceLabel" | "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds" > & { address: Address; }; From 6de0cefc5db2838b4e195ffd6882302ca331e1aa Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 11:03:49 -0700 Subject: [PATCH 07/10] Create tasty-toys-deliver.md --- .changeset/tasty-toys-deliver.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/tasty-toys-deliver.md diff --git a/.changeset/tasty-toys-deliver.md b/.changeset/tasty-toys-deliver.md new file mode 100644 index 0000000000..5a438a7b31 --- /dev/null +++ b/.changeset/tasty-toys-deliver.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/cli": patch +"@latticexyz/world": patch +--- + +Add a strongly typed `namespaceLabel` to the system config output. +It corresponds to the `label` of the namespace the system belongs to and can't be set manually. From 1e96f016e0321116ddf09d02200712c93aa2c2e8 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 11:05:46 -0700 Subject: [PATCH 08/10] Create quick-frogs-fold.md --- .changeset/quick-frogs-fold.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/quick-frogs-fold.md diff --git a/.changeset/quick-frogs-fold.md b/.changeset/quick-frogs-fold.md new file mode 100644 index 0000000000..4b79d1ec92 --- /dev/null +++ b/.changeset/quick-frogs-fold.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/config": patch +"@latticexyz/store": patch +--- + +Fixed a few type issues with `namespaceLabel` in tables and added/clarified TSDoc for config input/output objects. From 209521e47fed46b47039d3f4baa0644f7fb56ac2 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 19:07:32 +0100 Subject: [PATCH 09/10] copypasta --- packages/world/ts/config/v2/world.test.ts | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index d8f21db3c3..e055253980 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -671,39 +671,39 @@ describe("defineWorld", () => { defineWorld({ systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" label: "", }, }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); attest(() => defineWorld({ systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" namespaceLabel: "", }, }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); attest(() => defineWorld({ systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" namespace: "", }, }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); attest(() => @@ -712,7 +712,7 @@ describe("defineWorld", () => { CustomNS: { systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" label: "", }, }, @@ -720,7 +720,7 @@ describe("defineWorld", () => { }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); attest(() => @@ -729,7 +729,7 @@ describe("defineWorld", () => { CustomNS: { systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" namespaceLabel: "", }, }, @@ -737,7 +737,7 @@ describe("defineWorld", () => { }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); attest(() => @@ -746,7 +746,7 @@ describe("defineWorld", () => { CustomNS: { systems: { Example: { - // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context" + // @ts-expect-error "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context" namespace: "", }, }, @@ -754,7 +754,7 @@ describe("defineWorld", () => { }, }), ).throwsAndHasTypeError( - "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for tables in this context", + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", ); }); From 4e6e0f7945ec60e6185d7755a6fbea4ab2748dc3 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 22:02:24 +0100 Subject: [PATCH 10/10] update snapshot --- packages/store/ts/config/v2/store.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index 09fa26b390..9357f192fb 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -81,8 +81,8 @@ describe("defineStore", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -115,8 +115,8 @@ describe("defineStore", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -237,8 +237,8 @@ describe("defineStore", () => { readonly tables: { readonly Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "root" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -271,8 +271,8 @@ describe("defineStore", () => { readonly tables: { readonly root__Example: { readonly label: "Example" - readonly type: "table" readonly namespaceLabel: "root" + readonly type: "table" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\`