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/experimental/common.ts b/packages/store/ts/config/experimental/common.ts new file mode 100644 index 0000000000..659806fa54 --- /dev/null +++ b/packages/store/ts/config/experimental/common.ts @@ -0,0 +1,28 @@ +export type EmptyObject = { readonly [k: string]: never }; + +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 = Omit & Object2; + +/** @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/parseConfig.test.ts b/packages/store/ts/config/experimental/parseConfig.test.ts new file mode 100644 index 0000000000..ebd4af2b9e --- /dev/null +++ b/packages/store/ts/config/experimental/parseConfig.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseConfig } from "./parseConfig"; +import { resourceIdToHex } from "@latticexyz/common"; + +describe("parseConfig", () => { + it("outputs tables from config", () => { + const output = parseConfig({ + tables: { + Exists: "bool", + Position: { + valueSchema: { x: "uint32", y: "uint32" }, + }, + Messages: { + offchainOnly: true, + valueSchema: { + sender: "address", + message: "string", + }, + }, + }, + } as const); + + const expectedOutput = { + tables: { + Exists: { + type: "table", + namespace: "", + name: "Exists", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "Exists" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + value: "bool", + }, + }, + Position: { + type: "table", + namespace: "", + name: "Position", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "Position" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + x: "uint32", + y: "uint32", + }, + }, + Messages: { + type: "offchainTable", + namespace: "", + name: "Messages", + tableId: resourceIdToHex({ type: "offchainTable", namespace: "", name: "Messages" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + message: "string", + sender: "address", + }, + }, + }, + } as const; + + expect(output).toStrictEqual(expectedOutput); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("handles namespaces", () => { + const output = parseConfig({ + namespace: "DefaultNamespace", + tables: { + Exists: "bool", + Position: { + namespace: "TableNamespace", + valueSchema: { x: "uint32", y: "uint32" }, + }, + }, + namespaces: { + MyNamespace: { + tables: { + PlayerNames: "string", + Exists: { + // TODO: disable overriding namespace here + namespace: "OverrideNamespace", + valueSchema: { + exists: "bool", + }, + }, + }, + }, + }, + } as const); + + const expectedOutput = { + tables: { + Exists: { + type: "table", + namespace: "OverrideNamespace", + name: "Exists", + tableId: resourceIdToHex({ type: "table", namespace: "OverrideNamespace", name: "Exists" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + exists: "bool", + }, + }, + Position: { + type: "table", + namespace: "TableNamespace", + name: "Position", + tableId: resourceIdToHex({ type: "table", namespace: "TableNamespace", name: "Position" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + x: "uint32", + y: "uint32", + }, + }, + PlayerNames: { + type: "table", + namespace: "MyNamespace", + name: "PlayerNames", + tableId: resourceIdToHex({ type: "table", namespace: "MyNamespace", name: "PlayerNames" }), + keySchema: { + key: "bytes32", + }, + valueSchema: { + value: "string", + }, + }, + }, + } 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 new file mode 100644 index 0000000000..772effc61c --- /dev/null +++ b/packages/store/ts/config/experimental/parseConfig.ts @@ -0,0 +1,33 @@ +import { EmptyObject, Merge, Prettify } from "./common"; +import { ParseNamespacesInput, ParseNamespacesOutput, parseNamespaces } from "./parseNamespaces"; +import { ParseTablesInput, ParseTablesOutput, parseTables } from "./parseTables"; + +export type ParseConfigInput = { + readonly namespace?: string; + readonly tables?: ParseTablesInput; + readonly namespaces?: ParseNamespacesInput; +}; + +export type ParseConfigOutput< + input extends ParseConfigInput, + 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< + 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 namespacedTables = Object.entries(parseNamespaces(input.namespaces ?? {})); + return { + tables: Object.fromEntries([...tables, ...namespacedTables]), + } as ParseConfigOutput; +} diff --git a/packages/store/ts/config/experimental/parseKeySchema.test.ts b/packages/store/ts/config/experimental/parseKeySchema.test.ts new file mode 100644 index 0000000000..586aecf295 --- /dev/null +++ b/packages/store/ts/config/experimental/parseKeySchema.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseKeySchema } from "./parseKeySchema"; + +// TODO: add tests for failing cases (dynamic ABI types) + +describe("parseKeySchema", () => { + it("outputs a key schema for uint8", () => { + 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 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 output = parseKeySchema({ x: "uint32", y: "uint32" } as const); + 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 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/parseKeySchema.ts b/packages/store/ts/config/experimental/parseKeySchema.ts new file mode 100644 index 0000000000..25c6581a6f --- /dev/null +++ b/packages/store/ts/config/experimental/parseKeySchema.ts @@ -0,0 +1,21 @@ +import { StaticAbiType, isStaticAbiType } from "@latticexyz/schema-type"; + +export type KeySchema = { readonly [k: string]: StaticAbiType }; + +export const defaultKeySchema = { key: "bytes32" } as const satisfies KeySchema; + +export type ParseKeySchemaInput = StaticAbiType | KeySchema | undefined; + +export type ParseKeySchemaOutput = input extends undefined + ? typeof defaultKeySchema + : input extends StaticAbiType + ? { readonly key: input } + : input extends KeySchema + ? input + : never; + +export function parseKeySchema(input: input): ParseKeySchemaOutput { + return ( + input === undefined ? defaultKeySchema : isStaticAbiType(input) ? { key: input } : input + ) as ParseKeySchemaOutput; +} diff --git a/packages/store/ts/config/experimental/parseNamespace.ts b/packages/store/ts/config/experimental/parseNamespace.ts new file mode 100644 index 0000000000..dd6de95601 --- /dev/null +++ b/packages/store/ts/config/experimental/parseNamespace.ts @@ -0,0 +1,28 @@ +import { EmptyObject } from "./common"; +import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; + +export type ParseNamespaceInput = { + // TODO: omit namespace from table input + readonly tables?: { readonly [k: string]: ParseTableInput }; +}; + +export type ParseNamespaceOutput = { + readonly tables: input["tables"] extends ParseTableInput + ? { + readonly [name in keyof input["tables"]]: ParseTableOutput; + } + : EmptyObject; +}; + +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/parseNamespaces.ts b/packages/store/ts/config/experimental/parseNamespaces.ts new file mode 100644 index 0000000000..c3a5a241cb --- /dev/null +++ b/packages/store/ts/config/experimental/parseNamespaces.ts @@ -0,0 +1,17 @@ +import { ParseNamespaceInput, ParseNamespaceOutput, parseNamespace } from "./parseNamespace"; + +export type ParseNamespacesInput = { readonly [k: string]: ParseNamespaceInput }; + +export type ParseNamespacesOutput = { + readonly [namespace in keyof input]: ParseNamespaceOutput; +}[keyof input]["tables"]; + +// TODO: rename to parseNamespacedTables +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; +} diff --git a/packages/store/ts/config/experimental/parseTable.test.ts b/packages/store/ts/config/experimental/parseTable.test.ts new file mode 100644 index 0000000000..c794182b7e --- /dev/null +++ b/packages/store/ts/config/experimental/parseTable.test.ts @@ -0,0 +1,170 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { TableShapeInput, parseTable, tableInputShapeKeys } from "./parseTable"; +import { resourceIdToHex } 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 output = parseTable("", "SomeTable", "uint8"); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table from valueSchema shorthand", () => { + const output = parseTable("", "SomeTable", { valueSchema: "uint8" } as const); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table with undefined keySchema", () => { + const output = parseTable("", "SomeTable", { keySchema: undefined, valueSchema: "uint8" } as const); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "uint8" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table from keySchema shorthand", () => { + const output = parseTable("", "SomeTable", { keySchema: "bool", valueSchema: "uint8" } as const); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { key: "bool" }, + valueSchema: { value: "uint8" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table from keySchema", () => { + const output = parseTable("", "SomeTable", { + keySchema: { x: "uint32", y: "uint32" }, + valueSchema: "uint8", + } as const); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { x: "uint32", y: "uint32" }, + valueSchema: { value: "uint8" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table from valueSchema", () => { + const output = parseTable("", "SomeTable", { valueSchema: { exists: "bool" } } as const); + + const expectedOutput = { + type: "table", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { exists: "bool" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table with a default namespace", () => { + const output = parseTable("Namespace", "SomeTable", "bytes32"); + + const expectedOutput = { + type: "table", + namespace: "Namespace", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "Namespace", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "bytes32" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs a table with a namespace override", () => { + const output = parseTable("Namespace", "SomeTable", { + namespace: "CustomNamespace", + valueSchema: "string", + } as const); + + const expectedOutput = { + type: "table", + namespace: "CustomNamespace", + name: "SomeTable", + tableId: resourceIdToHex({ type: "table", namespace: "CustomNamespace", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "string" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); + + it("outputs an offchain table", () => { + const output = parseTable("", "SomeTable", { offchainOnly: true, valueSchema: "string" } as const); + + const expectedOutput = { + type: "offchainTable", + namespace: "", + name: "SomeTable", + tableId: resourceIdToHex({ type: "offchainTable", namespace: "", name: "SomeTable" }), + keySchema: { key: "bytes32" }, + valueSchema: { value: "string" }, + } as const; + + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); +}); diff --git a/packages/store/ts/config/experimental/parseTable.ts b/packages/store/ts/config/experimental/parseTable.ts new file mode 100644 index 0000000000..66ac71ee77 --- /dev/null +++ b/packages/store/ts/config/experimental/parseTable.ts @@ -0,0 +1,79 @@ +import { SchemaAbiType, isSchemaAbiType } from "@latticexyz/schema-type"; +import { isPlainObject } from "./common"; +import { ParseKeySchemaInput, ParseKeySchemaOutput, parseKeySchema } from "./parseKeySchema"; +import { ParseValueSchemaInput, ParseValueSchemaOutput, parseValueSchema } from "./parseValueSchema"; +import { assertExhaustive } from "@latticexyz/common/utils"; +import { resourceIdToHex } from "@latticexyz/common"; + +/** @internal */ +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 +export type ParseTableInput = SchemaAbiType | TableShapeInput; + +export type ParseTableOutput< + defaultNamespace extends string, + name extends string, + input extends ParseTableInput +> = input extends SchemaAbiType + ? ParseTableOutput + : input extends TableShapeInput + ? { + 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 + >; + readonly 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; + +/** @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]))) + return false; + return true; +} + +export function parseTable( + defaultNamespace: defaultNamespace, + name: name, + input: input +): ParseTableOutput { + return ( + isSchemaAbiType(input) + ? parseTable(defaultNamespace, name, { valueSchema: input }) + : isTableShapeInput(input) + ? { + type: input.offchainOnly === true ? "offchainTable" : "table", + namespace: input.namespace ?? defaultNamespace, + name, + tableId: resourceIdToHex({ + type: input.offchainOnly === true ? "offchainTable" : "table", + namespace: input.namespace ?? defaultNamespace, + name, + }), + keySchema: parseKeySchema(input.keySchema), + valueSchema: parseValueSchema(input.valueSchema), + } + : assertExhaustive(input, "invalid table input") + ) as ParseTableOutput; +} diff --git a/packages/store/ts/config/experimental/parseTables.ts b/packages/store/ts/config/experimental/parseTables.ts new file mode 100644 index 0000000000..f1b2bb8af4 --- /dev/null +++ b/packages/store/ts/config/experimental/parseTables.ts @@ -0,0 +1,20 @@ +import { ParseTableInput, ParseTableOutput, parseTable } from "./parseTable"; + +export type ParseTablesInput = { readonly [k: string]: ParseTableInput }; + +export type ParseTablesOutput = { + readonly [name in keyof input]: ParseTableOutput< + input["namespace"] extends string ? input["namespace"] : defaultNamespace, + name & string, + input[name] + >; +}; + +export function parseTables( + defaultNamespace: defaultNamespace, + input: input +): ParseTablesOutput { + return Object.fromEntries( + Object.entries(input).map(([name, tableInput]) => [name, parseTable(defaultNamespace, name, tableInput)]) + ) as ParseTablesOutput; +} diff --git a/packages/store/ts/config/experimental/parseValueSchema.test.ts b/packages/store/ts/config/experimental/parseValueSchema.test.ts new file mode 100644 index 0000000000..1ec2053e52 --- /dev/null +++ b/packages/store/ts/config/experimental/parseValueSchema.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; +import { parseValueSchema } from "./parseValueSchema"; + +describe("parseValueSchema", () => { + it("outputs a key schema for uint8", () => { + 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 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 output = parseValueSchema({ x: "uint32", y: "uint32" } as const); + const expectedOutput = { x: "uint32", y: "uint32" } as const; + expect(output).toStrictEqual(output); + expectTypeOf(output).toEqualTypeOf(expectedOutput); + expectTypeOf(output).toMatchTypeOf(expectedOutput); + }); +}); diff --git a/packages/store/ts/config/experimental/parseValueSchema.ts b/packages/store/ts/config/experimental/parseValueSchema.ts new file mode 100644 index 0000000000..8d8a4df669 --- /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 [k: string]: SchemaAbiType }; + +export type ParseValueSchemaInput = SchemaAbiType | ValueSchema; + +export type ParseValueSchemaOutput = input extends SchemaAbiType + ? { readonly value: input } + : input extends ValueSchema + ? input + : never; + +export function parseValueSchema(input: input): ParseValueSchemaOutput { + return (isSchemaAbiType(input) ? { value: input } : input) as ParseValueSchemaOutput; +}