From 3dbf3bf3a3295ad63264044e315dec075de528fd Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 8 May 2024 17:45:16 +0100 Subject: [PATCH] fix(world): config uses readonly arrays (#2805) --- .changeset/seven-windows-tickle.md | 5 +++++ packages/world/ts/config/defaults.ts | 8 ++++---- packages/world/ts/config/types.ts | 10 +++++----- packages/world/ts/config/v2/compat.ts | 12 ++++++------ packages/world/ts/config/v2/defaults.ts | 11 +++++++++-- packages/world/ts/config/v2/input.ts | 18 ++++++++++++++---- packages/world/ts/config/v2/output.ts | 6 +++--- packages/world/ts/config/v2/world.ts | 6 ++++-- packages/world/ts/config/worldConfig.ts | 11 +++++++---- 9 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 .changeset/seven-windows-tickle.md diff --git a/.changeset/seven-windows-tickle.md b/.changeset/seven-windows-tickle.md new file mode 100644 index 0000000000..4ca8c2e6f6 --- /dev/null +++ b/.changeset/seven-windows-tickle.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/world": patch +--- + +Updated World config types to use readonly arrays. diff --git a/packages/world/ts/config/defaults.ts b/packages/world/ts/config/defaults.ts index a6f33553e0..d00fc7e626 100644 --- a/packages/world/ts/config/defaults.ts +++ b/packages/world/ts/config/defaults.ts @@ -1,7 +1,7 @@ export const SYSTEM_DEFAULTS = { registerFunctionSelector: true, openAccess: true, - accessList: [] as string[], + accessList: [], } as const; export type SYSTEM_DEFAULTS = typeof SYSTEM_DEFAULTS; @@ -9,14 +9,14 @@ export type SYSTEM_DEFAULTS = typeof SYSTEM_DEFAULTS; export const WORLD_DEFAULTS = { worldContractName: undefined, worldInterfaceName: "IWorld", - systems: {} as Record, - excludeSystems: [] as string[], + systems: {}, + excludeSystems: [], postDeployScript: "PostDeploy", deploysDirectory: "./deploys", worldsFile: "./worlds.json", worldgenDirectory: "world", worldImportPath: "@latticexyz/world/src/", - modules: [] as [], + modules: [], } as const; export type WORLD_DEFAULTS = typeof WORLD_DEFAULTS; diff --git a/packages/world/ts/config/types.ts b/packages/world/ts/config/types.ts index 70825876f8..d07ee198f4 100644 --- a/packages/world/ts/config/types.ts +++ b/packages/world/ts/config/types.ts @@ -25,7 +25,7 @@ export type SystemUserConfig = { /** If openAccess is false, only the addresses or systems in `access` can call the system */ openAccess: false; /** An array of addresses or system names that can access the system */ - accessList: string[]; + accessList: readonly string[]; } ); @@ -38,7 +38,7 @@ export interface ExpandSystemConfig { - accessList: T extends { accessList: string[] } ? T["accessList"] : SYSTEM_DEFAULTS["accessList"]; + accessList: T extends { accessList: readonly string[] } ? T["accessList"] : SYSTEM_DEFAULTS["accessList"]; } export type SystemsUserConfig = Record; @@ -53,7 +53,7 @@ export type ModuleConfig = { /** Should this module be installed as a root module? */ root?: boolean; /** Arguments to be passed to the module's install method */ - args?: (ValueWithType | DynamicResolution)[]; + args?: readonly (ValueWithType | DynamicResolution)[]; }; // zod doesn't preserve doc comments @@ -71,7 +71,7 @@ export interface WorldUserConfig { */ systems?: SystemsUserConfig; /** Systems to exclude from automatic deployment */ - excludeSystems?: string[]; + excludeSystems?: readonly string[]; /** * Script to execute after the deployment is complete (Default "PostDeploy"). * Script must be placed in the forge scripts directory (see foundry.toml) and have a ".s.sol" extension. @@ -86,7 +86,7 @@ export interface WorldUserConfig { /** Path for world package imports. Default is "@latticexyz/world/src/" */ worldImportPath?: string; /** Modules to in the World */ - modules?: ModuleConfig[]; + 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 95a7850a90..04d7a82b9b 100644 --- a/packages/world/ts/config/v2/compat.ts +++ b/packages/world/ts/config/v2/compat.ts @@ -1,15 +1,15 @@ -import { conform, mutable } from "@arktype/util"; +import { conform } from "@arktype/util"; import { Module, World, Systems } from "./output"; import { Store } from "@latticexyz/store"; import { storeToV1 } from "@latticexyz/store/config/v2"; -type modulesToV1 = mutable<{ - [key in keyof modules]: Required; -}>; +type modulesToV1 = { + [key in keyof modules]: modules[key]; +}; function modulesToV1(modules: modules): modulesToV1 { return modules.map((module) => ({ - name: module.name, + ...module, root: module.root ?? false, args: module.args ?? [], })) as never; @@ -29,7 +29,7 @@ function systemsToV1(systems: systems): systemsToV1 = world extends World ? Omit, "v2"> & { systems: systemsToV1; - excludeSystems: mutable; + excludeSystems: world["excludeSystems"]; modules: modulesToV1; worldContractName: world["deploy"]["customWorldContract"]; postDeployScript: world["deploy"]["postDeployScript"]; diff --git a/packages/world/ts/config/v2/defaults.ts b/packages/world/ts/config/v2/defaults.ts index f8982f24d8..b070a1cbc6 100644 --- a/packages/world/ts/config/v2/defaults.ts +++ b/packages/world/ts/config/v2/defaults.ts @@ -1,11 +1,18 @@ export const SYSTEM_DEFAULTS = { registerFunctionSelectors: true, openAccess: true, - accessList: [] as string[], + accessList: [], } as const; export type SYSTEM_DEFAULTS = typeof SYSTEM_DEFAULTS; +export const MODULE_DEFAULTS = { + root: false, + args: [], +} as const; + +export type MODULE_DEFAULTS = typeof MODULE_DEFAULTS; + export const CODEGEN_DEFAULTS = { worldInterfaceName: "IWorld", worldgenDirectory: "world", @@ -27,7 +34,7 @@ export type DEPLOY_DEFAULTS = typeof DEPLOY_DEFAULTS; export const CONFIG_DEFAULTS = { systems: {}, tables: {}, - excludeSystems: [] as string[], + excludeSystems: [], modules: [], codegen: CODEGEN_DEFAULTS, deploy: DEPLOY_DEFAULTS, diff --git a/packages/world/ts/config/v2/input.ts b/packages/world/ts/config/v2/input.ts index e1ea7961b3..43f2f33770 100644 --- a/packages/world/ts/config/v2/input.ts +++ b/packages/world/ts/config/v2/input.ts @@ -1,6 +1,6 @@ import { evaluate } from "@arktype/util"; import { StoreInput, StoreWithShorthandsInput } from "@latticexyz/store/config/v2"; -import { Module } from "./output"; +import { DynamicResolution, ValueWithType } from "./dynamicResolution"; export type SystemInput = { /** The full resource selector consists of namespace and name */ @@ -16,11 +16,21 @@ export type SystemInput = { /** If openAccess is true, any address can call the system */ openAccess?: boolean; /** An array of addresses or system names that can access the system */ - accessList?: string[]; + accessList?: readonly string[]; }; export type SystemsInput = { [key: string]: SystemInput }; +export type ModuleInput = { + /** The name of the module */ + readonly name: string; + /** Should this module be installed as a root module? */ + readonly root?: boolean; + /** Arguments to be passed to the module's install method */ + // TODO: make more strongly typed by taking in tables input + readonly args?: readonly (ValueWithType | DynamicResolution)[]; +}; + export type DeployInput = { /** * Script to execute after the deployment is complete (Default "PostDeploy"). @@ -56,9 +66,9 @@ export type WorldInput = evaluate< */ systems?: SystemsInput; /** System names to exclude from automatic deployment */ - excludeSystems?: string[]; + excludeSystems?: readonly string[]; /** Modules to in the World */ - modules?: Module[]; + modules?: readonly ModuleInput[]; /** Deploy config */ deploy?: DeployInput; /** Codegen config */ diff --git a/packages/world/ts/config/v2/output.ts b/packages/world/ts/config/v2/output.ts index 99456b6af2..95c374767a 100644 --- a/packages/world/ts/config/v2/output.ts +++ b/packages/world/ts/config/v2/output.ts @@ -5,9 +5,9 @@ export type Module = { /** The name of the module */ readonly name: string; /** Should this module be installed as a root module? */ - readonly root?: boolean; + readonly root: boolean; /** Arguments to be passed to the module's install method */ - readonly args?: (ValueWithType | DynamicResolution)[]; + readonly args: readonly (ValueWithType | DynamicResolution)[]; }; export type System = { @@ -24,7 +24,7 @@ export type System = { /** If openAccess is true, any address can call the system */ readonly openAccess: boolean; /** An array of addresses or system names that can access the system */ - readonly accessList: string[]; + readonly accessList: readonly string[]; }; export type Systems = { readonly [key: string]: System }; diff --git a/packages/world/ts/config/v2/world.ts b/packages/world/ts/config/v2/world.ts index b26b358677..df5882a4de 100644 --- a/packages/world/ts/config/v2/world.ts +++ b/packages/world/ts/config/v2/world.ts @@ -14,7 +14,7 @@ import { isObject, } from "@latticexyz/store/config/v2"; import { SystemsInput, WorldInput } from "./input"; -import { CONFIG_DEFAULTS } from "./defaults"; +import { CONFIG_DEFAULTS, MODULE_DEFAULTS } from "./defaults"; import { Tables } from "@latticexyz/store/internal"; import { resolveSystems } from "./systems"; import { resolveNamespacedTables } from "./namespaces"; @@ -91,6 +91,8 @@ export function resolveWorld(world: world): reso const resolvedStore = resolveStore(world); + const modules = (world.modules ?? CONFIG_DEFAULTS.modules).map((mod) => mergeIfUndefined(mod, MODULE_DEFAULTS)); + return mergeIfUndefined( { ...resolvedStore, @@ -99,7 +101,7 @@ export function resolveWorld(world: world): reso deploy: resolveDeploy(world.deploy), systems: resolveSystems(world.systems ?? CONFIG_DEFAULTS.systems), excludeSystems: get(world, "excludeSystems"), - modules: world.modules, + modules, }, CONFIG_DEFAULTS, ) as never; diff --git a/packages/world/ts/config/worldConfig.ts b/packages/world/ts/config/worldConfig.ts index e846eff478..ee82848abd 100644 --- a/packages/world/ts/config/worldConfig.ts +++ b/packages/world/ts/config/worldConfig.ts @@ -4,7 +4,7 @@ import { SYSTEM_DEFAULTS, WORLD_DEFAULTS } from "./defaults"; const zSystemName = zObjectName; const zModuleName = zObjectName; -const zSystemAccessList = z.array(zSystemName.or(zEthereumAddress)).default(SYSTEM_DEFAULTS.accessList); +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 const zSystemConfig = z.intersection( @@ -32,7 +32,10 @@ const zDynamicResolution = z.object({ type: z.nativeEnum(DynamicResolutionType), const zModuleConfig = z.object({ name: zModuleName, root: z.boolean().default(false), - args: z.array(z.union([zValueWithType, zDynamicResolution])).default([]), + args: z + .array(z.union([zValueWithType, zDynamicResolution])) + .readonly() + .default([]), }); // The parsed world config is the result of parsing the user config @@ -40,13 +43,13 @@ export const zWorldConfig = z.object({ worldContractName: z.string().optional(), worldInterfaceName: z.string().default(WORLD_DEFAULTS.worldInterfaceName), systems: z.record(zSystemName, zSystemConfig).default(WORLD_DEFAULTS.systems), - excludeSystems: z.array(zSystemName).default(WORLD_DEFAULTS.excludeSystems), + excludeSystems: z.array(zSystemName).readonly().default(WORLD_DEFAULTS.excludeSystems), postDeployScript: z.string().default(WORLD_DEFAULTS.postDeployScript), deploysDirectory: z.string().default(WORLD_DEFAULTS.deploysDirectory), 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).default(WORLD_DEFAULTS.modules), + modules: z.array(zModuleConfig).readonly().default(WORLD_DEFAULTS.modules), }); // Catchall preserves other plugins' options