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
Changes from 4 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
@@ -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";
@@ -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");
15 changes: 7 additions & 8 deletions examples/minimal/packages/contracts/mud.config.ts
Original file line number Diff line number Diff line change
@@ -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.

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 { StoreConfig, Table, resolveConfig } from "@latticexyz/store";
import { debug } from "./debug";
import { World as RecsWorld, getComponentValue, hasComponent, removeComponent, setComponent } from "@latticexyz/recs";
import { defineInternalComponents } from "./defineInternalComponents";
@@ -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),
};

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,
});
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
@@ -55,6 +55,7 @@
"@latticexyz/config": "workspace:*",
"@latticexyz/schema-type": "workspace:*",
"abitype": "0.9.8",
"viem": "1.14.0",
"zod": "^3.21.4"
},
"devDependencies": {
Loading
Loading