Skip to content

Commit

Permalink
improvements and hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
dk1a committed Jun 15, 2023
1 parent c8d9368 commit a3ef8ce
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 51 deletions.
2 changes: 2 additions & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@
"esbuild": "^0.17.15",
"ethers": "^5.7.2",
"find-up": "^6.3.0",
"tapable": "^2.2.1",
"zod": "^3.21.4",
"zod-validation-error": "^1.3.0"
},
"devDependencies": {
"@types/tapable": "^2.2.3",
"tsup": "^6.7.0"
},
"gitHead": "914a1e0ae4a573d685841ca2ea921435057deb8f"
Expand Down
11 changes: 8 additions & 3 deletions packages/config/src/library/core.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { MergeReturnType, UnionToIntersection } from "@latticexyz/common/type-utils";
import { Plugins } from "./types";
import { MudPlugin, Plugins } from "./types";

// Helper type to infer the input types from a plugins config as union (InputA | InputB)
type PluginsInput<P extends Plugins> = Parameters<P[keyof P]["expandConfig"]>[0];
type PluginsInput<P extends Plugins> = Parameters<P[keyof P]["expandConfig"]>[1];

// Infer the plugin input types as intersection (InputA & InputB)
export type MergedPluginsInput<P extends Plugins> = UnionToIntersection<PluginsInput<P>>;

// Helper to strongly type a plugin definition
export function defineMUDPlugin<P extends MudPlugin>(plugin: P): P {
return plugin;
}

/**
* Helper function to sequentially apply `expandConfig` of each plugin and strongly type the result.
*/
Expand All @@ -16,7 +21,7 @@ export function mudCoreConfig<P extends Plugins, C extends Omit<MergedPluginsInp
): MergeReturnType<P[keyof P]["expandConfig"]> {
let expanded = config as any;
for (const { expandConfig } of Object.values(plugins)) {
expanded = { ...expanded, ...expandConfig(config) };
expanded = { ...expanded, ...expandConfig(plugins, config) };
}
return expanded;
}
9 changes: 6 additions & 3 deletions packages/config/src/library/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { Hook } from "tapable";

/*
* Every plugin defines an `Input`, an `Expanded<T = Input>` type,
* and a `expandConfig` function to map from `Input` to `Expanded`.
* Do distinguish plugins from each other in TypeScript, they also define a unique `id` string.
* To distinguish plugins from each other in TypeScript, they also define a unique `id` string.
*/
export interface MudPlugin<Input = any, Expanded = any> {
id: string;
expandConfig: <C extends Input>(config: C) => Expanded;
expandConfig: <C extends Input>(plugins: Plugins, config: C) => Expanded;
hooks: Record<string, Hook<unknown, unknown>>;
}

