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-sync): extra table definitions #1840

Merged
merged 8 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
17 changes: 15 additions & 2 deletions examples/minimal/packages/client-react/src/mud/setupNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs";
import { getNetworkConfig } from "./getNetworkConfig";
import { world } from "./world";
import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json";
import { ContractWrite, createBurnerAccount, getContract, transportObserver } from "@latticexyz/common";
import { ContractWrite, createBurnerAccount, getContract, resourceToHex, transportObserver } from "@latticexyz/common";
import { Subject, share } from "rxjs";
import mudConfig from "contracts/mud.config";
import { createClient as createFaucetClient } from "@latticexyz/faucet";
Expand Down Expand Up @@ -40,10 +40,23 @@ export async function setupNetwork() {
const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({
world,
config: mudConfig,
tables: {
Comment on lines 42 to +43
Copy link
Member

Choose a reason for hiding this comment

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

so syncToRecs syncs all tables passed mudConfig or in tables correct? What in mudConfig do we need in here besides the resolved tables? If it's only that, should we just pass a tables key (ie syncToRecs({ tables: { ...mudConfig.tables, ...additionalTables } }))? (mostly wondering about longer term when the config is resolved by default, fine to not do that here yet to avoid breaking changes)

Copy link
Member Author

Choose a reason for hiding this comment

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

long term I think we should do that, yep! I just didn't want to encourage the intermediate step of using the experimental resolveConfig in user land

KeysWithValue: {
namespace: "keywval",
name: "Inventory",
tableId: resourceToHex({ type: "table", namespace: "keywval", name: "Inventory" }),
keySchema: {
valueHash: { type: "bytes32" },
},
valueSchema: {
keysWithValue: { type: "bytes32[]" },
},
},
},
address: networkConfig.worldAddress as Hex,
publicClient,
startBlock: BigInt(networkConfig.initialBlockNumber),
});
} as const);
Copy link
Member

Choose a reason for hiding this comment

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

what would break without as const here? Does this break anything for current users who don't have as const yet?

Copy link
Member Author

Choose a reason for hiding this comment

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

the manually defined tables were missing strong types for namespace/name without this


try {
console.log("creating faucet client");
Expand Down
15 changes: 7 additions & 8 deletions examples/minimal/packages/contracts/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ export default mudConfig({
valueSchema: { amount: "uint32" },
},
},
// KeysWithValue doesn't seem to like singleton keys
// modules: [
// {
// name: "KeysWithValueModule",
// root: true,
// args: [resolveTableId("CounterTable")],
// },
// ],
modules: [
{
name: "KeysWithValueModule",
root: true,
args: [resolveTableId("Inventory")],
},
],
});
31 changes: 0 additions & 31 deletions packages/store-sync/src/recs/common.test-d.ts

This file was deleted.

