From 9be2bb863194e2beee03b3d783f925c79b3c8562 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 22 May 2024 09:38:10 +0100 Subject: [PATCH] fix(cli,world): resolve table by just name (#2850) --- .changeset/heavy-seas-learn.md | 13 +++++ .../minimal/packages/contracts/mud.config.ts | 2 +- .../minimal/packages/contracts/package.json | 1 - examples/minimal/pnpm-lock.yaml | 3 - packages/cli/src/deploy/configToModules.ts | 13 +---- .../deprecated/library/dynamicResolution.ts | 55 ------------------- .../config/src/deprecated/library/index.ts | 1 - .../config/src/deprecated/register/index.ts | 4 +- packages/store/ts/register/index.ts | 2 +- packages/world/ts/config/defaults.ts | 1 - .../world/ts/config/resolveWorldConfig.ts | 5 +- packages/world/ts/config/types.ts | 4 +- packages/world/ts/config/v2/compat.ts | 16 +----- .../world/ts/config/v2/dynamicResolution.ts | 37 ++++++++----- packages/world/ts/config/worldConfig.ts | 19 +------ packages/world/ts/exports/internal.ts | 2 + packages/world/ts/register/index.ts | 2 +- 17 files changed, 52 insertions(+), 128 deletions(-) create mode 100644 .changeset/heavy-seas-learn.md delete mode 100644 packages/config/src/deprecated/library/dynamicResolution.ts diff --git a/.changeset/heavy-seas-learn.md b/.changeset/heavy-seas-learn.md new file mode 100644 index 0000000000..d40112a516 --- /dev/null +++ b/.changeset/heavy-seas-learn.md @@ -0,0 +1,13 @@ +--- +"@latticexyz/cli": patch +"@latticexyz/world": patch +--- + +Fixed `resolveTableId` usage within config's module `args` to allow referencing both namespaced tables (e.g. `resolveTableId("app_Tasks")`) as well as tables by just their name (e.g. `resolveTableId("Tasks")`). Note that using just the table name requires it to be unique among all tables within the config. + +This helper is now exported from `@latticexyz/world` package as intended. The previous, deprecated export has been removed. + +```diff +-import { resolveTableId } from "@latticexyz/config/library"; ++import { resolveTableId } from "@latticexyz/world/internal"; +``` diff --git a/examples/minimal/packages/contracts/mud.config.ts b/examples/minimal/packages/contracts/mud.config.ts index 60325fca87..a6ced4ce58 100644 --- a/examples/minimal/packages/contracts/mud.config.ts +++ b/examples/minimal/packages/contracts/mud.config.ts @@ -1,5 +1,5 @@ import { defineWorld } from "@latticexyz/world"; -import { resolveTableId } from "@latticexyz/config/library"; +import { resolveTableId } from "@latticexyz/world/internal"; export default defineWorld({ systems: { diff --git a/examples/minimal/packages/contracts/package.json b/examples/minimal/packages/contracts/package.json index d7c8d0ddc0..6ec96fbad9 100644 --- a/examples/minimal/packages/contracts/package.json +++ b/examples/minimal/packages/contracts/package.json @@ -16,7 +16,6 @@ }, "devDependencies": { "@latticexyz/cli": "link:../../../../packages/cli", - "@latticexyz/config": "link:../../../../packages/config", "@latticexyz/faucet": "link:../../../../packages/faucet", "@latticexyz/schema-type": "link:../../../../packages/schema-type", "@latticexyz/store": "link:../../../../packages/store", diff --git a/examples/minimal/pnpm-lock.yaml b/examples/minimal/pnpm-lock.yaml index 90b5017377..64ae837d67 100644 --- a/examples/minimal/pnpm-lock.yaml +++ b/examples/minimal/pnpm-lock.yaml @@ -299,9 +299,6 @@ importers: '@latticexyz/cli': specifier: link:../../../../packages/cli version: link:../../../../packages/cli - '@latticexyz/config': - specifier: link:../../../../packages/config - version: link:../../../../packages/config '@latticexyz/faucet': specifier: link:../../../../packages/faucet version: link:../../../../packages/faucet diff --git a/packages/cli/src/deploy/configToModules.ts b/packages/cli/src/deploy/configToModules.ts index 3738c9e1b2..649de451e0 100644 --- a/packages/cli/src/deploy/configToModules.ts +++ b/packages/cli/src/deploy/configToModules.ts @@ -1,26 +1,19 @@ import path from "node:path"; import { Module } from "./common"; -import { resolveWithContext } from "@latticexyz/config/library"; import { encodeField } from "@latticexyz/protocol-parser/internal"; import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-type/internal"; -import { bytesToHex, hexToBytes } from "viem"; +import { bytesToHex } from "viem"; import { createPrepareDeploy } from "./createPrepareDeploy"; import { World } from "@latticexyz/world"; import { getContractArtifact } from "../utils/getContractArtifact"; import { knownModuleArtifacts } from "../utils/knownModuleArtifacts"; +import { resolveWithContext } from "@latticexyz/world/internal"; export async function configToModules( config: config, // TODO: remove/replace `forgeOutDir` forgeOutDir: string, ): Promise { - // this expects a namespaced table name when used with `resolveTableId` - const resolveContext = { - tableIds: Object.fromEntries( - Object.entries(config.tables).map(([tableName, table]) => [tableName, hexToBytes(table.tableId)]), - ), - }; - const modules = await Promise.all( config.modules.map(async (mod): Promise => { let artifactPath = mod.artifactPath; @@ -57,7 +50,7 @@ export async function configToModules( // TODO: replace args with something more strongly typed const installArgs = mod.args - .map((arg) => resolveWithContext(arg, resolveContext)) + .map((arg) => resolveWithContext(arg, { config })) .map((arg) => { const value = arg.value instanceof Uint8Array ? bytesToHex(arg.value) : arg.value; return encodeField(arg.type as SchemaAbiType, value as SchemaAbiTypeToPrimitiveType); diff --git a/packages/config/src/deprecated/library/dynamicResolution.ts b/packages/config/src/deprecated/library/dynamicResolution.ts deleted file mode 100644 index 7440b2dcaa..0000000000 --- a/packages/config/src/deprecated/library/dynamicResolution.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { MUDError } from "@latticexyz/common/errors"; - -export enum DynamicResolutionType { - TABLE_ID, - SYSTEM_ADDRESS, -} - -export type DynamicResolution = { - type: DynamicResolutionType; - input: string; -}; - -export type ValueWithType = { - value: string | number | Uint8Array; - type: string; -}; - -/** - * Dynamically resolve a table name to a table id at deploy time - */ -export function resolveTableId(tableName: string) { - return { - type: DynamicResolutionType.TABLE_ID, - input: tableName, - }; -} - -/** Type guard for DynamicResolution */ -export function isDynamicResolution(value: unknown): value is DynamicResolution { - return typeof value === "object" && value !== null && "type" in value && "input" in value; -} - -/** - * Turn a DynamicResolution object into a ValueWithType based on the provided context - * @deprecated - */ -export function resolveWithContext( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - unresolved: any, - context: { systemAddresses?: Record>; tableIds?: Record }, -): ValueWithType { - if (!isDynamicResolution(unresolved)) return unresolved; - let resolved: ValueWithType | undefined = undefined; - - if (unresolved.type === DynamicResolutionType.TABLE_ID) { - const tableId = context.tableIds?.[unresolved.input]; - resolved = tableId && { value: tableId, type: "bytes32" }; - } - - if (resolved === undefined) { - throw new MUDError(`Could not resolve dynamic resolution: \n${JSON.stringify(unresolved, null, 2)}`); - } - - return resolved; -} diff --git a/packages/config/src/deprecated/library/index.ts b/packages/config/src/deprecated/library/index.ts index a7b827f9e4..20460ba739 100644 --- a/packages/config/src/deprecated/library/index.ts +++ b/packages/config/src/deprecated/library/index.ts @@ -3,6 +3,5 @@ export * from "./commonSchemas"; export * from "./context"; export * from "./core"; -export * from "./dynamicResolution"; export * from "./errors"; export * from "./validation"; diff --git a/packages/config/src/deprecated/register/index.ts b/packages/config/src/deprecated/register/index.ts index 28ab49f358..36a6de1598 100644 --- a/packages/config/src/deprecated/register/index.ts +++ b/packages/config/src/deprecated/register/index.ts @@ -1,7 +1,7 @@ -import { mudCoreConfig, resolveTableId } from "../library"; +import { mudCoreConfig } from "../library"; import { MUDCoreContext } from "../library/context"; -export { mudCoreConfig, resolveTableId }; +export { mudCoreConfig }; // Importing this file has side-effects, and it should always be imported before MUD plugins. // Use this import for defining a MUD config. diff --git a/packages/store/ts/register/index.ts b/packages/store/ts/register/index.ts index 76ff7049aa..1f69f62792 100644 --- a/packages/store/ts/register/index.ts +++ b/packages/store/ts/register/index.ts @@ -4,7 +4,7 @@ // For convenience register and reexport config, to reduce the number of needed imports for users import "@latticexyz/config/register"; -export { mudCoreConfig, resolveTableId } from "@latticexyz/config/register"; +export { mudCoreConfig } from "@latticexyz/config/register"; // Extend core config and types import "./configExtensions"; import "./typeExtensions"; diff --git a/packages/world/ts/config/defaults.ts b/packages/world/ts/config/defaults.ts index d00fc7e626..61bd712d17 100644 --- a/packages/world/ts/config/defaults.ts +++ b/packages/world/ts/config/defaults.ts @@ -16,7 +16,6 @@ export const WORLD_DEFAULTS = { worldsFile: "./worlds.json", worldgenDirectory: "world", worldImportPath: "@latticexyz/world/src/", - modules: [], } as const; export type WORLD_DEFAULTS = typeof WORLD_DEFAULTS; diff --git a/packages/world/ts/config/resolveWorldConfig.ts b/packages/world/ts/config/resolveWorldConfig.ts index 8b9bea5dab..7dd9766464 100644 --- a/packages/world/ts/config/resolveWorldConfig.ts +++ b/packages/world/ts/config/resolveWorldConfig.ts @@ -11,7 +11,10 @@ export type ResolvedWorldConfig = ReturnType; * filtering out excluded systems, validate system names refer to existing contracts, and * splitting the access list into addresses and system names. */ -export function resolveWorldConfig(config: StoreConfig & WorldConfig, existingContracts?: string[]) { +export function resolveWorldConfig( + config: Pick, + existingContracts?: string[], +) { // Include contract names ending in "System", but not the base "System" contract, and not Interfaces const defaultSystemNames = existingContracts?.filter((name) => name.endsWith("System") && name !== "System" && !name.match(/^I[A-Z]/)) ?? []; diff --git a/packages/world/ts/config/types.ts b/packages/world/ts/config/types.ts index d07ee198f4..40f55f4930 100644 --- a/packages/world/ts/config/types.ts +++ b/packages/world/ts/config/types.ts @@ -1,8 +1,8 @@ import { z } from "zod"; -import { DynamicResolution, ValueWithType } from "@latticexyz/config/library"; import { OrDefaults } from "@latticexyz/common/type-utils"; import { zWorldConfig } from "./worldConfig"; import { SYSTEM_DEFAULTS } from "./defaults"; +import { DynamicResolution, ValueWithType } from "./v2/dynamicResolution"; // zod doesn't preserve doc comments export type SystemUserConfig = { @@ -85,8 +85,6 @@ export interface WorldUserConfig { worldgenDirectory?: string; /** Path for world package imports. Default is "@latticexyz/world/src/" */ worldImportPath?: string; - /** Modules to in the World */ - modules?: readonly ModuleConfig[]; } export type WorldConfig = z.output; diff --git a/packages/world/ts/config/v2/compat.ts b/packages/world/ts/config/v2/compat.ts index 8083360f5d..9f96ee76bb 100644 --- a/packages/world/ts/config/v2/compat.ts +++ b/packages/world/ts/config/v2/compat.ts @@ -1,20 +1,8 @@ import { conform } from "@arktype/util"; -import { Module, World, Systems } from "./output"; +import { World, Systems } from "./output"; import { Store } from "@latticexyz/store"; import { storeToV1 } from "@latticexyz/store/config/v2"; -type modulesToV1 = { - [key in keyof modules]: Omit; -}; - -function modulesToV1(modules: modules): modulesToV1 { - return modules.map((module) => ({ - ...module, - root: module.root ?? false, - args: module.args ?? [], - })) as never; -} - type systemsToV1 = { [key in keyof systems]: { name?: systems[key]["name"]; @@ -30,7 +18,6 @@ export type worldToV1 = world extends World ? Omit, "v2"> & { systems: systemsToV1; excludeSystems: world["excludeSystems"]; - modules: modulesToV1; worldContractName: world["deploy"]["customWorldContract"]; postDeployScript: world["deploy"]["postDeployScript"]; deploysDirectory: world["deploy"]["deploysDirectory"]; @@ -46,7 +33,6 @@ export function worldToV1(world: conform): worldToV1 const v1WorldConfig = { systems: systemsToV1(world.systems), excludeSystems: world.excludeSystems, - modules: modulesToV1(world.modules), worldContractName: world.deploy.customWorldContract, postDeployScript: world.deploy.postDeployScript, deploysDirectory: world.deploy.deploysDirectory, diff --git a/packages/world/ts/config/v2/dynamicResolution.ts b/packages/world/ts/config/v2/dynamicResolution.ts index 764aac9da7..f8ae60f62b 100644 --- a/packages/world/ts/config/v2/dynamicResolution.ts +++ b/packages/world/ts/config/v2/dynamicResolution.ts @@ -1,10 +1,8 @@ -export enum DynamicResolutionType { - TABLE_ID, - SYSTEM_ADDRESS, -} +import { World } from "./output"; export type DynamicResolution = { - type: DynamicResolutionType; + // TODO: add systemAddress support + type: "tableId"; input: string; }; @@ -18,9 +16,9 @@ export type ValueWithType = { */ export function resolveTableId(tableName: string) { return { - type: DynamicResolutionType.TABLE_ID, + type: "tableId", input: tableName, - }; + } as const; } /** Type guard for DynamicResolution */ @@ -38,20 +36,29 @@ export function isValueWithType(value: unknown): value is ValueWithType { */ export function resolveWithContext( input: unknown, - context: { systemAddresses?: Record>; tableIds?: Record }, + context: { config: World; systemAddresses?: Record> }, ): ValueWithType { if (isValueWithType(input)) return input; if (isDynamicResolution(input)) { - let resolved: ValueWithType | undefined = undefined; + if (input.type === "tableId") { + const tableEntries = Object.entries(context.config.tables).filter( + ([tableName, table]) => tableName === input.input || table.name === input.input, + ); - if (input.type === DynamicResolutionType.TABLE_ID) { - const tableId = context.tableIds?.[input.input]; - resolved = tableId && { value: tableId, type: "bytes32" }; - } + if (tableEntries.length > 1) { + throw new Error( + `Found more than one table with name "${input.input}". Try using one of the following table names instead: ${tableEntries.map(([tableName]) => tableName).join(", ")}`, + ); + } - if (resolved) return resolved; + if (tableEntries.length === 1) { + const [entry] = tableEntries; + const [, table] = entry; + return { type: "bytes32", value: table.tableId }; + } + } } - throw new Error(`Could not resolve dynamic resolution: \n${JSON.stringify(input, null, 2)}`); + throw new Error(`Could not resolve dynamic resolution:\n${JSON.stringify(input, null, 2)}`); } diff --git a/packages/world/ts/config/worldConfig.ts b/packages/world/ts/config/worldConfig.ts index ee82848abd..66cc01b2ac 100644 --- a/packages/world/ts/config/worldConfig.ts +++ b/packages/world/ts/config/worldConfig.ts @@ -1,9 +1,8 @@ import { z } from "zod"; -import { DynamicResolutionType, zEthereumAddress, zName, zObjectName } from "@latticexyz/config/library"; +import { zEthereumAddress, zName, zObjectName } from "@latticexyz/config/library"; import { SYSTEM_DEFAULTS, WORLD_DEFAULTS } from "./defaults"; const zSystemName = zObjectName; -const zModuleName = zObjectName; const zSystemAccessList = z.array(zSystemName.or(zEthereumAddress)).readonly().default(SYSTEM_DEFAULTS.accessList); // The system config is a combination of a name config and access config @@ -23,21 +22,6 @@ const zSystemConfig = z.intersection( ]), ); -const zValueWithType = z.object({ - value: z.union([z.string(), z.number(), z.instanceof(Uint8Array)]), - type: z.string(), -}); -const zDynamicResolution = z.object({ type: z.nativeEnum(DynamicResolutionType), input: z.string() }); - -const zModuleConfig = z.object({ - name: zModuleName, - root: z.boolean().default(false), - args: z - .array(z.union([zValueWithType, zDynamicResolution])) - .readonly() - .default([]), -}); - // The parsed world config is the result of parsing the user config export const zWorldConfig = z.object({ worldContractName: z.string().optional(), @@ -49,7 +33,6 @@ export const zWorldConfig = z.object({ worldsFile: z.string().default(WORLD_DEFAULTS.worldsFile), worldgenDirectory: z.string().default(WORLD_DEFAULTS.worldgenDirectory), worldImportPath: z.string().default(WORLD_DEFAULTS.worldImportPath), - modules: z.array(zModuleConfig).readonly().default(WORLD_DEFAULTS.modules), }); // Catchall preserves other plugins' options diff --git a/packages/world/ts/exports/internal.ts b/packages/world/ts/exports/internal.ts index ba1d0824cf..64819f4a4d 100644 --- a/packages/world/ts/exports/internal.ts +++ b/packages/world/ts/exports/internal.ts @@ -11,3 +11,5 @@ export * from "../encodeSystemCallsFrom"; export * from "../actions/callFrom"; export * from "../callWithSignatureTypes"; + +export { resolveTableId, resolveWithContext } from "../config/v2/dynamicResolution"; diff --git a/packages/world/ts/register/index.ts b/packages/world/ts/register/index.ts index 117af51481..2899f663b8 100644 --- a/packages/world/ts/register/index.ts +++ b/packages/world/ts/register/index.ts @@ -5,7 +5,7 @@ // For convenience register and reexport store (which does the same for core config), // to reduce the number of needed imports for users import "@latticexyz/store/register"; -export { mudConfig, mudCoreConfig, resolveTableId } from "@latticexyz/store/register"; +export { mudConfig, mudCoreConfig } from "@latticexyz/store/register"; // Extend core config and types import "./configExtensions"; import "./typeExtensions";