From e3de1a338fe110ac33ba9fb833366541e4cf4cf1 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 22 Aug 2023 02:36:00 -0700 Subject: [PATCH] chore: delete store-cache package (#1343) --- .changeset/metal-wombats-judge.md | 7 + .changeset/spicy-bees-teach.md | 5 + packages/react/README.md | 28 -- packages/react/package.json | 1 - packages/react/src/index.ts | 1 - packages/react/src/store-cache/index.ts | 2 - packages/react/src/store-cache/useRow.test.ts | 197 -------- packages/react/src/store-cache/useRow.ts | 23 - .../react/src/store-cache/useRows.test.ts | 168 ------- packages/react/src/store-cache/useRows.ts | 32 -- packages/std-client/package.json | 1 - packages/store-cache/.eslintrc | 6 - packages/store-cache/.gitignore | 10 - packages/store-cache/.npmignore | 6 - packages/store-cache/CHANGELOG.md | 47 -- packages/store-cache/README.md | 92 ---- packages/store-cache/package.json | 37 +- .../store-cache/src/createDatabase.test.ts | 10 - packages/store-cache/src/createDatabase.ts | 13 - .../src/createDatabaseClient.test.ts | 472 ------------------ .../store-cache/src/createDatabaseClient.ts | 47 -- packages/store-cache/src/index.ts | 4 - packages/store-cache/src/types.ts | 108 ---- packages/store-cache/src/utils.test.ts | 287 ----------- packages/store-cache/src/utils.ts | 288 ----------- packages/store-cache/tsconfig.json | 16 - packages/store-cache/tsup.config.ts | 11 - pnpm-lock.yaml | 106 +--- 28 files changed, 14 insertions(+), 2011 deletions(-) create mode 100644 .changeset/metal-wombats-judge.md create mode 100644 .changeset/spicy-bees-teach.md delete mode 100644 packages/react/src/store-cache/index.ts delete mode 100644 packages/react/src/store-cache/useRow.test.ts delete mode 100644 packages/react/src/store-cache/useRow.ts delete mode 100644 packages/react/src/store-cache/useRows.test.ts delete mode 100644 packages/react/src/store-cache/useRows.ts delete mode 100644 packages/store-cache/.eslintrc delete mode 100644 packages/store-cache/.gitignore delete mode 100644 packages/store-cache/.npmignore delete mode 100644 packages/store-cache/CHANGELOG.md delete mode 100644 packages/store-cache/README.md delete mode 100644 packages/store-cache/src/createDatabase.test.ts delete mode 100644 packages/store-cache/src/createDatabase.ts delete mode 100644 packages/store-cache/src/createDatabaseClient.test.ts delete mode 100644 packages/store-cache/src/createDatabaseClient.ts delete mode 100644 packages/store-cache/src/index.ts delete mode 100644 packages/store-cache/src/types.ts delete mode 100644 packages/store-cache/src/utils.test.ts delete mode 100644 packages/store-cache/src/utils.ts delete mode 100644 packages/store-cache/tsconfig.json delete mode 100644 packages/store-cache/tsup.config.ts diff --git a/.changeset/metal-wombats-judge.md b/.changeset/metal-wombats-judge.md new file mode 100644 index 0000000000..dadfcaf8ed --- /dev/null +++ b/.changeset/metal-wombats-judge.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/store-cache": major +--- + +Removes `store-cache` package. Please see the [changelog](https://mud.dev/changelog) for how to migrate your app to the new `store-sync` package. Or create a new project from an up-to-date template with `pnpm create mud@next your-app-name`. + +If you need reactivity, we recommend using `recs` package and `syncToRecs`. We'll be adding reactivity to `syncToSqlite` in a future release. diff --git a/.changeset/spicy-bees-teach.md b/.changeset/spicy-bees-teach.md new file mode 100644 index 0000000000..83d920fc8e --- /dev/null +++ b/.changeset/spicy-bees-teach.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/react": major +--- + +Removes `useRow` and `useRows` hooks, previously powered by `store-cache`, which is now deprecated. Please use `recs` and the corresponding `useEntityQuery` and `useComponentValue` hooks. We'll have more hooks soon for SQL.js sync backends. diff --git a/packages/react/README.md b/packages/react/README.md index 5b07f4851f..4f15ac9680 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -2,34 +2,6 @@ React hooks (and more) for building MUD clients. -## Hooks for `store-cache` - -### useRows - -Returns an array of all rows matching the provided filter. Re-renders if the filter changes or if table entries matching the provided filter change. - -```typescript -// get a list of all entries in the database -const allRows = useRows(storeCache); -// -> [ { namespace: "", table: "Position", key: { key: "0x01" }, value: { x: 1, y: 2 } }, ... ] - -// get a list of all entries in the Position table -const allPositionRows = useRows(storeCache, { table: "Position" }); - -// get a list of all entries in the position table with key greater than `0x0A` -const filteredRows = useRows(storeCache, { table: "Position", key: { gt: { key: "0x0A" } } }); -``` - -### useRow - -Returns a single row with the provided key in the provided table. Re-renders if the filter changes or if the value of this row changes. - -```typescript -// get the Position value of key `0x01` -const position = useRow(storeCache, { table: "Position", key: { key: "0x01" } }); -// -> { namespace: "", table: "Position", key: { key: "0x01" }, value: { x: 1, y: 2 } } -``` - ## Hooks for `recs` ### useComponentValue diff --git a/packages/react/package.json b/packages/react/package.json index dec9a10529..39d4a5d79e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -24,7 +24,6 @@ "dependencies": { "@latticexyz/recs": "workspace:*", "@latticexyz/store": "workspace:*", - "@latticexyz/store-cache": "workspace:*", "fast-deep-equal": "^3.1.3", "mobx": "^6.7.0", "react": "^18.2.0", diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 19f2630efb..ebc3b4f4fb 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -3,4 +3,3 @@ export * from "./useDeprecatedComputedValue"; export * from "./useEntityQuery"; export * from "./useObservableValue"; export * from "./usePromise"; -export * from "./store-cache"; diff --git a/packages/react/src/store-cache/index.ts b/packages/react/src/store-cache/index.ts deleted file mode 100644 index b72778557c..0000000000 --- a/packages/react/src/store-cache/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./useRows"; -export * from "./useRow"; diff --git a/packages/react/src/store-cache/useRow.test.ts b/packages/react/src/store-cache/useRow.test.ts deleted file mode 100644 index da4bdb3a91..0000000000 --- a/packages/react/src/store-cache/useRow.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { renderHook, act } from "@testing-library/react-hooks"; -import { UseRowFilterOptions, useRow } from "./useRow"; -import { mudConfig } from "@latticexyz/store/register"; -import { KeyValue, createDatabase, createDatabaseClient } from "@latticexyz/store-cache"; -import { describe, it, beforeEach, expect } from "vitest"; - -const config = mudConfig({ - tables: { - MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" }, - Position: { schema: { x: "int32", y: "int32" } }, - }, -}); - -describe("useRow", () => { - let db: ReturnType; - let client: ReturnType>; - - beforeEach(() => { - db = createDatabase(); - client = createDatabaseClient(db, config); - }); - - it("should return the row of the position table with the specified key", async () => { - const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x01" } })); - expect(result.current).toBe(undefined); - - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - await act(async () => { - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - }); - - expect(result.current).toEqual({ - key: { key: "0x01" }, - value: { x: 2, y: 3 }, - namespace: config["namespace"], - table: "Position", - }); - - await act(async () => { - for (const update of positionUpdates.slice(0, 3)) { - await client.tables.Position.remove(update.key); - } - }); - - expect(result.current).toBe(undefined); - }); - - it("should re-render only when the position value of the specified key changes", async () => { - const { result } = renderHook(() => useRow(client, { table: "Position", key: { key: "0x00" } })); - expect(result.all.length).toBe(1); - - // Update the position table - await act(async () => { - await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 }); - }); - expect(result.all.length).toBe(3); - expect(result.current).toEqual({ - key: { key: "0x00" }, - value: { x: 1, y: 2 }, - namespace: config["namespace"], - table: "Position", - }); - - // Update an unrelated table - await act(async () => { - await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 }); - }); - expect(result.all.length).toBe(3); - expect(result.current).toEqual({ - key: { key: "0x00" }, - value: { x: 1, y: 2 }, - namespace: config["namespace"], - table: "Position", - }); - - // Update the position table - await act(async () => { - await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 }); - }); - expect(result.all.length).toBe(4); - expect(result.current).toEqual({ - key: { key: "0x00" }, - value: { x: 2, y: 2 }, - namespace: config["namespace"], - table: "Position", - }); - - // Update an unrelated key - await act(async () => { - await client.tables.Position.set({ key: "0x01" }, { x: 2, y: 2 }); - }); - expect(result.all.length).toBe(4); - expect(result.current).toEqual({ - key: { key: "0x00" }, - value: { x: 2, y: 2 }, - namespace: config["namespace"], - table: "Position", - }); - - // Update the position table - await act(async () => { - await client.tables.Position.remove({ key: "0x00" }); - }); - expect(result.all.length).toBe(5); - expect(result.current).toEqual(undefined); - }); - - it("should re-render when the filter changes", async () => { - const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRow(client, filter), { - initialProps: { - filter: { table: "Position", key: { key: "0x01" } } as UseRowFilterOptions, - }, - }); - - expect(result.all.length).toBe(1); - expect(result.current).toBe(undefined); - - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - await act(async () => { - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - }); - - expect(result.all.length).toBe(3); - expect(result.current).toEqual({ - key: { key: "0x01" }, - value: { x: 2, y: 3 }, - namespace: config["namespace"], - table: "Position", - }); - - // Change the filter - rerender({ filter: { table: "Position", key: { key: "0x02" } } }); - await waitForNextUpdate(); - - // Expect hook to rerender three times: - // 1. New prop, everything else changes the same - // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop - // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned - expect(result.all.length).toBe(6); - expect(result.current).toEqual({ - key: { key: "0x02" }, - value: { x: 3, y: 4 }, - namespace: config["namespace"], - table: "Position", - }); - - // Change the filter - rerender({ filter: { table: "MultiKey", key: { first: "0x00", second: 4 } } }); - await waitForNextUpdate(); - - expect(result.all.length).toBe(9); - expect(result.current).toEqual({ - key: { first: "0x00", second: 4 }, - value: { value: 1 }, - namespace: config["namespace"], - table: "MultiKey", - }); - }); -}); diff --git a/packages/react/src/store-cache/useRow.ts b/packages/react/src/store-cache/useRow.ts deleted file mode 100644 index 34883bcf99..0000000000 --- a/packages/react/src/store-cache/useRow.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StoreConfig } from "@latticexyz/store"; -import { DatabaseClient, Key, ScanResult } from "@latticexyz/store-cache"; -import { useRows } from "./useRows"; - -export type UseRowFilterOptions< - C extends StoreConfig = StoreConfig, - T extends keyof C["tables"] & string = keyof C["tables"] & string -> = { - namespace?: C["namespace"]; - table: T; - key: Key; -}; - -/** - * Returns a single row of a given table at the given key, updates when the key changes - */ -export function useRow( - storeCache: DatabaseClient, - filter: UseRowFilterOptions -): ScanResult[0] | undefined { - const { namespace, table, key } = filter; - return useRows(storeCache, { namespace, table, key: { eq: key } })[0]; -} diff --git a/packages/react/src/store-cache/useRows.test.ts b/packages/react/src/store-cache/useRows.test.ts deleted file mode 100644 index 47675b4afd..0000000000 --- a/packages/react/src/store-cache/useRows.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { renderHook, act } from "@testing-library/react-hooks"; -import { useRows } from "./useRows"; -import { mudConfig } from "@latticexyz/store/register"; -import { KeyValue, createDatabase, createDatabaseClient } from "@latticexyz/store-cache"; -import { describe, it, beforeEach, expect } from "vitest"; - -const config = mudConfig({ - tables: { - MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" }, - Position: { schema: { x: "int32", y: "int32" } }, - }, -}); - -describe("useRows", () => { - let db: ReturnType; - let client: ReturnType>; - - beforeEach(() => { - db = createDatabase(); - client = createDatabaseClient(db, config); - }); - - it("should return all rows of the position table", async () => { - const { result } = renderHook(() => useRows(client, { table: "Position" })); - expect(result.current.length).toBe(0); - - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - await act(async () => { - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - }); - - expect(result.current.length).toBe(positionUpdates.length); - expect(result.current).toEqual([ - ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })), - ]); - - await act(async () => { - for (const update of positionUpdates.slice(0, 3)) { - await client.tables.Position.remove(update.key); - } - }); - - expect(result.current.length).toBe(1); - expect(result.current).toEqual([ - { key: { key: "0x03" }, value: { x: 4, y: 5 }, namespace: config["namespace"], table: "Position" }, - ]); - }); - - it("should re-render only when the position table changes", async () => { - const { result } = renderHook(() => useRows(client, { namespace: config["namespace"], table: "Position" })); - expect(result.all.length).toBe(1); - - // Update the position table - await act(async () => { - await client.tables.Position.set({ key: "0x00" }, { x: 1, y: 2 }); - }); - expect(result.all.length).toBe(3); - expect(result.current).toEqual([ - { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" }, - ]); - - // Update an unrelated table - await act(async () => { - await client.tables.MultiKey.set({ first: "0x03", second: 1 }, { value: 4 }); - }); - expect(result.all.length).toBe(3); - expect(result.current).toEqual([ - { key: { key: "0x00" }, value: { x: 1, y: 2 }, namespace: config["namespace"], table: "Position" }, - ]); - - // Update the position table - await act(async () => { - await client.tables.Position.set({ key: "0x00" }, { x: 2, y: 2 }); - }); - expect(result.all.length).toBe(4); - expect(result.current).toEqual([ - { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" }, - ]); - - // Update an unrelated table - await act(async () => { - await client.tables.MultiKey.remove({ first: "0x03", second: 1 }); - }); - expect(result.all.length).toBe(4); - expect(result.current).toEqual([ - { key: { key: "0x00" }, value: { x: 2, y: 2 }, namespace: config["namespace"], table: "Position" }, - ]); - - // Update the position table - await act(async () => { - await client.tables.Position.remove({ key: "0x00" }); - }); - expect(result.all.length).toBe(5); - expect(result.current).toEqual([]); - }); - - it("should re-render when the filter changes", async () => { - const { result, rerender, waitForNextUpdate } = renderHook(({ filter }) => useRows(client, filter), { - initialProps: { filter: { table: "Position" as keyof (typeof config)["tables"] } }, - }); - - expect(result.all.length).toBe(1); - expect(result.current.length).toBe(0); - - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - await act(async () => { - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - }); - - expect(result.all.length).toBe(6); - expect(result.current.length).toBe(positionUpdates.length); - expect(result.current).toEqual([ - ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })), - ]); - - // Change the filter - rerender({ filter: { table: "MultiKey" } }); - await waitForNextUpdate(); - - // Expect hook to rerender three times: - // 1. New prop, everything else changes the same - // 2. `filterMemo` is updated by `useDeepMemo` because of the new prop - // 3. `useEffect` runs because of the new `filterMemo`, scan is executed, new rows are returned - expect(result.all.length).toBe(9); - expect(result.current.length).toBe(multiKeyUpdates.length); - expect(result.current).toEqual([ - ...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })), - ]); - }); -}); diff --git a/packages/react/src/store-cache/useRows.ts b/packages/react/src/store-cache/useRows.ts deleted file mode 100644 index 640f4c24bd..0000000000 --- a/packages/react/src/store-cache/useRows.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DatabaseClient, FilterOptions, ScanResult } from "@latticexyz/store-cache"; -import { StoreConfig } from "@latticexyz/store"; -import { useEffect, useState } from "react"; -import { useDeepMemo } from "../utils/useDeepMemo"; -import { useMountedState } from "../utils/useMountedState"; - -/** - * Returns an array of all rows matching the provided filter - */ -export function useRows( - storeCache: DatabaseClient, - filter?: FilterOptions -) { - const [rows, setRows] = useMountedState>([]); - const filterMemo = useDeepMemo(filter); - - useEffect(() => { - storeCache.scan(filterMemo).then(setRows); - - const unsubscribePromise = storeCache.subscribe(() => { - // very naive implementation for now, but easier and probably more efficient than - // manually looping through the `rows` array for every update event - storeCache.scan(filterMemo).then(setRows); - }, filterMemo); - - return () => { - unsubscribePromise.then((unsubscribe) => unsubscribe()); - }; - }, [filterMemo, setRows, storeCache]); - - return rows; -} diff --git a/packages/std-client/package.json b/packages/std-client/package.json index 3a1094a132..1b4ac51b04 100644 --- a/packages/std-client/package.json +++ b/packages/std-client/package.json @@ -35,7 +35,6 @@ "@latticexyz/recs": "workspace:*", "@latticexyz/solecs": "workspace:*", "@latticexyz/store": "workspace:*", - "@latticexyz/store-cache": "workspace:*", "@latticexyz/utils": "workspace:*", "@latticexyz/world": "workspace:*", "abitype": "0.9.3", diff --git a/packages/store-cache/.eslintrc b/packages/store-cache/.eslintrc deleted file mode 100644 index 6db0063ad7..0000000000 --- a/packages/store-cache/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["../../.eslintrc"], - "rules": { - "@typescript-eslint/explicit-function-return-type": "error" - } -} diff --git a/packages/store-cache/.gitignore b/packages/store-cache/.gitignore deleted file mode 100644 index 904ee784fb..0000000000 --- a/packages/store-cache/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -cache -dist -docs -_docs -DOCS.md -artifacts -yarn-error.log -API -tsconfig.tsbuildinfo -tsconfig.vitest-temp.json \ No newline at end of file diff --git a/packages/store-cache/.npmignore b/packages/store-cache/.npmignore deleted file mode 100644 index eb5910442f..0000000000 --- a/packages/store-cache/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -* - -!dist/** -!src/** -!package.json -!README.md \ No newline at end of file diff --git a/packages/store-cache/CHANGELOG.md b/packages/store-cache/CHANGELOG.md deleted file mode 100644 index dd267a98cb..0000000000 --- a/packages/store-cache/CHANGELOG.md +++ /dev/null @@ -1,47 +0,0 @@ -# Change Log - -## 2.0.0-next.3 - -### Patch Changes - -- Updated dependencies [[`952cd534`](https://github.com/latticexyz/mud/commit/952cd534447d08e6231ab147ed1cc24fb49bbb57), [`bb6ada74`](https://github.com/latticexyz/mud/commit/bb6ada74016bdd5fdf83c930008c694f2f62505e), [`d5b73b12`](https://github.com/latticexyz/mud/commit/d5b73b12666699c442d182ee904fa8747b78fefd), [`433078c5`](https://github.com/latticexyz/mud/commit/433078c54c22fa1b4e32d7204fb41bd5f79ca1db), [`afaf2f5f`](https://github.com/latticexyz/mud/commit/afaf2f5ffb36fe389a3aba8da2f6d8c84bdb26ab), [`0d12db8c`](https://github.com/latticexyz/mud/commit/0d12db8c2170905f5116111e6bc417b6dca8eb61), [`331f0d63`](https://github.com/latticexyz/mud/commit/331f0d636f6f327824307570a63fb301d9b897d1)]: - - @latticexyz/store@2.0.0-next.3 - - @latticexyz/common@2.0.0-next.3 - - @latticexyz/config@2.0.0-next.3 - - @latticexyz/schema-type@2.0.0-next.3 - -## 2.0.0-next.2 - -### Patch Changes - -- Updated dependencies [[`a2588116`](https://github.com/latticexyz/mud/commit/a25881160cb3283e11d218be7b8a9fe38ee83062), [`939916bc`](https://github.com/latticexyz/mud/commit/939916bcd5c9f3caf0399e9ab7689e77e6bef7ad), [`b8a6158d`](https://github.com/latticexyz/mud/commit/b8a6158d63738ebfc1e7eb221909436d050c7e39), [`48c51b52`](https://github.com/latticexyz/mud/commit/48c51b52acab147a2ed97903c43bafa9b6769473), [`b8a6158d`](https://github.com/latticexyz/mud/commit/b8a6158d63738ebfc1e7eb221909436d050c7e39)]: - - @latticexyz/store@2.0.0-next.2 - - @latticexyz/common@2.0.0-next.2 - - @latticexyz/schema-type@2.0.0-next.2 - - @latticexyz/config@2.0.0-next.2 - -## 2.0.0-next.1 - -### Patch Changes - -- Updated dependencies [[`c963b46c`](https://github.com/latticexyz/mud/commit/c963b46c7eaceebc652930936643365b8c48a021), [`3fb9ce28`](https://github.com/latticexyz/mud/commit/3fb9ce2839271a0dcfe97f86394195f7a6f70f50), [`35c9f33d`](https://github.com/latticexyz/mud/commit/35c9f33dfb84b0bb94e0f7a8b0c9830761795bdb), [`5c965a91`](https://github.com/latticexyz/mud/commit/5c965a919355bf98d7ea69463890fe605bcde206), [`b02f9d0e`](https://github.com/latticexyz/mud/commit/b02f9d0e43089e5f9b46d817ea2032ce0a1b0b07), [`60cfd089`](https://github.com/latticexyz/mud/commit/60cfd089fc7a17b98864b631d265f36718df35a9), [`6071163f`](https://github.com/latticexyz/mud/commit/6071163f70599384c5034dd772ef6fc7cdae9983), [`6c673325`](https://github.com/latticexyz/mud/commit/6c6733256f91cddb0e913217cbd8e02e6bc484c7), [`cd5abcc3`](https://github.com/latticexyz/mud/commit/cd5abcc3b4744fab9a45c322bc76ff013355ffcb), [`cc2c8da0`](https://github.com/latticexyz/mud/commit/cc2c8da000c32c02a82a1a0fd17075d11eac56c3)]: - - @latticexyz/store@2.0.0-next.1 - - @latticexyz/common@2.0.0-next.1 - - @latticexyz/schema-type@2.0.0-next.1 - - @latticexyz/config@2.0.0-next.1 - -## 2.0.0-next.0 - -### Patch Changes - -- [#1179](https://github.com/latticexyz/mud/pull/1179) [`53522998`](https://github.com/latticexyz/mud/commit/535229984565539e6168042150b45fe0f9b48b0f) Thanks [@holic](https://github.com/holic)! - - bump to viem 1.3.0 and abitype 0.9.3 - - move `@wagmi/chains` imports to `viem/chains` - - refine a few types -- Updated dependencies [[`904fd7d4`](https://github.com/latticexyz/mud/commit/904fd7d4ee06a86e481e3e02fd5744224376d0c9), [`8d51a034`](https://github.com/latticexyz/mud/commit/8d51a03486bc20006d8cc982f798dfdfe16f169f), [`48909d15`](https://github.com/latticexyz/mud/commit/48909d151b3dfceab128c120bc6bb77de53c456b), [`66cc35a8`](https://github.com/latticexyz/mud/commit/66cc35a8ccb21c50a1882d6c741dd045acd8bc11), [`f03531d9`](https://github.com/latticexyz/mud/commit/f03531d97c999954a626ef63bc5bbae51a7b90f3), [`a7b30c79`](https://github.com/latticexyz/mud/commit/a7b30c79bcc78530d2d01858de46a0fb87954fda), [`53522998`](https://github.com/latticexyz/mud/commit/535229984565539e6168042150b45fe0f9b48b0f), [`0c4f9fea`](https://github.com/latticexyz/mud/commit/0c4f9fea9e38ba122316cdd52c3d158c62f8cfee)]: - - @latticexyz/store@2.0.0-next.0 - - @latticexyz/common@2.0.0-next.0 - - @latticexyz/schema-type@2.0.0-next.0 - - @latticexyz/config@2.0.0-next.0 - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/store-cache/README.md b/packages/store-cache/README.md deleted file mode 100644 index 73d0f10aba..0000000000 --- a/packages/store-cache/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Store Cache - -The Store Cache is a typed reactive database used to replicate the onchain Store of a MUD project into a Javascript client (Node or browser based). - -It is based on [Tuple Database](https://github.com/ccorcos/tuple-database) -- a reactive schema-less Typescript database. - -## Features - -1. **Reactive queries**: The Store Cache supports registering reactive queries and re-executing logic when changes happen. -2. **Magic typing from a MUD Config**: The Cache is automatically typed from your MUD config. No code-generation necessary. -3. **React-hooks**: This package includes React hooks to subscribe to a single value, or the result of a query. - -## Setup - -```typescript -const config = mudConfig({ - namespace: "somenamespace", - tables: { - Counter: { primaryKeys: { first: "bytes32", second: "uint256" }, schema: "uint256" }, - Position: { schema: { x: "int32", y: "int32" } }, - MultiKey: { primaryKeys: { first: "bytes32", second: "uint32" }, schema: "int32" }, - }, -}); -const db = createDatabase(); -const client = createDatabaseClient(db, config); -// This is typed! -client.tables.Position.set({ key: "0x00" }, { x: 10, y: 12 }); -// And this won't compile: key is not a bytes32, and x is not number -client.tables.Position.set({ key: "foo" }, { x: "genius", y: 12 }); -``` - -When using Store with the networking stack of MUD, _you do not need to set value into the Store yourself_: the networking stack will synchronize the onchain store and the your client store transparently. -You can simply read values and subscribe to changes, the data will be kept up to date and your client will re-render when the onchain state is modified. - -## API - -### Fetch data from the Store - -```typescript -// get the value for a given key -client.tables.MultiKey.get({ first: "0x00F", second: 12 }); -client.tables.Counter.get({ first: "0x00F", second: 23233n }); -// when the name of the primary keys is not defined, the default is a single key called "key" -// config: Position: { schema: { x: "int32", y: "int32" } } -client.tables.Position.get({ key: "0x00DEADDEADEAD" }); - -// get all rows of the Position table -client.tables.Position.scan(); -``` - -### Subscribe to a simple key-based query - -```typescript -// subscribe to all changes of the Position table -client.tables.Position.subscribe(({ set, remove }) => { - for (const entrySet of set) { - // { key: "0x00" }, { x: 1, y: 2 } - console.log(entrySet.key, entrySet.value); - } -}); -// subscribe to all changes of a single record with key 0x01 -client.tables.Position.subscribe( - ({ set, remove }) => { - for (const entrySet of set) { - // { key: "0x01" }, { x: 1, y: 2 } - console.log(entrySet.key, entrySet.value); - } - }, - { key: { eq: { key: "0x01" } } } -); -``` - -### Scan or subscribe to range queries - -The Store Cache is based on `tuple-database` and exposes its query layer for key range queries. The key is converted to a tuple based on the order in the table's `keySchema` in the MUD config (the first key in the `keySchema` object becomes the first entry in the tuple, etc). - -For more details on `tuple-database`'s sort-order for range queries, have a look at the [`tuple-database` documentation](https://github.com/ccorcos/tuple-database#sort-order). - -```typescript -// get all rows of the Position table with keys greater than `0x0A` and less than `0x0F` -client.tables.Position.scan({ key: { gt: { key: "0x0A" }, lt: { key: "0x0F" } } }); - -// subscribe to all updates of the Counter table where the first key is greater or equal to `0x0F` -client.tables.Counter.subscribe( - (updates) => { - for (const entrySet of set) { - // { first: "0x0F", second: 1n }, { value: 2n } - console.log(entrySet.key, entrySet.value); - }, - { key: { gte: { first: "0x0F" } } } -); -``` diff --git a/packages/store-cache/package.json b/packages/store-cache/package.json index 439db4f8b2..c4273e48a6 100644 --- a/packages/store-cache/package.json +++ b/packages/store-cache/package.json @@ -1,40 +1,5 @@ { "name": "@latticexyz/store-cache", "version": "2.0.0-next.3", - "description": "Database to replicate Store contract state on a TypeScript client", - "repository": { - "type": "git", - "url": "https://github.com/latticexyz/mud.git", - "directory": "packages/store-cache" - }, - "license": "MIT", - "type": "module", - "exports": { - ".": "./dist/index.js" - }, - "types": "src/index.ts", - "scripts": { - "build": "pnpm run build:js", - "build:js": "tsup", - "clean": "pnpm run clean:js", - "clean:js": "rimraf dist", - "dev": "tsup --watch", - "test": "vitest --run --passWithNoTests" - }, - "dependencies": { - "@latticexyz/common": "workspace:*", - "@latticexyz/config": "workspace:*", - "@latticexyz/schema-type": "workspace:*", - "@latticexyz/store": "workspace:*", - "abitype": "0.9.3", - "tuple-database": "^2.2.0" - }, - "devDependencies": { - "tsup": "^6.7.0", - "vitest": "0.31.4" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "914a1e0ae4a573d685841ca2ea921435057deb8f" + "private": true } diff --git a/packages/store-cache/src/createDatabase.test.ts b/packages/store-cache/src/createDatabase.test.ts deleted file mode 100644 index 6d990b3d6c..0000000000 --- a/packages/store-cache/src/createDatabase.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { createDatabase } from "."; -import { InMemoryTupleStorage, AsyncTupleDatabase } from "tuple-database"; - -describe("createDatabase", () => { - it("should create a tuple database", () => { - const db = createDatabase(); - expect(db).toEqual(new AsyncTupleDatabase(new InMemoryTupleStorage())); - }); -}); diff --git a/packages/store-cache/src/createDatabase.ts b/packages/store-cache/src/createDatabase.ts deleted file mode 100644 index 32f73762b3..0000000000 --- a/packages/store-cache/src/createDatabase.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { InMemoryTupleStorage, AsyncTupleStorageApi, AsyncTupleDatabase } from "tuple-database"; - -export type CreateDatabaseOptions = { - storage?: AsyncTupleStorageApi; -}; - -/** - * Create a new in-memory tuple database - */ -export function createDatabase(opts: CreateDatabaseOptions = {}): AsyncTupleDatabase { - const storage = opts.storage ?? new InMemoryTupleStorage(); - return new AsyncTupleDatabase(storage); -} diff --git a/packages/store-cache/src/createDatabaseClient.test.ts b/packages/store-cache/src/createDatabaseClient.test.ts deleted file mode 100644 index 525e583b39..0000000000 --- a/packages/store-cache/src/createDatabaseClient.test.ts +++ /dev/null @@ -1,472 +0,0 @@ -import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest"; -import { createDatabase, createDatabaseClient } from "."; -import { mudConfig } from "@latticexyz/store/register"; -import { KeyValue } from "./types"; - -const config = mudConfig({ - namespace: "somenamespace", - tables: { - Counter: { keySchema: { first: "bytes32", second: "uint256" }, schema: "uint256" }, - Position: { schema: { x: "int32", y: "int32" } }, - MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" }, - EnumTable: { keySchema: { first: "Enum1" }, schema: "Enum2" }, - MultiTable: { schema: { arr: "int32[]", str: "string", bts: "bytes" } }, - BigInt: { keySchema: { first: "uint256" }, schema: "uint256" }, - }, - enums: { - Enum1: ["A1", "A2"], - Enum2: ["B1", "B2"], - }, -}); - -describe("createDatabaseClient", () => { - let db: ReturnType; - let client: ReturnType>; - let tables: (typeof client)["tables"]; - - beforeEach(() => { - db = createDatabase(); - client = createDatabaseClient(db, config); - tables = client.tables; - }); - - describe("Types", () => { - it("should infer enums as numbers", () => { - expectTypeOf(tables.EnumTable.set).parameter(0).toMatchTypeOf<{ first: number }>(); - expectTypeOf(tables.EnumTable.set).parameter(1).toMatchTypeOf<{ value?: number }>(); - }); - - it("should infer bytes32 as string and uint256 as bigint", () => { - expectTypeOf(tables.Counter.set).parameter(0).toMatchTypeOf<{ first: string; second: bigint }>(); - expectTypeOf(tables.Counter.set).parameter(1).toMatchTypeOf<{ value?: bigint }>(); - }); - - it("should infer int32[] as number[]", () => { - expectTypeOf(tables.MultiTable.set).parameter(1).toMatchTypeOf<{ arr?: number[] }>(); - }); - - it("should infer string as string", () => { - expectTypeOf(tables.MultiTable.set).parameter(1).toMatchTypeOf<{ str?: string }>(); - }); - - it("should infer bytes as string", () => { - expectTypeOf(tables.MultiTable.set).parameter(1).toMatchTypeOf<{ bts?: string }>(); - }); - - it("should create a typed union for updates", () => { - client.subscribe((updates) => { - const update = updates[0]; - if (update.table === "Position") expectTypeOf(update.set[0].value).toMatchTypeOf<{ x: number; y: number }>(); - if (update.table === "Counter") expectTypeOf(update.set[0].value).toMatchTypeOf<{ value: bigint }>(); - if (update.table === "EnumTable") expectTypeOf(update.set[0].key).toMatchTypeOf<{ first: number }>(); - if (update.table === "MultiKey") - expectTypeOf(update.set[0].key).toMatchTypeOf<{ first: string; second: number }>(); - }); - }); - }); - - it("should set and get typed values", async () => { - const key = { first: "0x00", second: BigInt(1) } as const; - const value = { value: BigInt(2) }; - - // Set a value - await tables.Counter.set(key, value); - - // Expect the value to be set - expect(await tables.Counter.get(key)).toEqual(value); - }); - - it("should initialize with Solidity default values", async () => { - const key1 = { key: "0x00" } as const; - const key2 = { first: "0x00", second: 1n } as const; - - // Set a partial value - await tables.Position.set(key1, {}); - - // Expect the value to be initialized with default values - expect(await tables.Position.get(key1)).toEqual({ x: 0, y: 0 }); - - // Set a partial value and expect the value to be initialized with default values - await tables.Counter.set(key2, {}); - expect(await tables.Counter.get(key2)).toEqual({ value: 0n }); - - // Set a partial value and expect the value to be initialized with default values - await tables.MultiTable.set(key1, {}); - expect(await tables.MultiTable.get(key1)).toEqual({ arr: [], str: "", bts: "0x" }); - }); - - it("should partially update existing values", async () => { - const key = { key: "0x00" } as const; - - // Set a partial value - await tables.Position.set(key, { x: 1 }); - - // Expect the value to be set and extended with default values - expect(await tables.Position.get(key)).toEqual({ x: 1, y: 0 }); - - // Set another partial value - await tables.Position.set(key, { y: 2 }); - - // Expect the value to be partially updated - expect(await tables.Position.get(key)).toEqual({ x: 1, y: 2 }); - }); - - it("should remove values", async () => { - const key = { first: "0x00", second: BigInt(1) } as const; - const value = { value: BigInt(2) }; - - // Set the value - await tables.Counter.set(key, value); - expect(await tables.Counter.get(key)).toEqual(value); - - // Remove the value - await tables.Counter.remove(key); - expect(await tables.Counter.get(key)).toBeUndefined(); - }); - - describe("scan", () => { - it("should return all the values of the selected table", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await tables.MultiKey.set(update.key, update.value); - } - - expect(await tables.Position.scan()).toEqual( - positionUpdates.map(({ key, value }) => ({ key, value, namespace: config.namespace, table: "Position" })) - ); - }); - - it("should return a list of all database entries", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - const rows = await client.scan(); - - expect(rows.length).toBe(positionUpdates.length + multiKeyUpdates.length); - expect(rows).toEqual([ - ...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })), - ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })), - ]); - }); - }); - - describe("subscribe", () => { - it("should subscribe to updates of the selected table", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to updates of the Position table - tables.Position.subscribe(callback); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await tables.MultiKey.set(update.key, update.value); - } - - let i = 1; - - // Expect the callback to only have been called with updates of the PositionTable - for (const update of positionUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [update], remove: [], table: "Position", namespace: config.namespace }, - ]); - } - for (const update of multiKeyUpdates) { - expect(callback).not.toHaveBeenCalledWith([ - { set: [update], remove: [], table: "MultiKey", namespace: config.namespace }, - ]); - } - - // Remove all the table entries - for (const update of positionUpdates) { - await tables.Position.remove(update.key); - } - for (const update of multiKeyUpdates) { - await tables.MultiKey.remove(update.key); - } - - // Expect the callback to only have been called with remove updates of the Position table - for (const update of positionUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [], remove: [{ key: update.key }], table: "Position", namespace: config.namespace }, - ]); - } - for (const update of multiKeyUpdates) { - expect(callback).not.toHaveBeenCalledWith([ - { set: [], remove: [{ key: update.key }], table: "MultiKey", namespace: config.namespace }, - ]); - } - }); - - it("should only subscribe to updates greater than the provided key", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to Position table updates greater than key "0x01" - tables.Position.subscribe(callback, { key: { gt: { key: "0x01" } } }); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - - // Expect the callback to only have been called with updates of a key greater than 0x01 - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[2]], remove: [], table: "Position", namespace: config.namespace }, - ]); - expect(callback).toHaveBeenNthCalledWith(2, [ - { set: [positionUpdates[3]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - - it("should only subscribe to updates greater than or equal to provided key", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to Position table updates greater than or equal to key "0x01" - tables.Position.subscribe(callback, { key: { gte: { key: "0x01" } } }); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - - // Expect the callback to only have been called with updates of a key greater than or equal to 0x01 - expect(callback).toHaveBeenCalledTimes(3); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[1]], remove: [], table: "Position", namespace: config.namespace }, - ]); - expect(callback).toHaveBeenNthCalledWith(2, [ - { set: [positionUpdates[2]], remove: [], table: "Position", namespace: config.namespace }, - ]); - expect(callback).toHaveBeenNthCalledWith(3, [ - { set: [positionUpdates[3]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - - it("should only subscribe to updates equal to the provided key", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to Position table updates equal to key "0x01" - tables.Position.subscribe(callback, { key: { eq: { key: "0x01" } } }); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - - // Expect the callback to only have been called with updates of a key equal to 0x01 - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[1]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - - it("should only subscribe to updates less than or equal to the provided key", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to Position table updates less than or equal to key "0x01" - tables.Position.subscribe(callback, { key: { lte: { key: "0x01" } } }); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - - // Expect the callback to only have been called with updates of a key less than or equal to 0x01 - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[0]], remove: [], table: "Position", namespace: config.namespace }, - ]); - expect(callback).toHaveBeenNthCalledWith(2, [ - { set: [positionUpdates[1]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - - it("should only subscribe to updates less than the provided key", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe only to Position table updates less than key "0x01" - tables.Position.subscribe(callback, { key: { lt: { key: "0x01" } } }); - - // Set values in the tables - for (const update of positionUpdates) { - await tables.Position.set(update.key, update.value); - } - - // Expect the callback to only have been called with updates of a key less than 0x01 - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[0]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - - it("should not subscribe to updates anymore after unsubscribing", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const callback = vi.fn(); - - // Subscribe to all Position table updates - const unsubscribe = await tables.Position.subscribe(callback); - - // Set values in the tables - await tables.Position.set(positionUpdates[0].key, positionUpdates[0].value); - await tables.Position.set(positionUpdates[1].key, positionUpdates[1].value); - - // Unsubscribe from updates - unsubscribe(); - - // Set more values in the tables - await tables.Position.set(positionUpdates[2].key, positionUpdates[2].value); - await tables.Position.set(positionUpdates[3].key, positionUpdates[3].value); - - // Expect the callback to only have been called with updates of a key less than or equal to 0x01 - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: [positionUpdates[0]], remove: [], table: "Position", namespace: config.namespace }, - ]); - expect(callback).toHaveBeenNthCalledWith(2, [ - { set: [positionUpdates[1]], remove: [], table: "Position", namespace: config.namespace }, - ]); - }); - }); - - it("should expose global methods to modify values on any table", async () => { - const namespace = "some-other-namespace"; - const table = "some-other-table"; - const key = { key: "0x00" }; - const value = { someValue: 1 }; - - const mock = vi.fn(); - - client.subscribe(mock); - - await client.set(namespace, table, key, value); - expect(await client.get(namespace, table, key)).toStrictEqual(value); - - await client.remove(namespace, table, key); - expect(await client.get(namespace, table, key)).toBe(undefined); - - expect(mock).toHaveBeenCalledTimes(2); - expect(mock).toHaveBeenNthCalledWith(1, [{ set: [{ key, value }], remove: [], table, namespace }]); - expect(mock).toHaveBeenNthCalledWith(2, [{ set: [], remove: [{ key }], table, namespace }]); - }); - - // TODO: Remove bigint seralization to allow filter based on numeric value of bigints (see https://github.com/latticexyz/mud/issues/816) - // Requires bigint keys to be supported by `tuple-database`, see https://github.com/ccorcos/tuple-database/issues/2 - // For now we serialize bigints to use them as keys, which changes the comparison (eg. 2n < 10n but "2n" > "10n") - it.skip("should be possible to filter based on bigint keys", () => { - const mock = vi.fn(); - - // Subscribe to key values larger than 10n - tables.BigInt.subscribe(mock, { key: { gt: { first: 10n } } }); - - // Set a couple values on various keys - tables.BigInt.set({ first: 1n }, { value: 1n }); // should not be subscribed to - tables.BigInt.set({ first: 2n }, { value: 2n }); // should not be subscribed to - tables.BigInt.set({ first: 10n }, { value: 10n }); // should not be subscribed to - tables.BigInt.set({ first: 11n }, { value: 11n }); // should be subscribed to - - // Expect mock to only have been called with keys greater than 10n - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenNthCalledWith(1, [ - { - set: [{ key: { first: 11n }, value: { value: 11n } }], - remove: [], - table: "BigInt", - namespace: config.namespace, - }, - ]); - }); -}); diff --git a/packages/store-cache/src/createDatabaseClient.ts b/packages/store-cache/src/createDatabaseClient.ts deleted file mode 100644 index 64198184c0..0000000000 --- a/packages/store-cache/src/createDatabaseClient.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AsyncTupleDatabase, AsyncTupleDatabaseClient } from "tuple-database"; -import { DatabaseClient, Key, SetOptions, SubscriptionCallback, FilterOptions, Value } from "./types"; -import { set, get, remove, getDefaultValue, subscribe, scan } from "./utils"; -import { StoreConfig } from "@latticexyz/store"; -import { curry } from "@latticexyz/common/utils"; - -/** - * Create a typed database client from a tuple database and a store config - */ -export function createDatabaseClient( - database: AsyncTupleDatabase, - config: C -): DatabaseClient { - const _tupleDatabaseClient = new AsyncTupleDatabaseClient(database); - const { namespace } = config; - const tables: Record = {}; - - // Create utils with client argument prefilled - const utilsWithClient = { - set: curry(set, config, _tupleDatabaseClient), - get: curry(get, config, _tupleDatabaseClient), - remove: curry(remove, config, _tupleDatabaseClient), - subscribe: curry(subscribe, config, _tupleDatabaseClient), - scan: curry(scan, config, _tupleDatabaseClient), - }; - - // Create utils with client, namespace and table argument prefilled - for (const table in config.tables) { - tables[table] = { - set: (key: Key, value: Value, options: SetOptions) => - utilsWithClient.set(namespace, table, key, value, { - defaultValue: getDefaultValue(config.tables?.[table].schema), - ...options, - }), - get: curry(utilsWithClient.get, namespace, table), - remove: curry(utilsWithClient.remove, namespace, table), - subscribe: ( - callback: SubscriptionCallback, - filter?: Omit, "table" | "namespace"> - ) => subscribe(config, _tupleDatabaseClient, callback, { namespace, table, ...filter }), - scan: (filter?: Omit, "table" | "namespace">) => - scan(config, _tupleDatabaseClient, { namespace, table, ...filter }), - }; - } - - return { tables, _tupleDatabaseClient, ...utilsWithClient } as DatabaseClient; -} diff --git a/packages/store-cache/src/index.ts b/packages/store-cache/src/index.ts deleted file mode 100644 index 310d985e08..0000000000 --- a/packages/store-cache/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { createDatabase } from "./createDatabase"; -export { createDatabaseClient } from "./createDatabaseClient"; -export { set, get, remove, subscribe } from "./utils"; -export * from "./types"; diff --git a/packages/store-cache/src/types.ts b/packages/store-cache/src/types.ts deleted file mode 100644 index ed5a0864ee..0000000000 --- a/packages/store-cache/src/types.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { AsyncTupleDatabaseClient, AsyncTupleRootTransactionApi, Unsubscribe } from "tuple-database"; -import { FieldData, FullSchemaConfig, StoreConfig } from "@latticexyz/store"; -import { AbiTypeToPrimitiveType } from "@latticexyz/schema-type/deprecated"; - -type FieldTypeToPrimitiveType> = AbiTypeToPrimitiveType extends never - ? T extends `${string}[${string}]` // FieldType might include Enums and Enum arrays, which are mapped to uint8/uint8[] - ? number[] // Map enum arrays to `number[]` - : number // Map enums to `number` - : AbiTypeToPrimitiveType; - -/** Map a table schema like `{ value: "uint256 "}` to its primitive TypeScript types like `{ value: bigint }`*/ -export type SchemaToPrimitives = { [key in keyof T]: FieldTypeToPrimitiveType }; - -export type Tables = { - [key in keyof C["tables"]]: { - keyTuple: SchemaToPrimitives; - value: SchemaToPrimitives; - }; -}; - -export type Key> = Tables[Table]["keyTuple"]; -export type Value> = Tables[Table]["value"]; -export type KeyValue> = { - key: Key; - value: Value; -}; - -export type DatabaseClient = { - /** Utils for every table with the table argument prefilled */ - tables: { - [Table in keyof C["tables"]]: { - set: (key: Key, value: Partial>, options?: SetOptions) => Promise; - get: (key: Key) => Promise>; - remove: (key: Key, options?: RemoveOptions) => Promise; - subscribe: ( - callback: SubscriptionCallback, - // Omitting the namespace and table config option because it is prefilled when calling subscribe via the client - filter?: Omit, "table" | "namespace"> - ) => Promise; - scan: ( - filter?: Omit, "table" | "namespace"> - ) => Promise>; - }; - }; -} & { - /** Utils to set a custom table value */ - set:
( - namespace: string, - table: Table, - key: Key, - value: Partial>, - options?: SetOptions - ) => Promise; - get:
( - namespace: string, - table: Table, - key: Key - ) => Promise>; - remove:
( - namespace: string, - table: Table, - key: Key, - options?: RemoveOptions - ) => Promise; - subscribe:
( - callback: SubscriptionCallback, - filter?: FilterOptions - ) => Promise; - scan:
( - filter?: FilterOptions - ) => Promise>; - _tupleDatabaseClient: AsyncTupleDatabaseClient; -}; - -export type SetOptions = { - defaultValue?: Value; - /** Transaction to append the set operation to. If provided, the transaction will not be committed. */ - transaction?: AsyncTupleRootTransactionApi; -}; - -export type RemoveOptions = { - /** Transaction to append the remove operation to. If provided, the transaction will not be committed. */ - transaction?: AsyncTupleRootTransactionApi; -}; - -export type SubscriptionCallback< - C extends StoreConfig = StoreConfig, - T extends keyof C["tables"] = keyof C["tables"] -> = (updates: Update[]) => void; - -export type FilterOptions = { - table: T & string; - namespace?: string; - key?: { [key in "gt" | "gte" | "lt" | "lte" | "eq"]?: Key }; -}; - -export type Update = { - [key in Table]: { - namespace: C["namespace"]; - table: key; - set: KeyValue[]; - remove: { key: Key }[]; - }; -}[Table]; - -export type ScanResult = Array< - KeyValue & { namespace: C["namespace"]; table: T } ->; diff --git a/packages/store-cache/src/utils.test.ts b/packages/store-cache/src/utils.test.ts deleted file mode 100644 index 41e840ef00..0000000000 --- a/packages/store-cache/src/utils.test.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { createDatabase, createDatabaseClient } from "."; -import { mudConfig } from "@latticexyz/store/register"; -import { scan, subscribe } from "./utils"; -import { KeyValue } from "./types"; - -const config = mudConfig({ - tables: { - MultiKey: { keySchema: { first: "bytes32", second: "uint32" }, schema: "int32" }, - Position: { schema: { x: "int32", y: "int32" } }, - }, -}); - -describe("utils", () => { - let db: ReturnType; - let client: ReturnType>; - - beforeEach(() => { - db = createDatabase(); - client = createDatabaseClient(db, config); - }); - - describe("scan", () => { - it("should return a list of all database entries", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - const rows = await scan(config, client._tupleDatabaseClient); - - expect(rows.length).toBe(positionUpdates.length + multiKeyUpdates.length); - expect(rows).toEqual([ - ...multiKeyUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "MultiKey" })), - ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })), - ]); - }); - - it("should return a list of all entries of the Position table", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - const rows = await scan(config, client._tupleDatabaseClient, { namespace: "", table: "Position" }); - - expect(rows.length).toBe(positionUpdates.length); - expect(rows).toEqual([ - ...positionUpdates.map((row) => ({ ...row, namespace: config["namespace"], table: "Position" })), - ]); - }); - - it("should return a list of all entries of the MultiKey table with keys greater than { first: 0x02, second: 0 } and less than { first: 0x03, second: 4 }", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 1 }, value: { value: 1 } }, - { key: { first: "0x01", second: 2 }, value: { value: 2 } }, - { key: { first: "0x02", second: 3 }, value: { value: 3 } }, - { key: { first: "0x03", second: 4 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - const rows = await scan(config, client._tupleDatabaseClient, { - namespace: "", - table: "MultiKey", - key: { gt: { first: "0x02", second: 0 }, lt: { first: "0x03", second: 4 } }, - }); - - expect(rows.length).toBe(1); - expect(rows).toEqual([ - { key: { first: "0x02", second: 3 }, value: { value: 3 }, namespace: "", table: "MultiKey" }, - ]); - }); - - it("should not matter in which order the key is passed in", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 1 }, value: { value: 1 } }, - { key: { first: "0x01", second: 2 }, value: { value: 2 } }, - { key: { first: "0x02", second: 3 }, value: { value: 3 } }, - { key: { first: "0x03", second: 4 }, value: { value: 4 } }, - ]; - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - const rows = await scan(config, client._tupleDatabaseClient, { - namespace: "", - table: "MultiKey", - key: { gt: { second: 0, first: "0x02" }, lt: { first: "0x03", second: 4 } }, - }); - - expect(rows.length).toBe(1); - expect(rows).toEqual([ - { key: { first: "0x02", second: 3 }, value: { value: 3 }, namespace: "", table: "MultiKey" }, - ]); - }); - }); - - describe("subscribe", () => { - it("should subscribe to all table updates", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - const callback = vi.fn(); - - // Subscribe to all updates - subscribe(config, client._tupleDatabaseClient, callback); - - // Set values in the tables - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value); - } - - let i = 1; - - // Expect the callback to have been called with all set updates - for (const update of positionUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [update], remove: [], table: "Position", namespace: "" }, - ]); - } - for (const update of multiKeyUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [update], remove: [], table: "MultiKey", namespace: "" }, - ]); - } - - // Remove all the table entries - for (const update of positionUpdates) { - await client.tables.Position.remove(update.key); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.remove(update.key); - } - - // Expect the callback to have called with all remove updates - for (const update of positionUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [], remove: [{ key: update.key }], table: "Position", namespace: "" }, - ]); - } - for (const update of multiKeyUpdates) { - expect(callback).toHaveBeenNthCalledWith(i++, [ - { set: [], remove: [{ key: update.key }], table: "MultiKey", namespace: "" }, - ]); - } - }); - - it("should subscribe to table updates with multiple writes per transaction", async () => { - const positionUpdates: KeyValue[] = [ - { key: { key: "0x00" }, value: { x: 1, y: 2 } }, - { key: { key: "0x01" }, value: { x: 2, y: 3 } }, - { key: { key: "0x02" }, value: { x: 3, y: 4 } }, - { key: { key: "0x03" }, value: { x: 4, y: 5 } }, - ]; - - const multiKeyUpdates: KeyValue[] = [ - { key: { first: "0x00", second: 4 }, value: { value: 1 } }, - { key: { first: "0x01", second: 3 }, value: { value: 2 } }, - { key: { first: "0x02", second: 2 }, value: { value: 3 } }, - { key: { first: "0x03", second: 1 }, value: { value: 4 } }, - ]; - - const callback = vi.fn(); - - // Subscribe to all updates - await subscribe(config, client._tupleDatabaseClient, callback); - - // Set values in the tables - const tx = client._tupleDatabaseClient.transact(); - for (const update of positionUpdates) { - await client.tables.Position.set(update.key, update.value, { transaction: tx }); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.set(update.key, update.value, { transaction: tx }); - } - await tx.commit(); - - // Expect the callback to have been called with all set updates (in a single transaction) - expect(callback).toHaveBeenNthCalledWith(1, [ - { set: multiKeyUpdates, remove: [], table: "MultiKey", namespace: "" }, - { set: positionUpdates, remove: [], table: "Position", namespace: "" }, - ]); - - // Remove all the table entries - const tx2 = client._tupleDatabaseClient.transact(); - for (const update of positionUpdates) { - await client.tables.Position.remove(update.key, { transaction: tx2 }); - } - for (const update of multiKeyUpdates) { - await client.tables.MultiKey.remove(update.key, { transaction: tx2 }); - } - await tx2.commit(); - - // Expect the callback to have called with all remove updates (in a single transaction) - expect(callback).toHaveBeenNthCalledWith(2, [ - { - set: [], - remove: multiKeyUpdates.map(({ key }) => ({ key })), - table: "MultiKey", - namespace: "", - }, - { - set: [], - remove: positionUpdates.map(({ key }) => ({ key })), - table: "Position", - namespace: "", - }, - ]); - }); - - // More tests for subscriptions to specific tables are in `createDatabaseClient.test.ts` - }); -}); diff --git a/packages/store-cache/src/utils.ts b/packages/store-cache/src/utils.ts deleted file mode 100644 index 29d8548685..0000000000 --- a/packages/store-cache/src/utils.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { ScanArgs, Tuple, AsyncTupleDatabaseClient, Unsubscribe } from "tuple-database"; -import { StoreConfig } from "@latticexyz/store"; -import { - Key, - RemoveOptions, - SchemaToPrimitives, - SetOptions, - SubscriptionCallback, - FilterOptions, - Update, - Value, - KeyValue, - ScanResult, -} from "./types"; -import { getAbiTypeDefaultValue } from "@latticexyz/schema-type/deprecated"; -import { StaticPrimitiveType } from "@latticexyz/schema-type"; - -/** - * Set the value for the given key - * @param client AsyncTupleDatabaseClient - * @param table Table to set the given key in - * @param key Key to identify the record to set in the table - * @param value Value to set for the given key - * @param options { - * transaction?: Append to an existing transaction, do not commit - * defaultValue?: Default value to use if the key does not exist - * } - * @returns Transaction - */ -export async function set( - config: C, - client: AsyncTupleDatabaseClient, - namespace: C["namespace"], - table: T & string, - key: Key, - value: Partial>, - options?: SetOptions -): Promise { - const keyTuple = databaseKey(config, namespace, table, key); - const currentValue = (await client.get(keyTuple)) ?? options?.defaultValue; - const tx = options?.transaction ?? client.transact(); - tx.set(keyTuple, { ...currentValue, ...value }); - if (!options?.transaction) await tx.commit(); -} - -/** - * Get the value for the given key - * @param client AsyncTupleDatabaseClient - * @param table Table to get the given key from - * @param key Key to identify the record to get from the table - * @returns Value for the given key - */ -export async function get( - config: C, - client: AsyncTupleDatabaseClient, - namespace: C["namespace"], - table: T & string, - key: Key -): Promise> { - return client.get(databaseKey(config, namespace, table, key)); -} - -/** - * Remove the value for the given key - * @param client AsyncTupleDatabaseClient - * @param table Table to remove the given key from - * @param key Key to identify the record to remove from the table - * @param options { - * transaction?: Append to an existing transaction, do not commit - * defaultValue?: Default value to use if the key does not exist - * } - * @returns Transaction - */ -export async function remove( - config: C, - client: AsyncTupleDatabaseClient, - namespace: C["namespace"], - table: T & string, - key: Key, - options?: RemoveOptions -): Promise { - const tx = options?.transaction ?? client.transact(); - tx.remove(databaseKey(config, namespace, table, key)); - if (!options?.transaction) await tx.commit(); -} - -export async function scan( - config: C, - client: AsyncTupleDatabaseClient, - filter?: FilterOptions -): Promise> { - const scanArgs = getScanArgsFromFilter(config, filter); - const results = await client.scan(scanArgs); - - return results.map( - ({ key, value }) => - ({ namespace: key[0], table: key[1], key: tupleToRecord(key), value } as KeyValue & { - namespace: C["namespace"]; - table: T; - }) - ); -} - -/** - * Subscribe to changes in the database - * @param client AsyncTupleDatabaseClient - * @param callback Callback to be called when a change occurs - * @param filter { - * table?: Table to subscribe to. If none is given, all tables are subscribed to - * key?: Filters to determine key to subscribe to. If none is given, all keys are subscribed to - * } - * @returns Function to unsubscribe - */ -export async function subscribe( - config: C, - client: AsyncTupleDatabaseClient, - callback: SubscriptionCallback, - filter?: FilterOptions -): Promise { - const scanArgs = getScanArgsFromFilter(config, filter); - - return client.subscribe(scanArgs, (write) => { - const updates: Record = {}; - - // Transform the writes into the expected format - for (const update of write.set ?? []) { - // The first tuple element is always the table name - const [namespace, table] = update.key; - if (typeof namespace !== "string" || typeof table !== "string") { - console.warn( - "store-cache: Expected first tuple elements to be namespace and table, ignoring set operation:", - update - ); - continue; - } - - // Add the set event to the updates for this table - updates[toSelector(namespace, table)] ??= { namespace, table, set: [], remove: [] } satisfies Update; - updates[toSelector(namespace, table)].set.push({ key: tupleToRecord(update.key), value: update.value }); - } - - for (const removedKey of write.remove ?? []) { - // The first tuple element is always the table name - const [namespace, table] = removedKey; - if (typeof namespace !== "string" || typeof table !== "string") { - console.warn( - "store-cache: Expected first tuple elements to be namespace and table, ignoring remove operation:", - removedKey - ); - continue; - } - - // Add the remove event to the updates for this table - updates[toSelector(namespace, table)] ??= { namespace, table, set: [], remove: [] } satisfies Update; - updates[toSelector(namespace, table)].remove.push({ key: tupleToRecord(removedKey) }); - } - - callback(Object.values(updates) as Update[]); - }); -} - -/** - * Map a table schema to the corresponding default value - */ -export function getDefaultValue>(schema: Schema): SchemaToPrimitives { - // Map schema to its default values - const defaultValue: Record = {}; - for (const key in schema) { - defaultValue[key] = getAbiTypeDefaultValue(schema[key]); - } - - return defaultValue as SchemaToPrimitives; -} - -function getScanArgsFromFilter( - config: C, - filter?: FilterOptions -): ScanArgs { - const { table, key } = filter || {}; - // Default to the config namespace if a filter without namespace is provided - const namespace = filter ? filter.namespace ?? config.namespace : undefined; - - const prefix = table != null && namespace != null ? [namespace, table] : undefined; - const scanArgs: ScanArgs = {}; - - // Transform scan args - if (table) { - scanArgs.gte = - key?.gte && recordToTuple(key.gte as Record, getKeyOrder(config, table)); - scanArgs.gt = key?.gt && recordToTuple(key.gt as Record, getKeyOrder(config, table)); - scanArgs.lte = - key?.lte && recordToTuple(key.lte as Record, getKeyOrder(config, table)); - scanArgs.lt = key?.lt && recordToTuple(key.lt as Record, getKeyOrder(config, table)); - - // Override gte and lte if eq is set - if (key?.eq) { - scanArgs.gte = recordToTuple(key.eq as Record, getKeyOrder(config, table)); - scanArgs.lte = recordToTuple(key.eq as Record, getKeyOrder(config, table)); - } - } - - return { prefix, ...scanArgs }; -} - -/** - * Convert a table and key into the corresponding tuple expected by tuple-database - */ -function databaseKey( - config: C, - namespace: C["namespace"], - table: T & string, - key: Key -): Tuple { - return [ - namespace, - table, - ...recordToTuple(key as Record, getKeyOrder(config, table)), - ] satisfies Tuple; -} - -/** - * Get an array corresponding to the keys of the table's key schema - */ -function getKeyOrder(config: StoreConfig, table: string): string[] | undefined { - const tableConfig = config.tables[table]; - return tableConfig ? Object.getOwnPropertyNames(tableConfig.keySchema) : undefined; -} - -/** - * Convert a record like `{ a: string, b: number }` to a record tuple like `[{ a: string }, { b: number }]`, - * and sort it based on config's key order, as expected for keys in tuple-database - */ -function recordToTuple(record: Record, keyOrder?: string[]): Tuple { - const tuple = []; - for (const key of keyOrder ?? Object.keys(record)) { - tuple.push({ [key]: serializeKey(record[key]) }); - } - return tuple; -} - -/** - * Convert a record tuple like `[{ a: string }, { b: number }]` to a record like `{ a: string, b: number }`, - */ -function tupleToRecord(tuple: Tuple): Record { - const record: Record = {}; - - for (const entry of tuple) { - // Ignore all non-object tuple values - if (entry === null || Array.isArray(entry) || typeof entry !== "object") continue; - for (const [key, value] of Object.entries(entry)) { - record[key] = deserializeKey(value); - } - } - - return record; -} - -/** - * Helper to serialize values that are not natively supported in keys by tuple-database. - * (see https://github.com/ccorcos/tuple-database/issues/25) - * For now only `bigint` needs serialization. - */ -function serializeKey(key: string | number | bigint | boolean): string | number | boolean { - if (typeof key === "bigint") return `${key.toString()}n`; - return key; -} - -/** - * Helper to deserialize values that were serialized by `serializeKey` (because they are not natively supported in keys by tuple-database). - * (see https://github.com/ccorcos/tuple-database/issues/25) - * For now only `bigint` is serialized and need to be deserialized here. - */ -function deserializeKey(key: string): string | bigint { - // Check whether the key matches the mattern `${number}n` - // (serialization of bigint in `serializeKey`) - // and turn it back into a bigint - if (typeof key === "string" && /^-?\d+n$/.test(key)) { - return BigInt(key.slice(0, -1)); - } - return key; -} - -/** - * Helper to concat namespace and table with a separator - */ -function toSelector(namespace: string, table: string): string { - return namespace + "/" + table; -} diff --git a/packages/store-cache/tsconfig.json b/packages/store-cache/tsconfig.json deleted file mode 100644 index 0ee5bbf899..0000000000 --- a/packages/store-cache/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - "target": "es2021", - "module": "esnext", - "moduleResolution": "node", - "declaration": true, - "sourceMap": true, - "outDir": "dist", - "isolatedModules": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true - }, - "include": ["src"] -} diff --git a/packages/store-cache/tsup.config.ts b/packages/store-cache/tsup.config.ts deleted file mode 100644 index b755469f90..0000000000 --- a/packages/store-cache/tsup.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ["src/index.ts"], - target: "esnext", - format: ["esm"], - dts: false, - sourcemap: true, - clean: true, - minify: true, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9101557aef..3e5d4c1924 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -526,9 +526,6 @@ importers: '@latticexyz/store': specifier: workspace:* version: link:../store - '@latticexyz/store-cache': - specifier: workspace:* - version: link:../store-cache fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 @@ -736,9 +733,6 @@ importers: '@latticexyz/store': specifier: workspace:* version: link:../store - '@latticexyz/store-cache': - specifier: workspace:* - version: link:../store-cache '@latticexyz/utils': specifier: workspace:* version: link:../utils @@ -861,33 +855,7 @@ importers: specifier: 0.31.4 version: 0.31.4(jsdom@22.1.0) - packages/store-cache: - dependencies: - '@latticexyz/common': - specifier: workspace:* - version: link:../common - '@latticexyz/config': - specifier: workspace:* - version: link:../config - '@latticexyz/schema-type': - specifier: workspace:* - version: link:../schema-type - '@latticexyz/store': - specifier: workspace:* - version: link:../store - abitype: - specifier: 0.9.3 - version: 0.9.3(typescript@5.1.6)(zod@3.21.4) - tuple-database: - specifier: ^2.2.0 - version: 2.2.0 - devDependencies: - tsup: - specifier: ^6.7.0 - version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) - vitest: - specifier: 0.31.4 - version: 0.31.4(jsdom@22.1.0) + packages/store-cache: {} packages/store-indexer: dependencies: @@ -4595,10 +4563,6 @@ packages: /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - /charenc@0.0.2: - resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} - dev: false - /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true @@ -4925,10 +4889,6 @@ packages: shebang-command: 2.0.0 which: 2.0.2 - /crypt@0.0.2: - resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - dev: false - /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -5267,10 +5227,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /elen@1.0.10: - resolution: {integrity: sha512-ZL799/V/kzxYJ6Wlfktreq6qQWfGc3VkGUQJW5lZQ8/MhsQiKTAwERPfhEwIsV2movRGe2DfV7H2MjRw76Z7Wg==} - dev: false - /elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} dependencies: @@ -6277,11 +6233,6 @@ packages: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true - /fractional-indexing@3.2.0: - resolution: {integrity: sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==} - engines: {node: ^14.13.1 || >=16.0.0} - dev: false - /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: false @@ -6296,15 +6247,6 @@ packages: rimraf: 2.7.1 dev: true - /fs-extra@11.1.1: - resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} - engines: {node: '>=14.14'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: false - /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -6952,10 +6894,6 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - dev: false - /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -7779,14 +7717,6 @@ packages: optionalDependencies: graceful-fs: 4.2.11 - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.11 - dev: false - /jsx-ast-utils@3.3.3: resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} engines: {node: '>=4.0'} @@ -8252,14 +8182,6 @@ packages: safe-buffer: 5.2.1 dev: true - /md5@2.3.0: - resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} - dependencies: - charenc: 0.0.2 - crypt: 0.0.2 - is-buffer: 1.1.6 - dev: false - /memdown@5.1.0: resolution: {integrity: sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==} engines: {node: '>=6'} @@ -10782,22 +10704,6 @@ packages: safe-buffer: 5.2.1 dev: false - /tuple-database@2.2.0: - resolution: {integrity: sha512-sovO193K6NhAXmuc/1eFpfZ0iZSPf4NMi1X4bfK1Q//9DrBICRL1Cn3tOSjLsWYIG5wqOzhM7voUfne7RmSkdA==} - peerDependencies: - react: '*' - peerDependenciesMeta: - react: - optional: true - dependencies: - elen: 1.0.10 - fractional-indexing: 3.2.0 - fs-extra: 11.1.1 - lodash: 4.17.21 - md5: 2.3.0 - uuid: 9.0.0 - dev: false - /turbo-darwin-64@1.9.3: resolution: {integrity: sha512-0dFc2cWXl82kRE4Z+QqPHhbEFEpUZho1msHXHWbz5+PqLxn8FY0lEVOHkq5tgKNNEd5KnGyj33gC/bHhpZOk5g==} cpu: [x64] @@ -11000,11 +10906,6 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /universalify@2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} - dev: false - /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -11073,11 +10974,6 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - dev: false - /v8-to-istanbul@9.1.0: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'}