Skip to content

Commit

Permalink
feat(store-sync): additional table definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
holic committed Oct 23, 2023
1 parent 71264b9 commit b40e135
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 75 deletions.
22 changes: 21 additions & 1 deletion examples/minimal/packages/client-react/src/mud/setupNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ 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,
hexToResource,
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,6 +47,19 @@ export async function setupNetwork() {
const { components, latestBlock$, storedBlockLogs$, waitForTransaction } = await syncToRecs({
world,
config: mudConfig,
tables: {
KeysWithValue: {
namespace: "keywval",
name: "Inventory",
tableId: resourceToHex({ type: "table", namespace: "keywval", name: "Inventory" }),
keySchema: {
valueHash: "bytes32",
},
valueSchema: {
keysWithValue: "bytes32[]",
},
},
},
address: networkConfig.worldAddress as Hex,
publicClient,
startBlock: BigInt(networkConfig.initialBlockNumber),
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")],
},
],
});
48 changes: 31 additions & 17 deletions packages/store-sync/src/recs/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Component as RecsComponent, Metadata as RecsMetadata, Type as RecsType
import { SchemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType";
import { SchemaAbiType } from "@latticexyz/schema-type";
import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser";
import { Table } from "../common";
import { Hex } from "viem";

export type StoreComponentMetadata = RecsMetadata & {
componentName: string;
Expand All @@ -11,21 +13,33 @@ export type StoreComponentMetadata = RecsMetadata & {
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"];
}
>;
export type TableToRecsComponent<table extends Omit<Table, "address">> = RecsComponent<
{
__staticData: RecsType.OptionalString;
__encodedLengths: RecsType.OptionalString;
__dynamicData: RecsType.OptionalString;
} & {
[fieldName in keyof table["valueSchema"] & string]: RecsType &
SchemaAbiTypeToRecsType<SchemaAbiType & table["valueSchema"][fieldName]>;
},
StoreComponentMetadata & {
componentName: table["name"];
tableName: `${table["namespace"]}:${table["name"]}`;
keySchema: table["keySchema"];
valueSchema: table["valueSchema"];
}
>;

export type TablesToRecsComponents<tables extends Record<string, Omit<Table, "address">>> = {
[tableName in keyof tables]: TableToRecsComponent<tables[tableName]>;
};

export type ConfigToRecsComponents<config extends StoreConfig> = {
[tableName in keyof config["tables"] & string]: TableToRecsComponent<{
tableId: Hex;
namespace: config["namespace"];
name: tableName;
keySchema: config["tables"][tableName]["keySchema"] & KeySchema;
valueSchema: config["tables"][tableName]["valueSchema"] & ValueSchema;
}>;
};
53 changes: 21 additions & 32 deletions packages/store-sync/src/recs/configToRecsComponents.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
import { StoreConfig } from "@latticexyz/store";
import { SchemaAbiType } from "@latticexyz/schema-type";
import { StoreConfig, resolveUserTypes } from "@latticexyz/store";
import { resourceToHex } from "@latticexyz/common";
import { World, defineComponent, Type } from "@latticexyz/recs";
import { World } from "@latticexyz/recs";
import { ConfigToRecsComponents } from "./common";
import { schemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType";
import { tableToRecsComponent } from "./tableToRecsComponent";
import { KeySchema } from "@latticexyz/protocol-parser";

export function configToRecsComponents<TConfig extends StoreConfig>(
world: World,
config: TConfig
): ConfigToRecsComponents<TConfig> {
const userTypes = {
...config.userTypes,
...Object.fromEntries(Object.entries(config.enums).map(([key]) => [key, { internalType: "uint8" }] as const)),
};

return Object.fromEntries(
Object.entries(config.tables).map(([tableName, table]) => [
tableName,
defineComponent(
world,
{
...Object.fromEntries(
Object.entries(table.valueSchema).map(([fieldName, schemaAbiType]) => [
fieldName,
schemaAbiTypeToRecsType[schemaAbiType as SchemaAbiType],
])
),
__staticData: Type.OptionalString,
__encodedLengths: Type.OptionalString,
__dynamicData: Type.OptionalString,
},
{
// TODO: support table namespaces https://github.com/latticexyz/mud/issues/994
id: resourceToHex({
type: table.offchainOnly ? "offchainTable" : "table",
namespace: config.namespace,
name: tableName,
}),
metadata: {
componentName: tableName,
tableName: `${config.namespace}:${tableName}`,
keySchema: table.keySchema,
valueSchema: table.valueSchema,
},
}
),
tableToRecsComponent(world, {
tableId: resourceToHex({
type: table.offchainOnly ? "offchainTable" : "table",
namespace: config.namespace,
name: table.name, // using the parsed config's `table.name` here rather than `tableName` key because that's what's used by codegen
}),
namespace: config.namespace,
name: tableName,
// TODO: refine to static ABI types so we don't have to cast
keySchema: resolveUserTypes(table.keySchema, userTypes) as KeySchema,
valueSchema: resolveUserTypes(table.valueSchema, userTypes),
}),
])
) as ConfigToRecsComponents<TConfig>;
}
39 changes: 29 additions & 10 deletions packages/store-sync/src/recs/recsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,60 @@ 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, TablesToRecsComponents } from "./common";
import { StorageAdapter, StorageAdapterBlock, Table } 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 { tableToRecsComponent } from "./tableToRecsComponent";

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

export type RecsStorageAdapter<TConfig extends StoreConfig = StoreConfig> = {
export type RecsStorageAdapter<
config extends StoreConfig,
tables extends Record<string, Omit<Table, "address">> | undefined
> = {
storageAdapter: StorageAdapter;
components: ConfigToRecsComponents<TConfig> &
components: ConfigToRecsComponents<config> &
(tables extends Record<string, Omit<Table, "address">>
? ConfigToRecsComponents<config> & TablesToRecsComponents<tables>
: ConfigToRecsComponents<config>) &
ConfigToRecsComponents<typeof storeConfig> &
ConfigToRecsComponents<typeof worldConfig> &
ReturnType<typeof defineInternalComponents>;
};

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

const components = {
...configToRecsComponents(world, config),
...(tables
? Object.fromEntries(
Object.entries(tables).map(([tableName, table]) => [tableName, tableToRecsComponent(world, table)])
)
: null),
...configToRecsComponents(world, storeConfig),
...configToRecsComponents(world, worldConfig),
...defineInternalComponents(world),
};
} as RecsStorageAdapter<config, tables>["components"];

async function recsStorageAdapter({ logs }: StorageAdapterBlock): Promise<void> {
const newTables = logs.filter(isTableRegistrationLog).map(logToTable);
Expand Down
26 changes: 19 additions & 7 deletions packages/store-sync/src/recs/syncToRecs.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
import { StoreConfig } from "@latticexyz/store";
import { Component as RecsComponent, World as RecsWorld, getComponentValue, setComponent } from "@latticexyz/recs";
import { SyncOptions, SyncResult } from "../common";
import { SyncOptions, SyncResult, Table } 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,
tables extends Record<string, Omit<Table, "address">> | undefined
> = SyncOptions<config> & {
world: RecsWorld;
config: TConfig;
config: config;
tables?: tables;
startSync?: boolean;
};

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

export async function syncToRecs<TConfig extends StoreConfig = StoreConfig>({
export async function syncToRecs<
config extends StoreConfig,
tables extends Record<string, Omit<Table, "address">> | undefined
>({
world,
config,
tables,
address,
publicClient,
startBlock,
maxBlockRange,
initialState,
indexerUrl,
startSync = true,
}: SyncToRecsOptions<TConfig>): Promise<SyncToRecsResult<TConfig>> {
}: SyncToRecsOptions<config, tables>): Promise<SyncToRecsResult<config, tables>> {
const { storageAdapter, components } = recsStorage({
world,
config,
tables,
shouldSkipUpdateStream: (): boolean =>
getComponentValue(components.SyncProgress, singletonEntity)?.step !== SyncStep.LIVE,
});
Expand Down
34 changes: 34 additions & 0 deletions packages/store-sync/src/recs/tableToRecsComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Type, World, defineComponent } from "@latticexyz/recs";
import { Table } from "../common";
import { TableToRecsComponent } from "./common";
import { schemaAbiTypeToRecsType } from "./schemaAbiTypeToRecsType";
import { SchemaAbiType } from "@latticexyz/schema-type";

export function tableToRecsComponent<table extends Omit<Table, "address">>(
world: World,
table: table
): TableToRecsComponent<table> {
return defineComponent(
world,
{
...Object.fromEntries(
Object.entries(table.valueSchema).map(([fieldName, 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: table.keySchema,
valueSchema: table.valueSchema,
},
}
) as TableToRecsComponent<table>;
}
13 changes: 13 additions & 0 deletions packages/store-sync/src/recs/tablesToRecsComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { World } from "@latticexyz/recs";
import { Table } from "../common";
import { TablesToRecsComponents } from "./common";
import { tableToRecsComponent } from "./tableToRecsComponent";

export function tablesToRecsComponents<tables extends Record<string, Omit<Table, "address">>>(
world: World,
tables: tables
): TablesToRecsComponents<tables> {
return Object.fromEntries(
Object.entries(tables).map(([tableName, table]) => [tableName, tableToRecsComponent(world, table)])
) as TablesToRecsComponents<tables>;
}

0 comments on commit b40e135

Please sign in to comment.