27 changes: 3 additions & 24 deletions packages/store-sync/src/recs/common.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
import { StoreConfig } from "@latticexyz/store";
import { Component as RecsComponent, Metadata as RecsMetadata, Type as RecsType } from "@latticexyz/recs";
import { SchemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType";
import { SchemaAbiType } from "@latticexyz/schema-type";
import { Metadata } from "@latticexyz/recs";
import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser";

export type StoreComponentMetadata = RecsMetadata & {
export type StoreComponentMetadata = Metadata & {
componentName: string;
tableName: string;
// TODO: migrate to store's KeySchema/ValueSchema
keySchema: KeySchema;
valueSchema: ValueSchema;
};

export type ConfigToRecsComponents<TConfig extends StoreConfig> = {
[tableName in keyof TConfig["tables"] & string]: RecsComponent<
{
__staticData: RecsType.OptionalString;
__encodedLengths: RecsType.OptionalString;
__dynamicData: RecsType.OptionalString;
} & {
[fieldName in keyof TConfig["tables"][tableName]["valueSchema"] & string]: RecsType &
SchemaAbiTypeToRecsType<SchemaAbiType & TConfig["tables"][tableName]["valueSchema"][fieldName]>;
},
StoreComponentMetadata & {
componentName: tableName;
tableName: `${TConfig["namespace"]}:${tableName}`;
keySchema: TConfig["tables"][tableName]["keySchema"];
valueSchema: TConfig["tables"][tableName]["valueSchema"];
}
>;
};
45 changes: 0 additions & 45 deletions packages/store-sync/src/recs/configToRecsComponents.ts

This file was deleted.

8 changes: 5 additions & 3 deletions packages/store-sync/src/recs/recsStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { groupLogsByBlockNumber } from "@latticexyz/block-logs-stream";
import { StoreEventsLog } from "../common";
import { singletonEntity } from "./singletonEntity";
import { RpcLog, formatLog, decodeEventLog, Hex } from "viem";
import { storeEventsAbi } from "@latticexyz/store";
import { resolveConfig, storeEventsAbi } from "@latticexyz/store";

const tables = resolveConfig(mudConfig).tables;

// TODO: make test-data a proper package and export this
const blocks = groupLogsByBlockNumber(
Expand All @@ -25,15 +27,15 @@ const blocks = groupLogsByBlockNumber(
describe("recsStorage", () => {
it("creates components", async () => {
const world = createWorld();
const { components } = recsStorage({ world, config: mudConfig });
const { components } = recsStorage({ world, tables });
expect(components.NumberList.id).toMatchInlineSnapshot(
'"0x746200000000000000000000000000004e756d6265724c697374000000000000"'
);
});

it("sets component values from logs", async () => {
const world = createWorld();
const { storageAdapter, components } = recsStorage({ world, config: mudConfig });
const { storageAdapter, components } = recsStorage({ world, tables });

for (const block of blocks) {
await storageAdapter(block);
Expand Down
33 changes: 17 additions & 16 deletions packages/store-sync/src/recs/recsStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StoreConfig } from "@latticexyz/store";
import { Table, resolveConfig } from "@latticexyz/store";
import { debug } from "./debug";
import { World as RecsWorld, getComponentValue, hasComponent, removeComponent, setComponent } from "@latticexyz/recs";
import { defineInternalComponents } from "./defineInternalComponents";
Expand All @@ -9,39 +9,40 @@ import { Hex, size } from "viem";
import { isTableRegistrationLog } from "../isTableRegistrationLog";
import { logToTable } from "../logToTable";
import { hexKeyTupleToEntity } from "./hexKeyTupleToEntity";
import { ConfigToRecsComponents } from "./common";
import { StorageAdapter, StorageAdapterBlock } from "../common";
import { configToRecsComponents } from "./configToRecsComponents";
import { singletonEntity } from "./singletonEntity";
import storeConfig from "@latticexyz/store/mud.config";
import worldConfig from "@latticexyz/world/mud.config";
import { TablesToComponents, tablesToComponents } from "./tablesToComponents";

export type RecsStorageOptions<TConfig extends StoreConfig = StoreConfig> = {
const storeTables = resolveConfig(storeConfig).tables;
const worldTables = resolveConfig(worldConfig).tables;

export type RecsStorageOptions<tables extends Record<string, Table>> = {
world: RecsWorld;
// TODO: make config optional?
config: TConfig;
tables: tables;
shouldSkipUpdateStream?: () => boolean;
};

export type RecsStorageAdapter<TConfig extends StoreConfig = StoreConfig> = {
export type RecsStorageAdapter<tables extends Record<string, Table>> = {
storageAdapter: StorageAdapter;
Comment on lines -26 to +27
Copy link
Member

Choose a reason for hiding this comment

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

I'm still not used to tables instead of TTables 😭 Don't you find it confusing not to see on first sight whether you have to use typeof tables vs just tables? I'm just so used to custom types being uppercase and only primitive types being lowercase

components: ConfigToRecsComponents<TConfig> &
ConfigToRecsComponents<typeof storeConfig> &
ConfigToRecsComponents<typeof worldConfig> &
components: TablesToComponents<tables> &
TablesToComponents<typeof storeTables> &
TablesToComponents<typeof worldTables> &
ReturnType<typeof defineInternalComponents>;
};

export function recsStorage<TConfig extends StoreConfig = StoreConfig>({
export function recsStorage<tables extends Record<string, Table>>({
world,
config,
tables,
shouldSkipUpdateStream,
}: RecsStorageOptions<TConfig>): RecsStorageAdapter<TConfig> {
}: RecsStorageOptions<tables>): RecsStorageAdapter<tables> {
world.registerEntity({ id: singletonEntity });

const components = {
...configToRecsComponents(world, config),
...configToRecsComponents(world, storeConfig),
...configToRecsComponents(world, worldConfig),
...tablesToComponents(world, tables),
...tablesToComponents(world, storeTables),
...tablesToComponents(world, worldTables),
...defineInternalComponents(world),
};

Expand Down
23 changes: 15 additions & 8 deletions packages/store-sync/src/recs/syncToRecs.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import { StoreConfig } from "@latticexyz/store";
import { StoreConfig, Table, ResolvedStoreConfig, resolveConfig } from "@latticexyz/store";
import { Component as RecsComponent, World as RecsWorld, getComponentValue, setComponent } from "@latticexyz/recs";
import { SyncOptions, SyncResult } from "../common";
import { RecsStorageAdapter, recsStorage } from "./recsStorage";
import { createStoreSync } from "../createStoreSync";
import { singletonEntity } from "./singletonEntity";
import { SyncStep } from "../SyncStep";

type SyncToRecsOptions<TConfig extends StoreConfig = StoreConfig> = SyncOptions<TConfig> & {
type SyncToRecsOptions<config extends StoreConfig, extraTables extends Record<string, Table>> = SyncOptions<config> & {
world: RecsWorld;
config: TConfig;
config: config;
tables?: extraTables;
startSync?: boolean;
};

type SyncToRecsResult<TConfig extends StoreConfig = StoreConfig> = SyncResult & {
components: RecsStorageAdapter<TConfig>["components"];
type SyncToRecsResult<config extends StoreConfig, extraTables extends Record<string, Table>> = SyncResult & {
components: RecsStorageAdapter<ResolvedStoreConfig<config>["tables"] & extraTables>["components"];
stopSync: () => void;
};

export async function syncToRecs<TConfig extends StoreConfig = StoreConfig>({
export async function syncToRecs<config extends StoreConfig, extraTables extends Record<string, Table>>({
world,
config,
tables: extraTables,
startSync = true,
...syncOptions
}: SyncToRecsOptions<TConfig>): Promise<SyncToRecsResult<TConfig>> {
}: SyncToRecsOptions<config, extraTables>): Promise<SyncToRecsResult<config, extraTables>> {
const tables = {
...resolveConfig(config).tables,
...extraTables,
} as ResolvedStoreConfig<config>["tables"] & extraTables;

const { storageAdapter, components } = recsStorage({
world,
config,
tables,
shouldSkipUpdateStream: (): boolean =>
getComponentValue(components.SyncProgress, singletonEntity)?.step !== SyncStep.LIVE,
});
Expand Down
49 changes: 49 additions & 0 deletions packages/store-sync/src/recs/tableToComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, Type, World, defineComponent } from "@latticexyz/recs";
import { StoreComponentMetadata } from "./common";
import { SchemaAbiTypeToRecsType, schemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType";
import { SchemaAbiType } from "@latticexyz/schema-type";
import { Table } from "@latticexyz/store";
import { mapObject } from "@latticexyz/common/utils";

export type TableToComponent<table extends Table> = Component<
{
__staticData: Type.OptionalString;
__encodedLengths: Type.OptionalString;
__dynamicData: Type.OptionalString;
} & {
[fieldName in keyof table["valueSchema"] & string]: Type &
SchemaAbiTypeToRecsType<SchemaAbiType & table["valueSchema"][fieldName]["type"]>;
},
StoreComponentMetadata & {
componentName: table["name"];
tableName: `${table["namespace"]}:${table["name"]}`;
keySchema: { [name in keyof table["keySchema"] & string]: table["keySchema"][name]["type"] };
valueSchema: { [name in keyof table["valueSchema"] & string]: table["valueSchema"][name]["type"] };
}
>;

export function tableToComponent<table extends Table>(world: World, table: table): TableToComponent<table> {
return defineComponent(
world,
{
...Object.fromEntries(
Object.entries(table.valueSchema).map(([fieldName, { type: schemaAbiType }]) => [
fieldName,
schemaAbiTypeToRecsType[schemaAbiType as SchemaAbiType],
])
),
__staticData: Type.OptionalString,
__encodedLengths: Type.OptionalString,
__dynamicData: Type.OptionalString,
},
{
id: table.tableId,
metadata: {
componentName: table.name,
tableName: `${table.namespace}:${table.name}`,
keySchema: mapObject(table.keySchema, ({ type }) => type),
valueSchema: mapObject(table.valueSchema, ({ type }) => type),
},
}
) as TableToComponent<table>;
}
15 changes: 15 additions & 0 deletions packages/store-sync/src/recs/tablesToComponents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Table } from "@latticexyz/store";
import { TableToComponent, tableToComponent } from "./tableToComponent";
import { mapObject } from "@latticexyz/common/utils";
import { World } from "@latticexyz/recs";

export type TablesToComponents<tables extends Record<string, Table>> = {
[tableName in keyof tables]: TableToComponent<tables[tableName]>;
};

export function tablesToComponents<tables extends Record<string, Table>>(
world: World,
tables: tables
): TablesToComponents<tables> {
return mapObject(tables, (table) => tableToComponent(world, table));
}
1 change: 1 addition & 0 deletions packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@latticexyz/config": "workspace:*",
"@latticexyz/schema-type": "workspace:*",
"abitype": "0.9.8",
"viem": "1.14.0",
"zod": "^3.21.4"
},
"devDependencies": {
Expand Down
13 changes: 12 additions & 1 deletion packages/store/ts/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-type";
import { SchemaAbiType, SchemaAbiTypeToPrimitiveType, StaticAbiType } from "@latticexyz/schema-type";
import { FieldData, FullSchemaConfig, StoreConfig } from "./config";
import { Hex } from "viem";

export type KeySchema = Record<string, { type: StaticAbiType }>;
export type ValueSchema = Record<string, { type: SchemaAbiType }>;
export type Table = {
tableId: Hex;
namespace: string;
name: string;
keySchema: KeySchema;
valueSchema: ValueSchema;
};

export type ConfigFieldTypeToSchemaAbiType<T extends FieldData<string>> = T extends SchemaAbiType
? T
Expand Down
Loading
Loading