Skip to content

fix(react-db): reject same-ID source replacement in mounted queries - #2003

Merged
KyleAMathews merged 8 commits into
mainfrom
codex/issue-1991-duplicate-source-guard
Oct 5, 2026
Merged

KyleAMathews merged 8 commits into
mainfrom
codex/issue-1991-duplicate-source-guard

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

What changes

A mounted useLiveQuery({ query }) hook could keep reading an old source Collection after a new Collection reused its ID. The old source could then fail during cleanup. This PR rejects the replacement during render, before the hook returns stale rows.

The error tells the application to unmount the hook and clean up the old source and client scope before it reuses the ID. The hook remembers source IDs across intervening queries and client switches.

Separate hooks can use different source Collections with the same ID. Suspense cache keys include each source object's identity for client-scoped and unscoped hooks. The merge with main keeps its pooled-query path, whose partitions use source object identity.

Deferred source startup

The rejection path can prepare shared DbClient sources before it detects the ID collision. A failing sync start could prevent later sources from starting and leave their readers at idle. The hook now attempts each pending start once and then throws the first startup error. A later reader can start a healthy source and observe its row.

Scope

Issue #1991 requested automatic replacement or migration guidance. This PR uses the maintainer's selected fail-fast rule. Explicit queryKey, legacy dependency arrays, and direct Collection input keep their existing identity rules.

Verification

  • The React source ID oracle passes 14 cases. The new multiple-failure case failed on the previous PR head at idle versus ready.
  • Six surrounding React hook and Suspense test files pass 110 cases.
  • The pooled-query oracle, GC, and identity suites pass on the merged main branch.
  • The React TypeScript project check and Prettier check pass.
  • The versioned oracle review records the RED/GREEN evidence and coverage limits.

Checklist

  • I have tested this code locally with pnpm test.

Release impact

  • This change affects published code, and I added a changeset.
  • This change is docs/CI/dev-only.

Fixes #1991.

Summary by CodeRabbit

  • Bug Fixes
    • React live queries now detect when a source collection ID is reused for a different collection within the same client scope and report an error. Suspense queries can use separate sources with the same ID, and deferred collection startup continues even when one source fails.
  • Documentation
    • Clarified source ID and query key requirements, including when IDs may be reused across hooks and client scopes.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 690c58d8-bed9-4105-b668-7c5d9ee8e3fe

📥 Commits

Reviewing files that changed from the base of the PR and between 0da7eae and dd95c37.

📒 Files selected for processing (5)
  • docs/contributing/oracle-coverage.md
  • docs/contributing/oracle-reviews/issue-1991-react-source-id.md
  • packages/react-db/src/useLiveQuery.ts
  • packages/react-db/tests/source-id-reuse-oracle.test.tsx
  • packages/svelte-db/src/useLiveQuery.svelte.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/contributing/oracle-coverage.md
  • docs/contributing/oracle-reviews/issue-1991-react-source-id.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The React query hook now tracks source Collection objects by ID and client scope for derived-identity queries. It throws when a different object reuses an ID in that scope. Suspense cache keys include prepared source identities for scoped and unscoped queries. Documentation and tests describe the rules and supported reuse cases.

Changes

React source identity

Layer / File(s) Summary
Source ID binding and Suspense identity
packages/react-db/src/useLiveQuery.ts
The hook records source object identities by ID and client scope for derived-identity queries. It throws when a different source object reuses an ID in the same scope. Suspense keys use prepared source identities. Deferred resumption attempts every collection before reporting the first error.
Identity and recovery tests
packages/react-db/tests/source-id-reuse-oracle.test.tsx
Tests cover collisions, cleanup, hook and client scope boundaries, Suspense retries, and deferred sync recovery.
Documented identity contract
docs/framework/react/overview.md, docs/guides/ssr.md, docs/contributing/oracle-coverage.md, docs/contributing/oracle-reviews/issue-1991-react-source-id.md, .changeset/fix-react-source-id-reuse.md
The guides describe source ID reuse rules and explicit queryKey identity requirements. Oracle records document tested cases and coverage boundaries. The changeset records the patch.

Svelte import cleanup

