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. 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. 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; }; diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index d56e487acd..0e63a6069c 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. 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. + */ 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 table'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/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/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/store.test.ts b/packages/store/ts/config/v2/store.test.ts index d5efe6e04e..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}\` @@ -651,28 +651,48 @@ 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" + 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", + tables: { + Example: { + // @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 +700,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 +709,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 +719,26 @@ 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" + 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: { + CustomNS: { + tables: { + Example: { + // @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 +747,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..3c95eb484d 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.", + ); } } @@ -151,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 + ? typeof TABLE_DEFAULTS.namespaceLabel : input["namespaceLabel"]; + readonly type: undefined extends input["type"] ? typeof TABLE_DEFAULTS.type : input["type"]; readonly namespace: string; readonly name: string; readonly tableId: Hex; @@ -171,8 +176,10 @@ 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; + const label = input.label; const name = input.name ?? label.slice(0, 16); const type = input.type ?? TABLE_DEFAULTS.type; 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/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 00e78c83b5..1bf8cb6427 100644 --- a/packages/world/ts/config/v2/output.ts +++ b/packages/world/ts/config/v2/output.ts @@ -41,10 +41,14 @@ export type SystemDeploy = { export type System = { /** - * Human-readable system label. Used as config keys, interface names, and filenames. + * 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.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 cc201078a4..ea74a6f256 100644 --- a/packages/world/ts/config/v2/system.ts +++ b/packages/world/ts/config/v2/system.ts @@ -9,14 +9,14 @@ 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."> + ? ErrorMessage<"Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context."> : key extends keyof input ? narrow : never @@ -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.namespaceLabel + : 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.namespaceLabel; + // 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; } diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index 222fc2438f..e055253980 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}\` @@ -517,28 +517,48 @@ 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" + 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", + tables: { + Example: { + // @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 +566,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 +575,28 @@ 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({ + 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({ @@ -563,7 +604,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 +613,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", () => { @@ -586,6 +629,7 @@ describe("defineWorld", () => { attest(config.systems).snap({ Example: { label: "Example", + namespaceLabel: "app", namespace: "app", name: "Example", systemId: "0x737961707000000000000000000000004578616d706c65000000000000000000", @@ -596,6 +640,7 @@ describe("defineWorld", () => { }).type.toString.snap(`{ readonly Example: { readonly label: "Example" + readonly namespaceLabel: string readonly namespace: string readonly name: string readonly systemId: \`0x\${string}\` @@ -621,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 systems in this context" + label: "", + }, + }, + }), + ).throwsAndHasTypeError( + "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 systems in this context" + namespaceLabel: "", + }, + }, + }), + ).throwsAndHasTypeError( + "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 systems in this context" + namespace: "", + }, + }, + }), + ).throwsAndHasTypeError( + "Overrides of `label`, `namespaceLabel`, and `namespace` are not allowed for systems in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @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 systems in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @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 systems in this context", + ); + + attest(() => + defineWorld({ + namespaces: { + CustomNS: { + systems: { + Example: { + // @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 systems in this context", + ); + }); + it("should allow a const config as input", () => { const config = { tables: { 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 });