Skip to content

fix(db): keep a collection from garbage collection while its transaction persists - #2258

Merged
KyleAMathews merged 14 commits into
TanStack:mainfrom
Nabil372:fix/db-gc-waits-for-persisting-transactions
Oct 26, 2026
Merged

KyleAMathews merged 14 commits into
TanStack:mainfrom
Nabil372:fix/db-gc-waits-for-persisting-transactions

Conversation

@Nabil372

@Nabil372 Nabil372 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Keep a Collection alive while a transaction that touches it is persisting. A screen can submit a write and close while its mutation handler waits for server confirmation. On origin/main, automatic garbage collection can then clear the optimistic row and end the sync run before the handler settles. With Electric, cleanup can reject awaitTxId with StreamAbortedError even after the server accepts the write. Issue #2256 reports this failure in web apps and Expo apps on iOS and Android.

Every touched Collection now treats a persisting transaction as a retention hold, alongside active subscribers and pending preloads. After the final persisting hold ends, an otherwise inactive Collection starts a fresh gcTime delay. This keeps its optimistic state and sync run available while the mutation handler waits. A transaction that touches several Collections retains each one.

Retention deadlines and cleanup

Transaction settlement notifies all Collections that track the transaction or appear in its mutations. The lifecycle manager cancels obsolete timer and idle callbacks before it schedules the renewed deadline. The cleanup queue and lifecycle manager share one elapsed clock. An absolute handoff deadline preserves the remaining 50ms startup or preload grace when a transaction settles quickly. A real last unsubscribe uses gcTime without that startup minimum.

Explicit cleanup() still starts immediately, including while a transaction persists. Cleanup clears public rows synchronously, then waits for adapter cleanup before publishing cleaned-up status. Later transaction settlement now keeps another transaction's discarded optimistic rows absent during and after cleanup. The state manager still prunes settled transactions.

The public configuration and mutation guide document these boundaries:

  • Pending manual transactions do not retain Collections. Keep a subscription while you need draft visibility, until commit or rollback. Restart after GC does not guarantee restoration of discarded draft rows.
  • Rollback of a manual transaction that stayed pending preserves its existing GC deadline.
  • A transaction that never settles retains its Collections throughout its persisting phase.
  • Non-positive or non-finite gcTime disables automatic GC.
  • Eligible cleanup runs at a host idle opportunity. The public contract does not require cleanup at the first usable callback or promise exact elapsed latency.

The implementation shares transaction completion and mutation-merging paths. One timer entry point distinguishes settlement from last unsubscribe. Idle cleanup and queue processing no longer need separate dispatch methods. State calculation counts directly, and settled transactions use deferred sorted-map deletion. Current and prior rows share keyed reads and virtual-property snapshots, preserving upstream fields and previous-value semantics. These changes eliminate duplicate branches and temporary arrays while preserving the retention, publication, and cleanup contracts.

Oracle Coverage

Coverage-map owner: Collection lifecycle. The laws come from BaseCollectionConfig.gcTime, existing startup/preload, ownership and cleanup contracts, and the explicit pending-draft and pending-rollback decisions.

Independent models compute retention from logical holds, their last release, transaction phases, and the absolute handoff deadline. They import no production scheduler or lifecycle classifier. The boundary property runs 250 fixed-seed and 250 random histories. It requires all 33 declared reach states. Histories combine multiple subscribers, up to seven transactions, delayed commit, three persisting siblings, non-FIFO settlement, preloads, and held or rejecting automatic cleanup. The original two-Collection property requires all seven of its reach states.

A mutation receiving driver shares the retention model and adds an independent projection from source rows and mutation intent. It generates direct updates/deletes, shared update/delete, and canceled insert/delete ownership with a surviving peer update. Both Collection orientations, subscriber states, and completion/failure/rollback outcomes run across 150 fixed-seed and 150 random histories. All 11 declared states must be reached. A fully empty transaction cannot substitute for a genuinely persisting handler.

Comparisons check exact public keys and values, Collection status, transaction phases, caller receipts, and tracked unsettled transactions at the relevant cuts. Cleanup checks synchronous row discard separately from final status after adapter settlement. The driver verifies that idle delivery starts adapter cleanup before joining its promise. Rejection checks the cleanup error wrapper and exact adapter cause.