Layer / File(s) Summary
Remove import suppression
packages/svelte-db/src/useLiveQuery.svelte.ts
The duplicate-import ESLint suppression comment is removed. The untrack import remains unchanged.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: kevin-dp

Merge Risk: ⚪ Minimal · up to dd95c

The reviewed source-ID behavior has no identified merge-blocking issue; normal checks remain appropriate before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to dd95c

The change strengthens source identity isolation and prevents one startup failure from blocking other pending sources. No introduced security issue was established in the reviewed paths. Residual uncertainty concerns failed-source cleanup and overlapping or interrupted renders.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The traced exposure is application-supplied query and Collection identity interacting with hook-local bindings, client-owned collections, and shared query caches. Resumption can invoke configured sync callbacks even when rendering is rejected. The trace does not establish an independently attacker-controlled entrypoint or increased credential authority.

Trust Boundaries and Controls

  • inferred — The new guard is a query-integrity control, not tenant authentication or authorization. Its scope is the mounted hook and selected client; DbClient materialization still reuses an existing collection by ID. Applications must preserve appropriate client ownership rather than treating source IDs as a security boundary.

Resilience and Maintainability Implications

  • observed — The recovery tests assert that two failing startup callbacks are both attempted and that a later healthy reader reaches ready with one healthy startup. A separate test asserts that a descriptor without a requested startup remains available for a later reader. These assertions support shared-source failure containment, but do not establish failed-source registry cleanup or overlapping-reader recovery.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes support Issue #1991, including the React implementation, React tests, identity documentation, oracle records, and changeset. The change in packages/svelte-db/src/useLiveQuery.svelte.ts … Remove the unrelated Svelte-only comment change, or provide a concrete connection between that change and the Issue #1991 implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: rejecting same-ID source replacement in mounted React database queries.
Description check ✅ Passed The description explains the change, motivation, scope, deferred-startup behavior, verification, release impact, and changeset. It includes the required checklist, although some headings differ from t…
Linked Issues check ✅ Passed Issue #1991 requests same-ID replacement support and, at minimum, migration guidance. The PR uses the selected fail-fast contract for structured derived-identity queries. useLiveQuery.ts detects a d…
Full details: Out of Scope Changes check

Explanation

Most changes support Issue #1991, including the React implementation, React tests, identity documentation, oracle records, and changeset. The change in packages/svelte-db/src/useLiveQuery.svelte.ts only removes an ESLint suppression comment and does not connect to React source identity or same-ID replacement behavior.

Full details: Docstring Coverage

Explanation

Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@2003

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@2003

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@2003

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@2003

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@2003

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@2003

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@2003

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@2003

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@2003

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@2003

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@2003

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@2003

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@2003

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@2003

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@2003

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@2003

@tanstack/react-router-with-db

npm i https://pkg.pr.new/@tanstack/react-router-with-db@2003

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@2003

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@2003

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@2003

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@2003

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@2003

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@2003

