diff --git a/.changeset/warm-carrots-compare.md b/.changeset/warm-carrots-compare.md new file mode 100644 index 0000000000..299e2cc3ed --- /dev/null +++ b/.changeset/warm-carrots-compare.md @@ -0,0 +1,9 @@ +--- +"@latticexyz/config": patch +"@latticexyz/store-sync": patch +"@latticexyz/store": patch +"@latticexyz/world": patch +--- + +Add a strongly typed `namespaceLabel` to the table config output. +It corresponds to the `label` of the namespace the table belongs to and can't be set manually. diff --git a/packages/cli/src/deploy/getTables.ts b/packages/cli/src/deploy/getTables.ts index 27d56f3449..91eb894558 100644 --- a/packages/cli/src/deploy/getTables.ts +++ b/packages/cli/src/deploy/getTables.ts @@ -15,8 +15,8 @@ import { import { Schema, Table } from "@latticexyz/config"; import storeConfig from "@latticexyz/store/mud.config"; -// TODO: add label once we register it onchain -type DeployedTable = Omit; +// TODO: add label and namespaceLabel once we register it onchain +type DeployedTable = Omit; export async function getTables({ client, diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index 3577a2318d..d56e487acd 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -27,6 +27,7 @@ export type Table = { readonly label: string; readonly type: satisfy; readonly namespace: string; + readonly namespaceLabel: string; readonly name: string; readonly tableId: Hex; readonly schema: Schema; diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index 740935e2c9..721a4491b1 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -25,8 +25,8 @@ export const internalTableIds = Object.values(mudTables).map((table) => table.ta export type ChainId = number; export type WorldId = `${ChainId}:${Address}`; -// TODO: add label once we register it onchain -export type DeployedTable = Omit; +// TODO: add label and namespaceLabel once we register it onchain +export type DeployedTable = Omit; export type TableRecord = { readonly key: getSchemaPrimitives>; diff --git a/packages/store-sync/src/zustand/createStorageAdapter.test.ts b/packages/store-sync/src/zustand/createStorageAdapter.test.ts index b12ff57662..4ff4863559 100644 --- a/packages/store-sync/src/zustand/createStorageAdapter.test.ts +++ b/packages/store-sync/src/zustand/createStorageAdapter.test.ts @@ -62,6 +62,7 @@ describe("createStorageAdapter", async () => { "label": "Position", "name": "Position", "namespace": "", + "namespaceLabel": "", "schema": { "player": { "internalType": "address", @@ -113,6 +114,7 @@ describe("createStorageAdapter", async () => { "label": "Position", "name": "Position", "namespace": "", + "namespaceLabel": "", "schema": { "player": { "internalType": "address", @@ -164,6 +166,7 @@ describe("createStorageAdapter", async () => { "label": "Position", "name": "Position", "namespace": "", + "namespaceLabel": "", "schema": { "player": { "internalType": "address", @@ -215,6 +218,7 @@ describe("createStorageAdapter", async () => { "label": "Position", "name": "Position", "namespace": "", + "namespaceLabel": "", "schema": { "player": { "internalType": "address", diff --git a/packages/store/ts/config/v2/input.ts b/packages/store/ts/config/v2/input.ts index 43509ab357..ca103761bf 100644 --- a/packages/store/ts/config/v2/input.ts +++ b/packages/store/ts/config/v2/input.ts @@ -31,6 +31,11 @@ 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. @@ -47,7 +52,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/namespace.ts b/packages/store/ts/config/v2/namespace.ts index 4d0f22a40d..fd133a2f8d 100644 --- a/packages/store/ts/config/v2/namespace.ts +++ b/packages/store/ts/config/v2/namespace.ts @@ -36,7 +36,7 @@ export type resolveNamespace = input { readonly [label in keyof input["tables"]]: mergeIfUndefined< expandTableShorthand, - { readonly namespace: string } + { readonly namespace: string; readonly namespaceLabel: input["label"] } >; }, scope @@ -49,14 +49,14 @@ export function resolveNamespace { - const label = input.label; - const namespace = input.namespace ?? label.slice(0, 14); + const namespaceLabel = input.label; + const namespace = input.namespace ?? namespaceLabel.slice(0, 14); return { - label, + label: namespaceLabel, namespace, tables: resolveTables( flatMorph(input.tables ?? {}, (label, table) => { - return [label, mergeIfUndefined(expandTableShorthand(table, scope), { namespace })]; + return [label, mergeIfUndefined(expandTableShorthand(table, scope), { namespace, namespaceLabel })]; }), scope, ), diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index 6943925c8e..d5efe6e04e 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -28,6 +28,7 @@ describe("defineStore", () => { label: "Example", type: "table", namespace: "", + namespaceLabel: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", schema: { @@ -47,6 +48,7 @@ describe("defineStore", () => { label: "Example", type: "table", namespace: "", + namespaceLabel: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", schema: { @@ -80,6 +82,7 @@ describe("defineStore", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -113,6 +116,7 @@ describe("defineStore", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -180,6 +184,7 @@ describe("defineStore", () => { label: "Example", type: "table", namespace: "", + namespaceLabel: "root", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", schema: { @@ -199,6 +204,7 @@ describe("defineStore", () => { label: "Example", type: "table", namespace: "", + namespaceLabel: "root", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", schema: { @@ -232,6 +238,7 @@ describe("defineStore", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "root" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -265,6 +272,7 @@ describe("defineStore", () => { readonly root__Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "root" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` diff --git a/packages/store/ts/config/v2/table.test.ts b/packages/store/ts/config/v2/table.test.ts index 69d99eafea..690822c5cf 100644 --- a/packages/store/ts/config/v2/table.test.ts +++ b/packages/store/ts/config/v2/table.test.ts @@ -57,6 +57,7 @@ describe("resolveTable", () => { label: "", type: "table", namespace: "" as string, + namespaceLabel: "", name: "" as string, tableId: resourceToHex({ type: "table", namespace: "", name: "" }), schema: { @@ -82,6 +83,7 @@ describe("resolveTable", () => { label: "", type: "table", namespace: "" as string, + namespaceLabel: "", name: "" as string, tableId: resourceToHex({ type: "table", namespace: "", name: "" }), schema: { @@ -113,6 +115,7 @@ describe("resolveTable", () => { label: "", type: "table", namespace: "" as string, + namespaceLabel: "", name: "" as string, tableId: resourceToHex({ type: "table", namespace: "", name: "" }), schema: { diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index 6937a671ba..2287cd81ec 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -99,6 +99,18 @@ export function validateTable( if (hasOwnKey(input, "namespace") && typeof input.namespace === "string" && input.namespace.length > 14) { throw new Error(`Table \`namespace\` must fit into a \`bytes14\`, but "${input.namespace}" is too long.`); } + + if ( + hasOwnKey(input, "namespaceLabel") && + typeof input.namespaceLabel === "string" && + (!hasOwnKey(input, "namespace") || typeof input.namespace !== "string") && + input.namespaceLabel.length > 14 + ) { + throw new Error( + `Table \`namespace\` defaults to \`namespaceLabel\`, but must fit into a \`bytes14\` and "${input.namespaceLabel}" is too long. Provide explicit \`namespace\` override.`, + ); + } + if (hasOwnKey(input, "name") && typeof input.name === "string" && input.name.length > 16) { throw new Error(`Table \`name\` must fit into a \`bytes16\`, but "${input.name}" is too long.`); } @@ -140,6 +152,9 @@ export type resolveTable = input extends Tab ? { 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 namespace: string; readonly name: string; readonly tableId: Hex; @@ -156,16 +171,18 @@ export function resolveTable { + const namespaceLabel = input.namespaceLabel ?? TABLE_DEFAULTS.namespace; + const namespace = input.namespace ?? namespaceLabel; const label = input.label; - const type = input.type ?? TABLE_DEFAULTS.type; - const namespace = input.namespace ?? TABLE_DEFAULTS.namespace; const name = input.name ?? label.slice(0, 16); + const type = input.type ?? TABLE_DEFAULTS.type; const tableId = resourceToHex({ type, namespace, name }); return { label, type, namespace, + namespaceLabel, name, tableId, schema: resolveSchema(input.schema, scope), diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index 90c6a2aefd..222fc2438f 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -143,6 +143,7 @@ describe("defineWorld", () => { Example: { label: "Example", type: "table", + namespaceLabel: "", namespace: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", @@ -163,6 +164,7 @@ describe("defineWorld", () => { Example: { label: "Example", type: "table", + namespaceLabel: "", namespace: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", @@ -205,6 +207,7 @@ describe("defineWorld", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -255,6 +258,7 @@ describe("defineWorld", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -324,6 +328,7 @@ describe("defineWorld", () => { Example: { label: "Example", type: "table", + namespaceLabel: "root", namespace: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", @@ -344,6 +349,7 @@ describe("defineWorld", () => { root__Example: { label: "Example", type: "table", + namespaceLabel: "root", namespace: "", name: "Example", tableId: "0x746200000000000000000000000000004578616d706c65000000000000000000", @@ -386,6 +392,7 @@ describe("defineWorld", () => { readonly root__Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "root" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\` @@ -436,6 +443,7 @@ describe("defineWorld", () => { readonly Example: { readonly label: "Example" readonly type: "table" + readonly namespaceLabel: "root" readonly namespace: string readonly name: string readonly tableId: \`0x\${string}\`