Skip to content

Commit 3c9526c

Browse files
authored
fix: dedupe filtering for non-optimistic mutations (#715)
* Fix optimistic mutation check in transaction processing * Add test for synced delete after non-optimistic delete * Add changeset for dedupe filtering non-optimistic mutations fix
1 parent 5ab979c commit 3c9526c

File tree

3 files changed

+70
-0
lines changed

3 files changed

+70
-0
lines changed

.changeset/full-meals-ask.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tanstack/db": patch
3+
---
4+
5+
Fix synced propagation when preceding mutation was non-optimistic

packages/db/src/collection/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,7 @@ export class CollectionStateManager<
700700
if (transaction.state === `completed`) {
701701
for (const mutation of transaction.mutations) {
702702
if (
703+
mutation.optimistic &&
703704
this.isThisCollection(mutation.collection) &&
704705
changedKeys.has(mutation.key)
705706
) {

packages/db/tests/collection-subscribe-changes.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,70 @@ describe(`Collection.subscribeChanges`, () => {
12501250
expect(collection.state.has(1)).toBe(false)
12511251
})
12521252

1253+
it(`should emit synced delete after a non-optimistic delete`, async () => {
1254+
const emitter = mitt()
1255+
const callback = vi.fn()
1256+
1257+
const collection = createCollection<{ id: number; value: string }>({
1258+
id: `non-optimistic-delete-sync`,
1259+
getKey: (item) => item.id,
1260+
sync: {
1261+
sync: ({ begin, write, commit }) => {
1262+
// replay any pending mutations emitted via mitt
1263+
// @ts-expect-error Mitt typings are loose for our test helpers
1264+
emitter.on(`*`, (_, changes: Array<PendingMutation>) => {
1265+
begin()
1266+
changes.forEach((change) => {
1267+
write({
1268+
type: change.type,
1269+
// @ts-expect-error test helper
1270+
value: change.modified,
1271+
})
1272+
})
1273+
commit()
1274+
})
1275+
1276+
// seed initial row
1277+
begin()
1278+
write({
1279+
type: `insert`,
1280+
value: { id: 1, value: `initial` },
1281+
})
1282+
commit()
1283+
},
1284+
},
1285+
onDelete: async ({ transaction }) => {
1286+
emitter.emit(`sync`, transaction.mutations)
1287+
},
1288+
})
1289+
1290+
const subscription = collection.subscribeChanges(callback, {
1291+
includeInitialState: true,
1292+
})
1293+
1294+
// initial insert emitted
1295+
expect(callback).toHaveBeenCalledTimes(1)
1296+
callback.mockClear()
1297+
1298+
const tx = collection.delete(1, { optimistic: false })
1299+
await tx.isPersisted.promise
1300+
1301+
expect(callback).toHaveBeenCalledTimes(1)
1302+
const deleteChanges = callback.mock.calls[0]![0] as ChangesPayload<{
1303+
value: string
1304+
}>
1305+
expect(deleteChanges).toEqual([
1306+
{
1307+
type: `delete`,
1308+
key: 1,
1309+
value: { id: 1, value: `initial` },
1310+
},
1311+
])
1312+
expect(collection.state.has(1)).toBe(false)
1313+
1314+
subscription.unsubscribe()
1315+
})
1316+
12531317
it(`truncate + optimistic insert: server did NOT reinsert key -> inserted optimistically`, async () => {
12541318
const changeEvents: Array<any> = []
12551319
let f: any = null

0 commit comments

Comments
 (0)