Skip to content

Commit

Permalink
feat(store,world): usable enum values from config (#2807)
Browse files Browse the repository at this point in the history
Co-authored-by: alvarius <alvarius@lattice.xyz>
  • Loading branch information
holic and alvrs committed May 14, 2024
1 parent f03a179 commit 27f888c
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 16 deletions.
30 changes: 30 additions & 0 deletions .changeset/sweet-lemons-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"@latticexyz/store": patch
"@latticexyz/world": patch
---

`defineStore` and `defineWorld` now maps your `enums` to usable, strongly-typed enums on `enumValues`.

```ts
const config = defineStore({
enums: {
TerrainType: ["Water", "Grass", "Sand"],
},
});

config.enumValues.TerrainType.Water;
// ^? (property) Water: 0

config.enumValues.TerrainType.Grass;
// ^? (property) Grass: 1
```

This allows for easier referencing of enum values (i.e. `uint8` equivalent) in contract calls.

```ts
writeContract({
//
functionName: "setTerrainType",
args: [config.enumValues.TerrainType.Grass],
});
```
31 changes: 26 additions & 5 deletions packages/store/ts/config/v2/enums.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Enums } from "./output";
import { flatMorph } from "@arktype/util";
import { EnumsInput } from "./input";
import { AbiTypeScope, extendScope } from "./scope";
import { parseNumber } from "./generics";

function isEnums(enums: unknown): enums is Enums {
function isEnums(enums: unknown): enums is EnumsInput {
return (
typeof enums === "object" &&
enums != null &&
Object.values(enums).every((item) => Array.isArray(item) && item.every((element) => typeof element === "string"))
);
}

export type scopeWithEnums<enums, scope extends AbiTypeScope = AbiTypeScope> = Enums extends enums
export type scopeWithEnums<enums, scope extends AbiTypeScope = AbiTypeScope> = EnumsInput extends enums
? scope
: enums extends Enums
: enums extends EnumsInput
? extendScope<scope, { [key in keyof enums]: "uint8" }>
: scope;

Expand All @@ -26,4 +28,23 @@ export function scopeWithEnums<enums, scope extends AbiTypeScope = AbiTypeScope>
return scope as never;
}

export type resolveEnums<enums> = { readonly [key in keyof enums]: Readonly<enums[key]> };
export type resolveEnums<enums> = {
readonly [key in keyof enums]: Readonly<enums[key]>;
};

export function resolveEnums<enums extends EnumsInput>(enums: enums): resolveEnums<enums> {
return enums;
}

export type mapEnums<enums> = {
readonly [key in keyof enums]: {
readonly [element in keyof enums[key] as enums[key][element] & string]: parseNumber<element>;
};
};

export function mapEnums<enums extends EnumsInput>(enums: enums): resolveEnums<enums> {
return flatMorph(enums as EnumsInput, (enumName, enumElements) => [
enumName,
flatMorph(enumElements, (enumIndex, enumElement) => [enumElement, enumIndex]),
]) as never;
}
2 changes: 2 additions & 0 deletions packages/store/ts/config/v2/generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ export function mergeIfUndefined<base extends object, merged extends object>(
allKeys.map((key) => [key, base[key as keyof base] ?? merged[key as keyof merged]]),
) as never;
}

export type parseNumber<T> = T extends `${infer N extends number}` ? N : never;
8 changes: 6 additions & 2 deletions packages/store/ts/config/v2/input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Hex } from "viem";
import { Codegen, Enums, TableCodegen, TableDeploy, UserTypes } from "./output";
import { Codegen, TableCodegen, TableDeploy, UserTypes } from "./output";
import { Scope } from "./scope";
import { evaluate } from "@arktype/util";

export type EnumsInput = {
readonly [enumName: string]: readonly [string, ...string[]];
};

export type SchemaInput = {
readonly [key: string]: string;
};
Expand Down Expand Up @@ -30,7 +34,7 @@ export type StoreInput = {
readonly namespace?: string;
readonly tables?: TablesInput;
readonly userTypes?: UserTypes;
readonly enums?: Enums;
readonly enums?: EnumsInput;
readonly codegen?: Partial<Codegen>;
};

Expand Down
15 changes: 11 additions & 4 deletions packages/store/ts/config/v2/output.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { evaluate } from "@arktype/util";
import { AbiType, Schema, Table as BaseTable } from "@latticexyz/config";
import { EnumsInput } from "./input";

export type { AbiType, Schema };

export type UserTypes = {
readonly [userTypeName: string]: { type: AbiType; filePath: string };
readonly [userTypeName: string]: {
readonly type: AbiType;
readonly filePath: string;
};
};

