TanStack

Content temporarily unavailable

Source batches written inside a mutation handler

Pre-repair executable revision: 34095aa6d (oracle extension on f2f92c5b2). Repair executable revision: 3ab428439.

Law

A direct insert, update, or delete hands its request to the Collection's handler. The handler may confirm the request through sync before it returns. That source batch is the same event as a batch written while the new request is active: it waits for settlement unless it is immediate or a truncate, and ordinary publication then retires the accepted snapshot. Authority: the optimistic-history contract in packages/db/tests/optimistic-history-oracle.ts ("After success it remains as an accepted local snapshot until source acknowledgement retires it") and the virtual-property definitions in docs/guides/live-queries.md.

Gap

The direct paths in packages/db/src/collection/mutations.ts committed the transaction before they registered it. commit() runs the handler synchronously up to its first await, so a synchronous confirmation found no persisting request and applied at once. It acknowledged nothing. Registration then installed the optimistic overlay over the confirmed row, and nothing retired it:

  • the row kept its optimistic value with $hasPendingWrites: true after the transaction completed;
  • the next remote write to the key reported $origin: 'local'.

The oracle missed it because its grammar wrote every source batch after the edit call returned. No generated history reached the handler's synchronous body.

The gap was found while classifying survivors of a code-weight mutant run on state.ts. A probe for mutant B4 showed main violating the documented $hasPendingWrites contract in this history.

Repair and witnesses

  • The grammar lets edit and delete steps carry a source batch that the shared handler writes before it returns, about one step in four. The model applies that batch with sync right after author, and both views are allowed publication cuts. The driver asserts that the handler wrote the batch.
  • Six pinned replays confirm an insert, an update, and a delete inside their own handlers, immediate and not, then settle and receive a later remote write.
  • A sampling witness requires the fixed campaign (seed 86103) to generate in-handler batches for edits and deletes, immediate and not, with rows.
  • Each direct path now registers the transaction, schedules its cleanup, and recomputes optimistic state before it commits. The production change is net −2 lines.
RunResult
Owner on 34095aa6d (oracle only)7 failed of 95: 5 of 6 pinned cases and both campaigns. The first failure is the batch publishing the server row as synced before the overlay.
Owner on 3ab42843995 passed
Mutant: one path commits before it registers (insert, update, delete)assertion failure: 4, 4, and 3 of 95
Mutant: one path registers, commits, then recomputesassertion failure: 2 of 95 on each path

The immediate delete case also passed before the repair: an immediate delete batch applies at once either way, and the delete leaves no overlay to retire.

Limits

  • The handler writes one batch, synchronously, before any await. A batch written after an await was already in the grammar as an ordinary sync step.
  • Transactions from createTransaction and createOptimisticAction register through mutate() before the caller commits, so they never had this gap. This change adds no witness for them.
  • Local-only collections reach the repaired path: their wrapper confirms synchronously when no user handler is given. Their rows never report pending writes, so the old order had no visible effect there.

Verification

On 3ab428439, with the built dist:

  • packages/db Vitest, typecheck off: 200 files, 7,857 tests. tsc --noEmit: no errors.
  • pnpm --filter @tanstack/db test:dist (290 tests) and pnpm test:minified-db pass.
  • Package unit tests: query-db-collection 558, electric-db-collection 629, offline-transactions 73.