RED/GREEN evidence: The retention oracle rejects automatic GC during persistence without the fix and passes with the final implementation. Hostile source mutants fail the intended comparisons. They include missing retention, obsolete idle cleanup, lost startup grace, rows returning after cleanup, skipped transaction pruning, collapsed subscribers, and ownership limited to surviving mutations. An exact guarded seed/path replay rejects premature automatic-cleanup status, then passes with production restored.

Three Chromium histories pass with native idle provenance, budget, and cancellation evidence. Twelve Electric/Postgres paths pass with real provider I/O and controlled idle delivery. They cross gcTime 10/100ms with absent subscribers or a final unsubscribe. Eight paths use two separate SQL transactions and both local settlement orders. After the first genuine acknowledgement, the survivor remains SQL-open, unacknowledged, persisting, and caller-pending beyond the first-release deadline. Final confirmation checks source rows, virtual fields, caller success, and renewed GC. A partial-hold runtime mutant fails all eight sibling paths. A missing-hold mutant fails the four one-write paths. CI runs both receiving drivers and records native browser evidence.

These finite histories establish native-host and real-provider behavior separately. They do not establish combined browser/provider scheduling, three-or-more provider writers, provider failure/recovery, or persistence-wrapper combinations. Fresh optimistic keys exclude combined GC histories with same-key rollback cascades or queued sync transactions. The coverage map names the receiving witness needed before extending those claims. Paced/offline restoration and restoration of discarded pending drafts remain outside this coverage.

Retention evidence, cleanup/provider evidence and contract decisions, interaction and mutation receiving evidence, and source fingerprints record experiments, observation cuts, reach counts, and limits.

Validation

  • Final focused run: 1,033 tests across 21 files passed, with no type errors. Coverage includes GC, cleanup, queue behavior, transaction ownership, optimistic payloads, composition, outcomes, metadata publication, virtual row fields, and settlement boundaries. Native Chromium passed three histories. Actual Electric/Postgres passed twelve paths. Three replay registry checks also passed after combining both branches’ entries.
  • A hostile ordinary-deletion mutant failed ten optimistic-outcome checks at the public rows observed when the receipt settled. A prior-state snapshot mutant also failed event comparisons in the publication and metadata owners. After exact source restoration, all 76 tests in the four calibration files passed. The restored source passed the full focused run. The 19 production-error oracle tests also pass with the original frozen error template. The documentation check covers 757 examples with no new failures or obsolete suppressions. DB source/test TypeScript, changed-file lint, formatting, private-member cache, and whitespace checks passed. The broader guarded replay subprocess campaign stalled and was interrupted. Its partial run receives no clean GREEN credit.
  • pnpm size:compare --packages db --files compares the whole PR with merge-base 1640de834 on origin/main. DB decreases by 170B gzip / 39B Brotli. The typical app decreases by 182B gzip / 72B Brotli. Every measured feature scenario also decreases in both formats. The integration size record records every scenario, the shared-state refactors, calibration and verification evidence. No tests or expected results changed for this refactor.
  • All 25 CI checks pass for ccbcbecc22131383c6d3d0f3ed891ca22eee535a, including browser E2E and its native GC oracle. The latest CodeRabbit review covers baa7d7d730329fdbf1fab30cc25a0932abc757da. Automated review is paused, so it does not cover the size refactor.

Checklist

  • Tested locally with focused core and oracle checks, native Chromium, and actual Electric/Postgres. The validation section records the final results.

Release Impact

  • This change affects published code and includes a patch changeset for @tanstack/db.
  • This change is docs/CI/dev-only (no release).

Closes #2256

…ion persists

Automatic GC checked subscribers and pending preloads but not transactions. A collection whose last subscriber left while a mutation handler was still running was cleaned up after gcTime: its optimistic rows disappeared and waits on the sync run, such as Electric's awaitTxId, rejected, so the write failed even when the server accepted it.

A persisting transaction that touches the collection now holds it, and the gcTime delay starts again when the last one completes or fails. Explicit cleanup() is unchanged.

A new generated-history oracle checks retirement against an independent deadline model across subscribe, unsubscribe, insert, settlement, clock steps and explicit cleanup.
…ections

