From c5dca9e820f1c509eee71e841c205f84bcaa0c75 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 19 Sep 2023 12:31:38 +0100 Subject: [PATCH 01/15] wip --- packages/store/ts/config/tableConfig.ts | 69 +++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/store/ts/config/tableConfig.ts diff --git a/packages/store/ts/config/tableConfig.ts b/packages/store/ts/config/tableConfig.ts new file mode 100644 index 0000000000..3aa21764b3 --- /dev/null +++ b/packages/store/ts/config/tableConfig.ts @@ -0,0 +1,69 @@ +import { SchemaAbiType, StaticAbiType, schemaAbiTypes, staticAbiTypes } from "@latticexyz/schema-type"; +import { z } from "zod"; + +// TODO: combine with protocol-parser def and move up to schema-type? +export type KeySchema = Readonly>; +export type ValueSchema = Readonly>; + +export type TableType = "table" | "offchainTable"; + +export type TableConfig = Readonly<{ + type: TableType; + namespace: string; + name: string; + tableId: `0x${string}`; + keySchema: KeySchema; + valueSchema: ValueSchema; + codegen: { + outputDirectory: string; + tableIdArgument: boolean; + storeArgument: boolean; + dataStruct: boolean; + }; +}>; + +const tableSchema = z.object({ + type: z.enum(["table", "offchainTable"]).default("table").optional(), + namespace: z.string().optional(), + name: z.string().optional(), + // TODO: refine/validate key/field names + keySchema: z.record(z.enum(staticAbiTypes)).default({ key: "bytes32" }).optional(), + valueSchema: z.record(z.enum(schemaAbiTypes)), + // TODO: codegen +}); + +// TODO: refine/validate table names +const tablesSchema = z.record(tableSchema); + +const namespaceSchema = z.object({ + tables: tablesSchema.optional(), +}); + +// TODO: overall codegen options +const configSchema = namespaceSchema.merge( + z.object({ + // TODO: refine/validate namespace keys + namespaces: z.record(namespaceSchema).optional(), + }) +); + +function storeConfig>( + config: TConfig +): Readonly<{ tables: TableConfig[] }> { + const parsed = configSchema.parse(config); + return { tables: [] }; +} + +const config = storeConfig({ + tables: { + Position: { + keySchema: { + x: "uint32", + y: "uint32", + }, + valueSchema: { + entity: "bytes32", + }, + }, + }, +}); From c4a1a5c90a4e228ee26c24d146442ed958216f57 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 19 Sep 2023 19:55:46 +0100 Subject: [PATCH 02/15] hitting limits of zod --- packages/store/ts/config/tableConfig.ts | 119 ++++++++++++++++++------ 1 file changed, 93 insertions(+), 26 deletions(-) diff --git a/packages/store/ts/config/tableConfig.ts b/packages/store/ts/config/tableConfig.ts index 3aa21764b3..9305467568 100644 --- a/packages/store/ts/config/tableConfig.ts +++ b/packages/store/ts/config/tableConfig.ts @@ -1,5 +1,6 @@ +import { tableIdToHex } from "@latticexyz/common"; import { SchemaAbiType, StaticAbiType, schemaAbiTypes, staticAbiTypes } from "@latticexyz/schema-type"; -import { z } from "zod"; +import { ZodAny, ZodTypeAny, z } from "zod"; // TODO: combine with protocol-parser def and move up to schema-type? export type KeySchema = Readonly>; @@ -22,41 +23,93 @@ export type TableConfig = Readonly<{ }; }>; -const tableSchema = z.object({ - type: z.enum(["table", "offchainTable"]).default("table").optional(), - namespace: z.string().optional(), - name: z.string().optional(), - // TODO: refine/validate key/field names - keySchema: z.record(z.enum(staticAbiTypes)).default({ key: "bytes32" }).optional(), - valueSchema: z.record(z.enum(schemaAbiTypes)), +const configSchema = z.object({ + namespace: z.string().default(""), + // TODO: refine/validate table names + tables: z + .record( + z.string(), + z.object({ + type: z.enum(["table", "offchainTable"]).default("table"), + namespace: z.string().optional(), + // TODO: refine/validate key/field names + keySchema: z.record(z.enum(staticAbiTypes)).default({ key: "bytes32" } as const), + valueSchema: z.record(z.enum(schemaAbiTypes)), + // TODO: codegen + }) + ) + .default({}), // TODO: codegen }); -// TODO: refine/validate table names -const tablesSchema = z.record(tableSchema); +type ConfigInput = z.input; +type ConfigInputParsed = z.output; -const namespaceSchema = z.object({ - tables: tablesSchema.optional(), -}); +type TableInput = ConfigInputParsed["tables"][string]; +type TableInputParsed = ConfigInputParsed["tables"][string]; +type TableOutput = table & { + namespace: table["namespace"] extends string ? table["namespace"] : defaultNamespace; + name: tableName; + tableId: `0x${string}`; +}; + +type ConfigOutput = { + tables: { + [tableName in keyof NonNullable & string]: TableOutput< + input["namespace"] extends string ? input["namespace"] : "", + tableName, + NonNullable[tableName] + >; + }; +}; + +type ExpandedConfig< + schema extends ZodTypeAny, + input extends z.input = z.input, + output extends z.output = z.output +> = { + tables: { + [tableName in keyof output["tables"] & string]: output["tables"][tableName] & { + namespace: output["tables"][tableName]["namespace"] extends string + ? output["tables"][tableName]["namespace"] + : output["namespace"]; + }; + }; +}; + +function transformTable( + namespace: namespace, + tableName: tableName, + table: table +): TableOutput { + return { + ...table, + namespace, + name: tableName, + tableId: tableIdToHex(namespace, tableName), + }; +} + +function storeConfig(config: TConfig): ConfigOutput { + const parsedConfig = configSchema.parse(config); -// TODO: overall codegen options -const configSchema = namespaceSchema.merge( - z.object({ - // TODO: refine/validate namespace keys - namespaces: z.record(namespaceSchema).optional(), - }) -); + const tables = Object.fromEntries( + Object.entries(parsedConfig.tables).map(([tableName, table]) => [ + tableName, + transformTable(table.namespace ?? parsedConfig.namespace, tableName, table), + ]) + ) as ConfigOutput["tables"]; -function storeConfig>( - config: TConfig -): Readonly<{ tables: TableConfig[] }> { - const parsed = configSchema.parse(config); - return { tables: [] }; + return { + tables, + }; } const config = storeConfig({ + namespace: "hello", tables: { Position: { + namespace: "overridden", keySchema: { x: "uint32", y: "uint32", @@ -65,5 +118,19 @@ const config = storeConfig({ entity: "bytes32", }, }, + Entity: { + valueSchema: { + dead: "bool", + }, + }, }, -}); +} as const); + +config.tables.Position.namespace; +// ^? +config.tables.Position.name; +// ^? +config.tables.Position.tableId; +// ^? +config.tables.Entity.keySchema; +// ^? From 1704157695cf52834ec5d47c4bd8f37d2583e51a Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 19 Sep 2023 22:22:45 +0100 Subject: [PATCH 03/15] just use TS --- .../src/typescript/dynamicAbiTypes.ts | 5 +- .../src/typescript/schemaAbiTypes.ts | 4 + .../src/typescript/staticAbiTypes.ts | 2 +- packages/store/ts/config/tableConfig.ts | 193 +++++++++++------- 4 files changed, 123 insertions(+), 81 deletions(-) diff --git a/packages/schema-type/src/typescript/dynamicAbiTypes.ts b/packages/schema-type/src/typescript/dynamicAbiTypes.ts index 8c4549c2f2..271ef47fea 100644 --- a/packages/schema-type/src/typescript/dynamicAbiTypes.ts +++ b/packages/schema-type/src/typescript/dynamicAbiTypes.ts @@ -1,7 +1,6 @@ import { Hex } from "viem"; -import { DynamicAbiType, SchemaAbiType, dynamicAbiTypes } from "./schemaAbiTypes"; +import { DynamicAbiType, dynamicAbiTypes } from "./schemaAbiTypes"; import { LiteralToBroad } from "./utils"; -import { isArrayAbiType } from "./arrayAbiTypes"; // Variable-length ABI types, where their lengths are encoded by a PackedCounter within the record @@ -124,6 +123,6 @@ export type DynamicAbiTypeToPrimitiveType; -export function isDynamicAbiType(abiType: string): abiType is DynamicAbiType { +export function isDynamicAbiType(abiType: unknown): abiType is DynamicAbiType { return dynamicAbiTypes.includes(abiType as DynamicAbiType); } diff --git a/packages/schema-type/src/typescript/schemaAbiTypes.ts b/packages/schema-type/src/typescript/schemaAbiTypes.ts index c7f3886e15..58792943c5 100644 --- a/packages/schema-type/src/typescript/schemaAbiTypes.ts +++ b/packages/schema-type/src/typescript/schemaAbiTypes.ts @@ -211,3 +211,7 @@ export const dynamicAbiTypes = schemaAbiTypes.slice(98) as any as TupleSplit; -export function isStaticAbiType(abiType: string): abiType is StaticAbiType { +export function isStaticAbiType(abiType: unknown): abiType is StaticAbiType { return staticAbiTypes.includes(abiType as StaticAbiType); } diff --git a/packages/store/ts/config/tableConfig.ts b/packages/store/ts/config/tableConfig.ts index 9305467568..e0aa8dbdd9 100644 --- a/packages/store/ts/config/tableConfig.ts +++ b/packages/store/ts/config/tableConfig.ts @@ -1,6 +1,6 @@ import { tableIdToHex } from "@latticexyz/common"; -import { SchemaAbiType, StaticAbiType, schemaAbiTypes, staticAbiTypes } from "@latticexyz/schema-type"; -import { ZodAny, ZodTypeAny, z } from "zod"; +import { assertExhaustive } from "@latticexyz/common/utils"; +import { SchemaAbiType, StaticAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; // TODO: combine with protocol-parser def and move up to schema-type? export type KeySchema = Readonly>; @@ -8,97 +8,122 @@ export type ValueSchema = Readonly>; export type TableType = "table" | "offchainTable"; -export type TableConfig = Readonly<{ - type: TableType; - namespace: string; - name: string; - tableId: `0x${string}`; - keySchema: KeySchema; - valueSchema: ValueSchema; - codegen: { - outputDirectory: string; - tableIdArgument: boolean; - storeArgument: boolean; - dataStruct: boolean; - }; +// export type TableConfig = Readonly<{ +// type: TableType; +// namespace: string; +// name: string; +// tableId: `0x${string}`; +// keySchema: KeySchema; +// valueSchema: ValueSchema; +// codegen: { +// outputDirectory: string; +// tableIdArgument: boolean; +// storeArgument: boolean; +// dataStruct: boolean; +// }; +// }>; + +type TableInput = SchemaAbiType | TableInputShape; +type TableInputShape = Readonly<{ + namespace?: string; + keySchema?: KeySchema; + valueSchema: SchemaAbiType | ValueSchema; + offchainOnly?: boolean; }>; -const configSchema = z.object({ - namespace: z.string().default(""), - // TODO: refine/validate table names - tables: z - .record( - z.string(), - z.object({ - type: z.enum(["table", "offchainTable"]).default("table"), - namespace: z.string().optional(), - // TODO: refine/validate key/field names - keySchema: z.record(z.enum(staticAbiTypes)).default({ key: "bytes32" } as const), - valueSchema: z.record(z.enum(schemaAbiTypes)), - // TODO: codegen - }) - ) - .default({}), - // TODO: codegen -}); - -type ConfigInput = z.input; -type ConfigInputParsed = z.output; - -type TableInput = ConfigInputParsed["tables"][string]; -type TableInputParsed = ConfigInputParsed["tables"][string]; -type TableOutput = table & { - namespace: table["namespace"] extends string ? table["namespace"] : defaultNamespace; - name: tableName; - tableId: `0x${string}`; -}; +type ConfigInput = Readonly<{ + namespace?: string; + tables?: Record; +}>; -type ConfigOutput = { - tables: { +const defaultKeySchema = { key: "bytes32" } as const; + +type TableOutput< + defaultNamespace extends string, + tableName extends string, + input extends TableInput +> = input extends SchemaAbiType + ? // TODO: some shared output type so we can ensure each branch of the conditional are complete + Readonly<{ + type: "table"; + namespace: defaultNamespace; + name: tableName; + tableId: `0x${string}`; + keySchema: typeof defaultKeySchema; + valueSchema: Readonly<{ value: input }>; + }> + : input extends TableInputShape + ? Readonly<{ + type: input["offchainOnly"] extends true ? "offchainTable" : "table"; + namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; + name: tableName; + tableId: `0x${string}`; + keySchema: input["keySchema"] extends KeySchema + ? input["keySchema"] + : input["keySchema"] extends undefined + ? typeof defaultKeySchema + : never; + valueSchema: input["valueSchema"] extends ValueSchema + ? input["valueSchema"] + : input["valueSchema"] extends SchemaAbiType + ? Readonly<{ value: input["valueSchema"] }> + : never; + }> + : never; + +type ConfigOutput = { + tables: Readonly<{ [tableName in keyof NonNullable & string]: TableOutput< input["namespace"] extends string ? input["namespace"] : "", tableName, NonNullable[tableName] >; - }; -}; - -type ExpandedConfig< - schema extends ZodTypeAny, - input extends z.input = z.input, - output extends z.output = z.output -> = { - tables: { - [tableName in keyof output["tables"] & string]: output["tables"][tableName] & { - namespace: output["tables"][tableName]["namespace"] extends string - ? output["tables"][tableName]["namespace"] - : output["namespace"]; - }; - }; + }>; }; -function transformTable( - namespace: namespace, - tableName: tableName, - table: table -): TableOutput { - return { - ...table, - namespace, - name: tableName, - tableId: tableIdToHex(namespace, tableName), - }; +function isTable(table: SchemaAbiType | TableInputShape): table is TableInputShape { + return !isSchemaAbiType(table); } -function storeConfig(config: TConfig): ConfigOutput { - const parsedConfig = configSchema.parse(config); +function parseTableInput( + defaultNamespace: defaultNamespace, + name: name, + input: input +): TableOutput { + if (isSchemaAbiType(input)) { + // TODO: figure out how to do this without casting + // or at least so casting does some enforcement of the object + return { + namespace: defaultNamespace, + name, + tableId: tableIdToHex(defaultNamespace, name), + keySchema: defaultKeySchema, + valueSchema: { value: input }, + } as TableOutput; + } + + if (isTable(input)) { + // TODO: figure out how to do this without casting + // or at least so casting does some enforcement of the object + return { + ...(input as TableInputShape), + namespace: input.namespace ?? defaultNamespace, + name, + tableId: tableIdToHex(input.namespace ?? defaultNamespace, name), + } as TableOutput; + } + assertExhaustive(input); +} + +function storeConfig(input: input): ConfigOutput { + const namespace = input.namespace ?? ""; const tables = Object.fromEntries( - Object.entries(parsedConfig.tables).map(([tableName, table]) => [ + Object.entries(input.tables ?? {}).map(([tableName, table]) => [ tableName, - transformTable(table.namespace ?? parsedConfig.namespace, tableName, table), + parseTableInput(namespace, tableName, table), ]) - ) as ConfigOutput["tables"]; + ) as ConfigOutput["tables"]; return { tables, @@ -123,6 +148,10 @@ const config = storeConfig({ dead: "bool", }, }, + SimpleSchema: { + valueSchema: "bytes32", + }, + JustValue: "uint256", }, } as const); @@ -132,5 +161,15 @@ config.tables.Position.name; // ^? config.tables.Position.tableId; // ^? +config.tables.Entity.namespace; +// ^? config.tables.Entity.keySchema; // ^? +config.tables.Entity.valueSchema; +// ^? +config.tables.SimpleSchema.valueSchema; +// ^? +config.tables.JustValue.keySchema; +// ^? +config.tables.JustValue.valueSchema; +// ^? From 6a4c157db5bc9a867b6bd376672cfbe747107909 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 19 Sep 2023 22:58:42 +0100 Subject: [PATCH 04/15] more fiddling --- packages/store/ts/config/tableConfig.ts | 58 +++++++++++++++++-------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/store/ts/config/tableConfig.ts b/packages/store/ts/config/tableConfig.ts index e0aa8dbdd9..36d7955cfd 100644 --- a/packages/store/ts/config/tableConfig.ts +++ b/packages/store/ts/config/tableConfig.ts @@ -8,6 +8,15 @@ export type ValueSchema = Readonly>; export type TableType = "table" | "offchainTable"; +// type Narrow = +// | (unknown extends TType ? unknown : never) +// // eslint-disable-next-line @typescript-eslint/ban-types +// | (TType extends Function ? TType : never) +// | (TType extends bigint | boolean | number | string ? TType : never) +// | (TType extends [] ? [] : never) +// | { [K in keyof TType]: Narrow }; +export type Pretty = { [K in keyof T]: T[K] } & unknown; + // export type TableConfig = Readonly<{ // type: TableType; // namespace: string; @@ -38,36 +47,42 @@ type ConfigInput = Readonly<{ const defaultKeySchema = { key: "bytes32" } as const; +// type GenericTableOutput = Readonly<{ +// type: "table" | "offchainTable"; +// namespace: string; +// name: string; +// tableId: `0x${string}`; +// keySchema: KeySchema; +// valueSchema: ValueSchema; +// }>; + type TableOutput< defaultNamespace extends string, tableName extends string, input extends TableInput > = input extends SchemaAbiType ? // TODO: some shared output type so we can ensure each branch of the conditional are complete - Readonly<{ - type: "table"; - namespace: defaultNamespace; - name: tableName; - tableId: `0x${string}`; - keySchema: typeof defaultKeySchema; - valueSchema: Readonly<{ value: input }>; - }> + // Readonly<{ + // type: "table"; + // namespace: defaultNamespace; + // name: tableName; + // tableId: `0x${string}`; + // keySchema: typeof defaultKeySchema; + // valueSchema: Readonly<{ value: input }>; + // }> + TableOutput : input extends TableInputShape ? Readonly<{ type: input["offchainOnly"] extends true ? "offchainTable" : "table"; namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; name: tableName; tableId: `0x${string}`; - keySchema: input["keySchema"] extends KeySchema - ? input["keySchema"] - : input["keySchema"] extends undefined - ? typeof defaultKeySchema - : never; + keySchema: input["keySchema"] extends KeySchema ? input["keySchema"] : typeof defaultKeySchema; valueSchema: input["valueSchema"] extends ValueSchema ? input["valueSchema"] : input["valueSchema"] extends SchemaAbiType ? Readonly<{ value: input["valueSchema"] }> - : never; + : input["valueSchema"]; }> : never; @@ -81,6 +96,8 @@ type ConfigOutput = { }>; }; +type Table = TableOutput; + function isTable(table: SchemaAbiType | TableInputShape): table is TableInputShape { return !isSchemaAbiType(table); } @@ -103,14 +120,17 @@ function parseTableInput; + type: input.offchainOnly === true ? "offchainTable" : "table", + namespace, + name: name, + tableId: tableIdToHex(namespace, name), + keySchema: input.keySchema ?? defaultKeySchema, + valueSchema: isSchemaAbiType(input.valueSchema) ? ({ value: input.valueSchema } as const) : input.valueSchema, + } as Table; } assertExhaustive(input); From e8df6263f96ba3f0038a1acd1ecfdd5f24287313 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 11:09:28 +0100 Subject: [PATCH 05/15] do config parsing in smaller steps --- .../store/ts/config/experimental/common.ts | 9 + .../ts/config/experimental/keySchema.test.ts | 30 +++ .../store/ts/config/experimental/keySchema.ts | 21 ++ .../ts/config/experimental/table.test.ts | 233 ++++++++++++++++++ .../store/ts/config/experimental/table.ts | 72 ++++++ .../config/experimental/valueSchema.test.ts | 22 ++ .../ts/config/experimental/valueSchema.ts | 15 ++ 7 files changed, 402 insertions(+) create mode 100644 packages/store/ts/config/experimental/common.ts create mode 100644 packages/store/ts/config/experimental/keySchema.test.ts create mode 100644 packages/store/ts/config/experimental/keySchema.ts create mode 100644 packages/store/ts/config/experimental/table.test.ts create mode 100644 packages/store/ts/config/experimental/table.ts create mode 100644 packages/store/ts/config/experimental/valueSchema.test.ts create mode 100644 packages/store/ts/config/experimental/valueSchema.ts diff --git a/packages/store/ts/config/experimental/common.ts b/packages/store/ts/config/experimental/common.ts new file mode 100644 index 0000000000..ea6327697e --- /dev/null +++ b/packages/store/ts/config/experimental/common.ts @@ -0,0 +1,9 @@ +/** @internal */ +export function isPlainObject(value: unknown): value is Record { + return ( + typeof value === "object" && + value !== null && + value.constructor === Object && + Object.prototype.toString.call(value) === "[object Object]" + ); +} diff --git a/packages/store/ts/config/experimental/keySchema.test.ts b/packages/store/ts/config/experimental/keySchema.test.ts new file mode 100644 index 0000000000..555dbd4f9d --- /dev/null +++ b/packages/store/ts/config/experimental/keySchema.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseKeySchema } from "./keySchema"; + +// TODO: add tests for failing cases (dynamic ABI types) + +describe("parseKeySchema", () => { + it("outputs a key schema for uint8", () => { + const keySchema = parseKeySchema("uint8"); + expect(keySchema).toStrictEqual({ key: "uint8" }); + expectTypeOf().toEqualTypeOf>(); + }); + + it("outputs a key schema for bool", () => { + const keySchema = parseKeySchema("bool"); + expect(keySchema).toStrictEqual({ key: "bool" }); + expectTypeOf().toEqualTypeOf>(); + }); + + it("returns a full key schema", () => { + const keySchema = parseKeySchema({ x: "uint32", y: "uint32" }); + expect(keySchema).toStrictEqual({ x: "uint32", y: "uint32" }); + expectTypeOf().toEqualTypeOf>(); + }); + + it("defaults key schema when undefined", () => { + const keySchema = parseKeySchema(undefined); + expect(keySchema).toStrictEqual({ key: "bytes32" }); + expectTypeOf().toEqualTypeOf>(); + }); +}); diff --git a/packages/store/ts/config/experimental/keySchema.ts b/packages/store/ts/config/experimental/keySchema.ts new file mode 100644 index 0000000000..19f89506d4 --- /dev/null +++ b/packages/store/ts/config/experimental/keySchema.ts @@ -0,0 +1,21 @@ +import { StaticAbiType, isStaticAbiType } from "@latticexyz/schema-type"; + +export type KeySchema = Readonly>; + +export const defaultKeySchema = { key: "bytes32" } as const satisfies KeySchema; + +export type KeySchemaInput = StaticAbiType | KeySchema | undefined; + +export type KeySchemaOutput = input extends undefined + ? typeof defaultKeySchema + : input extends StaticAbiType + ? Readonly<{ key: input }> + : input extends KeySchema + ? Readonly + : never; + +export function parseKeySchema(input: input): KeySchemaOutput { + return ( + input === undefined ? defaultKeySchema : isStaticAbiType(input) ? { key: input } : input + ) as KeySchemaOutput; +} diff --git a/packages/store/ts/config/experimental/table.test.ts b/packages/store/ts/config/experimental/table.test.ts new file mode 100644 index 0000000000..e76fd3c102 --- /dev/null +++ b/packages/store/ts/config/experimental/table.test.ts @@ -0,0 +1,233 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { TableShapeInput, parseTable, tableInputShapeKeys } from "./table"; +import { tableIdToHex } from "@latticexyz/common"; + +describe("tableInputShapeKeys", () => { + it("contains the same keys as TableShapeInput", () => { + expectTypeOf<(typeof tableInputShapeKeys)[number]>().toEqualTypeOf(); + }); +}); + +describe("parseTable", () => { + it("outputs a table from just a schema ABI type", () => { + const table = parseTable("", "SomeTable", "uint8"); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "uint8" }>; + }> + >(); + }); + + it("outputs a table from valueSchema shorthand", () => { + const table = parseTable("", "SomeTable", { valueSchema: "uint8" } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "uint8" }>; + }> + >(); + }); + + it("outputs a table with undefined keySchema", () => { + const table = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "uint8" }>; + }> + >(); + }); + + it("outputs a table from keySchema shorthand", () => { + const table = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bool" }, + valueSchema: { value: "uint8" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bool" }>; + valueSchema: Readonly<{ value: "uint8" }>; + }> + >(); + }); + + it("outputs a table from keySchema", () => { + const table = parseTable("", "SomeTable", { + keySchema: { x: "uint32", y: "uint32" }, + valueSchema: "uint8", + } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { x: "uint32", y: "uint32" }, + valueSchema: { value: "uint8" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ x: "uint32"; y: "uint32" }>; + valueSchema: Readonly<{ value: "uint8" }>; + }> + >(); + }); + + it("outputs a table from valueSchema", () => { + const table = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { exists: "bool" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ exists: "bool" }>; + }> + >(); + }); + + it("outputs a table with a default namespace", () => { + const table = parseTable("Namespace", "SomeTable", "bytes32"); + + expect(table).toStrictEqual({ + type: "table", + namespace: "Namespace", + name: "SomeTable", + tableId: tableIdToHex("Namespace", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "bytes32" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: "Namespace"; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "bytes32" }>; + }> + >(); + }); + + it("outputs a table with a namespace override", () => { + const table = parseTable("Namespace", "SomeTable", { + namespace: "CustomNamespace", + valueSchema: "string", + } as const); + + expect(table).toStrictEqual({ + type: "table", + namespace: "CustomNamespace", + name: "SomeTable", + tableId: tableIdToHex("CustomNamespace", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "string" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "table"; + namespace: "CustomNamespace"; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "string" }>; + }> + >(); + }); + + it("outputs an offchain table", () => { + const table = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" } as const); + + expect(table).toStrictEqual({ + type: "offchainTable", + namespace: "", + name: "SomeTable", + tableId: tableIdToHex("", "SomeTable"), + keySchema: { key: "bytes32" }, + valueSchema: { value: "string" }, + }); + + expectTypeOf().toEqualTypeOf< + Readonly<{ + type: "offchainTable"; + namespace: ""; + name: "SomeTable"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "string" }>; + }> + >(); + }); +}); diff --git a/packages/store/ts/config/experimental/table.ts b/packages/store/ts/config/experimental/table.ts new file mode 100644 index 0000000000..151dc8b85e --- /dev/null +++ b/packages/store/ts/config/experimental/table.ts @@ -0,0 +1,72 @@ +import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; +import { isPlainObject } from "./common"; +import { KeySchemaInput, KeySchemaOutput, parseKeySchema } from "./keySchema"; +import { ValueSchemaInput, ValueSchemaOutput, parseValueSchema } from "./valueSchema"; +import { assertExhaustive } from "@latticexyz/common/utils"; +import { tableIdToHex } from "@latticexyz/common"; + +export type TableShapeInput = Readonly<{ + namespace?: string; + keySchema?: KeySchemaInput; + valueSchema: ValueSchemaInput; + offchainOnly?: boolean; +}>; + +// TODO: add support for ValueSchemaInput instead of SchemaAbiType? +// requires an isValueSchemaInput helper that is distinct enough from isTableInputShape +export type TableInput = SchemaAbiType | TableShapeInput; + +export type TableOutput< + defaultNamespace extends string, + name extends string, + input extends TableInput +> = input extends SchemaAbiType + ? TableOutput + : input extends TableShapeInput + ? Readonly<{ + type: input["offchainOnly"] extends true ? "offchainTable" : "table"; + namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; + name: name; + tableId: `0x${string}`; + keySchema: KeySchemaOutput< + input["keySchema"] extends KeySchemaInput + ? input["keySchema"] + : never extends input["keySchema"] + ? undefined + : never + >; + valueSchema: ValueSchemaOutput; + }> + : never; + +/** @internal */ +export const tableInputShapeKeys = ["namespace", "keySchema", "valueSchema", "offchainOnly"] as const; + +// TODO: is there a better way to check this aside from just looking at the shape/keys of the object? +export function isTableShapeInput(input: unknown): input is TableShapeInput { + if (!isPlainObject(input)) return false; + if (Object.keys(input).some((key) => !tableInputShapeKeys.includes(key as (typeof tableInputShapeKeys)[number]))) + return false; + return true; +} + +export function parseTable( + defaultNamespace: defaultNamespace, + name: name, + input: input +): TableOutput { + return ( + isSchemaAbiType(input) + ? parseTable(defaultNamespace, name, { valueSchema: input }) + : isTableShapeInput(input) + ? { + type: input.offchainOnly === true ? "offchainTable" : "table", + namespace: input.namespace ?? defaultNamespace, + name, + tableId: tableIdToHex(input.namespace ?? defaultNamespace, name), + keySchema: parseKeySchema(input.keySchema), + valueSchema: parseValueSchema(input.valueSchema), + } + : assertExhaustive(input, "invalid table input") + ) as TableOutput; +} diff --git a/packages/store/ts/config/experimental/valueSchema.test.ts b/packages/store/ts/config/experimental/valueSchema.test.ts new file mode 100644 index 0000000000..9311b61406 --- /dev/null +++ b/packages/store/ts/config/experimental/valueSchema.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseValueSchema } from "./valueSchema"; + +describe("parseValueSchema", () => { + it("outputs a key schema for uint8", () => { + const valueSchema = parseValueSchema("uint8"); + expect(valueSchema).toStrictEqual({ value: "uint8" }); + expectTypeOf().toEqualTypeOf>(); + }); + + it("outputs a key schema for string", () => { + const valueSchema = parseValueSchema("string"); + expect(valueSchema).toStrictEqual({ value: "string" }); + expectTypeOf().toEqualTypeOf>(); + }); + + it("returns a full value schema", () => { + const valueSchema = parseValueSchema({ x: "uint32", y: "uint32" }); + expect(valueSchema).toStrictEqual({ x: "uint32", y: "uint32" }); + expectTypeOf().toEqualTypeOf>(); + }); +}); diff --git a/packages/store/ts/config/experimental/valueSchema.ts b/packages/store/ts/config/experimental/valueSchema.ts new file mode 100644 index 0000000000..0ffa42f7f3 --- /dev/null +++ b/packages/store/ts/config/experimental/valueSchema.ts @@ -0,0 +1,15 @@ +import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; + +export type ValueSchema = Readonly>; + +export type ValueSchemaInput = SchemaAbiType | ValueSchema; + +export type ValueSchemaOutput = input extends SchemaAbiType + ? Readonly<{ value: input }> + : input extends ValueSchema + ? Readonly + : never; + +export function parseValueSchema(input: input): ValueSchemaOutput { + return (isSchemaAbiType(input) ? { value: input } : input) as ValueSchemaOutput; +} From e35566b684596c6660d6b4b99a044a98854ff29a Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 11:34:25 +0100 Subject: [PATCH 06/15] add config parsing --- .../ts/config/experimental/config.test.ts | 163 +++++++++++++++ .../store/ts/config/experimental/config.ts | 27 +++ packages/store/ts/config/tableConfig.ts | 195 ------------------ 3 files changed, 190 insertions(+), 195 deletions(-) create mode 100644 packages/store/ts/config/experimental/config.test.ts create mode 100644 packages/store/ts/config/experimental/config.ts delete mode 100644 packages/store/ts/config/tableConfig.ts diff --git a/packages/store/ts/config/experimental/config.test.ts b/packages/store/ts/config/experimental/config.test.ts new file mode 100644 index 0000000000..ea3945a070 --- /dev/null +++ b/packages/store/ts/config/experimental/config.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseConfig } from "./config"; +import { tableIdToHex } from "@latticexyz/common"; + +describe("parseConfig", () => { + it("outputs tables from config", () => { + const config = parseConfig({ + tables: { + Exists: "bool", + Position: { + valueSchema: { x: "uint32", y: "uint32" }, + }, + Messages: { + offchainOnly: true, + valueSchema: { + sender: "address", + message: "string", + }, + }, + }, + }); + + expect(config).toStrictEqual({ + tables: { + Exists: { + type: "table", + name: "Exists", + namespace: "", + tableId: tableIdToHex("", "Exists"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + value: "bool", + }, + }, + Position: { + type: "table", + name: "Position", + namespace: "", + tableId: tableIdToHex("", "Position"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + x: "uint32", + y: "uint32", + }, + }, + Messages: { + type: "offchainTable", + name: "Messages", + namespace: "", + tableId: tableIdToHex("", "Messages"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + message: "string", + sender: "address", + }, + }, + }, + } as const); + + expectTypeOf().toMatchTypeOf< + Readonly<{ + tables: Readonly<{ + Exists: Readonly<{ + type: "table"; + namespace: ""; + name: "Exists"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "bool" }>; + }>; + Position: Readonly<{ + type: "table"; + namespace: ""; + name: "Position"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ x: "uint32"; y: "uint32" }>; + }>; + Messages: Readonly<{ + type: "offchainTable"; + namespace: ""; + name: "Messages"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ sender: "address"; message: "string" }>; + }>; + }>; + }> + >(); + }); + + it("handles default namespace and per-table namespaces", () => { + const config = parseConfig({ + namespace: "MyNamespace", + tables: { + Exists: "bool", + Position: { + namespace: "OtherNamespace", + valueSchema: { x: "uint32", y: "uint32" }, + }, + }, + } as const); + + expect(config).toStrictEqual({ + tables: { + Exists: { + type: "table", + name: "Exists", + namespace: "MyNamespace", + tableId: tableIdToHex("MyNamespace", "Exists"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + value: "bool", + }, + }, + Position: { + type: "table", + name: "Position", + namespace: "OtherNamespace", + tableId: tableIdToHex("OtherNamespace", "Position"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + x: "uint32", + y: "uint32", + }, + }, + }, + }); + + expectTypeOf().toMatchTypeOf< + Readonly<{ + tables: Readonly<{ + Exists: Readonly<{ + type: "table"; + namespace: "MyNamespace"; + name: "Exists"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "bool" }>; + }>; + Position: Readonly<{ + type: "table"; + namespace: "OtherNamespace"; + name: "Position"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ x: "uint32"; y: "uint32" }>; + }>; + }>; + }> + >(); + }); +}); diff --git a/packages/store/ts/config/experimental/config.ts b/packages/store/ts/config/experimental/config.ts new file mode 100644 index 0000000000..791c38293b --- /dev/null +++ b/packages/store/ts/config/experimental/config.ts @@ -0,0 +1,27 @@ +import { TableInput, TableOutput, parseTable } from "./table"; + +export type ConfigInput = Readonly<{ + namespace?: string; + tables?: Record; +}>; + +export type ConfigOutput = Readonly<{ + tables: Readonly<{ + [tableName in keyof NonNullable & string]: TableOutput< + input["namespace"] extends string ? input["namespace"] : "", + tableName, + NonNullable[tableName] + >; + }>; +}>; + +export function parseConfig(input: input): ConfigOutput { + return { + tables: Object.fromEntries( + Object.entries(input.tables ?? {}).map(([tableName, table]) => [ + tableName, + parseTable(input.namespace ?? "", tableName, table), + ]) + ), + } as ConfigOutput; +} diff --git a/packages/store/ts/config/tableConfig.ts b/packages/store/ts/config/tableConfig.ts deleted file mode 100644 index 36d7955cfd..0000000000 --- a/packages/store/ts/config/tableConfig.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { tableIdToHex } from "@latticexyz/common"; -import { assertExhaustive } from "@latticexyz/common/utils"; -import { SchemaAbiType, StaticAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; - -// TODO: combine with protocol-parser def and move up to schema-type? -export type KeySchema = Readonly>; -export type ValueSchema = Readonly>; - -export type TableType = "table" | "offchainTable"; - -// type Narrow = -// | (unknown extends TType ? unknown : never) -// // eslint-disable-next-line @typescript-eslint/ban-types -// | (TType extends Function ? TType : never) -// | (TType extends bigint | boolean | number | string ? TType : never) -// | (TType extends [] ? [] : never) -// | { [K in keyof TType]: Narrow }; -export type Pretty = { [K in keyof T]: T[K] } & unknown; - -// export type TableConfig = Readonly<{ -// type: TableType; -// namespace: string; -// name: string; -// tableId: `0x${string}`; -// keySchema: KeySchema; -// valueSchema: ValueSchema; -// codegen: { -// outputDirectory: string; -// tableIdArgument: boolean; -// storeArgument: boolean; -// dataStruct: boolean; -// }; -// }>; - -type TableInput = SchemaAbiType | TableInputShape; -type TableInputShape = Readonly<{ - namespace?: string; - keySchema?: KeySchema; - valueSchema: SchemaAbiType | ValueSchema; - offchainOnly?: boolean; -}>; - -type ConfigInput = Readonly<{ - namespace?: string; - tables?: Record; -}>; - -const defaultKeySchema = { key: "bytes32" } as const; - -// type GenericTableOutput = Readonly<{ -// type: "table" | "offchainTable"; -// namespace: string; -// name: string; -// tableId: `0x${string}`; -// keySchema: KeySchema; -// valueSchema: ValueSchema; -// }>; - -type TableOutput< - defaultNamespace extends string, - tableName extends string, - input extends TableInput -> = input extends SchemaAbiType - ? // TODO: some shared output type so we can ensure each branch of the conditional are complete - // Readonly<{ - // type: "table"; - // namespace: defaultNamespace; - // name: tableName; - // tableId: `0x${string}`; - // keySchema: typeof defaultKeySchema; - // valueSchema: Readonly<{ value: input }>; - // }> - TableOutput - : input extends TableInputShape - ? Readonly<{ - type: input["offchainOnly"] extends true ? "offchainTable" : "table"; - namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; - name: tableName; - tableId: `0x${string}`; - keySchema: input["keySchema"] extends KeySchema ? input["keySchema"] : typeof defaultKeySchema; - valueSchema: input["valueSchema"] extends ValueSchema - ? input["valueSchema"] - : input["valueSchema"] extends SchemaAbiType - ? Readonly<{ value: input["valueSchema"] }> - : input["valueSchema"]; - }> - : never; - -type ConfigOutput = { - tables: Readonly<{ - [tableName in keyof NonNullable & string]: TableOutput< - input["namespace"] extends string ? input["namespace"] : "", - tableName, - NonNullable[tableName] - >; - }>; -}; - -type Table = TableOutput; - -function isTable(table: SchemaAbiType | TableInputShape): table is TableInputShape { - return !isSchemaAbiType(table); -} - -function parseTableInput( - defaultNamespace: defaultNamespace, - name: name, - input: input -): TableOutput { - if (isSchemaAbiType(input)) { - // TODO: figure out how to do this without casting - // or at least so casting does some enforcement of the object - return { - namespace: defaultNamespace, - name, - tableId: tableIdToHex(defaultNamespace, name), - keySchema: defaultKeySchema, - valueSchema: { value: input }, - } as TableOutput; - } - - if (isTable(input)) { - const namespace = input.namespace ?? defaultNamespace; - // TODO: figure out how to do this without casting - // or at least so casting does some enforcement of the object - return { - type: input.offchainOnly === true ? "offchainTable" : "table", - namespace, - name: name, - tableId: tableIdToHex(namespace, name), - keySchema: input.keySchema ?? defaultKeySchema, - valueSchema: isSchemaAbiType(input.valueSchema) ? ({ value: input.valueSchema } as const) : input.valueSchema, - } as Table; - } - - assertExhaustive(input); -} - -function storeConfig(input: input): ConfigOutput { - const namespace = input.namespace ?? ""; - const tables = Object.fromEntries( - Object.entries(input.tables ?? {}).map(([tableName, table]) => [ - tableName, - parseTableInput(namespace, tableName, table), - ]) - ) as ConfigOutput["tables"]; - - return { - tables, - }; -} - -const config = storeConfig({ - namespace: "hello", - tables: { - Position: { - namespace: "overridden", - keySchema: { - x: "uint32", - y: "uint32", - }, - valueSchema: { - entity: "bytes32", - }, - }, - Entity: { - valueSchema: { - dead: "bool", - }, - }, - SimpleSchema: { - valueSchema: "bytes32", - }, - JustValue: "uint256", - }, -} as const); - -config.tables.Position.namespace; -// ^? -config.tables.Position.name; -// ^? -config.tables.Position.tableId; -// ^? -config.tables.Entity.namespace; -// ^? -config.tables.Entity.keySchema; -// ^? -config.tables.Entity.valueSchema; -// ^? -config.tables.SimpleSchema.valueSchema; -// ^? -config.tables.JustValue.keySchema; -// ^? -config.tables.JustValue.valueSchema; -// ^? From af32ed96ebc8e94c2c2ff4b11d53247175cfc241 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 11:39:50 +0100 Subject: [PATCH 07/15] rename --- .../{config.test.ts => parseConfig.test.ts} | 2 +- .../{config.ts => parseConfig.ts} | 14 +++---- ...ySchema.test.ts => parseKeySchema.test.ts} | 2 +- .../{keySchema.ts => parseKeySchema.ts} | 8 ++-- .../{table.test.ts => parseTable.test.ts} | 2 +- .../experimental/{table.ts => parseTable.ts} | 37 ++++++++++--------- ...chema.test.ts => parseValueSchema.test.ts} | 2 +- .../config/experimental/parseValueSchema.ts | 15 ++++++++ .../ts/config/experimental/valueSchema.ts | 15 -------- 9 files changed, 50 insertions(+), 47 deletions(-) rename packages/store/ts/config/experimental/{config.test.ts => parseConfig.test.ts} (98%) rename packages/store/ts/config/experimental/{config.ts => parseConfig.ts} (54%) rename packages/store/ts/config/experimental/{keySchema.test.ts => parseKeySchema.test.ts} (95%) rename packages/store/ts/config/experimental/{keySchema.ts => parseKeySchema.ts} (60%) rename packages/store/ts/config/experimental/{table.test.ts => parseTable.test.ts} (99%) rename packages/store/ts/config/experimental/{table.ts => parseTable.ts} (69%) rename packages/store/ts/config/experimental/{valueSchema.test.ts => parseValueSchema.test.ts} (94%) create mode 100644 packages/store/ts/config/experimental/parseValueSchema.ts delete mode 100644 packages/store/ts/config/experimental/valueSchema.ts diff --git a/packages/store/ts/config/experimental/config.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts similarity index 98% rename from packages/store/ts/config/experimental/config.test.ts rename to packages/store/ts/config/experimental/parseConfig.test.ts index ea3945a070..e90f624729 100644 --- a/packages/store/ts/config/experimental/config.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -1,5 +1,5 @@ import { describe, expect, expectTypeOf, it } from "vitest"; -import { parseConfig } from "./config"; +import { parseConfig } from "./parseConfig"; import { tableIdToHex } from "@latticexyz/common"; describe("parseConfig", () => { diff --git a/packages/store/ts/config/experimental/config.ts b/packages/store/ts/config/experimental/parseConfig.ts similarity index 54% rename from packages/store/ts/config/experimental/config.ts rename to packages/store/ts/config/experimental/parseConfig.ts index 791c38293b..747c2b90ab 100644 --- a/packages/store/ts/config/experimental/config.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -1,13 +1,13 @@ -import { TableInput, TableOutput, parseTable } from "./table"; +import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; -export type ConfigInput = Readonly<{ +export type ParseConfigInput = Readonly<{ namespace?: string; - tables?: Record; + tables?: Record; }>; -export type ConfigOutput = Readonly<{ +export type ParseConfigOutput = Readonly<{ tables: Readonly<{ - [tableName in keyof NonNullable & string]: TableOutput< + [tableName in keyof NonNullable & string]: ParseTableOutput< input["namespace"] extends string ? input["namespace"] : "", tableName, NonNullable[tableName] @@ -15,7 +15,7 @@ export type ConfigOutput = Readonly<{ }>; }>; -export function parseConfig(input: input): ConfigOutput { +export function parseConfig(input: input): ParseConfigOutput { return { tables: Object.fromEntries( Object.entries(input.tables ?? {}).map(([tableName, table]) => [ @@ -23,5 +23,5 @@ export function parseConfig(input: input): ConfigOutp parseTable(input.namespace ?? "", tableName, table), ]) ), - } as ConfigOutput; + } as ParseConfigOutput; } diff --git a/packages/store/ts/config/experimental/keySchema.test.ts b/packages/store/ts/config/experimental/parseKeySchema.test.ts similarity index 95% rename from packages/store/ts/config/experimental/keySchema.test.ts rename to packages/store/ts/config/experimental/parseKeySchema.test.ts index 555dbd4f9d..6c14c4dd91 100644 --- a/packages/store/ts/config/experimental/keySchema.test.ts +++ b/packages/store/ts/config/experimental/parseKeySchema.test.ts @@ -1,5 +1,5 @@ import { describe, expect, expectTypeOf, it } from "vitest"; -import { parseKeySchema } from "./keySchema"; +import { parseKeySchema } from "./parseKeySchema"; // TODO: add tests for failing cases (dynamic ABI types) diff --git a/packages/store/ts/config/experimental/keySchema.ts b/packages/store/ts/config/experimental/parseKeySchema.ts similarity index 60% rename from packages/store/ts/config/experimental/keySchema.ts rename to packages/store/ts/config/experimental/parseKeySchema.ts index 19f89506d4..083cf4b2fb 100644 --- a/packages/store/ts/config/experimental/keySchema.ts +++ b/packages/store/ts/config/experimental/parseKeySchema.ts @@ -4,9 +4,9 @@ export type KeySchema = Readonly>; export const defaultKeySchema = { key: "bytes32" } as const satisfies KeySchema; -export type KeySchemaInput = StaticAbiType | KeySchema | undefined; +export type ParseKeySchemaInput = StaticAbiType | KeySchema | undefined; -export type KeySchemaOutput = input extends undefined +export type ParseKeySchemaOutput = input extends undefined ? typeof defaultKeySchema : input extends StaticAbiType ? Readonly<{ key: input }> @@ -14,8 +14,8 @@ export type KeySchemaOutput = input extends undefi ? Readonly : never; -export function parseKeySchema(input: input): KeySchemaOutput { +export function parseKeySchema(input: input): ParseKeySchemaOutput { return ( input === undefined ? defaultKeySchema : isStaticAbiType(input) ? { key: input } : input - ) as KeySchemaOutput; + ) as ParseKeySchemaOutput; } diff --git a/packages/store/ts/config/experimental/table.test.ts b/packages/store/ts/config/experimental/parseTable.test.ts similarity index 99% rename from packages/store/ts/config/experimental/table.test.ts rename to packages/store/ts/config/experimental/parseTable.test.ts index e76fd3c102..dfe3eff6d7 100644 --- a/packages/store/ts/config/experimental/table.test.ts +++ b/packages/store/ts/config/experimental/parseTable.test.ts @@ -1,5 +1,5 @@ import { describe, expect, expectTypeOf, it } from "vitest"; -import { TableShapeInput, parseTable, tableInputShapeKeys } from "./table"; +import { TableShapeInput, parseTable, tableInputShapeKeys } from "./parseTable"; import { tableIdToHex } from "@latticexyz/common"; describe("tableInputShapeKeys", () => { diff --git a/packages/store/ts/config/experimental/table.ts b/packages/store/ts/config/experimental/parseTable.ts similarity index 69% rename from packages/store/ts/config/experimental/table.ts rename to packages/store/ts/config/experimental/parseTable.ts index 151dc8b85e..6be49e11cf 100644 --- a/packages/store/ts/config/experimental/table.ts +++ b/packages/store/ts/config/experimental/parseTable.ts @@ -1,48 +1,51 @@ import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; import { isPlainObject } from "./common"; -import { KeySchemaInput, KeySchemaOutput, parseKeySchema } from "./keySchema"; -import { ValueSchemaInput, ValueSchemaOutput, parseValueSchema } from "./valueSchema"; +import { ParseKeySchemaInput, ParseKeySchemaOutput, parseKeySchema } from "./parseKeySchema"; +import { ParseValueSchemaInput, ParseValueSchemaOutput, parseValueSchema } from "./parseValueSchema"; import { assertExhaustive } from "@latticexyz/common/utils"; import { tableIdToHex } from "@latticexyz/common"; +/** @internal */ export type TableShapeInput = Readonly<{ namespace?: string; - keySchema?: KeySchemaInput; - valueSchema: ValueSchemaInput; + keySchema?: ParseKeySchemaInput; + valueSchema: ParseValueSchemaInput; offchainOnly?: boolean; }>; -// TODO: add support for ValueSchemaInput instead of SchemaAbiType? -// requires an isValueSchemaInput helper that is distinct enough from isTableInputShape -export type TableInput = SchemaAbiType | TableShapeInput; +// TODO: add support for ParseValueSchemaInput instead of SchemaAbiType? +// requires an isValueSchemaInput helper that is distinct enough from isParseTableInputShape +export type ParseTableInput = SchemaAbiType | TableShapeInput; -export type TableOutput< +export type ParseTableOutput< defaultNamespace extends string, name extends string, - input extends TableInput + input extends ParseTableInput > = input extends SchemaAbiType - ? TableOutput + ? ParseTableOutput : input extends TableShapeInput ? Readonly<{ type: input["offchainOnly"] extends true ? "offchainTable" : "table"; namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; name: name; tableId: `0x${string}`; - keySchema: KeySchemaOutput< - input["keySchema"] extends KeySchemaInput + keySchema: ParseKeySchemaOutput< + input["keySchema"] extends ParseKeySchemaInput ? input["keySchema"] : never extends input["keySchema"] ? undefined : never >; - valueSchema: ValueSchemaOutput; + valueSchema: ParseValueSchemaOutput; }> : never; +// TODO: is there a better way to check this aside from just looking at the shape/keys of the object? + /** @internal */ export const tableInputShapeKeys = ["namespace", "keySchema", "valueSchema", "offchainOnly"] as const; -// TODO: is there a better way to check this aside from just looking at the shape/keys of the object? +/** @internal */ export function isTableShapeInput(input: unknown): input is TableShapeInput { if (!isPlainObject(input)) return false; if (Object.keys(input).some((key) => !tableInputShapeKeys.includes(key as (typeof tableInputShapeKeys)[number]))) @@ -50,11 +53,11 @@ export function isTableShapeInput(input: unknown): input is TableShapeInput { return true; } -export function parseTable( +export function parseTable( defaultNamespace: defaultNamespace, name: name, input: input -): TableOutput { +): ParseTableOutput { return ( isSchemaAbiType(input) ? parseTable(defaultNamespace, name, { valueSchema: input }) @@ -68,5 +71,5 @@ export function parseTable; + ) as ParseTableOutput; } diff --git a/packages/store/ts/config/experimental/valueSchema.test.ts b/packages/store/ts/config/experimental/parseValueSchema.test.ts similarity index 94% rename from packages/store/ts/config/experimental/valueSchema.test.ts rename to packages/store/ts/config/experimental/parseValueSchema.test.ts index 9311b61406..7e63e7e7ce 100644 --- a/packages/store/ts/config/experimental/valueSchema.test.ts +++ b/packages/store/ts/config/experimental/parseValueSchema.test.ts @@ -1,5 +1,5 @@ import { describe, expect, expectTypeOf, it } from "vitest"; -import { parseValueSchema } from "./valueSchema"; +import { parseValueSchema } from "./parseValueSchema"; describe("parseValueSchema", () => { it("outputs a key schema for uint8", () => { diff --git a/packages/store/ts/config/experimental/parseValueSchema.ts b/packages/store/ts/config/experimental/parseValueSchema.ts new file mode 100644 index 0000000000..7b52750dfa --- /dev/null +++ b/packages/store/ts/config/experimental/parseValueSchema.ts @@ -0,0 +1,15 @@ +import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; + +export type ValueSchema = Readonly>; + +export type ParseValueSchemaInput = SchemaAbiType | ValueSchema; + +export type ParseValueSchemaOutput = input extends SchemaAbiType + ? Readonly<{ value: input }> + : input extends ValueSchema + ? Readonly + : never; + +export function parseValueSchema(input: input): ParseValueSchemaOutput { + return (isSchemaAbiType(input) ? { value: input } : input) as ParseValueSchemaOutput; +} diff --git a/packages/store/ts/config/experimental/valueSchema.ts b/packages/store/ts/config/experimental/valueSchema.ts deleted file mode 100644 index 0ffa42f7f3..0000000000 --- a/packages/store/ts/config/experimental/valueSchema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; - -export type ValueSchema = Readonly>; - -export type ValueSchemaInput = SchemaAbiType | ValueSchema; - -export type ValueSchemaOutput = input extends SchemaAbiType - ? Readonly<{ value: input }> - : input extends ValueSchema - ? Readonly - : never; - -export function parseValueSchema(input: input): ValueSchemaOutput { - return (isSchemaAbiType(input) ? { value: input } : input) as ValueSchemaOutput; -} From 870d72552ae34a565a07dbbeaa8bc824e92837fa Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 12:05:14 +0100 Subject: [PATCH 08/15] move out tables parsing --- .../ts/config/experimental/parseConfig.ts | 19 ++++-------------- .../ts/config/experimental/parseTables.ts | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 packages/store/ts/config/experimental/parseTables.ts diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index 747c2b90ab..3274cd0890 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -1,27 +1,16 @@ -import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; +import { ParseTablesInput, ParseTablesOutput, parseTables } from "./parseTables"; export type ParseConfigInput = Readonly<{ namespace?: string; - tables?: Record; + tables?: ParseTablesInput; }>; export type ParseConfigOutput = Readonly<{ - tables: Readonly<{ - [tableName in keyof NonNullable & string]: ParseTableOutput< - input["namespace"] extends string ? input["namespace"] : "", - tableName, - NonNullable[tableName] - >; - }>; + tables: ParseTablesOutput>; }>; export function parseConfig(input: input): ParseConfigOutput { return { - tables: Object.fromEntries( - Object.entries(input.tables ?? {}).map(([tableName, table]) => [ - tableName, - parseTable(input.namespace ?? "", tableName, table), - ]) - ), + tables: parseTables(input.namespace ?? "", input.tables ?? {}), } as ParseConfigOutput; } diff --git a/packages/store/ts/config/experimental/parseTables.ts b/packages/store/ts/config/experimental/parseTables.ts new file mode 100644 index 0000000000..4c8ea6085e --- /dev/null +++ b/packages/store/ts/config/experimental/parseTables.ts @@ -0,0 +1,20 @@ +import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; + +export type ParseTablesInput = Readonly>; + +export type ParseTablesOutput = Readonly<{ + [tableName in keyof input & string]: ParseTableOutput< + input["namespace"] extends string ? input["namespace"] : defaultNamespace, + tableName, + input[tableName] + >; +}>; + +export function parseTables( + defaultNamespace: defaultNamespace, + input: input +): ParseTablesOutput { + return Object.fromEntries( + Object.entries(input).map(([tableName, table]) => [tableName, parseTable(defaultNamespace, tableName, table)]) + ) as ParseTablesOutput; +} From b135abaf726b3ca2aeafcf655456a4dd92ca3fd6 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 15:43:17 +0100 Subject: [PATCH 09/15] add namespaces --- .../store/ts/config/experimental/common.ts | 6 ++ .../config/experimental/parseConfig.test.ts | 61 +++++++++++++++---- .../ts/config/experimental/parseConfig.ts | 30 +++++++-- .../ts/config/experimental/parseNamespace.ts | 29 +++++++++ .../ts/config/experimental/parseTables.ts | 8 +-- 5 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 packages/store/ts/config/experimental/parseNamespace.ts diff --git a/packages/store/ts/config/experimental/common.ts b/packages/store/ts/config/experimental/common.ts index ea6327697e..9bc7d5bd5c 100644 --- a/packages/store/ts/config/experimental/common.ts +++ b/packages/store/ts/config/experimental/common.ts @@ -1,3 +1,9 @@ +export type EmptyObject = { [k: string]: never }; + +export type Prettify = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +} & unknown; + /** @internal */ export function isPlainObject(value: unknown): value is Record { return ( diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index e90f624729..a62377e315 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -24,8 +24,8 @@ describe("parseConfig", () => { tables: { Exists: { type: "table", - name: "Exists", namespace: "", + name: "Exists", tableId: tableIdToHex("", "Exists"), keySchema: { key: "bytes32", @@ -36,8 +36,8 @@ describe("parseConfig", () => { }, Position: { type: "table", - name: "Position", namespace: "", + name: "Position", tableId: tableIdToHex("", "Position"), keySchema: { key: "bytes32", @@ -49,8 +49,8 @@ describe("parseConfig", () => { }, Messages: { type: "offchainTable", - name: "Messages", namespace: "", + name: "Messages", tableId: tableIdToHex("", "Messages"), keySchema: { key: "bytes32", @@ -63,7 +63,7 @@ describe("parseConfig", () => { }, } as const); - expectTypeOf().toMatchTypeOf< + expectTypeOf().toEqualTypeOf< Readonly<{ tables: Readonly<{ Exists: Readonly<{ @@ -95,25 +95,40 @@ describe("parseConfig", () => { >(); }); - it("handles default namespace and per-table namespaces", () => { + it("handles namespaces", () => { const config = parseConfig({ - namespace: "MyNamespace", + namespace: "DefaultNamespace", tables: { Exists: "bool", Position: { - namespace: "OtherNamespace", + namespace: "TableNamespace", valueSchema: { x: "uint32", y: "uint32" }, }, }, + namespaces: { + MyNamespace: { + tables: { + PlayerNames: "string", + // TODO: make table overrides work or "throw" + // Exists: { + // // TODO: disable overriding namespace here + // namespace: "OverrideNamespace", + // valueSchema: { + // exists: "bool", + // }, + // }, + }, + }, + }, } as const); expect(config).toStrictEqual({ tables: { Exists: { type: "table", + namespace: "DefaultNamespace", name: "Exists", - namespace: "MyNamespace", - tableId: tableIdToHex("MyNamespace", "Exists"), + tableId: tableIdToHex("DefaultNamespace", "Exists"), keySchema: { key: "bytes32", }, @@ -123,9 +138,9 @@ describe("parseConfig", () => { }, Position: { type: "table", + namespace: "TableNamespace", name: "Position", - namespace: "OtherNamespace", - tableId: tableIdToHex("OtherNamespace", "Position"), + tableId: tableIdToHex("TableNamespace", "Position"), keySchema: { key: "bytes32", }, @@ -134,6 +149,18 @@ describe("parseConfig", () => { y: "uint32", }, }, + PlayerNames: { + type: "table", + namespace: "MyNamespace", + name: "PlayerNames", + tableId: tableIdToHex("MyNamespace", "PlayerNames"), + keySchema: { + key: "bytes32", + }, + valueSchema: { + value: "string", + }, + }, }, }); @@ -142,7 +169,7 @@ describe("parseConfig", () => { tables: Readonly<{ Exists: Readonly<{ type: "table"; - namespace: "MyNamespace"; + namespace: "DefaultNamespace"; name: "Exists"; tableId: `0x${string}`; keySchema: Readonly<{ key: "bytes32" }>; @@ -150,12 +177,20 @@ describe("parseConfig", () => { }>; Position: Readonly<{ type: "table"; - namespace: "OtherNamespace"; + namespace: "TableNamespace"; name: "Position"; tableId: `0x${string}`; keySchema: Readonly<{ key: "bytes32" }>; valueSchema: Readonly<{ x: "uint32"; y: "uint32" }>; }>; + PlayerNames: Readonly<{ + type: "table"; + namespace: "MyNamespace"; + name: "PlayerNames"; + tableId: `0x${string}`; + keySchema: Readonly<{ key: "bytes32" }>; + valueSchema: Readonly<{ value: "string" }>; + }>; }>; }> >(); diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index 3274cd0890..3886776e7c 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -1,16 +1,36 @@ +import { EmptyObject, Prettify } from "./common"; +import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./parseNamespace"; import { ParseTablesInput, ParseTablesOutput, parseTables } from "./parseTables"; +type Namespaces = Readonly>; + export type ParseConfigInput = Readonly<{ namespace?: string; tables?: ParseTablesInput; + namespaces?: Namespaces; }>; -export type ParseConfigOutput = Readonly<{ - tables: ParseTablesOutput>; -}>; +export type ParseConfigOutput< + input extends ParseConfigInput, + namespaces extends Namespaces = input["namespaces"] extends Namespaces ? input["namespaces"] : EmptyObject +> = { + // TODO: ensure that tables of the same name get replaced and are not a union + readonly tables: Prettify< + ParseTablesOutput> & + { + [namespace in keyof namespaces & string]: ParseNamespaceOutput; + }[keyof namespaces & string]["tables"] + >; +}; + +export function parseConfig(input: input): Prettify> { + const tables = Object.entries(parseTables(input.namespace ?? "", input.tables ?? {})); + const namespaces = Object.entries(input.namespaces ?? {}).map(([namespace, namespaceInput]) => + parseNamespace(namespace, namespaceInput) + ); + const namespacedTables = namespaces.flatMap((namespace) => Object.entries(namespace.tables)); -export function parseConfig(input: input): ParseConfigOutput { return { - tables: parseTables(input.namespace ?? "", input.tables ?? {}), + tables: Object.fromEntries([...tables, ...namespacedTables]), } as ParseConfigOutput; } diff --git a/packages/store/ts/config/experimental/parseNamespace.ts b/packages/store/ts/config/experimental/parseNamespace.ts new file mode 100644 index 0000000000..5e7dfb234b --- /dev/null +++ b/packages/store/ts/config/experimental/parseNamespace.ts @@ -0,0 +1,29 @@ +import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; + +export type ParseNamespaceInput = { + // TODO: omit namespace from table input + tables?: Readonly>; +}; + +export type ParseNamespaceOutput = Readonly<{ + tables: Readonly<{ + [name in keyof NonNullable & string]: ParseTableOutput< + namespace, + name, + NonNullable[name] + >; + }>; +}>; + +export function parseNamespace( + namespace: namespace, + input: input +): ParseNamespaceOutput { + return { + tables: Object.fromEntries( + // TODO: remove namespace from tableInput or override with the namespace we have + // though this may not be needed if our types omit it + Object.entries(input.tables ?? {}).map(([name, tableInput]) => [name, parseTable(namespace, name, tableInput)]) + ), + } as ParseNamespaceOutput; +} diff --git a/packages/store/ts/config/experimental/parseTables.ts b/packages/store/ts/config/experimental/parseTables.ts index 4c8ea6085e..110d84cf92 100644 --- a/packages/store/ts/config/experimental/parseTables.ts +++ b/packages/store/ts/config/experimental/parseTables.ts @@ -3,10 +3,10 @@ import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; export type ParseTablesInput = Readonly>; export type ParseTablesOutput = Readonly<{ - [tableName in keyof input & string]: ParseTableOutput< + [name in keyof input & string]: ParseTableOutput< input["namespace"] extends string ? input["namespace"] : defaultNamespace, - tableName, - input[tableName] + name, + input[name] >; }>; @@ -15,6 +15,6 @@ export function parseTables { return Object.fromEntries( - Object.entries(input).map(([tableName, table]) => [tableName, parseTable(defaultNamespace, tableName, table)]) + Object.entries(input).map(([name, tableInput]) => [name, parseTable(defaultNamespace, name, tableInput)]) ) as ParseTablesOutput; } From 2fcbc78cc0e9131b3e2a048175ffcf8888138e91 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 21 Sep 2023 16:12:47 +0100 Subject: [PATCH 10/15] refactor tests --- .../config/experimental/parseConfig.test.ts | 78 ++------ .../experimental/parseKeySchema.test.ts | 32 ++-- .../ts/config/experimental/parseTable.test.ts | 174 ++++++------------ .../experimental/parseValueSchema.test.ts | 24 ++- 4 files changed, 103 insertions(+), 205 deletions(-) diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index a62377e315..b9efaac20a 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -4,7 +4,7 @@ import { tableIdToHex } from "@latticexyz/common"; describe("parseConfig", () => { it("outputs tables from config", () => { - const config = parseConfig({ + const output = parseConfig({ tables: { Exists: "bool", Position: { @@ -20,7 +20,7 @@ describe("parseConfig", () => { }, }); - expect(config).toStrictEqual({ + const expectedOutput = { tables: { Exists: { type: "table", @@ -61,42 +61,15 @@ describe("parseConfig", () => { }, }, }, - } as const); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - tables: Readonly<{ - Exists: Readonly<{ - type: "table"; - namespace: ""; - name: "Exists"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "bool" }>; - }>; - Position: Readonly<{ - type: "table"; - namespace: ""; - name: "Position"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ x: "uint32"; y: "uint32" }>; - }>; - Messages: Readonly<{ - type: "offchainTable"; - namespace: ""; - name: "Messages"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ sender: "address"; message: "string" }>; - }>; - }>; - }> - >(); + expect(output).toStrictEqual(expectedOutput); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("handles namespaces", () => { - const config = parseConfig({ + const output = parseConfig({ namespace: "DefaultNamespace", tables: { Exists: "bool", @@ -122,7 +95,7 @@ describe("parseConfig", () => { }, } as const); - expect(config).toStrictEqual({ + const expectedOutput = { tables: { Exists: { type: "table", @@ -162,37 +135,10 @@ describe("parseConfig", () => { }, }, }, - }); + } as const; - expectTypeOf().toMatchTypeOf< - Readonly<{ - tables: Readonly<{ - Exists: Readonly<{ - type: "table"; - namespace: "DefaultNamespace"; - name: "Exists"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "bool" }>; - }>; - Position: Readonly<{ - type: "table"; - namespace: "TableNamespace"; - name: "Position"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ x: "uint32"; y: "uint32" }>; - }>; - PlayerNames: Readonly<{ - type: "table"; - namespace: "MyNamespace"; - name: "PlayerNames"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "string" }>; - }>; - }>; - }> - >(); + expect(output).toStrictEqual(expectedOutput); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); }); diff --git a/packages/store/ts/config/experimental/parseKeySchema.test.ts b/packages/store/ts/config/experimental/parseKeySchema.test.ts index 6c14c4dd91..f0ddb28263 100644 --- a/packages/store/ts/config/experimental/parseKeySchema.test.ts +++ b/packages/store/ts/config/experimental/parseKeySchema.test.ts @@ -5,26 +5,34 @@ import { parseKeySchema } from "./parseKeySchema"; describe("parseKeySchema", () => { it("outputs a key schema for uint8", () => { - const keySchema = parseKeySchema("uint8"); - expect(keySchema).toStrictEqual({ key: "uint8" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseKeySchema("uint8"); + const expectedOutput = { key: "uint8" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a key schema for bool", () => { - const keySchema = parseKeySchema("bool"); - expect(keySchema).toStrictEqual({ key: "bool" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseKeySchema("bool"); + const expectedOutput = { key: "bool" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("returns a full key schema", () => { - const keySchema = parseKeySchema({ x: "uint32", y: "uint32" }); - expect(keySchema).toStrictEqual({ x: "uint32", y: "uint32" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseKeySchema({ x: "uint32", y: "uint32" }); + const expectedOutput = { x: "uint32", y: "uint32" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("defaults key schema when undefined", () => { - const keySchema = parseKeySchema(undefined); - expect(keySchema).toStrictEqual({ key: "bytes32" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseKeySchema(undefined); + const expectedOutput = { key: "bytes32" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); }); diff --git a/packages/store/ts/config/experimental/parseTable.test.ts b/packages/store/ts/config/experimental/parseTable.test.ts index dfe3eff6d7..99c14c7191 100644 --- a/packages/store/ts/config/experimental/parseTable.test.ts +++ b/packages/store/ts/config/experimental/parseTable.test.ts @@ -10,224 +10,162 @@ describe("tableInputShapeKeys", () => { describe("parseTable", () => { it("outputs a table from just a schema ABI type", () => { - const table = parseTable("", "SomeTable", "uint8"); + const output = parseTable("", "SomeTable", "uint8"); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "uint8" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table from valueSchema shorthand", () => { - const table = parseTable("", "SomeTable", { valueSchema: "uint8" } as const); + const output = parseTable("", "SomeTable", { valueSchema: "uint8" }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "uint8" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table with undefined keySchema", () => { - const table = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" } as const); + const output = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "uint8" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table from keySchema shorthand", () => { - const table = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" } as const); + const output = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bool" }, valueSchema: { value: "uint8" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bool" }>; - valueSchema: Readonly<{ value: "uint8" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table from keySchema", () => { - const table = parseTable("", "SomeTable", { + const output = parseTable("", "SomeTable", { keySchema: { x: "uint32", y: "uint32" }, valueSchema: "uint8", - } as const); + }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { x: "uint32", y: "uint32" }, valueSchema: { value: "uint8" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ x: "uint32"; y: "uint32" }>; - valueSchema: Readonly<{ value: "uint8" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table from valueSchema", () => { - const table = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } } as const); + const output = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { exists: "bool" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ exists: "bool" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table with a default namespace", () => { - const table = parseTable("Namespace", "SomeTable", "bytes32"); + const output = parseTable("Namespace", "SomeTable", "bytes32"); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "Namespace", name: "SomeTable", tableId: tableIdToHex("Namespace", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "bytes32" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: "Namespace"; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "bytes32" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a table with a namespace override", () => { - const table = parseTable("Namespace", "SomeTable", { + const output = parseTable("Namespace", "SomeTable", { namespace: "CustomNamespace", valueSchema: "string", + // TODO: figure out if we can preserve namespace string literal type without as const requirement } as const); - expect(table).toStrictEqual({ + const expectedOutput = { type: "table", namespace: "CustomNamespace", name: "SomeTable", tableId: tableIdToHex("CustomNamespace", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "string" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "table"; - namespace: "CustomNamespace"; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "string" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs an offchain table", () => { - const table = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" } as const); + const output = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" }); - expect(table).toStrictEqual({ + const expectedOutput = { type: "offchainTable", namespace: "", name: "SomeTable", tableId: tableIdToHex("", "SomeTable"), keySchema: { key: "bytes32" }, valueSchema: { value: "string" }, - }); + } as const; - expectTypeOf().toEqualTypeOf< - Readonly<{ - type: "offchainTable"; - namespace: ""; - name: "SomeTable"; - tableId: `0x${string}`; - keySchema: Readonly<{ key: "bytes32" }>; - valueSchema: Readonly<{ value: "string" }>; - }> - >(); + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); }); diff --git a/packages/store/ts/config/experimental/parseValueSchema.test.ts b/packages/store/ts/config/experimental/parseValueSchema.test.ts index 7e63e7e7ce..ff1baa598a 100644 --- a/packages/store/ts/config/experimental/parseValueSchema.test.ts +++ b/packages/store/ts/config/experimental/parseValueSchema.test.ts @@ -3,20 +3,26 @@ import { parseValueSchema } from "./parseValueSchema"; describe("parseValueSchema", () => { it("outputs a key schema for uint8", () => { - const valueSchema = parseValueSchema("uint8"); - expect(valueSchema).toStrictEqual({ value: "uint8" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseValueSchema("uint8"); + const expectedOutput = { value: "uint8" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("outputs a key schema for string", () => { - const valueSchema = parseValueSchema("string"); - expect(valueSchema).toStrictEqual({ value: "string" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseValueSchema("string"); + const expectedOutput = { value: "string" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); it("returns a full value schema", () => { - const valueSchema = parseValueSchema({ x: "uint32", y: "uint32" }); - expect(valueSchema).toStrictEqual({ x: "uint32", y: "uint32" }); - expectTypeOf().toEqualTypeOf>(); + const output = parseValueSchema({ x: "uint32", y: "uint32" }); + const expectedOutput = { x: "uint32", y: "uint32" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); }); }); From 158ad61eaaa2523c83c44462a8e69e981f60fff4 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 28 Sep 2023 13:23:17 +0100 Subject: [PATCH 11/15] update after merge --- .../config/experimental/parseConfig.test.ts | 14 ++++++------- .../ts/config/experimental/parseTable.test.ts | 20 +++++++++---------- .../ts/config/experimental/parseTable.ts | 8 ++++++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index b9efaac20a..cc998254dc 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { parseConfig } from "./parseConfig"; -import { tableIdToHex } from "@latticexyz/common"; +import { resourceIdToHex } from "@latticexyz/common"; describe("parseConfig", () => { it("outputs tables from config", () => { @@ -26,7 +26,7 @@ describe("parseConfig", () => { type: "table", namespace: "", name: "Exists", - tableId: tableIdToHex("", "Exists"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "Exists" }), keySchema: { key: "bytes32", }, @@ -38,7 +38,7 @@ describe("parseConfig", () => { type: "table", namespace: "", name: "Position", - tableId: tableIdToHex("", "Position"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "Position" }), keySchema: { key: "bytes32", }, @@ -51,7 +51,7 @@ describe("parseConfig", () => { type: "offchainTable", namespace: "", name: "Messages", - tableId: tableIdToHex("", "Messages"), + tableId: resourceIdToHex({ type: "offchainTable", namespace: "", name: "Messages" }), keySchema: { key: "bytes32", }, @@ -101,7 +101,7 @@ describe("parseConfig", () => { type: "table", namespace: "DefaultNamespace", name: "Exists", - tableId: tableIdToHex("DefaultNamespace", "Exists"), + tableId: resourceIdToHex({ type: "table", namespace: "DefaultNamespace", name: "Exists" }), keySchema: { key: "bytes32", }, @@ -113,7 +113,7 @@ describe("parseConfig", () => { type: "table", namespace: "TableNamespace", name: "Position", - tableId: tableIdToHex("TableNamespace", "Position"), + tableId: resourceIdToHex({ type: "table", namespace: "TableNamespace", name: "Position" }), keySchema: { key: "bytes32", }, @@ -126,7 +126,7 @@ describe("parseConfig", () => { type: "table", namespace: "MyNamespace", name: "PlayerNames", - tableId: tableIdToHex("MyNamespace", "PlayerNames"), + tableId: resourceIdToHex({ type: "table", namespace: "MyNamespace", name: "PlayerNames" }), keySchema: { key: "bytes32", }, diff --git a/packages/store/ts/config/experimental/parseTable.test.ts b/packages/store/ts/config/experimental/parseTable.test.ts index 99c14c7191..6b26bd207d 100644 --- a/packages/store/ts/config/experimental/parseTable.test.ts +++ b/packages/store/ts/config/experimental/parseTable.test.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { TableShapeInput, parseTable, tableInputShapeKeys } from "./parseTable"; -import { tableIdToHex } from "@latticexyz/common"; +import { resourceIdToHex } from "@latticexyz/common"; describe("tableInputShapeKeys", () => { it("contains the same keys as TableShapeInput", () => { @@ -16,7 +16,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, } as const; @@ -33,7 +33,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, } as const; @@ -50,7 +50,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "uint8" }, } as const; @@ -67,7 +67,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { key: "bool" }, valueSchema: { value: "uint8" }, } as const; @@ -87,7 +87,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { x: "uint32", y: "uint32" }, valueSchema: { value: "uint8" }, } as const; @@ -104,7 +104,7 @@ describe("parseTable", () => { type: "table", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { exists: "bool" }, } as const; @@ -121,7 +121,7 @@ describe("parseTable", () => { type: "table", namespace: "Namespace", name: "SomeTable", - tableId: tableIdToHex("Namespace", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "Namespace", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "bytes32" }, } as const; @@ -142,7 +142,7 @@ describe("parseTable", () => { type: "table", namespace: "CustomNamespace", name: "SomeTable", - tableId: tableIdToHex("CustomNamespace", "SomeTable"), + tableId: resourceIdToHex({ type: "table", namespace: "CustomNamespace", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "string" }, } as const; @@ -159,7 +159,7 @@ describe("parseTable", () => { type: "offchainTable", namespace: "", name: "SomeTable", - tableId: tableIdToHex("", "SomeTable"), + tableId: resourceIdToHex({ type: "offchainTable", namespace: "", name: "SomeTable" }), keySchema: { key: "bytes32" }, valueSchema: { value: "string" }, } as const; diff --git a/packages/store/ts/config/experimental/parseTable.ts b/packages/store/ts/config/experimental/parseTable.ts index 6be49e11cf..1467e01419 100644 --- a/packages/store/ts/config/experimental/parseTable.ts +++ b/packages/store/ts/config/experimental/parseTable.ts @@ -3,7 +3,7 @@ import { isPlainObject } from "./common"; import { ParseKeySchemaInput, ParseKeySchemaOutput, parseKeySchema } from "./parseKeySchema"; import { ParseValueSchemaInput, ParseValueSchemaOutput, parseValueSchema } from "./parseValueSchema"; import { assertExhaustive } from "@latticexyz/common/utils"; -import { tableIdToHex } from "@latticexyz/common"; +import { resourceIdToHex } from "@latticexyz/common"; /** @internal */ export type TableShapeInput = Readonly<{ @@ -66,7 +66,11 @@ export function parseTable Date: Thu, 28 Sep 2023 14:41:30 +0100 Subject: [PATCH 12/15] playing with namespaces/merging objects --- .../store/ts/config/experimental/common.ts | 13 +++++++++ .../config/experimental/parseConfig.test.ts | 21 +++++++------- .../ts/config/experimental/parseConfig.ts | 29 +++++++++---------- .../ts/config/experimental/parseNamespace.ts | 13 ++++----- .../ts/config/experimental/parseNamespaces.ts | 18 ++++++++++++ 5 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 packages/store/ts/config/experimental/parseNamespaces.ts diff --git a/packages/store/ts/config/experimental/common.ts b/packages/store/ts/config/experimental/common.ts index 9bc7d5bd5c..835095a713 100644 --- a/packages/store/ts/config/experimental/common.ts +++ b/packages/store/ts/config/experimental/common.ts @@ -4,6 +4,19 @@ export type Prettify = { [K in keyof T as T[K] extends never ? never : K]: T[K]; } & unknown; +/** + * Merges two object types into new type + * + * @param Object1 - Object to merge into + * @param Object2 - Object to merge and override keys from {@link Object1} + * @returns New object type with keys from {@link Object1} and {@link Object2}. If a key exists in both {@link Object1} and {@link Object2}, the key from {@link Object2} will be used. + * + * @example + * type Result = Merge<{ foo: string }, { foo: number; bar: string }> + * // ^? type Result = { foo: number; bar: string } + */ +export type Merge = keyof Object2 extends never ? Object1 : Omit & Object2; + /** @internal */ export function isPlainObject(value: unknown): value is Record { return ( diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index cc998254dc..e1ca2de4bc 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -82,14 +82,13 @@ describe("parseConfig", () => { MyNamespace: { tables: { PlayerNames: "string", - // TODO: make table overrides work or "throw" - // Exists: { - // // TODO: disable overriding namespace here - // namespace: "OverrideNamespace", - // valueSchema: { - // exists: "bool", - // }, - // }, + Exists: { + // TODO: disable overriding namespace here + namespace: "OverrideNamespace", + valueSchema: { + exists: "bool", + }, + }, }, }, }, @@ -99,14 +98,14 @@ describe("parseConfig", () => { tables: { Exists: { type: "table", - namespace: "DefaultNamespace", + namespace: "OverrideNamespace", name: "Exists", - tableId: resourceIdToHex({ type: "table", namespace: "DefaultNamespace", name: "Exists" }), + tableId: resourceIdToHex({ type: "table", namespace: "OverrideNamespace", name: "Exists" }), keySchema: { key: "bytes32", }, valueSchema: { - value: "bool", + exists: "bool", }, }, Position: { diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index 3886776e7c..b32bcb0cde 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -1,35 +1,34 @@ -import { EmptyObject, Prettify } from "./common"; -import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./parseNamespace"; +import { EmptyObject, Merge, Prettify } from "./common"; +import { ParseNamespacesInput, ParseNamespacesOutput, parseNamespaces } from "./parseNamespaces"; import { ParseTablesInput, ParseTablesOutput, parseTables } from "./parseTables"; -type Namespaces = Readonly>; - export type ParseConfigInput = Readonly<{ namespace?: string; tables?: ParseTablesInput; - namespaces?: Namespaces; + namespaces?: ParseNamespacesInput; }>; export type ParseConfigOutput< input extends ParseConfigInput, - namespaces extends Namespaces = input["namespaces"] extends Namespaces ? input["namespaces"] : EmptyObject + namespaces extends ParseNamespacesInput = input["namespaces"] extends ParseNamespacesInput + ? input["namespaces"] + : EmptyObject > = { // TODO: ensure that tables of the same name get replaced and are not a union readonly tables: Prettify< - ParseTablesOutput> & - { - [namespace in keyof namespaces & string]: ParseNamespaceOutput; - }[keyof namespaces & string]["tables"] + Merge< + ParseTablesOutput< + input["namespace"] extends string ? input["namespace"] : "", + input["tables"] extends ParseTablesInput ? input["tables"] : EmptyObject + >, + ParseNamespacesOutput + > >; }; export function parseConfig(input: input): Prettify> { const tables = Object.entries(parseTables(input.namespace ?? "", input.tables ?? {})); - const namespaces = Object.entries(input.namespaces ?? {}).map(([namespace, namespaceInput]) => - parseNamespace(namespace, namespaceInput) - ); - const namespacedTables = namespaces.flatMap((namespace) => Object.entries(namespace.tables)); - + const namespacedTables = Object.entries(parseNamespaces(input.namespaces ?? {})); return { tables: Object.fromEntries([...tables, ...namespacedTables]), } as ParseConfigOutput; diff --git a/packages/store/ts/config/experimental/parseNamespace.ts b/packages/store/ts/config/experimental/parseNamespace.ts index 5e7dfb234b..08a2383846 100644 --- a/packages/store/ts/config/experimental/parseNamespace.ts +++ b/packages/store/ts/config/experimental/parseNamespace.ts @@ -1,3 +1,4 @@ +import { EmptyObject } from "./common"; import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; export type ParseNamespaceInput = { @@ -6,13 +7,11 @@ export type ParseNamespaceInput = { }; export type ParseNamespaceOutput = Readonly<{ - tables: Readonly<{ - [name in keyof NonNullable & string]: ParseTableOutput< - namespace, - name, - NonNullable[name] - >; - }>; + tables: input["tables"] extends ParseTableInput + ? Readonly<{ + [name in keyof input["tables"] & string]: ParseTableOutput; + }> + : EmptyObject; }>; export function parseNamespace( diff --git a/packages/store/ts/config/experimental/parseNamespaces.ts b/packages/store/ts/config/experimental/parseNamespaces.ts new file mode 100644 index 0000000000..c580d340bd --- /dev/null +++ b/packages/store/ts/config/experimental/parseNamespaces.ts @@ -0,0 +1,18 @@ +import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./parseNamespace"; + +export type ParseNamespacesInput = Readonly>; + +export type ParseNamespacesOutput = Readonly< + { + [namespace in keyof input & string]: ParseNamespaceOutput; + }[keyof input & string]["tables"] +>; + +export function parseNamespaces(input: input): ParseNamespacesOutput { + return Object.fromEntries( + Object.entries(input).flatMap(([namespace, namespaceInput]) => { + const { tables } = parseNamespace(namespace, namespaceInput); + return Object.entries(tables); + }) + ) as ParseNamespacesOutput; +} From 3b3b91578682c8df713a2eee53f0a305e508154c Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Fri, 29 Sep 2023 14:49:35 +0100 Subject: [PATCH 13/15] Readonly helper to readonly keys --- .../store/ts/config/experimental/common.ts | 8 ++++-- .../ts/config/experimental/parseConfig.ts | 10 +++---- .../ts/config/experimental/parseKeySchema.ts | 6 ++-- .../ts/config/experimental/parseNamespace.ts | 14 +++++----- .../ts/config/experimental/parseNamespaces.ts | 10 +++---- .../ts/config/experimental/parseTable.ts | 28 +++++++++---------- .../ts/config/experimental/parseTables.ts | 8 +++--- .../config/experimental/parseValueSchema.ts | 6 ++-- 8 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/store/ts/config/experimental/common.ts b/packages/store/ts/config/experimental/common.ts index 835095a713..b0292f166b 100644 --- a/packages/store/ts/config/experimental/common.ts +++ b/packages/store/ts/config/experimental/common.ts @@ -1,4 +1,4 @@ -export type EmptyObject = { [k: string]: never }; +export type EmptyObject = { readonly [k: string]: never }; export type Prettify = { [K in keyof T as T[K] extends never ? never : K]: T[K]; @@ -15,7 +15,11 @@ export type Prettify = { * type Result = Merge<{ foo: string }, { foo: number; bar: string }> * // ^? type Result = { foo: number; bar: string } */ -export type Merge = keyof Object2 extends never ? Object1 : Omit & Object2; +export type Merge = keyof Object2 extends never + ? Object1 + : keyof Object1 extends never + ? Object2 + : Omit & Object2; /** @internal */ export function isPlainObject(value: unknown): value is Record { diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index b32bcb0cde..b54a58122e 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -2,11 +2,11 @@ import { EmptyObject, Merge, Prettify } from "./common"; import { ParseNamespacesInput, ParseNamespacesOutput, parseNamespaces } from "./parseNamespaces"; import { ParseTablesInput, ParseTablesOutput, parseTables } from "./parseTables"; -export type ParseConfigInput = Readonly<{ - namespace?: string; - tables?: ParseTablesInput; - namespaces?: ParseNamespacesInput; -}>; +export type ParseConfigInput = { + readonly namespace?: string; + readonly tables?: ParseTablesInput; + readonly namespaces?: ParseNamespacesInput; +}; export type ParseConfigOutput< input extends ParseConfigInput, diff --git a/packages/store/ts/config/experimental/parseKeySchema.ts b/packages/store/ts/config/experimental/parseKeySchema.ts index 083cf4b2fb..25c6581a6f 100644 --- a/packages/store/ts/config/experimental/parseKeySchema.ts +++ b/packages/store/ts/config/experimental/parseKeySchema.ts @@ -1,6 +1,6 @@ import { StaticAbiType, isStaticAbiType } from "@latticexyz/schema-type"; -export type KeySchema = Readonly>; +export type KeySchema = { readonly [k: string]: StaticAbiType }; export const defaultKeySchema = { key: "bytes32" } as const satisfies KeySchema; @@ -9,9 +9,9 @@ export type ParseKeySchemaInput = StaticAbiType | KeySchema | undefined; export type ParseKeySchemaOutput = input extends undefined ? typeof defaultKeySchema : input extends StaticAbiType - ? Readonly<{ key: input }> + ? { readonly key: input } : input extends KeySchema - ? Readonly + ? input : never; export function parseKeySchema(input: input): ParseKeySchemaOutput { diff --git a/packages/store/ts/config/experimental/parseNamespace.ts b/packages/store/ts/config/experimental/parseNamespace.ts index 08a2383846..f3dce95b01 100644 --- a/packages/store/ts/config/experimental/parseNamespace.ts +++ b/packages/store/ts/config/experimental/parseNamespace.ts @@ -3,16 +3,16 @@ import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; export type ParseNamespaceInput = { // TODO: omit namespace from table input - tables?: Readonly>; + readonly tables?: { readonly [k: string]: ParseTableInput }; }; -export type ParseNamespaceOutput = Readonly<{ - tables: input["tables"] extends ParseTableInput - ? Readonly<{ - [name in keyof input["tables"] & string]: ParseTableOutput; - }> +export type ParseNamespaceOutput = { + readonly tables: input["tables"] extends ParseTableInput + ? { + readonly [name in keyof input["tables"] & string]: ParseTableOutput; + } : EmptyObject; -}>; +}; export function parseNamespace( namespace: namespace, diff --git a/packages/store/ts/config/experimental/parseNamespaces.ts b/packages/store/ts/config/experimental/parseNamespaces.ts index c580d340bd..58211ebc3d 100644 --- a/packages/store/ts/config/experimental/parseNamespaces.ts +++ b/packages/store/ts/config/experimental/parseNamespaces.ts @@ -1,12 +1,10 @@ import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./parseNamespace"; -export type ParseNamespacesInput = Readonly>; +export type ParseNamespacesInput = { readonly [k: string]: ParseNamespaceInput }; -export type ParseNamespacesOutput = Readonly< - { - [namespace in keyof input & string]: ParseNamespaceOutput; - }[keyof input & string]["tables"] ->; +export type ParseNamespacesOutput = { + readonly [namespace in keyof input & string]: ParseNamespaceOutput; +}[keyof input & string]["tables"]; export function parseNamespaces(input: input): ParseNamespacesOutput { return Object.fromEntries( diff --git a/packages/store/ts/config/experimental/parseTable.ts b/packages/store/ts/config/experimental/parseTable.ts index 1467e01419..66ac71ee77 100644 --- a/packages/store/ts/config/experimental/parseTable.ts +++ b/packages/store/ts/config/experimental/parseTable.ts @@ -6,12 +6,12 @@ import { assertExhaustive } from "@latticexyz/common/utils"; import { resourceIdToHex } from "@latticexyz/common"; /** @internal */ -export type TableShapeInput = Readonly<{ - namespace?: string; - keySchema?: ParseKeySchemaInput; - valueSchema: ParseValueSchemaInput; - offchainOnly?: boolean; -}>; +export type TableShapeInput = { + readonly namespace?: string; + readonly keySchema?: ParseKeySchemaInput; + readonly valueSchema: ParseValueSchemaInput; + readonly offchainOnly?: boolean; +}; // TODO: add support for ParseValueSchemaInput instead of SchemaAbiType? // requires an isValueSchemaInput helper that is distinct enough from isParseTableInputShape @@ -24,20 +24,20 @@ export type ParseTableOutput< > = input extends SchemaAbiType ? ParseTableOutput : input extends TableShapeInput - ? Readonly<{ - type: input["offchainOnly"] extends true ? "offchainTable" : "table"; - namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; - name: name; - tableId: `0x${string}`; - keySchema: ParseKeySchemaOutput< + ? { + readonly type: input["offchainOnly"] extends true ? "offchainTable" : "table"; + readonly namespace: input["namespace"] extends string ? input["namespace"] : defaultNamespace; + readonly name: name; + readonly tableId: `0x${string}`; + readonly keySchema: ParseKeySchemaOutput< input["keySchema"] extends ParseKeySchemaInput ? input["keySchema"] : never extends input["keySchema"] ? undefined : never >; - valueSchema: ParseValueSchemaOutput; - }> + readonly valueSchema: ParseValueSchemaOutput; + } : never; // TODO: is there a better way to check this aside from just looking at the shape/keys of the object? diff --git a/packages/store/ts/config/experimental/parseTables.ts b/packages/store/ts/config/experimental/parseTables.ts index 110d84cf92..6ba3cc899b 100644 --- a/packages/store/ts/config/experimental/parseTables.ts +++ b/packages/store/ts/config/experimental/parseTables.ts @@ -1,14 +1,14 @@ import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; -export type ParseTablesInput = Readonly>; +export type ParseTablesInput = { readonly [k: string]: ParseTableInput }; -export type ParseTablesOutput = Readonly<{ - [name in keyof input & string]: ParseTableOutput< +export type ParseTablesOutput = { + readonly [name in keyof input & string]: ParseTableOutput< input["namespace"] extends string ? input["namespace"] : defaultNamespace, name, input[name] >; -}>; +}; export function parseTables( defaultNamespace: defaultNamespace, diff --git a/packages/store/ts/config/experimental/parseValueSchema.ts b/packages/store/ts/config/experimental/parseValueSchema.ts index 7b52750dfa..8d8a4df669 100644 --- a/packages/store/ts/config/experimental/parseValueSchema.ts +++ b/packages/store/ts/config/experimental/parseValueSchema.ts @@ -1,13 +1,13 @@ import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; -export type ValueSchema = Readonly>; +export type ValueSchema = { readonly [k: string]: SchemaAbiType }; export type ParseValueSchemaInput = SchemaAbiType | ValueSchema; export type ParseValueSchemaOutput = input extends SchemaAbiType - ? Readonly<{ value: input }> + ? { readonly value: input } : input extends ValueSchema - ? Readonly + ? input : never; export function parseValueSchema(input: input): ParseValueSchemaOutput { From 9234e3eb9525e5b3286c6bb5813f4f04c28808e2 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Fri, 29 Sep 2023 16:06:05 +0100 Subject: [PATCH 14/15] small tweaks --- .../ts/config/experimental/parseConfig.test.ts | 2 +- .../store/ts/config/experimental/parseConfig.ts | 14 +++++--------- .../ts/config/experimental/parseKeySchema.test.ts | 2 +- .../ts/config/experimental/parseNamespaces.ts | 1 + .../config/experimental/parseValueSchema.test.ts | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index e1ca2de4bc..bcd4ec450a 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -18,7 +18,7 @@ describe("parseConfig", () => { }, }, }, - }); + } as const); const expectedOutput = { tables: { diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index b54a58122e..316fd48c17 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -15,15 +15,11 @@ export type ParseConfigOutput< : EmptyObject > = { // TODO: ensure that tables of the same name get replaced and are not a union - readonly tables: Prettify< - Merge< - ParseTablesOutput< - input["namespace"] extends string ? input["namespace"] : "", - input["tables"] extends ParseTablesInput ? input["tables"] : EmptyObject - >, - ParseNamespacesOutput - > - >; + readonly tables: ParseTablesOutput< + input["namespace"] extends string ? input["namespace"] : "", + input["tables"] extends ParseTablesInput ? input["tables"] : EmptyObject + > & + ParseNamespacesOutput; }; export function parseConfig(input: input): Prettify> { diff --git a/packages/store/ts/config/experimental/parseKeySchema.test.ts b/packages/store/ts/config/experimental/parseKeySchema.test.ts index f0ddb28263..586aecf295 100644 --- a/packages/store/ts/config/experimental/parseKeySchema.test.ts +++ b/packages/store/ts/config/experimental/parseKeySchema.test.ts @@ -21,7 +21,7 @@ describe("parseKeySchema", () => { }); it("returns a full key schema", () => { - const output = parseKeySchema({ x: "uint32", y: "uint32" }); + const output = parseKeySchema({ x: "uint32", y: "uint32" } as const); const expectedOutput = { x: "uint32", y: "uint32" } as const; expect(output).toStrictEqual(output); expectTypeOf(output).toEqualTypeOf(expectedOutput); diff --git a/packages/store/ts/config/experimental/parseNamespaces.ts b/packages/store/ts/config/experimental/parseNamespaces.ts index 58211ebc3d..0827f85f42 100644 --- a/packages/store/ts/config/experimental/parseNamespaces.ts +++ b/packages/store/ts/config/experimental/parseNamespaces.ts @@ -6,6 +6,7 @@ export type ParseNamespacesOutput = { readonly [namespace in keyof input & string]: ParseNamespaceOutput; }[keyof input & string]["tables"]; +// TODO: rename to parseNamespacedTables export function parseNamespaces(input: input): ParseNamespacesOutput { return Object.fromEntries( Object.entries(input).flatMap(([namespace, namespaceInput]) => { diff --git a/packages/store/ts/config/experimental/parseValueSchema.test.ts b/packages/store/ts/config/experimental/parseValueSchema.test.ts index ff1baa598a..1ec2053e52 100644 --- a/packages/store/ts/config/experimental/parseValueSchema.test.ts +++ b/packages/store/ts/config/experimental/parseValueSchema.test.ts @@ -19,7 +19,7 @@ describe("parseValueSchema", () => { }); it("returns a full value schema", () => { - const output = parseValueSchema({ x: "uint32", y: "uint32" }); + const output = parseValueSchema({ x: "uint32", y: "uint32" } as const); const expectedOutput = { x: "uint32", y: "uint32" } as const; expect(output).toStrictEqual(output); expectTypeOf(output).toEqualTypeOf(expectedOutput); From e3363d3ec9011eefdebbb54497724fc6c02a5c37 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Fri, 29 Sep 2023 20:13:29 +0100 Subject: [PATCH 15/15] clean up --- packages/store/ts/config/experimental/common.ts | 6 +----- .../ts/config/experimental/parseConfig.test.ts | 1 + .../store/ts/config/experimental/parseConfig.ts | 12 +++++++----- .../store/ts/config/experimental/parseNamespace.ts | 2 +- .../store/ts/config/experimental/parseNamespaces.ts | 4 ++-- .../store/ts/config/experimental/parseTable.test.ts | 13 ++++++------- .../store/ts/config/experimental/parseTables.ts | 4 ++-- 7 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/store/ts/config/experimental/common.ts b/packages/store/ts/config/experimental/common.ts index b0292f166b..659806fa54 100644 --- a/packages/store/ts/config/experimental/common.ts +++ b/packages/store/ts/config/experimental/common.ts @@ -15,11 +15,7 @@ export type Prettify = { * type Result = Merge<{ foo: string }, { foo: number; bar: string }> * // ^? type Result = { foo: number; bar: string } */ -export type Merge = keyof Object2 extends never - ? Object1 - : keyof Object1 extends never - ? Object2 - : Omit & Object2; +export type Merge = Omit & Object2; /** @internal */ export function isPlainObject(value: unknown): value is Record { diff --git a/packages/store/ts/config/experimental/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts index bcd4ec450a..ebd4af2b9e 100644 --- a/packages/store/ts/config/experimental/parseConfig.test.ts +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -136,6 +136,7 @@ describe("parseConfig", () => { }, } as const; + // TODO: why is PlayerNames disappearing? expect(output).toStrictEqual(expectedOutput); expectTypeOf(output).toEqualTypeOf(expectedOutput); expectTypeOf(output).toMatchTypeOf(expectedOutput); diff --git a/packages/store/ts/config/experimental/parseConfig.ts b/packages/store/ts/config/experimental/parseConfig.ts index 316fd48c17..772effc61c 100644 --- a/packages/store/ts/config/experimental/parseConfig.ts +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -15,11 +15,13 @@ export type ParseConfigOutput< : EmptyObject > = { // TODO: ensure that tables of the same name get replaced and are not a union - readonly tables: ParseTablesOutput< - input["namespace"] extends string ? input["namespace"] : "", - input["tables"] extends ParseTablesInput ? input["tables"] : EmptyObject - > & - ParseNamespacesOutput; + readonly tables: Prettify< + ParseTablesOutput< + input["namespace"] extends string ? input["namespace"] : "", + input["tables"] extends ParseTablesInput ? input["tables"] : EmptyObject + > & + ParseNamespacesOutput + >; }; export function parseConfig(input: input): Prettify> { diff --git a/packages/store/ts/config/experimental/parseNamespace.ts b/packages/store/ts/config/experimental/parseNamespace.ts index f3dce95b01..dd6de95601 100644 --- a/packages/store/ts/config/experimental/parseNamespace.ts +++ b/packages/store/ts/config/experimental/parseNamespace.ts @@ -9,7 +9,7 @@ export type ParseNamespaceInput = { export type ParseNamespaceOutput = { readonly tables: input["tables"] extends ParseTableInput ? { - readonly [name in keyof input["tables"] & string]: ParseTableOutput; + readonly [name in keyof input["tables"]]: ParseTableOutput; } : EmptyObject; }; diff --git a/packages/store/ts/config/experimental/parseNamespaces.ts b/packages/store/ts/config/experimental/parseNamespaces.ts index 0827f85f42..c3a5a241cb 100644 --- a/packages/store/ts/config/experimental/parseNamespaces.ts +++ b/packages/store/ts/config/experimental/parseNamespaces.ts @@ -3,8 +3,8 @@ import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./par export type ParseNamespacesInput = { readonly [k: string]: ParseNamespaceInput }; export type ParseNamespacesOutput = { - readonly [namespace in keyof input & string]: ParseNamespaceOutput; -}[keyof input & string]["tables"]; + readonly [namespace in keyof input]: ParseNamespaceOutput; +}[keyof input]["tables"]; // TODO: rename to parseNamespacedTables export function parseNamespaces(input: input): ParseNamespacesOutput { diff --git a/packages/store/ts/config/experimental/parseTable.test.ts b/packages/store/ts/config/experimental/parseTable.test.ts index 6b26bd207d..c794182b7e 100644 --- a/packages/store/ts/config/experimental/parseTable.test.ts +++ b/packages/store/ts/config/experimental/parseTable.test.ts @@ -27,7 +27,7 @@ describe("parseTable", () => { }); it("outputs a table from valueSchema shorthand", () => { - const output = parseTable("", "SomeTable", { valueSchema: "uint8" }); + const output = parseTable("", "SomeTable", { valueSchema: "uint8" } as const); const expectedOutput = { type: "table", @@ -44,7 +44,7 @@ describe("parseTable", () => { }); it("outputs a table with undefined keySchema", () => { - const output = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" }); + const output = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" } as const); const expectedOutput = { type: "table", @@ -61,7 +61,7 @@ describe("parseTable", () => { }); it("outputs a table from keySchema shorthand", () => { - const output = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" }); + const output = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" } as const); const expectedOutput = { type: "table", @@ -81,7 +81,7 @@ describe("parseTable", () => { const output = parseTable("", "SomeTable", { keySchema: { x: "uint32", y: "uint32" }, valueSchema: "uint8", - }); + } as const); const expectedOutput = { type: "table", @@ -98,7 +98,7 @@ describe("parseTable", () => { }); it("outputs a table from valueSchema", () => { - const output = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } }); + const output = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } } as const); const expectedOutput = { type: "table", @@ -135,7 +135,6 @@ describe("parseTable", () => { const output = parseTable("Namespace", "SomeTable", { namespace: "CustomNamespace", valueSchema: "string", - // TODO: figure out if we can preserve namespace string literal type without as const requirement } as const); const expectedOutput = { @@ -153,7 +152,7 @@ describe("parseTable", () => { }); it("outputs an offchain table", () => { - const output = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" }); + const output = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" } as const); const expectedOutput = { type: "offchainTable", diff --git a/packages/store/ts/config/experimental/parseTables.ts b/packages/store/ts/config/experimental/parseTables.ts index 6ba3cc899b..f1b2bb8af4 100644 --- a/packages/store/ts/config/experimental/parseTables.ts +++ b/packages/store/ts/config/experimental/parseTables.ts @@ -3,9 +3,9 @@ import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; export type ParseTablesInput = { readonly [k: string]: ParseTableInput }; export type ParseTablesOutput = { - readonly [name in keyof input & string]: ParseTableOutput< + readonly [name in keyof input]: ParseTableOutput< input["namespace"] extends string ? input["namespace"] : defaultNamespace, - name, + name & string, input[name] >; };