Skip to content

feat(db): ordered snapshot / layout-revision contract (RFC #1623 phase 4)#1669

Open
kevin-dp wants to merge 4 commits into
refactor/live-query-observerfrom
phase4/ordered-layout-contract
Open

feat(db): ordered snapshot / layout-revision contract (RFC #1623 phase 4)#1669
kevin-dp wants to merge 4 commits into
refactor/live-query-observerfrom
phase4/ordered-layout-contract

Conversation

@kevin-dp

Copy link
Copy Markdown
Contributor

Phase 4 of the live-query platform RFC (#1623): the ordered snapshot / layout contract. Stacked on #1642 (observer migration) — review/merge that first; this PR's base is refactor/live-query-observer so the diff is Phase 4 only.

Problem

An orderBy live query that reorders its rows without changing any projected row value (an "order-only move") is swallowed by the collection's value-diff: .values()/.entries() re-sort internally, but no change event fires, so useLiveQuery keeps rendering the stale order. This is the last universal expected-fail in the cross-adapter conformance suite (tracked as #1601).

Approach

The RFC is explicit that this should be "an explicit layout-revision requirement, not a hidden forced-update path". So instead of forging a row update:

  • The live-query flush captures the retracted side of each change and, after commit(), detects an order-only move (projected value deep-equal, orderByIndex moved) and publishes a first-class empty layout-change notification via a new CollectionChangesManager.emitLayoutChangeEvent(). It reuses the existing empty-batch delivery already used for the ready signal — subscribers re-read, nothing is faked.
  • The shared observer snapshot gains layoutRevision, which increments on any visible membership, ordering, or order-only-move change. This is the canonical contract the RFC wants for future fine-grained materializers; adapters currently pick up the reorder through their existing wholesale re-read.

Result

order-only-move is removed from UNIVERSAL_EXPECTED_FAIL and now passes on all five adapters (React, Vue, Svelte, Solid, Angular) — 26/26 conformance each. Full @tanstack/db suite green (2464 tests), all five adapter suites green.

Coverage

  • packages/db/tests/live-query-order-only-move.test.ts — core mechanism: republish + layoutRevision bump on an order-only move; no bump when order is unchanged (no spurious notification); bump on membership change.
  • Conformance order-only-move scenario now a real pass across all adapters.

Relationship to #1601

@v-anton's #1601 fixes the same bug via a forced row update. This PR takes the RFC-sanctioned layout-revision approach instead (a distinct, first-class notification + layoutRevision), so it supersedes rather than duplicates that path. Happy to coordinate on which lands — flagging for maintainer decision.

🤖 Generated with Claude Code

…1623 phase 4)

An `orderBy` live query that reorders its rows without changing any projected
row value (an "order-only move") was swallowed by the collection's value-diff:
`.values()`/`.entries()` re-sorted, but no change event fired, so subscribers
kept the stale order. This is the last universal expected-fail in the
cross-adapter conformance suite (issue #1601).

Phase 4 of the live-query platform RFC calls for an explicit layout-revision
contract rather than a forged row `update`. This does that:

- The live-query flush captures the retracted side of each change and, after
  commit, detects an order-only move (value deep-equal, `orderByIndex` moved)
  and publishes a first-class empty layout-change notification via a new
  `CollectionChangesManager.emitLayoutChangeEvent()`.
- The shared observer snapshot gains `layoutRevision`, which increments on any
  visible membership, ordering, or order-only-move change.

All five adapters pick this up through their existing wholesale re-read, so the
`order-only-move` conformance scenario is removed from UNIVERSAL_EXPECTED_FAIL
and now passes on React, Vue, Svelte, Solid, and Angular.

Distinct from PR #1601 (v-anton), which fixes the same bug via a forced row
`update`; this uses the RFC's layout-revision approach instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd2086e1-8343-4b09-ad2c-0719d2053f3c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase4/ordered-layout-contract

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 Jul 13, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: d762855

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Size Change: +413 B (+0.33%)

Total Size: 127 kB

📦 View Changed
Filename Size Change
packages/db/dist/esm/collection/changes.js 1.56 kB +183 B (+13.25%) ⚠️
packages/db/dist/esm/live-query-observer.js 1.16 kB +122 B (+11.76%) ⚠️
packages/db/dist/esm/query/live/collection-config-builder.js 9.21 kB +108 B (+1.19%)
ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.43 kB
packages/db/dist/esm/collection/cleanup-queue.js 810 B
packages/db/dist/esm/collection/events.js 434 B
packages/db/dist/esm/collection/index.js 3.62 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.4 kB
packages/db/dist/esm/collection/subscription.js 3.74 kB
packages/db/dist/esm/collection/sync.js 2.88 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.1 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/index.js 3.19 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 767 B
packages/db/dist/esm/indexes/basic-index.js 2.06 kB
packages/db/dist/esm/indexes/btree-index.js 2.19 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 557 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/local-only.js 916 B
packages/db/dist/esm/local-storage.js 2.12 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 496 B
packages/db/dist/esm/proxy.js 3.75 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/index.js 5.84 kB
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.89 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 3.56 kB
packages/db/dist/esm/query/compiler/index.js 6.67 kB
packages/db/dist/esm/query/compiler/joins.js 2.5 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 923 B
packages/db/dist/esm/query/compiler/order-by.js 1.74 kB
packages/db/dist/esm/query/compiler/select.js 1.53 kB
packages/db/dist/esm/query/effect.js 4.77 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir.js 1.25 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/utils.js 1.81 kB
packages/db/dist/esm/query/optimizer.js 2.92 kB
packages/db/dist/esm/query/predicate-utils.js 2.97 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/subset-dedupe.js 960 B
packages/db/dist/esm/scheduler.js 1.3 kB
packages/db/dist/esm/SortedMap.js 1.3 kB
packages/db/dist/esm/strategies/debounceStrategy.js 247 B
packages/db/dist/esm/strategies/queueStrategy.js 428 B
packages/db/dist/esm/strategies/throttleStrategy.js 246 B
packages/db/dist/esm/transactions.js 3.04 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 5.61 kB
packages/db/dist/esm/utils/comparison.js 1.11 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 2.39 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

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

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 3.81 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/index.js 249 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
packages/react-db/dist/esm/useLiveQuery.js 924 B
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

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

kevin-dp and others added 2 commits July 13, 2026 16:51
Addresses independent review of the layoutRevision contract:

- The join-with-separator signature could collide: a key value equal to the
  concatenation of neighboring keys around the separator produces the same
  string as two separate keys, so a real layout change (a membership change
  whose combined key spans the separator) was missed. Compare the ordered key
  sequence directly instead - collision-free, and it avoids materializing a
  large string on every snapshot rebuild (a new key array is only allocated
  when the layout actually moved). Adds a regression test.
- Correct the layoutRevision doc comment: it is NOT in lockstep with snapshot
  identity (a value-only update yields a new snapshot but the same
  layoutRevision).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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.

1 participant