commit: dd95c37

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 180 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/client.js 3.61 kB
packages/db/dist/esm/collection-options.js 236 B
packages/db/dist/esm/collection/change-events.js 1.64 kB
packages/db/dist/esm/collection/changes.js 2.38 kB
packages/db/dist/esm/collection/cleanup-queue.js 808 B
packages/db/dist/esm/collection/events.js 481 B
packages/db/dist/esm/collection/index.js 4.57 kB
packages/db/dist/esm/collection/indexes.js 2.06 kB
packages/db/dist/esm/collection/lifecycle.js 2.66 kB
packages/db/dist/esm/collection/mutations.js 3.07 kB
packages/db/dist/esm/collection/state.js 8.27 kB
packages/db/dist/esm/collection/subscription.js 8.44 kB
packages/db/dist/esm/collection/sync.js 5.37 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 5.61 kB
packages/db/dist/esm/event-emitter.js 961 B
packages/db/dist/esm/index.js 4 kB
packages/db/dist/esm/indexes/auto-index.js 841 B
packages/db/dist/esm/indexes/base-index.js 1.25 kB
packages/db/dist/esm/indexes/basic-index.js 2.01 kB
packages/db/dist/esm/indexes/btree-index.js 2.3 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 370 B
packages/db/dist/esm/live-query-adapter.js 338 B
packages/db/dist/esm/live-query-observer.js 4.59 kB
packages/db/dist/esm/live-query-options.js 1.06 kB
packages/db/dist/esm/live-query-window-controller.js 4.12 kB
packages/db/dist/esm/local-only.js 1.08 kB
packages/db/dist/esm/local-storage.js 2.17 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 702 B
packages/db/dist/esm/persisted-readiness.js 195 B
packages/db/dist/esm/proxy.js 3.17 kB
packages/db/dist/esm/query/builder/clone-query.js 766 B
packages/db/dist/esm/query/builder/functions.js 1.45 kB
packages/db/dist/esm/query/builder/index.js 6.81 kB
packages/db/dist/esm/query/builder/query-ir.js 116 B
packages/db/dist/esm/query/builder/ref-proxy-identity.js 292 B
packages/db/dist/esm/query/builder/ref-proxy.js 1.48 kB
packages/db/dist/esm/query/builder/wrapper-identity.js 221 B
packages/db/dist/esm/query/compiler/evaluators.js 2.1 kB
packages/db/dist/esm/query/compiler/expressions.js 603 B
packages/db/dist/esm/query/compiler/group-by.js 4.2 kB
packages/db/dist/esm/query/compiler/index.js 9.39 kB
packages/db/dist/esm/query/compiler/joins.js 3.06 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 1.14 kB
packages/db/dist/esm/query/compiler/order-by.js 2 kB
packages/db/dist/esm/query/compiler/parent-routes.js 319 B
packages/db/dist/esm/query/compiler/query-equivalence.js 455 B
packages/db/dist/esm/query/compiler/route-metadata.js 1.24 kB
packages/db/dist/esm/query/compiler/select.js 1.59 kB
packages/db/dist/esm/query/effect.js 4.86 kB
packages/db/dist/esm/query/equality-conjunct.js 486 B
packages/db/dist/esm/query/equality-value-identity.js 591 B
packages/db/dist/esm/query/expression-helpers.js 1.45 kB
packages/db/dist/esm/query/ir-stable-identity.js 4.22 kB
packages/db/dist/esm/query/ir.js 1.7 kB
packages/db/dist/esm/query/live-query-collection.js 391 B
packages/db/dist/esm/query/live/bucket-facade-adapter.js 2.67 kB
packages/db/dist/esm/query/live/collection-config-builder.js 6.47 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 2.05 kB
packages/db/dist/esm/query/live/graph-scheduler.js 303 B
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/materialized-pipeline.js 2.32 kB
packages/db/dist/esm/query/live/ordered-source-loader.js 4.14 kB
packages/db/dist/esm/query/live/subset-demand-controller.js 1.65 kB
packages/db/dist/esm/query/live/utils.js 1.2 kB
packages/db/dist/esm/query/optimizer.js 2.92 kB
packages/db/dist/esm/query/pooled-live-query.js 4.06 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/runtime-reference-identity.js 630 B
packages/db/dist/esm/query/subset-dedupe.js 493 B
packages/db/dist/esm/scheduler.js 1.13 kB
packages/db/dist/esm/SortedMap.js 1.6 kB
packages/db/dist/esm/strategies/debounceStrategy.js 331 B
packages/db/dist/esm/strategies/queueStrategy.js 488 B
packages/db/dist/esm/strategies/throttleStrategy.js 386 B
packages/db/dist/esm/sync-persistence.js 530 B
packages/db/dist/esm/transactions.js 3.89 kB
packages/db/dist/esm/utils.js 1.49 kB
packages/db/dist/esm/utils/array-utils.js 270 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 3.02 kB
packages/db/dist/esm/utils/callbacks.js 174 B
packages/db/dist/esm/utils/comparison.js 1.59 kB
packages/db/dist/esm/utils/cursor.js 677 B
packages/db/dist/esm/utils/error.js 167 B
packages/db/dist/esm/utils/get-or-create.js 155 B
packages/db/dist/esm/utils/index-optimization.js 2.42 kB
packages/db/dist/esm/utils/source-record.js 140 B
packages/db/dist/esm/utils/type-guards.js 230 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 413 B

