Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(store,world): usable enum values from config #2807

Merged
merged 11 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 `mappedEnums`.

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

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

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

This should allow for easier referencing of enum elements in contract calls.

```ts
writeContract({
// …
functionName: "setTerrainType",
args: [config.mappedEnums.TerrainType.Grass],
Copy link
Member Author

@holic holic May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
args: [config.mappedEnums.TerrainType.Grass],
args: [config.enumValues.TerrainType.Grass],

I can't decide which 🤔

decided on enumValues

});
```
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 MappedEnums = {
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 mappedEnums: MappedEnums;
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: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -96,6 +97,7 @@ describe("defineStore", () => {
dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -141,6 +143,7 @@ describe("defineStore", () => {
},
userTypes: {},
enums: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -213,6 +216,7 @@ describe("defineStore", () => {
},
userTypes: {},
enums: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -292,6 +296,7 @@ describe("defineStore", () => {
Dynamic: { type: "string", filePath: "path/to/file" },
},
enums: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -403,6 +408,12 @@ describe("defineStore", () => {
enums: {
ValidNames: ["first", "second"],
},
mappedEnums: {
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 }).mappedEnums).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 mappedEnums: "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 ?? {}),
mappedEnums: 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: {},
mappedEnums: {},
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: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -108,6 +110,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: {},
enums: {},
mappedEnums: {},
namespace: "",
codegen: CODEGEN_DEFAULTS,
} as const;
Expand Down Expand Up @@ -148,6 +151,7 @@ describe("defineStoreWithShorthands", () => {
},
userTypes: {},
enums: {},
mappedEnums: {},
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: {},
mappedEnums: {},
namespace: "",
} as const;

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

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

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

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

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

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

Expand Down
Loading
Loading