The GC oracle now runs every history over two Collections. An insert writes
to one Collection through its onInsert handler, or to both through
createTransaction with a manual commit, and the model keeps a deadline per
Collection. A settlement that restarts the delay only for the first
Collection a transaction touched now fails the oracle; the one-Collection
grammar let it pass. The oracle also runs in the package's test:oracles
campaign.
@changeset-bot

changeset-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ccbcbec

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

This PR includes changesets to release 25 packages
Name Type
@tanstack/db Patch
@tanstack/angular-db Patch
@tanstack/capacitor-db-sqlite-persistence-e2e-app Patch
@tanstack/db-sqlite-persistence-core Patch
@tanstack/electric-db-collection Patch
@tanstack/expo-db-sqlite-persistence-e2e-app Patch
@tanstack/offline-transactions Patch
@tanstack/powersync-db-collection Patch
@tanstack/preact-db Patch
@tanstack/query-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/tauri-db-sqlite-persistence-e2e-app Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db Patch
@tanstack/browser-db-sqlite-persistence Patch
@tanstack/capacitor-db-sqlite-persistence Patch
@tanstack/cloudflare-durable-objects-db-sqlite-persistence Patch
@tanstack/electron-db-sqlite-persistence Patch
@tanstack/expo-db-sqlite-persistence Patch
@tanstack/node-db-sqlite-persistence Patch
@tanstack/react-native-db-sqlite-persistence Patch
@tanstack/tauri-db-sqlite-persistence 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

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c0c5887e-aadd-4d27-b118-cabaaada57f1
Commits

Reviewing files that changed from the base of the PR and between d061858 and baa7d7d.

Files selected for processing: 10
  • docs/contributing/oracle-coverage.md
  • docs/contributing/oracle-reviews/pr-2258-gc-retention-round3.md
  • docs/contributing/oracle-reviews/pr-2258-gc-retention.sources.json
  • packages/db/package.json
  • packages/db/tests/collection-gc-mutation-oracle.property.test.ts
  • packages/db/tests/collection-gc-retention-oracle.property.test.ts
  • packages/db/tests/collection-gc-retention-oracle.ts
  • packages/db/tests/oracle-config.ts
  • packages/db/tests/oracle-replay-manifest.ts
  • packages/electric-db-collection/e2e/collection-gc.e2e.test.ts
Files skipped from review as they are similar to previous changes: 2
  • docs/contributing/oracle-reviews/pr-2258-gc-retention.sources.json
  • docs/contributing/oracle-coverage.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

Automatic collection garbage collection now waits while a transaction that touches the collection is persisting. Settlement or failure starts GC scheduling with gcTime and any remaining startup grace. Explicit cleanup() remains immediate. New model-based, Chromium, and Electric/PostgreSQL checks cover retention behavior.

Changes: Collection garbage-collection lifecycle

