diff --git a/.eslintrc b/.eslintrc index 8c8261853c..b67a090914 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,9 +5,19 @@ "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", ], "rules": { - "max-len": ["error", { "code": 180, "comments": 180 }] - } + "max-len": ["error", { "code": 180, "comments": 180 }], + "@typescript-eslint/ban-types": [ + "error", + { + "extendDefaults": true, + "types": { + // don't throw an error for the empty object type ({}) + "{}": false, + }, + }, + ], + }, } diff --git a/.vscode/settings.json b/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/packages/store/ts/config/v2/schema.test.ts b/packages/store/ts/config/v2/schema.test.ts index 2c73a1c520..eaec622505 100644 --- a/packages/store/ts/config/v2/schema.test.ts +++ b/packages/store/ts/config/v2/schema.test.ts @@ -18,7 +18,7 @@ describe("resolveSchema", () => { }, } as const; attest(resolved).type.toString.snap( - '{ regular: { type: "uint256"; internalType: "uint256"; }; user: { type: "address"; internalType: "CustomType"; }; }', + '{ readonly regular: { readonly type: "uint256"; readonly internalType: "uint256"; }; readonly user: { readonly type: "address"; readonly internalType: "CustomType"; }; }', ); }); diff --git a/packages/store/ts/config/v2/schema.ts b/packages/store/ts/config/v2/schema.ts index 162a9b6bd0..0134a81e37 100644 --- a/packages/store/ts/config/v2/schema.ts +++ b/packages/store/ts/config/v2/schema.ts @@ -6,11 +6,11 @@ export type SchemaInput = { }; export type resolveSchema, scope extends AbiTypeScope> = evaluate<{ - [key in keyof schema]: { + readonly [key in keyof schema]: { /** the Solidity primitive ABI type */ - type: scope["types"][schema[key]]; + readonly type: scope["types"][schema[key]]; /** the user defined type or Solidity primitive ABI type */ - internalType: schema[key]; + readonly internalType: schema[key]; }; }>; diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index 7435f093f9..22c1cff2ea 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -5,151 +5,170 @@ import { attest } from "@arktype/attest"; describe("resolveStoreConfig", () => { it("should accept a shorthand store config as input and expand it", () => { const config = resolveStoreConfig({ tables: { Name: "address" } }); - attest<{ + const expected = { tables: { Name: { schema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; + type: "bytes32", + internalType: "bytes32", + }, value: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, keySchema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; - }; + type: "bytes32", + internalType: "bytes32", + }, + }, valueSchema: { value: { - type: "address"; - internalType: "address"; - }; - }; - primaryKey: ["key"]; - }; - }; - }>(config); + type: "address", + internalType: "address", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("it should accept a user type as input and expand it", () => { const config = resolveStoreConfig({ tables: { Name: "CustomType" }, userTypes: { CustomType: "address" } }); - attest<{ + const expected = { tables: { Name: { schema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; + type: "bytes32", + internalType: "bytes32", + }, value: { - type: "address"; - internalType: "CustomType"; - }; - }; + type: "address", + internalType: "CustomType", + }, + }, keySchema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; - }; + type: "bytes32", + internalType: "bytes32", + }, + }, valueSchema: { value: { - type: "address"; - internalType: "CustomType"; - }; - }; - primaryKey: ["key"]; - }; - }; - userTypes: { CustomType: "address" }; - }>(config); + type: "address", + internalType: "CustomType", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: { CustomType: "address" }, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("given a schema with a key field with static ABI type, it should use `key` as single key", () => { const config = resolveStoreConfig({ tables: { Example: { key: "address", name: "string", age: "uint256" } } }); - attest<{ + const expected = { tables: { Example: { schema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { key: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; - primaryKey: ["key"]; - }; - }; - }>(config); + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("given a schema with a key field with static custom type, it should use `key` as single key", () => { const config = resolveStoreConfig({ tables: { Example: { key: "address", name: "string", age: "uint256" } } }); - attest<{ + const expected = { tables: { Example: { schema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { key: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; - primaryKey: ["key"]; - }; - }; - }>(config); + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("throw an error if the shorthand doesn't include a key field", () => { @@ -192,43 +211,48 @@ describe("resolveStoreConfig", () => { }, }, }); - attest<{ + const expected = { tables: { Example: { schema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, valueSchema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "string"; - }; - }; - primaryKey: ["age"]; - }; - }; - }>(config); + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("should return the full config given a full config with one key and user types", () => { @@ -241,93 +265,101 @@ describe("resolveStoreConfig", () => { }, userTypes: { static: "address", dynamic: "string" }, }); - attest<{ + const expected = { tables: { Example: { schema: { key: { - type: "string"; - internalType: "dynamic"; - }; + type: "string", + internalType: "dynamic", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "address"; - internalType: "static"; - }; - }; + type: "address", + internalType: "static", + }, + }, keySchema: { age: { - type: "address"; - internalType: "static"; - }; - }; + type: "address", + internalType: "static", + }, + }, valueSchema: { key: { - type: "string"; - internalType: "dynamic"; - }; + type: "string", + internalType: "dynamic", + }, name: { - type: "string"; - internalType: "string"; - }; - }; - primaryKey: ["age"]; - }; - }; - userTypes: { static: "address"; dynamic: "string" }; - }>(config); - }); - - it("it should return the full config given a full config with two primaryKey", () => { - const config = resolveStoreConfig({ - tables: { - Example: { - schema: { key: "address", name: "string", age: "uint256" }, - primaryKey: ["age", "key"], + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age"], }, }, + userTypes: { static: "address", dynamic: "string" }, + enums: {}, + namespace: "", + } as const; + + attest(config); + }), + it("it should return the full config given a full config with two primaryKey", () => { + const config = resolveStoreConfig({ + tables: { + Example: { + schema: { key: "address", name: "string", age: "uint256" }, + primaryKey: ["age", "key"], + }, + }, + }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + age: { + type: "uint256", + internalType: "uint256", + }, + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + name: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age", "key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); - attest<{ - tables: { - Example: { - schema: { - key: { - type: "address"; - internalType: "address"; - }; - name: { - type: "string"; - internalType: "string"; - }; - age: { - type: "uint256"; - internalType: "uint256"; - }; - }; - keySchema: { - age: { - type: "uint256"; - internalType: "uint256"; - }; - key: { - type: "address"; - internalType: "address"; - }; - }; - valueSchema: { - name: { - type: "string"; - internalType: "string"; - }; - }; - primaryKey: ["age", "key"]; - }; - }; - }>(config); - }); it("should resolve two tables in the config with different schemas", () => { const config = resolveStoreConfig({ @@ -342,76 +374,81 @@ describe("resolveStoreConfig", () => { }, }, }); - attest<{ + const expected = { tables: { First: { schema: { firstKey: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, firstName: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, firstAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { firstKey: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, firstAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, valueSchema: { firstName: { - type: "string"; - internalType: "string"; - }; - }; - primaryKey: ["firstKey", "firstAge"]; - }; + type: "string", + internalType: "string", + }, + }, + primaryKey: ["firstKey", "firstAge"], + }, Second: { schema: { secondKey: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, secondName: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, secondAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { secondKey: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, secondAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, valueSchema: { secondName: { - type: "string"; - internalType: "string"; - }; - }; - primaryKey: ["secondKey", "secondAge"]; - }; - }; - }>(config); + type: "string", + internalType: "string", + }, + }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("should resolve two tables in the config with different schemas and user types", () => { @@ -428,77 +465,81 @@ describe("resolveStoreConfig", () => { }, userTypes: { Static: "address", Dynamic: "string" }, }); - attest<{ + const expected = { tables: { First: { schema: { firstKey: { - type: "address"; - internalType: "Static"; - }; + type: "address", + internalType: "Static", + }, firstName: { - type: "string"; - internalType: "Dynamic"; - }; + type: "string", + internalType: "Dynamic", + }, firstAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { firstKey: { - type: "address"; - internalType: "Static"; - }; + type: "address", + internalType: "Static", + }, firstAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, valueSchema: { firstName: { - type: "string"; - internalType: "Dynamic"; - }; - }; - primaryKey: ["firstKey", "firstAge"]; - }; + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["firstKey", "firstAge"], + }, Second: { schema: { secondKey: { - type: "address"; - internalType: "Static"; - }; + type: "address", + internalType: "Static", + }, secondName: { - type: "string"; - internalType: "Dynamic"; - }; + type: "string", + internalType: "Dynamic", + }, secondAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { secondKey: { - type: "address"; - internalType: "Static"; - }; + type: "address", + internalType: "Static", + }, secondAge: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, valueSchema: { secondName: { - type: "string"; - internalType: "Dynamic"; - }; - }; - primaryKey: ["secondKey", "secondAge"]; - }; - }; - userTypes: { Static: "address"; Dynamic: "string" }; - }>(config); + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + userTypes: { Static: "address", Dynamic: "string" }, + enums: {}, + namespace: "", + } as const; + + attest(config); }); it("should throw if referring to fields of different tables", () => { @@ -563,46 +604,55 @@ describe("resolveStoreConfig", () => { ValidNames: ["first", "second"], }, }); - attest<{ + const expected = { tables: { Example: { schema: { key: { - type: "string"; - internalType: "dynamic"; - }; + type: "string", + internalType: "dynamic", + }, name: { - type: "uint8"; - internalType: "ValidNames"; - }; + type: "uint8", + internalType: "ValidNames", + }, age: { - type: "address"; - internalType: "static"; - }; - }; + type: "address", + internalType: "static", + }, + }, keySchema: { name: { - type: "uint8"; - internalType: "ValidNames"; - }; - }; + type: "uint8", + internalType: "ValidNames", + }, + }, valueSchema: { age: { - type: "address"; - internalType: "static"; - }; + type: "address", + internalType: "static", + }, key: { - type: "string"; - internalType: "dynamic"; - }; - }; - primaryKey: ["name"]; - }; - }; - userTypes: { static: "address"; dynamic: "string" }; + type: "string", + internalType: "dynamic", + }, + }, + primaryKey: ["name"], + }, + }, + userTypes: { static: "address", dynamic: "string" }, enums: { - ValidNames: ["first", "second"]; - }; - }>(config); + ValidNames: ["first", "second"], + }, + namespace: "", + } as const; + + attest(config); + }); + + it("should use the root namespace as default namespace", () => { + const config = resolveStoreConfig({ tables: { Example: {} } }); + + attest<"">(config.namespace); }); }); diff --git a/packages/store/ts/config/v2/store.ts b/packages/store/ts/config/v2/store.ts index d07b903944..598d3b58aa 100644 --- a/packages/store/ts/config/v2/store.ts +++ b/packages/store/ts/config/v2/store.ts @@ -1,13 +1,14 @@ import { Dict, evaluate, narrow } from "@arktype/util"; +import { get } from "./generics"; import { SchemaInput } from "./schema"; import { AbiType, AbiTypeScope, extendScope } from "./scope"; import { TableInput, resolveTableConfig, validateTableConfig } from "./table"; -import { get } from "./generics"; -type UserTypes = Dict; -type Enums = Dict; +export type UserTypes = Dict; +export type Enums = Dict; export type StoreConfigInput = { + namespace?: string; tables: StoreTablesConfigInput>; userTypes?: userTypes; enums?: enums; @@ -22,27 +23,26 @@ export type validateStoreTablesConfig = evaluate<{ - [key in keyof input]: resolveTableConfig; + readonly [key in keyof input]: resolveTableConfig; }>; -type scopeWithUserTypes = UserTypes extends userTypes +export type scopeWithUserTypes = UserTypes extends userTypes ? scope : userTypes extends UserTypes ? extendScope : scope; -type scopeWithEnums = Enums extends enums +export type scopeWithEnums = Enums extends enums ? scope : enums extends Enums ? extendScope : scope; +export type extendedScope = scopeWithEnums, scopeWithUserTypes>>; + export type validateStoreConfig = { [key in keyof input]: key extends "tables" - ? validateStoreTablesConfig< - input[key], - scopeWithEnums, scopeWithUserTypes>> - > + ? validateStoreTablesConfig> : key extends "userTypes" ? UserTypes : key extends "enums" @@ -50,16 +50,16 @@ export type validateStoreConfig = { : input[key]; }; +export type resolveEnums = { readonly [key in keyof enums]: Readonly }; + export type resolveStoreConfig = evaluate<{ - [key in keyof input]: key extends "tables" - ? resolveStoreTablesConfig< - input[key], - scopeWithEnums, scopeWithUserTypes>> - > - : input[key]; + readonly tables: "tables" extends keyof input ? resolveStoreTablesConfig> : {}; + readonly userTypes: "userTypes" extends keyof input ? input["userTypes"] : {}; + readonly enums: "enums" extends keyof input ? resolveEnums : {}; + readonly namespace: "namespace" extends keyof input ? input["namespace"] : ""; }>; -export function resolveStoreConfig(input: validateStoreConfig): resolveStoreConfig { +export function resolveStoreConfig(input: validateStoreConfig): resolveStoreConfig { // TODO: runtime implementation return {} as never; } diff --git a/packages/store/ts/config/v2/table.test.ts b/packages/store/ts/config/v2/table.test.ts index 5c44fc0bb1..505dc8e45b 100644 --- a/packages/store/ts/config/v2/table.test.ts +++ b/packages/store/ts/config/v2/table.test.ts @@ -13,6 +13,7 @@ describe("validateKeys", () => { it("should return a tuple of valid keys with an extended scope", () => { const scope = extendScope(AbiTypeScope, { static: "address", dynamic: "string" }); + attest< ["static", "customStatic"], validateKeys< @@ -34,6 +35,7 @@ describe("validateKeys", () => { it("should return a tuple of valid keys with an extended scope", () => { const scope = extendScope(AbiTypeScope, { static: "address", dynamic: "string" }); + attest< ["static", "customStatic"], validateKeys< @@ -50,6 +52,7 @@ describe("validateKeys", () => { describe("resolveTableShorthand", () => { it("should expand a single ABI type into a key/value schema", () => { const table = resolveTableShorthand("address"); + attest<{ schema: { key: "bytes32"; @@ -62,6 +65,7 @@ describe("resolveTableShorthand", () => { it("should expand a single custom into a key/value schema", () => { const scope = extendScope(AbiTypeScope, { CustomType: "uint256" }); const table = resolveTableShorthand("CustomType", scope); + attest<{ schema: { key: "bytes32"; @@ -88,6 +92,7 @@ describe("resolveTableShorthand", () => { it("should use `key` as single key if it has a static ABI type", () => { const table = resolveTableShorthand({ key: "address", name: "string", age: "uint256" }); + attest<{ schema: { key: "address"; @@ -124,6 +129,7 @@ describe("resolveTableShorthand", () => { it("should use `key` as single key if it has a static custom type", () => { const scope = extendScope(AbiTypeScope, { CustomType: "uint256" }); const table = resolveTableShorthand({ key: "CustomType", name: "string", age: "uint256" }, scope); + attest<{ schema: { key: "CustomType"; name: "string"; age: "uint256" }; primaryKey: ["key"]; @@ -134,6 +140,7 @@ describe("resolveTableShorthand", () => { it("should throw an error if `key` is not a custom static type", () => { const scope = extendScope(AbiTypeScope, { CustomType: "bytes" }); + // @ts-expect-error "Error: Provide a `key` field with static ABI type or a full config with explicit primaryKey override." attest(resolveTableShorthand({ key: "CustomType", name: "string", age: "uint256" }, scope)).type.errors( `Provide a \`key\` field with static ABI type or a full config with explicit primaryKey override.`, @@ -144,136 +151,144 @@ describe("resolveTableShorthand", () => { describe("resolveTableConfig", () => { it("should expand a single ABI type into a key/value schema", () => { const table = resolveTableConfig("address"); - attest<{ + const expected = { schema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; + type: "bytes32", + internalType: "bytes32", + }, value: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, keySchema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; - }; + type: "bytes32", + internalType: "bytes32", + }, + }, valueSchema: { value: { - type: "address"; - internalType: "address"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "address", + internalType: "address", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should expand a single custom type into a key/value schema", () => { const scope = extendScope(AbiTypeScope, { CustomType: "address" }); const table = resolveTableConfig("CustomType", scope); - attest<{ + const expected = { schema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; + type: "bytes32", + internalType: "bytes32", + }, value: { - type: "address"; - internalType: "CustomType"; - }; - }; + type: "address", + internalType: "CustomType", + }, + }, keySchema: { key: { - type: "bytes32"; - internalType: "bytes32"; - }; - }; + type: "bytes32", + internalType: "bytes32", + }, + }, valueSchema: { value: { - type: "address"; - internalType: "CustomType"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "address", + internalType: "CustomType", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should use `key` as single key if it has a static ABI type", () => { const table = resolveTableConfig({ key: "address", name: "string", age: "uint256" }); - attest<{ + const expected = { schema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { key: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should use `key` as single key if it has a static custom type", () => { const scope = extendScope(AbiTypeScope, { CustomType: "uint256" }); const table = resolveTableConfig({ key: "CustomType", name: "string", age: "uint256" }, scope); - attest<{ + const expected = { schema: { key: { - type: "uint256"; - internalType: "CustomType"; - }; + type: "uint256", + internalType: "CustomType", + }, name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; + type: "uint256", + internalType: "uint256", + }, + }, keySchema: { key: { - type: "uint256"; - internalType: "CustomType"; - }; - }; + type: "uint256", + internalType: "CustomType", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "string"; - }; + type: "string", + internalType: "string", + }, age: { - type: "uint256"; - internalType: "uint256"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should throw if the shorthand key is a dynamic ABI type", () => { @@ -310,21 +325,23 @@ describe("resolveTableConfig", () => { schema: { key: "address", name: "string", age: "uint256" }, primaryKey: ["age"], }); - attest<{ + const expected = { schema: { - key: { type: "address"; internalType: "address" }; - name: { type: "string"; internalType: "string" }; - age: { type: "uint256"; internalType: "uint256" }; - }; + key: { type: "address", internalType: "address" }, + name: { type: "string", internalType: "string" }, + age: { type: "uint256", internalType: "uint256" }, + }, keySchema: { - age: { type: "uint256"; internalType: "uint256" }; - }; + age: { type: "uint256", internalType: "uint256" }, + }, valueSchema: { - key: { type: "address"; internalType: "address" }; - name: { type: "string"; internalType: "string" }; - }; - primaryKey: ["age"]; - }>(table); + key: { type: "address", internalType: "address" }, + name: { type: "string", internalType: "string" }, + }, + primaryKey: ["age"], + } as const; + + attest(table); }); it("should return the full config given a full config with two primaryKey", () => { @@ -332,97 +349,103 @@ describe("resolveTableConfig", () => { schema: { key: "address", name: "string", age: "uint256" }, primaryKey: ["age", "key"], }); - attest<{ + const expected = { schema: { - key: { type: "address"; internalType: "address" }; - name: { type: "string"; internalType: "string" }; - age: { type: "uint256"; internalType: "uint256" }; - }; + key: { type: "address", internalType: "address" }, + name: { type: "string", internalType: "string" }, + age: { type: "uint256", internalType: "uint256" }, + }, keySchema: { - age: { type: "uint256"; internalType: "uint256" }; - key: { type: "address"; internalType: "address" }; - }; + age: { type: "uint256", internalType: "uint256" }, + key: { type: "address", internalType: "address" }, + }, valueSchema: { - name: { type: "string"; internalType: "string" }; - }; - primaryKey: ["age", "key"]; - }>(table); + name: { type: "string", internalType: "string" }, + }, + primaryKey: ["age", "key"], + } as const; + + attest(table); }); it("should return the full config given a config with custom types as values", () => { const scope = extendScope(AbiTypeScope, { CustomString: "string", CustomNumber: "uint256" }); const table = resolveTableConfig({ key: "address", name: "CustomString", age: "CustomNumber" }, scope); - attest<{ + const expected = { schema: { key: { - type: "address"; - internalType: "address"; - }; + type: "address", + internalType: "address", + }, name: { - type: "string"; - internalType: "CustomString"; - }; + type: "string", + internalType: "CustomString", + }, age: { - type: "uint256"; - internalType: "CustomNumber"; - }; - }; + type: "uint256", + internalType: "CustomNumber", + }, + }, keySchema: { key: { - type: "address"; - internalType: "address"; - }; - }; + type: "address", + internalType: "address", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "CustomString"; - }; + type: "string", + internalType: "CustomString", + }, age: { - type: "uint256"; - internalType: "CustomNumber"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "uint256", + internalType: "CustomNumber", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should return the full config given a config with custom type as key", () => { const scope = extendScope(AbiTypeScope, { CustomString: "string", CustomNumber: "uint256" }); const table = resolveTableConfig({ key: "CustomNumber", name: "CustomString", age: "CustomNumber" }, scope); - attest<{ + const expected = { schema: { key: { - type: "uint256"; - internalType: "CustomNumber"; - }; + type: "uint256", + internalType: "CustomNumber", + }, name: { - type: "string"; - internalType: "CustomString"; - }; + type: "string", + internalType: "CustomString", + }, age: { - type: "uint256"; - internalType: "CustomNumber"; - }; - }; + type: "uint256", + internalType: "CustomNumber", + }, + }, keySchema: { key: { - type: "uint256"; - internalType: "CustomNumber"; - }; - }; + type: "uint256", + internalType: "CustomNumber", + }, + }, valueSchema: { name: { - type: "string"; - internalType: "CustomString"; - }; + type: "string", + internalType: "CustomString", + }, age: { - type: "uint256"; - internalType: "CustomNumber"; - }; - }; - primaryKey: ["key"]; - }>(table); + type: "uint256", + internalType: "CustomNumber", + }, + }, + primaryKey: ["key"], + } as const; + + attest(table); }); it("should throw if the provided key is a dynamic ABI type", () => { diff --git a/packages/store/ts/config/v2/table.ts b/packages/store/ts/config/v2/table.ts index 8574a9119b..314e86e5f2 100644 --- a/packages/store/ts/config/v2/table.ts +++ b/packages/store/ts/config/v2/table.ts @@ -1,11 +1,12 @@ -import { ErrorMessage, evaluate } from "@arktype/util"; +import { ErrorMessage, conform, evaluate } from "@arktype/util"; import { SchemaInput, resolveSchema } from "./schema"; import { AbiTypeScope, getStaticAbiTypeKeys } from "./scope"; +import { get } from "./generics"; export type NoStaticKeyFieldError = ErrorMessage<"Provide a `key` field with static ABI type or a full config with explicit primaryKey override.">; -export type ValidKeys, scope extends AbiTypeScope> = [ +export type ValidKeys, scope extends AbiTypeScope> = readonly [ getStaticAbiTypeKeys, ...getStaticAbiTypeKeys[], ]; @@ -75,21 +76,13 @@ export function validateKeys = - input extends TableFullInput, scope> - ? { - primaryKey: validateKeys, input["primaryKey"]>; - schema: input["schema"]; - } - : input extends { primaryKey: unknown; schema: SchemaInput } - ? { - primaryKey: validateKeys, input["primaryKey"]>; - schema: SchemaInput; - } - : { - primaryKey: string[]; - schema: SchemaInput; - }; +export type validateTableFull = { + [key in keyof input]: key extends "primaryKey" + ? validateKeys, SchemaInput>, scope>, input[key]> + : key extends "schema" + ? conform> + : input[key]; +}; export type validateTableConfig = input extends TableShorthandInput @@ -102,17 +95,17 @@ export type resolveTableFullConfig< input extends TableFullInput, scope>, scope extends AbiTypeScope = AbiTypeScope, > = evaluate<{ - primaryKey: input["primaryKey"]; - schema: resolveSchema; - keySchema: resolveSchema< + readonly primaryKey: Readonly; + readonly schema: resolveSchema; + readonly keySchema: resolveSchema< { - [key in input["primaryKey"][number]]: input["schema"][key]; + readonly [key in input["primaryKey"][number]]: input["schema"][key]; }, scope >; - valueSchema: resolveSchema< + readonly valueSchema: resolveSchema< { - [key in Exclude]: input["schema"][key]; + readonly [key in Exclude]: input["schema"][key]; }, scope >; @@ -123,7 +116,7 @@ export type resolveTableConfig ? resolveTableFullConfig, scope> : input extends TableFullInput, scope> ? resolveTableFullConfig - : never + : input >; /** diff --git a/packages/world/package.json b/packages/world/package.json index dd3d3369e9..f87bae2f1f 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -46,11 +46,13 @@ "test:ci": "pnpm run test" }, "dependencies": { + "@arktype/util": "0.0.25", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", "abitype": "1.0.0", + "arktype": "1.0.29-alpha", "viem": "2.7.12", "zod": "^3.21.4" }, diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts new file mode 100644 index 0000000000..5ea74d13c8 --- /dev/null +++ b/packages/world/ts/config/v2/world.test.ts @@ -0,0 +1,885 @@ +import { describe, it } from "vitest"; +import { resolveWorldConfig } from "./world"; +import { attest } from "@arktype/attest"; + +describe("resolveWorldConfig", () => { + it("should resolve namespaced tables", () => { + const config = resolveWorldConfig({ + namespaces: { + ExampleNamespace: { + tables: { + ExampleTable: { + schema: { + key: "address", + value: "uint256", + dynamic: "string", + }, + primaryKey: ["key"], + }, + }, + }, + }, + }); + + const expected = { + tables: { + ExampleNamespace__ExampleTable: { + schema: { + key: { + type: "address", + internalType: "address", + }, + value: { + type: "uint256", + internalType: "uint256", + }, + dynamic: { + type: "string", + internalType: "string", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + value: { + type: "uint256", + internalType: "uint256", + }, + dynamic: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["key"], + }, + }, + namespaces: { + ExampleNamespace: { + tables: { + ExampleTable: { + schema: { + key: { + type: "address", + internalType: "address", + }, + value: { + type: "uint256", + internalType: "uint256", + }, + dynamic: { + type: "string", + internalType: "string", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + value: { + type: "uint256", + internalType: "uint256", + }, + dynamic: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["key"], + }, + }, + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + } as const; + + attest(config); + }); + + it("should resolve namespaced table config with user types and enums", () => { + const config = resolveWorldConfig({ + namespaces: { + ExampleNamespace: { + tables: { + ExampleTable: { + schema: { + key: "Static", + value: "MyEnum", + dynamic: "Dynamic", + }, + primaryKey: ["key"], + }, + }, + }, + }, + userTypes: { + Static: "address", + Dynamic: "string", + }, + enums: { + MyEnum: ["First", "Second"], + }, + }); + + const expected = { + tables: { + ExampleNamespace__ExampleTable: { + schema: { + key: { + type: "address", + internalType: "Static", + }, + value: { + type: "uint8", + internalType: "MyEnum", + }, + dynamic: { + type: "string", + internalType: "Dynamic", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "Static", + }, + }, + valueSchema: { + value: { + type: "uint8", + internalType: "MyEnum", + }, + dynamic: { + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["key"], + }, + }, + namespaces: { + ExampleNamespace: { + tables: { + ExampleTable: { + schema: { + key: { + type: "address", + internalType: "Static", + }, + value: { + type: "uint8", + internalType: "MyEnum", + }, + dynamic: { + type: "string", + internalType: "Dynamic", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "Static", + }, + }, + valueSchema: { + value: { + type: "uint8", + internalType: "MyEnum", + }, + dynamic: { + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["key"], + }, + }, + }, + }, + userTypes: { + Static: "address", + Dynamic: "string", + }, + enums: { + MyEnum: ["First", "Second"], + }, + namespace: "", + } as const; + + attest(config); + }); + + describe("should have the same output as `resolveWorldConfig` for store config inputs", () => { + it("should accept a shorthand store config as input and expand it", () => { + const config = resolveWorldConfig({ tables: { Name: "address" } }); + const expected = { + tables: { + Name: { + schema: { + key: { + type: "bytes32", + internalType: "bytes32", + }, + value: { + type: "address", + internalType: "address", + }, + }, + keySchema: { + key: { + type: "bytes32", + internalType: "bytes32", + }, + }, + valueSchema: { + value: { + type: "address", + internalType: "address", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(expected); + }); + + it("it should accept a user type as input and expand it", () => { + const config = resolveWorldConfig({ tables: { Name: "CustomType" }, userTypes: { CustomType: "address" } }); + const expected = { + tables: { + Name: { + schema: { + key: { + type: "bytes32", + internalType: "bytes32", + }, + value: { + type: "address", + internalType: "CustomType", + }, + }, + keySchema: { + key: { + type: "bytes32", + internalType: "bytes32", + }, + }, + valueSchema: { + value: { + type: "address", + internalType: "CustomType", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: { CustomType: "address" }, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("given a schema with a key field with static ABI type, it should use `key` as single key", () => { + const config = resolveWorldConfig({ tables: { Example: { key: "address", name: "string", age: "uint256" } } }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("given a schema with a key field with static custom type, it should use `key` as single key", () => { + const config = resolveWorldConfig({ tables: { Example: { key: "address", name: "string", age: "uint256" } } }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + primaryKey: ["key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("throw an error if the shorthand doesn't include a key field", () => { + attest( + resolveWorldConfig({ + tables: { + // @ts-expect-error Provide a `key` field with static ABI type or a full config with explicit primaryKey override. + Example: { + name: "string", + age: "uint256", + }, + }, + }), + ).type.errors("Provide a `key` field with static ABI type or a full config with explicit primaryKey override."); + }); + + it("throw an error if the shorthand config includes a non-static key field", () => { + attest( + // @ts-expect-error Provide a `key` field with static ABI type or a full config with explicit primaryKey override. + resolveWorldConfig({ tables: { Example: { key: "string", name: "string", age: "uint256" } } }), + ).type.errors("Provide a `key` field with static ABI type or a full config with explicit primaryKey override."); + }); + + it("throw an error if the shorthand config includes a non-static user type as key field", () => { + attest( + resolveWorldConfig({ + // @ts-expect-error Provide a `key` field with static ABI type or a full config with explicit primaryKey override. + tables: { Example: { key: "dynamic", name: "string", age: "uint256" } }, + userTypes: { dynamic: "string", static: "address" }, + }), + ).type.errors("Provide a `key` field with static ABI type or a full config with explicit primaryKey override."); + }); + + it("should return the full config given a full config with one key", () => { + const config = resolveWorldConfig({ + tables: { + Example: { + schema: { key: "address", name: "string", age: "uint256" }, + primaryKey: ["age"], + }, + }, + }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + age: { + type: "uint256", + internalType: "uint256", + }, + }, + valueSchema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("should return the full config given a full config with one key and user types", () => { + const config = resolveWorldConfig({ + tables: { + Example: { + schema: { key: "dynamic", name: "string", age: "static" }, + primaryKey: ["age"], + }, + }, + userTypes: { static: "address", dynamic: "string" }, + }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "string", + internalType: "dynamic", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "address", + internalType: "static", + }, + }, + keySchema: { + age: { + type: "address", + internalType: "static", + }, + }, + valueSchema: { + key: { + type: "string", + internalType: "dynamic", + }, + name: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age"], + }, + }, + userTypes: { static: "address", dynamic: "string" }, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("it should return the full config given a full config with two primaryKey", () => { + const config = resolveWorldConfig({ + tables: { + Example: { + schema: { key: "address", name: "string", age: "uint256" }, + primaryKey: ["age", "key"], + }, + }, + }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "address", + internalType: "address", + }, + name: { + type: "string", + internalType: "string", + }, + age: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + age: { + type: "uint256", + internalType: "uint256", + }, + key: { + type: "address", + internalType: "address", + }, + }, + valueSchema: { + name: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["age", "key"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("should resolve two tables in the config with different schemas", () => { + const config = resolveWorldConfig({ + tables: { + First: { + schema: { firstKey: "address", firstName: "string", firstAge: "uint256" }, + primaryKey: ["firstKey", "firstAge"], + }, + Second: { + schema: { secondKey: "address", secondName: "string", secondAge: "uint256" }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + }); + const expected = { + tables: { + First: { + schema: { + firstKey: { + type: "address", + internalType: "address", + }, + firstName: { + type: "string", + internalType: "string", + }, + firstAge: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + firstKey: { + type: "address", + internalType: "address", + }, + firstAge: { + type: "uint256", + internalType: "uint256", + }, + }, + valueSchema: { + firstName: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["firstKey", "firstAge"], + }, + Second: { + schema: { + secondKey: { + type: "address", + internalType: "address", + }, + secondName: { + type: "string", + internalType: "string", + }, + secondAge: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + secondKey: { + type: "address", + internalType: "address", + }, + secondAge: { + type: "uint256", + internalType: "uint256", + }, + }, + valueSchema: { + secondName: { + type: "string", + internalType: "string", + }, + }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + userTypes: {}, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("should resolve two tables in the config with different schemas and user types", () => { + const config = resolveWorldConfig({ + tables: { + First: { + schema: { firstKey: "Static", firstName: "Dynamic", firstAge: "uint256" }, + primaryKey: ["firstKey", "firstAge"], + }, + Second: { + schema: { secondKey: "Static", secondName: "Dynamic", secondAge: "uint256" }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + userTypes: { Static: "address", Dynamic: "string" }, + }); + const expected = { + tables: { + First: { + schema: { + firstKey: { + type: "address", + internalType: "Static", + }, + firstName: { + type: "string", + internalType: "Dynamic", + }, + firstAge: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + firstKey: { + type: "address", + internalType: "Static", + }, + firstAge: { + type: "uint256", + internalType: "uint256", + }, + }, + valueSchema: { + firstName: { + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["firstKey", "firstAge"], + }, + Second: { + schema: { + secondKey: { + type: "address", + internalType: "Static", + }, + secondName: { + type: "string", + internalType: "Dynamic", + }, + secondAge: { + type: "uint256", + internalType: "uint256", + }, + }, + keySchema: { + secondKey: { + type: "address", + internalType: "Static", + }, + secondAge: { + type: "uint256", + internalType: "uint256", + }, + }, + valueSchema: { + secondName: { + type: "string", + internalType: "Dynamic", + }, + }, + primaryKey: ["secondKey", "secondAge"], + }, + }, + userTypes: { Static: "address", Dynamic: "string" }, + enums: {}, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("should throw if referring to fields of different tables", () => { + attest( + resolveWorldConfig({ + tables: { + First: { + schema: { firstKey: "address", firstName: "string", firstAge: "uint256" }, + primaryKey: ["firstKey", "firstAge"], + }, + Second: { + schema: { secondKey: "address", secondName: "string", secondAge: "uint256" }, + // @ts-expect-error Type '"firstKey"' is not assignable to type '"secondKey" | "secondAge"' + primaryKey: ["firstKey", "secondAge"], + }, + }, + }), + ).type.errors(`Type '"firstKey"' is not assignable to type '"secondKey" | "secondAge"'`); + }); + + it("should throw an error if the provided key is not a static field", () => { + attest( + resolveWorldConfig({ + tables: { + Example: { + schema: { key: "address", name: "string", age: "uint256" }, + // @ts-expect-error Type '"name"' is not assignable to type '"key" | "age"'. + primaryKey: ["name"], + }, + }, + }), + ).type.errors(`Type '"name"' is not assignable to type '"key" | "age"'`); + }); + + it("should throw an error if the provided key is not a static field with user types", () => { + attest( + resolveWorldConfig({ + tables: { + Example: { + schema: { key: "address", name: "Dynamic", age: "uint256" }, + // @ts-expect-error Type '"name"' is not assignable to type '"key" | "age"'. + primaryKey: ["name"], + }, + }, + userTypes: { + Dynamic: "string", + }, + }), + ).type.errors(`Type '"name"' is not assignable to type '"key" | "age"'`); + }); + + it("should return the full config given a full config with enums and user types", () => { + const config = resolveWorldConfig({ + tables: { + Example: { + schema: { key: "dynamic", name: "ValidNames", age: "static" }, + primaryKey: ["name"], + }, + }, + userTypes: { static: "address", dynamic: "string" }, + enums: { + ValidNames: ["first", "second"], + }, + }); + const expected = { + tables: { + Example: { + schema: { + key: { + type: "string", + internalType: "dynamic", + }, + name: { + type: "uint8", + internalType: "ValidNames", + }, + age: { + type: "address", + internalType: "static", + }, + }, + keySchema: { + name: { + type: "uint8", + internalType: "ValidNames", + }, + }, + valueSchema: { + age: { + type: "address", + internalType: "static", + }, + key: { + type: "string", + internalType: "dynamic", + }, + }, + primaryKey: ["name"], + }, + }, + userTypes: { static: "address", dynamic: "string" }, + enums: { + ValidNames: ["first", "second"], + }, + namespace: "", + namespaces: {}, + } as const; + + attest(config); + }); + + it("should use the root namespace as default namespace", () => { + const config = resolveWorldConfig({ tables: { Example: {} } }); + + attest<"">(config.namespace); + }); + }); +}); diff --git a/packages/world/ts/config/v2/world.ts b/packages/world/ts/config/v2/world.ts new file mode 100644 index 0000000000..5c96cc0d04 --- /dev/null +++ b/packages/world/ts/config/v2/world.ts @@ -0,0 +1,77 @@ +import { evaluate, narrow } from "@arktype/util"; +import { + UserTypes, + Enums, + StoreConfigInput, + resolveStoreConfig, + validateStoreTablesConfig, + resolveStoreTablesConfig, + extendedScope, +} from "@latticexyz/store/config"; +import { get } from "@latticexyz/store/ts/config/v2/generics"; +import { AbiTypeScope } from "@latticexyz/store/ts/config/v2/scope"; +import { resolveTableConfig } from "@latticexyz/store/ts/config/v2/table"; + +export type WorldConfigInput = evaluate< + StoreConfigInput & { + namespaces?: NamespacesInput; + } +>; + +export type NamespacesInput = { [key: string]: NamespaceInput }; + +export type NamespaceInput = Pick; + +export type validateNamespaces = { + [namespace in keyof input]: { + [key in keyof input[namespace]]: key extends "tables" + ? validateStoreTablesConfig + : input[namespace][key]; + }; +}; + +export type validateWorldConfig = { + readonly [key in keyof input]: key extends "tables" + ? validateStoreTablesConfig> + : key extends "userTypes" + ? UserTypes + : key extends "enums" + ? narrow + : key extends "namespaces" + ? validateNamespaces> + : input[key]; +}; + +export type namespacedTableKeys = "namespaces" extends keyof input + ? "tables" extends keyof input["namespaces"][keyof input["namespaces"]] + ? `${keyof input["namespaces"] & string}__${keyof input["namespaces"][keyof input["namespaces"]]["tables"] & + string}` + : never + : never; + +export type resolveWorldConfig = evaluate< + resolveStoreConfig & { + readonly tables: "namespaces" extends keyof input + ? { + readonly [key in namespacedTableKeys]: key extends `${infer namespace}__${infer table}` + ? resolveTableConfig< + get, namespace>, "tables">, table>, + extendedScope + > + : never; + } + : {}; + readonly namespaces: "namespaces" extends keyof input + ? { + [key in keyof input["namespaces"]]: { + readonly tables: resolveStoreTablesConfig, extendedScope>; + }; + } + : {}; + } +>; + +export function resolveWorldConfig(input: validateWorldConfig): resolveWorldConfig { + // TODO: runtime implementation + return {} as never; +} diff --git a/packages/world/tsconfig.json b/packages/world/tsconfig.json index 9060822400..8dc41f1285 100644 --- a/packages/world/tsconfig.json +++ b/packages/world/tsconfig.json @@ -13,7 +13,8 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "noErrorTruncation": true }, "include": ["mud.config.ts", "ts"] } diff --git a/packages/world/vitest.config.ts b/packages/world/vitest.config.ts new file mode 100644 index 0000000000..b6a66f2a98 --- /dev/null +++ b/packages/world/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globalSetup: "vitestSetup.ts", + }, +}); diff --git a/packages/world/vitestSetup.ts b/packages/world/vitestSetup.ts new file mode 100644 index 0000000000..13353cdfd6 --- /dev/null +++ b/packages/world/vitestSetup.ts @@ -0,0 +1 @@ +export { setup, cleanup as teardown } from "@arktype/attest"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f5997b726..1f24687934 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1003,6 +1003,9 @@ importers: packages/world: dependencies: + '@arktype/util': + specifier: 0.0.25 + version: 0.0.25 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -1018,6 +1021,9 @@ importers: abitype: specifier: 1.0.0 version: 1.0.0(typescript@5.4.2)(zod@3.21.4) + arktype: + specifier: 1.0.29-alpha + version: 1.0.29-alpha viem: specifier: 2.7.12 version: 2.7.12(typescript@5.4.2)(zod@3.21.4) @@ -8399,7 +8405,7 @@ packages: resolution: {integrity: sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ==} engines: {node: '>=10'} dependencies: - semver: 7.6.0 + semver: 7.5.0 dev: false /node-fetch@2.6.9: @@ -8427,7 +8433,7 @@ packages: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.6.0 + semver: 7.5.0 tar: 6.2.0 which: 2.0.2 transitivePeerDependencies: diff --git a/templates/phaser/.vscode/settings.json b/templates/phaser/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/templates/phaser/.vscode/settings.json +++ b/templates/phaser/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/templates/react-ecs/.vscode/settings.json b/templates/react-ecs/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/templates/react-ecs/.vscode/settings.json +++ b/templates/react-ecs/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/templates/react/.vscode/settings.json b/templates/react/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/templates/react/.vscode/settings.json +++ b/templates/react/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/templates/threejs/.vscode/settings.json b/templates/threejs/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/templates/threejs/.vscode/settings.json +++ b/templates/threejs/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/templates/vanilla/.vscode/settings.json b/templates/vanilla/.vscode/settings.json index 0967ef424b..25fa6215fd 100644 --- a/templates/vanilla/.vscode/settings.json +++ b/templates/vanilla/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "typescript.tsdk": "node_modules/typescript/lib" +}