Skip to content

Commit ee388ed

Browse files
authored
fix(cli): throw error on schema changes (#3336)
1 parent b8afc07 commit ee388ed

File tree

4 files changed

+74
-14
lines changed

4 files changed

+74
-14
lines changed

.changeset/nice-ligers-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/cli": patch
3+
---
4+
5+
Deployer will now throw an error if it detects an already registered table with a different schema than the one you are trying to deploy.

packages/cli/src/deploy/ensureTables.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
getSchemaTypes,
99
getValueSchema,
1010
getKeySchema,
11-
KeySchema,
1211
} from "@latticexyz/protocol-parser/internal";
1312
import { debug } from "./debug";
1413
import { getTables } from "./getTables";
1514
import pRetry from "p-retry";
1615
import { Table } from "@latticexyz/config";
16+
import { isDefined } from "@latticexyz/common/utils";
1717

1818
export async function ensureTables({
1919
client,
@@ -24,15 +24,56 @@ export async function ensureTables({
2424
readonly worldDeploy: WorldDeploy;
2525
readonly tables: readonly Table[];
2626
}): Promise<readonly Hex[]> {
27-
const worldTables = await getTables({ client, worldDeploy });
28-
const worldTableIds = worldTables.map((table) => table.tableId);
27+
const configTables = new Map(
28+
tables.map((table) => {
29+
const keySchema = getSchemaTypes(getKeySchema(table));
30+
const valueSchema = getSchemaTypes(getValueSchema(table));
31+
const keySchemaHex = keySchemaToHex(keySchema);
32+
const valueSchemaHex = valueSchemaToHex(valueSchema);
33+
return [
34+
table.tableId,
35+
{
36+
...table,
37+
keySchema,
38+
keySchemaHex,
39+
valueSchema,
40+
valueSchemaHex,
41+
},
42+
];
43+
}),
44+
);
2945

30-
const existingTables = tables.filter((table) => worldTableIds.includes(table.tableId));
46+
const worldTables = await getTables({ client, worldDeploy });
47+
const existingTables = worldTables.filter(({ tableId }) => configTables.has(tableId));
3148
if (existingTables.length) {
3249
debug("existing tables:", existingTables.map(resourceToLabel).join(", "));
50+
51+
const schemaErrors = existingTables
52+
.map((table) => {
53+
const configTable = configTables.get(table.tableId)!;
54+
if (table.keySchemaHex !== configTable.keySchemaHex || table.valueSchemaHex !== configTable.valueSchemaHex) {
55+
return [
56+
`"${resourceToLabel(table)}" table:`,
57+
` Registered schema: ${JSON.stringify({ schema: getSchemaTypes(table.schema), key: table.key })}`,
58+
` Config schema: ${JSON.stringify({ schema: getSchemaTypes(configTable.schema), key: configTable.key })}`,
59+
].join("\n");
60+
}
61+
})
62+
.filter(isDefined);
63+
64+
if (schemaErrors.length) {
65+
throw new Error(
66+
[
67+
"Table schemas are immutable, but found registered tables with a different schema than what you have configured.",
68+
...schemaErrors,
69+
"You can either update your config with the registered schema or change the table name to register a new table.",
70+
].join("\n\n") + "\n",
71+
);
72+
}
3373
}
3474

35-
const missingTables = tables.filter((table) => !worldTableIds.includes(table.tableId));
75+
const existingTableIds = new Set(existingTables.map(({ tableId }) => tableId));
76+
const missingTables = tables.filter((table) => !existingTableIds.has(table.tableId));
3677
if (missingTables.length) {
3778
debug("registering tables:", missingTables.map(resourceToLabel).join(", "));
3879
return await Promise.all(
@@ -50,7 +91,7 @@ export async function ensureTables({
5091
args: [
5192
table.tableId,
5293
valueSchemaToFieldLayoutHex(valueSchema),
53-
keySchemaToHex(keySchema as KeySchema),
94+
keySchemaToHex(keySchema),
5495
valueSchemaToHex(valueSchema),
5596
Object.keys(keySchema),
5697
Object.keys(valueSchema),

packages/cli/src/deploy/getTables.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client, decodeAbiParameters, parseAbiParameters } from "viem";
1+
import { Client, Hex, decodeAbiParameters, parseAbiParameters } from "viem";
22
import { hexToResource } from "@latticexyz/common";
33
import { WorldDeploy } from "./common";
44
import { debug } from "./debug";
@@ -16,7 +16,12 @@ import { fetchBlockLogs } from "@latticexyz/block-logs-stream";
1616
import { flattenStoreLogs, getStoreLogs } from "@latticexyz/store/internal";
1717

1818
// TODO: add label and namespaceLabel once we register it onchain
19-
type DeployedTable = Omit<Table, "label" | "namespaceLabel">;
19+
type DeployedTable = Omit<Table, "label" | "namespaceLabel"> & {
20+
readonly keySchema: Schema;
21+
readonly keySchemaHex: Hex;
22+
readonly valueSchema: Schema;
23+
readonly valueSchemaHex: Hex;
24+
};
2025

2126
export async function getTables({
2227
client,
@@ -49,12 +54,17 @@ export async function getTables({
4954
log.args.keyTuple,
5055
);
5156
const { type, namespace, name } = hexToResource(tableId);
52-
const value = decodeValueArgs(getSchemaTypes(getValueSchema(storeConfig.namespaces.store.tables.Tables)), log.args);
57+
const recordValue = decodeValueArgs(
58+
getSchemaTypes(getValueSchema(storeConfig.namespaces.store.tables.Tables)),
59+
log.args,
60+
);
5361

54-
const solidityKeySchema = hexToSchema(value.keySchema);
55-
const solidityValueSchema = hexToSchema(value.valueSchema);
56-
const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0];
57-
const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0];
62+
const keySchemaHex = recordValue.keySchema;
63+
const valueSchemaHex = recordValue.valueSchema;
64+
const solidityKeySchema = hexToSchema(keySchemaHex);
65+
const solidityValueSchema = hexToSchema(valueSchemaHex);
66+
const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), recordValue.abiEncodedKeyNames)[0];
67+
const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), recordValue.abiEncodedFieldNames)[0];
5868

5969
const valueAbiTypes = [...solidityValueSchema.staticFields, ...solidityValueSchema.dynamicFields];
6070

@@ -73,6 +83,10 @@ export async function getTables({
7383
tableId,
7484
schema: { ...keySchema, ...valueSchema },
7585
key: Object.keys(keySchema),
86+
keySchema,
87+
keySchemaHex,
88+
valueSchema,
89+
valueSchemaHex,
7690
};
7791
});
7892

packages/protocol-parser/src/getKeySchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type getKeySchema<table extends PartialTable> = PartialTable extends tabl
1515
? ResolvedKeySchema
1616
: {
1717
readonly [fieldName in Extract<keyof table["schema"], table["key"][number]>]: table["schema"][fieldName] & {
18-
type: StaticAbiType;
18+
readonly type: StaticAbiType;
1919
};
2020
};
2121

0 commit comments

Comments
 (0)