Repository navigation
fix(persistence): preserve metadata on implicit equal re-inserts - #2303
KyleAMathews wants to merge 3 commits into
Conversation
Snapshot accepted row metadata for durable inserts that omit explicit metadata writes. Keep explicit metadata writes authoritative and avoid attributing the preserved metadata to the source. Share completed-reach classification between the controlled and native SQLite receiving drivers. Law: Sync metadata session decisions4,5,6,8,9,14,15,18 in docs/contributing/oracle-reviews/2026-10-22-sync-metadata-session.md, the implicit row-metadata rules, and glossary hydration-seed contract. An inner equal re-insert preserves accepted metadata through persistence and peer reload. Reach: Each controlled/native host campaign completes40 histories in each of three lanes. Fixed seed527962096 reaches2 completed bare-key1 implicit keeps and2 completed non-bare keeps per lane, with21 named/19 fullreload notices. RED: M01 controlled extended owner fails unchanged parent production at seed2281001/path7:0; M06/M07 native SQLite receiving replays/property fail the same metadata law, generated path7:0:0. GREEN: M08/M09 paired40-run campaigns and receiving replays pass. H02 repeats both hosts with the shared helper; H01b proves final-helper equivalence over 2304 histories/102912 comparisons. Final types/lint/format pass. Mutant: Original durable-metadata map and always-delete rival both fail the shared metadata comparison in controlled/native generated histories. An all-explicit-inner grammar ablation fails the completed implicit-keep reaches. Limits: One process/native SQLite with two production coordinators. No OPFS, Electron IPC, transport-loss/leadership-turnover or broader2272 claim. Random scarce reach is measured; fixed guards enforce both scarce branches. Host runs precede only an explicit terminal undefined return, linked by exact equivalence.
🦋 Changeset detectedLatest commit: ea769ec The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Persist metadata for implicit re-inserts packages/db-sqlite-persistence-core/src/persisted.ts, .changeset/preserve-implicit-reinsert-metadata.md |
Insert operations without explicit metadata writes now add a metadata set or delete based on the current row value. Explicit writes remain authoritative. |
Define seed and re-insert histories packages/db/tests/sync-metadata-session-oracle.ts |
The shared oracle adds generated and fixed histories, plus a classifier for completed bare and non-bare seed regimes. |
Exercise persisted peer lanes packages/db-sqlite-persistence-core/tests/sync-metadata-session-persisted.test.ts, packages/db/tests/oracle-config.ts, packages/db/tests/oracle-replay-manifest.ts |
Replay and generated campaigns cover peer, full-row peer, and on-demand lanes. The persisted property is added to oracle configuration and replay ownership. |
Exercise SQLite-backed receiving packages/db-sqlite-persistence-core/tests/sync-metadata-session-sqlite.test.ts, docs/contributing/oracle-coverage.md |
SQLite-backed replay and generated campaigns cover partial-row, full-row, and on-demand lanes with two coordinated collections. The coverage record describes the campaigns and remaining host and transport gaps. |
Priority:
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Bug fix
Possibly related issues
- Issue 2272 — Adds a real two-Collection SQLite receiving campaign with production coordinators.
- Issue 2281 — Addresses the recorded metadata counterexample. Deleted-row resurrection behavior remains outside this change.
Suggested reviewers: nabil372
Merge Risk: 
Minimal · up to ea769
-
No actionable issue identified in this change remains before merge; normal checks still apply.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Title check
-
Explanation
The title "fix(persistence): preserve metadata on implicit equal re-inserts" matches the main change. The PR makes durable inserts record accepted row metadata, or its absence, when the source omits an explicit metadata write. The title is concise, uses a conventional type prefix, and names the affected area and behavior.
Full details: Description check
-
Explanation
The description follows the repository template and covers the required sections. It states the change: a durable insert now records accepted metadata, or its absence, when the source omits an explicit metadata write. The Oracle Coverage section names the owner (packages/db/tests/sync-metadata-session-oracle.ts), gives the law, reach, RED/GREEN, mutant, and limits, and cites the authority decisions. The Release Impact section checks the changeset box and explains the size change. The unchecked 'pnpm test' box is explained by the narrower local validation. The description is mostly complete and specific.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 6 files. (2 skipped: 2 unsupported.)
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
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
-
- 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.
Comment @coderabbitai help to get the list of available commands.
Incremental update benchmarkComparing Overall median write time vs base: 1.01× · cold hydrate time: 0.96× (geometric mean of per-case ratios; lower is faster). Writes: 2 regression(s), 0 improvement(s) (threshold: ±20% and >0.05ms). Cold hydrate: 0 regression(s), 1 improvement(s) (threshold: ±50% and >5ms). Per-case flags are noisy on shared runners. Read the geometric means first. Writes
Cold hydrate
Each row aggregates the 14.4 scale/index/write-mode configurations of that query; per-configuration tables below. 100 rows/collection | source indexes: none | synced writes — geomean 0.98×, cold 1.02×
100 rows/collection | source indexes: none | optimistic writes — geomean 1.04×, cold 1.04×
100 rows/collection | source indexes: manual | synced writes — geomean 0.98×, cold 1.02×
100 rows/collection | source indexes: manual | optimistic writes — geomean 1.09×, cold 1.34×
1,000 rows/collection | source indexes: none | synced writes — geomean 1.20×, cold 0.83×, 1 change(s)
1,000 rows/collection | source indexes: none | optimistic writes — geomean 1.02×, cold 0.88×
1,000 rows/collection | source indexes: manual | synced writes — geomean 0.96×, cold 0.95×
1,000 rows/collection | source indexes: manual | optimistic writes — geomean 0.96×, cold 1.07×
10,000 rows/collection | source indexes: none | synced writes — geomean 1.09×, cold 0.93×, 1 change(s)
10,000 rows/collection | source indexes: none | optimistic writes — geomean 0.95×, cold 1.06×
10,000 rows/collection | source indexes: manual | synced writes — geomean 0.92×, cold 0.88×
10,000 rows/collection | source indexes: manual | optimistic writes — geomean 0.95×, cold 0.68×, 1 change(s)
Runner: node v24.8.0, linux 7.0.0-1017-azure, AMD EPYC 7763 64-Core Processor. Timings on shared CI runners are noisy; treat small deltas as indicative only. |
|
Size Change: 0 B Total Size: 238 kB ℹ️ View Unchanged
|
More templates
@tanstack/angular-db
@tanstack/browser-db-sqlite-persistence
@tanstack/capacitor-db-sqlite-persistence
@tanstack/cloudflare-durable-objects-db-sqlite-persistence
@tanstack/convex-db-collection
@tanstack/db
@tanstack/db-ivm
@tanstack/db-sqlite-persistence-core
@tanstack/electric-db-collection
@tanstack/electron-db-sqlite-persistence
@tanstack/expo-db-sqlite-persistence
@tanstack/node-db-sqlite-persistence
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/preact-db
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/react-native-db-sqlite-persistence
@tanstack/react-router-with-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/tauri-db-sqlite-persistence
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
🎯 Changes
An equal source re-insert can keep accepted metadata in memory yet leave older cached metadata in SQLite. A later peer reload then restores stale metadata, even when the accepted metadata is absent. The durable insert now records the accepted metadata value or its absence whenever the source omitted an explicit metadata write.
The reported history starts with cached metadata, adds a bare hydration seed under an open source insert, then commits an independent inner equal re-insert. A later peer reload previously restored
{ cached: true }where the source expected no metadata. The same defect also replaces nonempty metadata from a later hydration seed.The fix copies explicit metadata writes into the durable transaction, then supplies accepted metadata for the remaining inserted keys. Explicit writes retain priority. The source-authority map stays unchanged, so an implicit keep does not become a source metadata write.
This addresses the separate metadata counterexample recorded under #2281. The issue's multi-tab Electric row-loss and resurrection reports remain separate. The new SQLite receiver also supplies bounded evidence toward #2272.
🔬 Oracle Coverage
Primary owner:
packages/db/tests/sync-metadata-session-oracle.ts. Its independent model and comparison stay unchanged. Both receiving drivers use the shared generated histories and judgment.The declared bug class is an equal re-insert persisting metadata that differs from the accepted metadata, including absence and later hydration seeds. The generated seed/re-insert/reload histories, controlled/native production paths, and observation cuts below bound the closure evidence. The broader #2272 host, transport, leadership, and notice-shape gaps remain receiving work. This PR claims no closure for those regimes, and the accepted metadata law still applies there.
527962096reaches 40 inner commits, 21 named notices, and 19 full reloads per host/lane. Each also reaches two completed bare-key-1 implicit keeps and two completed non-bare implicit keeps through reload. Fixed campaigns enforce these scarce completed states. Random campaigns measure them. All older guards still apply to both campaigns.2281001, path7:0. Native SQLite generation also fails, at path7:0:0, alongside three failing native replays. Failures compare the source's accepted metadata with the unchanged model. Eager lanes fail at peer step 6. Native on-demand can fail earlier at inner commit step 5 because real coordination reads storage sooner.pnpm lint:changed, and combined integration checks pass. This change preserves all prior budgets, guards, and comparisons.The historical RED commands run from
packages/db-sqlite-persistence-corewith unchanged production and the extended tests:The GREEN host command runs both campaigns and the original receiving replays:
✅ Checklist
pnpm test.Local verification targeted this change's metadata law, receiving drivers, and neighboring hydration/reload contracts. The full workspace
pnpm testcommand was not run locally. That command spans all packages and the IndexedDB package suite, so the unchecked box records the narrower local validation.🚀 Release Impact
Patch release for
@tanstack/db-sqlite-persistence-core. Package gzip changes by +56 bytes and Brotli by +35 bytes. The persisted module adds 173 minified bytes. Existing application-size scenarios do not import persistence, so their unchanged size does not measure this fix.Related: #2281 (metadata comment only), #2272 (bounded receiving coverage), and #2298 (separate multi-tab fix).