MCPcopy Create free account
hub / github.com/TanStack/db / mergePendingMutations

Function mergePendingMutations

packages/db/src/transactions.ts:43–103  ·  view source on GitHub ↗

* Merges two pending mutations for the same item within a transaction * * Merge behavior truth table: * - (insert, update) → insert (merge changes, keep empty original) * - (insert, delete) → null (cancel both mutations) * - (update, delete) → delete (delete dominates) * - (update, update) → u

(
  existing: PendingMutation<T>,
  incoming: PendingMutation<T>,
)

Source from the content-addressed store, hash-verified

41 * @returns The merged mutation, or null if both should be removed
42 */
43function mergePendingMutations<T extends object>(
44 existing: PendingMutation<T>,
45 incoming: PendingMutation<T>,
46): PendingMutation<T> | null {
47 // Truth table implementation
48 switch (`${existing.type}-${incoming.type}` as const) {
49 case `insert-update`: {
50 // Update after insert: keep as insert but merge changes
51 // For insert-update, the key should remain the same since collections don't allow key changes
52 return {
53 ...existing,
54 type: `insert` as const,
55 original: {},
56 modified: incoming.modified,
57 changes: { ...existing.changes, ...incoming.changes },
58 // Keep existing keys (key changes not allowed in updates)
59 key: existing.key,
60 globalKey: existing.globalKey,
61 // Merge metadata (last-write-wins)
62 metadata: incoming.metadata ?? existing.metadata,
63 syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },
64 // Update tracking info
65 mutationId: incoming.mutationId,
66 updatedAt: incoming.updatedAt,
67 }
68 }
69
70 case `insert-delete`:
71 // Delete after insert: cancel both mutations
72 return null
73
74 case `update-delete`:
75 // Delete after update: delete dominates
76 return incoming
77
78 case `update-update`: {
79 // Update after update: replace with latest, union changes
80 return {
81 ...incoming,
82 // Keep original from first update
83 original: existing.original,
84 // Union the changes from both updates
85 changes: { ...existing.changes, ...incoming.changes },
86 // Merge metadata
87 metadata: incoming.metadata ?? existing.metadata,
88 syncMetadata: { ...existing.syncMetadata, ...incoming.syncMetadata },
89 }
90 }
91
92 case `delete-delete`:
93 case `insert-insert`:
94 // Same type: replace with latest
95 return incoming
96
97 default: {
98 // Exhaustiveness check
99 const _exhaustive: never = `${existing.type}-${incoming.type}` as never
100 throw new Error(`Unhandled mutation combination: ${_exhaustive}`)

Callers 1

applyMutationsMethod · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected