Skip to content

Commit b8239d8

Browse files
authored
fix(stash): maintain update atomicity in subscribeQuery (#3663)
1 parent 26d2e3a commit b8239d8

3 files changed

Lines changed: 63 additions & 8 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@latticexyz/stash": patch
3+
---
4+
5+
Stash now preserves batch updates when subscribing to query results.
6+
Previously, while Stash supported batching table updates for atomic onchain changes, subscribing to query results would split these updates by table.

packages/stash/src/actions/subscribeQuery.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ describe("defineQuery", () => {
217217
// Update multiple records but only advance timer once
218218
setRecord({ stash, table: Health, key: { player: `0x2` }, value: { health: 2 } });
219219
setRecord({ stash, table: Health, key: { player: `0x1` }, value: { health: 1 } });
220+
setRecord({ stash, table: Position, key: { player: `0x1` }, value: { x: 1, y: 2 } });
221+
setRecord({ stash, table: Position, key: { player: `0x2` }, value: { x: 2, y: 3 } });
220222
vi.advanceTimersToNextTimer();
221223

222224
expect(subscriber).toBeCalledTimes(2);
@@ -263,6 +265,50 @@ describe("defineQuery", () => {
263265
current: { player: "0x1", health: 1 },
264266
type: "enter",
265267
},
268+
{
269+
table: {
270+
label: "Position",
271+
type: "table",
272+
namespace: "namespace1",
273+
namespaceLabel: "namespace1",
274+
name: "Position",
275+
tableId: "0x74626e616d6573706163653100000000506f736974696f6e0000000000000000",
276+
schema: {
277+
player: { type: "bytes32", internalType: "bytes32" },
278+
x: { type: "int32", internalType: "int32" },
279+
y: { type: "int32", internalType: "int32" },
280+
},
281+
key: ["player"],
282+
codegen: { outputDirectory: "tables", tableIdArgument: false, storeArgument: false, dataStruct: true },
283+
deploy: { disabled: false },
284+
},
285+
key: { player: "0x1" },
286+
previous: { player: "0x1", x: 1, y: 4 },
287+
current: { player: "0x1", x: 1, y: 2 },
288+
type: "update",
289+
},
290+
{
291+
table: {
292+
label: "Position",
293+
type: "table",
294+
namespace: "namespace1",
295+
namespaceLabel: "namespace1",
296+
name: "Position",
297+
tableId: "0x74626e616d6573706163653100000000506f736974696f6e0000000000000000",
298+
schema: {
299+
player: { type: "bytes32", internalType: "bytes32" },
300+
x: { type: "int32", internalType: "int32" },
301+
y: { type: "int32", internalType: "int32" },
302+
},
303+
key: ["player"],
304+
codegen: { outputDirectory: "tables", tableIdArgument: false, storeArgument: false, dataStruct: true },
305+
deploy: { disabled: false },
306+
},
307+
key: { player: "0x2" },
308+
previous: { player: "0x2", x: 2, y: 3 },
309+
current: { player: "0x2", x: 2, y: 3 },
310+
type: "update",
311+
},
266312
]);
267313
});
268314
});

packages/stash/src/actions/subscribeQuery.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
Key,
1717
TableRecord,
1818
} from "../common";
19-
import { getTable } from "./getTable";
2019
import { runQuery } from "./runQuery";
2120
import { encodeKey } from "./encodeKey";
21+
import { subscribeStash } from "./subscribeStash";
2222

2323
export type SubscribeQueryOptions = CommonQueryOptions;
2424

@@ -119,13 +119,16 @@ export function subscribeQuery<query extends Query>({
119119
subscribers.forEach((subscriber) => subscriber(updates));
120120
};
121121

122-
// Subscribe to each table's update stream and return the unsubscribers
123-
const unsubsribers = query.map((fragment) =>
124-
getTable({ stash, table: fragment.table }).subscribe({
125-
subscriber: (updates) => updateQueryResult(updates),
126-
}),
127-
);
128-
const unsubscribe = () => unsubsribers.forEach((unsub) => unsub());
122+
// Too maintain atomicity of stash updates we don't subscribe to tables individually but filter global stash updates.
123+
const relevantTables = new Set(query.map((fragment) => fragment.table.tableId));
124+
const unsubscribe = subscribeStash({
125+
stash,
126+
subscriber: (event) => {
127+
if (event.type !== "records") return;
128+
const relevantUpdates = event.updates.filter((update) => relevantTables.has(update.table.tableId));
129+
updateQueryResult(relevantUpdates);
130+
},
131+
});
129132

130133
// Notify initial subscribers
131134
const updates: QueryUpdates = Object.values(matching).map((key) => ({ key, type: "enter" }));

0 commit comments

Comments
 (0)