Layer / File(s) Summary
Retain collections through transaction settlement
packages/db/src/collection/cleanup-queue.ts, packages/db/src/collection/lifecycle.ts, packages/db/src/transactions.ts, packages/db/src/collection/state.ts, packages/db/src/types.ts, packages/db/mangle-cache.json, .changeset/gc-waits-for-persisting-transactions.md, docs/guides/mutations.md
Automatic GC waits while a transaction persists. Settlement or failure starts GC scheduling with gcTime and any remaining startup grace. Cleanup-state handling skips replaying settled transaction mutations after cleanup starts.
Model and test GC retention boundaries
packages/db/tests/collection-gc-retention-oracle.ts, packages/db/tests/collection-gc-retention-oracle*.test.ts, packages/db/tests/collection-gc-transaction-oracle.property.test.ts, packages/db/tests/collection-gc-mutation-oracle.property.test.ts, packages/db/tests/oracle-config.ts, packages/db/tests/oracle-replay-manifest.ts, packages/db/package.json, docs/contributing/oracle-coverage.md, docs/contributing/oracle-reviews/pr-2258-gc-retention*
Independent models and generated histories check retirement, transaction states, and visible rows. Oracle registration, coverage documentation, and review records describe the checks and their limits.
Exercise native idle retention scenarios
packages/db/e2e/collection-gc/*, packages/db/playwright.collection-gc.config.ts, packages/db/tsconfig.json, .github/workflows/e2e-tests.yml
Chromium tests exercise idle, startup, and pending-transaction scenarios. The workflow runs the browser checks and collects their evidence.
Verify retention with Electric and PostgreSQL
packages/electric-db-collection/e2e/collection-gc.e2e.test.ts
The end-to-end test checks that an Electric collection remains retained while PostgreSQL transactions await provider confirmation, then checks settlement and idle cleanup.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • TanStack/db#2256 — The change prevents automatic cleanup while a touching transaction persists and starts GC scheduling after settlement.

Suggested reviewers: kyleamathews

Merge Risk: Minimal · up to baa7d

This change keeps a Collection alive while a transaction touching it is persisting, and it adds model-based, browser, and Electric/PostgreSQL checks around that behavior. No merge-blocking issue was identified in the supplied review context.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 16 files. (4 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the main behavior change: a collection is kept from garbage collection while a transaction it touches is persisting. It matches the raw summary, which describes a persisting transactio…
Linked Issues check Passed Issue #2256 requires that a Collection stay available while a transaction touching it persists. Its optimistic rows and awaitTxId/awaitMatch waits must survive until settlement. GC timing may star…
Out of Scope Changes check Passed Production changes are limited to the GC retention and cleanup path for Issue #2256:
- lifecycle.ts: persisting-hold gate, startup grace, renewed deadline.
- transactions.ts: hold release on settl…
Description check Passed The pull request description provides complete sections for changes, oracle coverage, validation, checklist, and release impact. It documents the motivation, behavior, evidence, test results, known li…
Full details: Title check

Explanation

The title names the main behavior change: a collection is kept from garbage collection while a transaction it touches is persisting. It matches the raw summary, which describes a persisting transaction acting as a retention hold, and the PR objectives, which describe the same fix for issue #2256. The title is in the conventional commit form and fits the scope (db package).

Full details: Linked Issues check

Explanation

Issue #2256 requires that a Collection stay available while a transaction touching it persists. Its optimistic rows and awaitTxId/awaitMatch waits must survive until settlement. GC timing may start after completion or rollback. Explicit cleanup() keeps its current behavior.

Whole-PR diff (merge base 427a7c3 to head baa7d7d) shows the following implementation:

  • packages/db/src/collection/lifecycle.ts: canGarbageCollect() now also requires !this.state.hasPersistingTransaction(). Automatic GC therefore cannot start while a persisting transaction exists.
  • packages/db/src/transactions.ts: touchCollection(releasedHold) calls startGCTimerAfterSettlement() for each touched Collection when a persisting hold is released. This covers successful commits and failures. Rollback passes this flag only when the transaction was persisting, so a rollback of a pending manual transaction keeps its existing deadline. This matches the issue text and the documented boundary.
  • startGCTimerAfterSettlement() cancels obsolete scheduled work before it schedules the renewed deadline. This prevents stale idle cleanup from running on the old deadline.
  • Explicit cleanup() is not gated by the new hold. The PR description and the hasStartedCleanup() guard in state.ts keep explicit cleanup authoritative.

Test coverage is claimed in the PR: property oracles for retention and mutation shapes, a native Chromium harness, and an Electric/Postgres E2E test that checks awaitTxId across the GC deadline. I did not run these tests. The status of this check depends on the stated validation results. The full local DB run had one replay child timeout, and its isolated rerun passed.

All issue requirements are coding requirements. No non-coding requirement applies. The issue's public hold API request is conditional and does not apply, because the PR chose transaction-based retention.

Full details: Out of Scope Changes check

Explanation

Production changes are limited to the GC retention and cleanup path for Issue #2256:

  • lifecycle.ts: persisting-hold gate, startup grace, renewed deadline.
  • transactions.ts: hold release on settlement and rollback.
  • cleanup-queue.ts: shared cleanupNow() clock. This supports the absolute handoff deadline.
  • state.ts: skips replay of settled-transaction mutations after cleanup starts. This prevents discarded rows from returning. The change is needed because retention can now keep a transaction alive past cleanup. It does not alter explicit cleanup timing.
  • types.ts and docs/guides/mutations.md: document the new contract.
  • mangle-cache.json: mangled names for the new private members.

Test and harness files support the same behavior: oracle property tests and models, oracle-config.ts and oracle-replay-manifest.ts registrations, the Chromium GC receiver (e2e/collection-gc/*, Playwright config, tsconfig.json, e2e-tests.yml), and the Electric E2E test. These are supporting tests and infrastructure. Their connection to the objective is explicit.

The review-record documents and sources.json fingerprints are process artifacts. They describe the same GC behavior and do not add unrelated product features. They increase review volume, but they do not show an unrelated change. The changeset is a patch for @tanstack/db, which matches the behavior change.

No changed code shows an unrelated feature.

Full details: Description check

Explanation

The pull request description provides complete sections for changes, oracle coverage, validation, checklist, and release impact. It documents the motivation, behavior, evidence, test results, known limits, changeset, and issue closure. The headings differ slightly from the template, but all required information is present.

Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 16 files. (4 skipped: 4 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 26, 2026 •

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

npm i https://pkg.pr.new/TanStack/db/@tanstack/offline-transactions@2258

@tanstack/powersync-db-collection

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

@tanstack/preact-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/preact-db@2258

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/react-router-with-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: ccbcbec

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔇 Additional comments: 19
packages/db/src/collection/cleanup-queue.ts: 1

3-6: LGTM!

Also applies to: 38-38, 83-83, 96-96

packages/db/src/collection/lifecycle.ts: 1

13-13: LGTM!

Also applies to: 54-54, 289-290, 308-320, 334-334

packages/db/src/transactions.ts: 1

679-679: LGTM!

Also applies to: 698-698, 711-711, 720-720, 783-783, 838-838

packages/db/mangle-cache.json: 1

418-419: LGTM!

packages/db/tests/collection-gc-retention-oracle.ts: 1

1-185: LGTM!

packages/db/tests/collection-gc-transaction-oracle.property.test.ts: 1

8-8: LGTM!

Also applies to: 14-16, 38-40, 104-104, 191-191, 253-253, 259-259, 301-301, 307-308, 321-321, 326-337, 396-398, 412-414

packages/db/tests/collection-gc-retention-oracle.property.test.ts: 1

1-434: LGTM!

packages/db/package.json: 1

26-26: LGTM!

Also applies to: 29-29

packages/db/tests/oracle-config.ts: 1

55-55: LGTM!

packages/db/tests/oracle-replay-manifest.ts: 1

246-249: LGTM!

Also applies to: 280-287

packages/db/tsconfig.json: 1

29-29: LGTM!

packages/db/e2e/collection-gc/browser.html: 1

1-6: LGTM!

packages/db/e2e/collection-gc/browser.ts: 1

1-193: LGTM!

packages/db/e2e/collection-gc/retention.spec.ts: 1

1-46: LGTM!

packages/db/playwright.collection-gc.config.ts: 1

1-19: LGTM!

.github/workflows/e2e-tests.yml: 1

117-120: LGTM!

Also applies to: 129-129

docs/contributing/oracle-reviews/pr-2258-gc-retention.md: 1

1-147: LGTM!

docs/contributing/oracle-reviews/pr-2258-gc-retention.sources.json: 1

1-24: LGTM!

docs/contributing/oracle-coverage.md-619-619: 1

619-619: Minor · Maintainability & Code Quality · Quick win

⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Clarify the compound term.

Change sync entry to sync-entry processing or entry into sync. LanguageTool flags the missing hyphen, and the current wording is ambiguous.

Source: Linters/SAST tools


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6f9e4c68-9aff-4b64-9e91-956e765d9a2d
Commits

Reviewing files that changed from the base of the PR and between f19f490 and 4251927.

Files selected for processing: 21
  • .changeset/gc-waits-for-persisting-transactions.md
  • .github/workflows/e2e-tests.yml
  • docs/contributing/oracle-coverage.md
  • docs/contributing/oracle-reviews/pr-2258-gc-retention.md
  • docs/contributing/oracle-reviews/pr-2258-gc-retention.sources.json
  • packages/db/e2e/collection-gc/browser.html
  • packages/db/e2e/collection-gc/browser.ts
  • packages/db/e2e/collection-gc/retention.spec.ts
  • packages/db/mangle-cache.json
  • packages/db/package.json
  • packages/db/playwright.collection-gc.config.ts
  • packages/db/src/collection/cleanup-queue.ts
  • packages/db/src/collection/lifecycle.ts
  • packages/db/src/transactions.ts
  • packages/db/src/types.ts
  • packages/db/tests/collection-gc-retention-oracle.property.test.ts
  • packages/db/tests/collection-gc-retention-oracle.ts
  • packages/db/tests/collection-gc-transaction-oracle.property.test.ts
  • packages/db/tests/oracle-config.ts
  • packages/db/tests/oracle-replay-manifest.ts
  • packages/db/tsconfig.json
Files skipped from review as they are similar to previous changes: 2
  • packages/db/src/types.ts
  • .changeset/gc-waits-for-persisting-transactions.md

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔇 Additional comments: 1
docs/contributing/oracle-coverage.md-619-619: 1

619-619: Minor · Maintainability & Code Quality · Quick win

⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Reword the ready-callback sentence.

The phrase during entry into sync is difficult to parse and triggered the hyphenation check. Rewrite it as: A ready callback that throws while entering sync surfaces when the sync function returns.

Source: Linters/SAST tools


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4a6db227-9cac-4948-91ea-538063e97622
Commits

Reviewing files that changed from the base of the PR and between 4251927 and f1a3997.

Files selected for processing: 1
  • docs/contributing/oracle-coverage.md

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

@KyleAMathews
KyleAMathews merged commit 7538afd into TanStack:main Oct 26, 2026
26 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 26, 2026
KyleAMathews pushed a commit that referenced this pull request Oct 27, 2026
Merges main at b8d8ee0 (#2258 GC waits for persisting transactions,
#2257 electric shape-stream listener cleanup).

- lifecycle: main's GC grace/handoff and persisting-transaction hold kept
  beside the PR's health state; main's syncError field is replaced by the
  PR's statusError.
- load-subset replay oracle: main's keyed-request/reacquisition properties
  kept under the PR's request-error law: after initial-query readiness a
  fresh keyed request failure is a request error, not terminal, and a failed
  replay ends nothing.
- GC retention oracle uses fail() for its first-load failure.
- test:oracles runs both main's GC oracles and the PR's hydration oracle;
  mangle cache regenerated.

Co-authored-by: Isaac <no-reply@databricks.com>
KyleAMathews pushed a commit that referenced this pull request Oct 28, 2026
#2168 splits Collection status from sync errors. Its law 16 makes every
lazy demand failure after initial-query readiness a request error, not a
terminal one, and its design already starts new demand before it releases
retired coverage, and treats a synchronous loadSubset throw as a rejection
whose owner's release unloads it. The five conflicted production files
take #2168's version, with only the fixes it lacks ported onto it:

- the shared replay barrier across a query's subscriptions to one source
  (siblingReplays), and the failure hold behind a sibling's failed replay
  (failReplacement);
- the deferred window move's wait for every replay that holds
  publication. A move now also rejects when a reload ends the sync run of
  the query or of a source, which #2168's source-health oracle requires.

Dropped as superseded: the error-228 rule outside a replay, the replay a
truncate opened without demand, this branch's synchronous-throw and
aborted-request handling, and the window-request reset (a move can no
longer throw, and deleting it failed nothing).

Replay oracle: the strict terminal-error law becomes law 16 (a terminal
error only after a load failed while the query was loading, at each
history cut and at convergence), with #2168's request-error witness, and
the dead excusal machinery goes. A thrown load counts as acquired, and a
synchronous setWindow throw is an escaped error.
Mutants: without the deferred-move loop 8 tests fail (#2221 trace 2 among
them); without failReplacement the two-failures replay fails; without the
sibling wait that replay and both failed-replay campaigns fail.

ARCHITECTURE.md, the glossary's acquisition-attempt entry, the coverage
map, and the changeset follow. Evidence: @tanstack/db 298 files, 15,520 of
15,521 tests (one random-seed reach miss in the GC oracle from #2258,
which passes 3 of 3 reruns); query-db 42 files, 1,492 tests (2 skipped);
tsc, ESLint, Prettier, and the mangle check pass.

Co-authored-by: Isaac <no-reply@databricks.com>
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.

Garbage collection cleans up a collection while one of its transactions is still persisting

2 participants