/**
* The core config only expects a map of plugins.
* The config is later expanded by calling the expandConfig method of each
* plugin in order of appearance in the map. We use a map instead of an array,
* because it makes it easier to type check for the existence of expected
* plugins in the map. (Object keys order is guaranteed since ES2015, see
* plugins in the map. Object keys order is guaranteed since ES2015, see
* https://www.stefanjudis.com/today-i-learned/property-order-is-predictable-in-javascript-objects-since-es2015/
*/
export type Config = { plugins: Plugins };
Expand Down
2 changes: 2 additions & 0 deletions packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@
"@latticexyz/config": "workspace:*",
"@latticexyz/schema-type": "workspace:*",
"ethers": "^5.7.2",
"tapable": "^2.2.1",
"zod": "^3.21.4"
},
"devDependencies": {
"@typechain/ethers-v5": "^10.2.0",
"@types/ejs": "^3.1.1",
"@types/mocha": "^9.1.1",
"@types/node": "^18.15.11",
"@types/tapable": "^2.2.3",
"ds-test": "https://github.com/dapphub/ds-test.git#c9ce3f25bde29fc5eb9901842bf02850dfd2d084",
"ejs": "^3.1.8",
"forge-std": "https://github.com/foundry-rs/forge-std.git#b4f121555729b3afb3c5ffccb62ff4b6e2818fd3",
Expand Down
83 changes: 49 additions & 34 deletions packages/store/ts/library/config/mudConfig.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { describe, expectTypeOf } from "vitest";
import { mudConfig } from ".";
import { mudConfig, TABLE_DEFAULTS } from ".";
import { storePlugin } from "./plugin";

/*
describe("mudConfig", () => {
// Test possible inference confusion.
// This would fail if you remove `AsDependent` from `MUDUserConfig`
expectTypeOf<
ReturnType<
typeof mudConfig<
type AutoExpandedConfig = ReturnType<
typeof mudConfig<
{ storePlugin: typeof storePlugin },
{
plugins: { storePlugin: typeof storePlugin },
tables: {
Table1: {
keySchema: {
Expand All @@ -33,32 +27,53 @@ describe("mudConfig", () => {
};
}
>
>
>().toEqualTypeOf<{
plugins: { storePlugin: typeof storePlugin },
enums: {
Enum1: ["E1"];
Enum2: ["E1"];
};
tables: {
Table1: {
keySchema: {
a: "Enum1";
};
schema: {
b: "Enum2";
};
>;

type ManuallyExpandedConfig = {
enums: {
Enum1: ["E1"];
Enum2: ["E1"];
};
tables: {
Table1: {
keySchema: {
a: "Enum1";
};
Table2: {
schema: {
a: "uint32";
};
schema: {
b: "Enum2";
};
directory: typeof TABLE_DEFAULTS.directory;
name: "Table1";
tableIdArgument: typeof TABLE_DEFAULTS.tableIdArgument;
storeArgument: typeof TABLE_DEFAULTS.storeArgument;
dataStruct: boolean;
ephemeral: typeof TABLE_DEFAULTS.ephemeral;
};
namespace: "";
storeImportPath: "@latticexyz/store/src/";
userTypesPath: "Types";
codegenDirectory: "codegen";
}>();
Table2: {
schema: {
a: "uint32";
};
directory: typeof TABLE_DEFAULTS.directory;
name: "Table2";
tableIdArgument: typeof TABLE_DEFAULTS.tableIdArgument;
storeArgument: typeof TABLE_DEFAULTS.storeArgument;
dataStruct: boolean;
keySchema: typeof TABLE_DEFAULTS.keySchema;
ephemeral: typeof TABLE_DEFAULTS.ephemeral;
};
};
namespace: "";
storeImportPath: "@latticexyz/store/src/";
userTypesPath: "Types";
codegenDirectory: "codegen";
};

const _test1: AutoExpandedConfig = {} as ManuallyExpandedConfig;
// TODO fix weak types
const _test2: ManuallyExpandedConfig = {} as AutoExpandedConfig;

describe("mudConfig", () => {
// Test possible inference confusion.
// This would fail if you remove `AsDependent` from `MUDUserConfig`
expectTypeOf<AutoExpandedConfig>().toEqualTypeOf<ManuallyExpandedConfig>();
});
*/
13 changes: 9 additions & 4 deletions packages/store/ts/library/config/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { fromZodErrorCustom, MudPlugin } from "@latticexyz/config";
import { defineMUDPlugin, fromZodErrorCustom, Plugins } from "@latticexyz/config";
import { SyncHook } from "tapable";
import { ZodError } from "zod";
import { ExpandStoreUserConfig, StoreUserConfig, zPluginStoreConfig } from "./storeConfig";

export function expandConfig<C extends StoreUserConfig>(config: C) {
export function expandConfig<C extends StoreUserConfig>(plugins: Plugins, config: C) {
// This function gets called within mudConfig.
// The call order of config extenders depends on the order of their imports.
// Any config validation and transformation should be placed here.
Expand All @@ -17,7 +18,11 @@ export function expandConfig<C extends StoreUserConfig>(config: C) {
}
}

export const storePlugin = {
export const storePlugin = defineMUDPlugin({
id: "mud-store-plugin",
expandConfig,
} as const satisfies MudPlugin;
hooks: {
preTablegen: new SyncHook(["mudConfig"]),
postTablegen: new SyncHook(["mudConfig"]),
},
});
19 changes: 13 additions & 6 deletions packages/store/ts/library/config/storeConfig.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { MergedPluginsInput } from "@latticexyz/config";
import { describe, expectTypeOf } from "vitest";
import { z } from "zod";
import { zStoreConfig, MUDUserConfig } from "./storeConfig";
/*
import { storePlugin } from "./plugin";
import { zStoreConfig, StoreUserConfig } from "./storeConfig";

describe("StoreUserConfig", () => {
// Check that the plugin uses the correct type
expectTypeOf<
Omit<MergedPluginsInput<{ storePlugin: typeof storePlugin }>, "plugins">
>().toEqualTypeOf<StoreUserConfig>();

// Typecheck manual interfaces against zod
expectTypeOf<MUDUserConfig>().toEqualTypeOf<z.input<typeof zStoreConfig>>();
expectTypeOf<StoreUserConfig>().toEqualTypeOf<z.input<typeof zStoreConfig>>();

// type equality isn't deep for optionals
expectTypeOf<MUDUserConfig["tables"][string]>().toEqualTypeOf<z.input<typeof zStoreConfig>["tables"][string]>();
expectTypeOf<NonNullable<MUDUserConfig["enums"]>[string]>().toEqualTypeOf<
expectTypeOf<StoreUserConfig["tables"][string]>().toEqualTypeOf<z.input<typeof zStoreConfig>["tables"][string]>();
expectTypeOf<NonNullable<StoreUserConfig["enums"]>[string]>().toEqualTypeOf<
NonNullable<NonNullable<z.input<typeof zStoreConfig>>["enums"]>[string]
>();
// TODO If more nested schemas are added, provide separate tests for them
});*/
});
24 changes: 23 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a3ef8ce

Please sign in to comment.