compressed-size-action::db-package-size

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 8.66 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/DbProvider.js 317 B
packages/react-db/dist/esm/development.js 190 B
packages/react-db/dist/esm/HydrationBoundary.js 263 B
packages/react-db/dist/esm/index.js 330 B
packages/react-db/dist/esm/live-query-internals.js 282 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.93 kB
packages/react-db/dist/esm/useLiveQuery.js 3.27 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 1.33 kB
packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@changeset-bot

changeset-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dd95c37

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/react-db Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@KyleAMathews
KyleAMathews merged commit e5a3d0e into main Oct 5, 2026
12 checks passed
@KyleAMathews
KyleAMathews deleted the codex/issue-1991-duplicate-source-guard branch October 5, 2026 15:49
KyleAMathews added a commit that referenced this pull request Oct 5, 2026
…iate }) (#2030)

* fix(db): drop optimistic state at settlement and queue direct writes

Remove begin({ immediate }) and post-completion retention. A sync
transaction committed while an optimistic transaction persists is accepted,
queued, and applied when that transaction settles. Query Collection direct
writes and persisted internal applies queue the same way. A Query refetch no
longer cancels an accepted result (#1990).

Work in progress: oracle and dependent tests are not yet aligned.

Co-authored-by: Isaac <no-reply@databricks.com>

* wip: align oracles and fixtures with settlement-time drop

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(db): track accepted and visible moments of a sync transaction

Commit receipts resolve when a transaction is visible again. Handler-facing
writes (Query Collection direct writes, persisted mutation confirmation)
wait for acceptance through whenSyncAccepted, so a handler can await its
own write. Local origin attribution covers only confirmations held at the
completion boundary.

Co-authored-by: Isaac <no-reply@databricks.com>

* wip: align db tests with settlement-time drop and two-stage receipts

Co-authored-by: Isaac <no-reply@databricks.com>

* wip: align metadata, replay, and load-subset oracles with acceptance boundary

Co-authored-by: Isaac <no-reply@databricks.com>

* wip: readiness waits for every accepted transaction

Co-authored-by: Isaac <no-reply@databricks.com>

* fix: hold eager fetches older than a direct write; PowerSync handler waits for acceptance

Also align Query Collection tests, glossary, docs, and add the changeset.

Co-authored-by: Isaac <no-reply@databricks.com>

* test(node-db-sqlite-persistence): handler awaits acceptance behind a pending source write

Co-authored-by: Isaac <no-reply@databricks.com>

* test: remove remaining immediate arguments; align TrailBase abort test; refresh mangle cache

Co-authored-by: Isaac <no-reply@databricks.com>

* test(query-db): keep an accepted superseded result's ownership

A refetch that supersedes an accepted result held by a persisting mutation
must diff against that result's ownership. Earlier supersession tests held
durable storage, where the core transaction had already applied, so the
ownership rollback guard was never observed.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix: reject an aborted subset load while its accepted rows apply

Electric and TrailBase check the signal before commit to discard a stale
page, and reject the load with AbortError when the caller aborted after
acceptance. The load-subset oracles state the law.

Co-authored-by: Isaac <no-reply@databricks.com>

* test(query-db): hold the first durable write in the #1990 reproduction

The refetch variant now holds either the result's own row write or the first
write after the refetch starts. The second interleaving fails on origin/main
with the collection in error.

Co-authored-by: Isaac <no-reply@databricks.com>

* docs: add the settlement-drop oracle review record

Co-authored-by: Isaac <no-reply@databricks.com>

* test(db): type the subset error matrix sources; format two oracle files

The matrix assigned static mock-sync sources to a type that admits only
failing sources, which vitest reported as four unhandled type errors on main.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(db): only accepted sync transactions hold rows or attribute origin

A sync transaction still open when an optimistic transaction settled counted
as queued, so a later remote write could be marked local. The
optimistic-history grammar now opens a sync transaction across settlement and
commits or aborts it later, with pinned replays of both review probes.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(db): accept a DbClient chunk ahead of an open source transaction

A hydration chunk was pushed raw after any open source transaction, so
accepted-row readers missed it and the source's commit() targeted the chunk.
The chunk now goes through the accepted path ahead of open transactions.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix: Collection readiness counts accepted rows

A mutation handler that awaited its own Collection's readiness during
startup waited for rows its own persisting transaction held. Readiness now
resolves once the startup rows are accepted; they still publish with the
drop. Core drops its readiness deferral, Query and Electric mark ready at
acceptance, and PowerSync's startup workaround is gone. Subset loads still
wait for visible rows.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix: reject an aborted subset load at every cut

Electric and TrailBase now reject with AbortError when the caller aborts
before the fetch, when the fetch fails because of the abort, after the fetch
but before commit, and between pages. Accepted pages still apply, and a
TrailBase load waits for them to be visible; cleanup stays quiet. The
main-pinned Electric and TrailBase abort tests now expect the rejection.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(query-db): merge an older fetch with the direct writes made since

A fetch that started before a direct write was discarded whole, losing
server rows for keys the write never touched. It now keeps the server's rows
and takes the accepted row only for keys written after the fetch began. The
per-key write generations are cleared when no fetch is in flight. The
ownership oracle states the law at four cuts.

Co-authored-by: Isaac <no-reply@databricks.com>

* docs(db): state the settlement-drop law in the live-query architecture

Replace the retired retention and insert-dependency paragraph.

Co-authored-by: Isaac <no-reply@databricks.com>

* refactor(db): delete unreachable dependent-invalidation code

commit(signal) is the only public path to cancellation, and it always
targets the open last transaction, so no committed transaction can depend on
a canceled one. Delete the invalid-committed replay branch,
rejectInvalidCommittedTransactions, invalidationError, duplicateKeyError, and
admittedAgainstExistingRow. A cancel of any other transaction, or a replay
that invalidates one, now throws SyncQueueInvariantError; two tests witness
both throws.

Co-authored-by: Isaac <no-reply@databricks.com>

* docs: cover readiness, aborted loads, older fetches, and DbClient chunks in the changeset

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(db): keep open-transaction invalidation; hold the newest completed row

The R3 deletion removed invalidation of an open sync transaction, which a
later transaction begun inside it can reach by committing first; the R3
review probe showed the accepted receipt then never resolved. Restore it for
open transactions only; a committed one still throws the invariant error.

The extended optimistic-history campaign found two more counterexamples. A
rollback while a sync transaction on its key was still open lost its delete
event, because pre-sync capture counted the open transaction. When two
completed transactions were held on one key, the older one's row showed after
the newer one left the transaction map. Capture and the drain filter now
count only accepted transactions, and the hold keeps the newest owner's row.
All three are pinned replays.

Co-authored-by: Isaac <no-reply@databricks.com>

* fix(svelte-db): restore the import-duplicates comments

The pre-commit eslint --fix in the last merge treated `svelte` and
`svelte/reactivity` as one module. It merged the imports into
`import { SvelteMap, untrack } from 'svelte'`, and `svelte` does not export
`SvelteMap`, so 65 svelte-db tests failed with `SvelteMap is not a
constructor`. #2003 had dropped the comments that disable that rule here.
Restore them, as before #2003, so a local hook cannot merge the imports
again.

Co-authored-by: Isaac <no-reply@databricks.com>

* chore: describe the rollback and held-row fixes in the changeset

Co-authored-by: Isaac <no-reply@databricks.com>

* test(react-db): drop begin({ immediate }) from tests merged from main

Co-authored-by: Isaac <no-reply@databricks.com>

* test: remove begin({ immediate }) from merged tests and examples; load one @tanstack/db in the DO fixture

The persisted oracle tests merged from main still pinned immediate source
writes. They now state the queued-source-write law under settlement drop.
The SSR examples drop the option, and the on-demand fixture returns its
commit receipt so the load settles when its rows are visible.

The Cloudflare Durable Object E2E fixture imported createCollection from
db's dist while wrangler loaded the persistence code's @tanstack/db from
source, so the worker ran two copies of @tanstack/db. Import it from one
module graph, as AGENTS.md requires.

Co-authored-by: Isaac <no-reply@databricks.com>

---------

Co-authored-by: Isaac <isaac@example.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot mentioned this pull request Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useLiveQuery({ query }) keeps a cleaned source after same-ID collection instance replacement

1 participant