export type Enums = {
readonly [enumName: string]: readonly [string, ...string[]];
export type EnumValues = {
readonly [enumName: string]: {
readonly [enumElement: string]: number;
};
};

export type TableCodegen = {
Expand Down Expand Up @@ -41,7 +47,8 @@ export type Store = {
readonly [namespacedTableName: string]: Table;
};
readonly userTypes: UserTypes;
readonly enums: Enums;
readonly enums: EnumsInput;
readonly enumValues: EnumValues;
readonly namespace: string;
readonly codegen: Codegen;
};
22 changes: 21 additions & 1 deletion packages/store/ts/config/v2/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe("defineStore", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -96,6 +97,7 @@ describe("defineStore", () => {
dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -141,6 +143,7 @@ describe("defineStore", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -213,6 +216,7 @@ describe("defineStore", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -292,6 +296,7 @@ describe("defineStore", () => {
Dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -403,6 +408,12 @@ describe("defineStore", () => {
enums: {
ValidNames: ["first", "second"],
},
enumValues: {
ValidNames: {
first: 0,
second: 1,
},
},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -501,7 +512,16 @@ describe("defineStore", () => {
Example: ["First", "Second"],
} as const;

attest(defineStore({ enums }).enums).equals(enums);
attest(defineStore({ enums }).enums).equals({
Example: ["First", "Second"],
});

attest(defineStore({ enums }).enumValues).equals({
Example: {
First: 0,
Second: 1,
},
});
});

it("should allow a const config as input", () => {
Expand Down
10 changes: 6 additions & 4 deletions packages/store/ts/config/v2/store.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ErrorMessage, flatMorph, narrow } from "@arktype/util";
import { ErrorMessage, evaluate, flatMorph, narrow } from "@arktype/util";
import { get, hasOwnKey, mergeIfUndefined } from "./generics";
import { UserTypes } from "./output";
import { CONFIG_DEFAULTS } from "./defaults";
import { StoreInput } from "./input";
import { resolveTables, validateTables } from "./tables";
import { scopeWithUserTypes, validateUserTypes } from "./userTypes";
import { resolveEnums, scopeWithEnums } from "./enums";
import { mapEnums, resolveEnums, scopeWithEnums } from "./enums";
import { resolveCodegen } from "./codegen";

export type extendedScope<input> = scopeWithEnums<get<input, "enums">, scopeWithUserTypes<get<input, "userTypes">>>;
Expand Down Expand Up @@ -56,7 +56,8 @@ export type resolveStore<store> = {
>
: {};
readonly userTypes: "userTypes" extends keyof store ? store["userTypes"] : {};
readonly enums: "enums" extends keyof store ? resolveEnums<store["enums"]> : {};
readonly enums: "enums" extends keyof store ? evaluate<resolveEnums<store["enums"]>> : {};
readonly enumValues: "enums" extends keyof store ? evaluate<mapEnums<store["enums"]>> : {};
readonly namespace: "namespace" extends keyof store ? store["namespace"] : CONFIG_DEFAULTS["namespace"];
readonly codegen: "codegen" extends keyof store ? resolveCodegen<store["codegen"]> : resolveCodegen<{}>;
};
Expand All @@ -71,7 +72,8 @@ export function resolveStore<const store extends StoreInput>(store: store): reso
extendedScope(store),
),
userTypes: store.userTypes ?? {},
enums: store.enums ?? {},
enums: resolveEnums(store.enums ?? {}),
enumValues: mapEnums(store.enums ?? {}),
namespace: store.namespace ?? CONFIG_DEFAULTS["namespace"],
codegen: resolveCodegen(store.codegen),
} as never;
Expand Down
4 changes: 4 additions & 0 deletions packages/store/ts/config/v2/storeWithShorthands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -68,6 +69,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: { CustomType: { type: "address", filePath: "path/to/file" } },
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -108,6 +110,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -148,6 +151,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down
18 changes: 18 additions & 0 deletions packages/world/ts/config/v2/world.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe("defineWorld", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
} as const;

Expand Down Expand Up @@ -128,6 +129,12 @@ describe("defineWorld", () => {
enums: {
MyEnum: ["First", "Second"],
},
enumValues: {
MyEnum: {
First: 0,
Second: 1,
},
},
namespace: "",
} as const;

Expand Down Expand Up @@ -227,6 +234,7 @@ describe("defineWorld", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
} as const;

Expand Down Expand Up @@ -279,6 +287,7 @@ describe("defineWorld", () => {
dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
enumValues: {},
namespace: "",
} as const;

Expand Down Expand Up @@ -324,6 +333,7 @@ describe("defineWorld", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
deploy: DEPLOY_DEFAULTS,
} as const;
Expand Down Expand Up @@ -397,6 +407,7 @@ describe("defineWorld", () => {
},
userTypes: {},
enums: {},
enumValues: {},
namespace: "",
} as const;

Expand Down Expand Up @@ -477,6 +488,7 @@ describe("defineWorld", () => {
Dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
enumValues: {},
namespace: "",
} as const;

Expand Down Expand Up @@ -589,6 +601,12 @@ describe("defineWorld", () => {
enums: {
ValidNames: ["first", "second"],
},
enumValues: {
ValidNames: {
first: 0,
second: 1,
},
},
namespace: "",
} as const;

Expand Down

0 comments on commit 27f888c

Please sign in to comment.