Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jan 27, 2026

Summary

Fixes isReady tracking for on-demand live queries without orderBy. Previously, non-ordered live queries using syncMode: 'on-demand' were incorrectly marked as ready before data finished loading. This also fixes preload() promises hanging when cleanup occurs before the collection becomes ready.

Root Cause

For non-ordered live queries, subscribeChanges is called with includeInitialState: true, which internally calls requestSnapshot({ trackLoadSubsetPromise: false }). This disables the subscription's status tracking—the subscription never transitions to loadingSubset status, so the live query's isReady stayed true during loading.

This only affected non-ordered queries because ordered queries (with limit/offset) use a different code path (subscribeToOrderedChanges) that calls requestSnapshot() with default tracking enabled.

Approach

The fix couldn't simply change trackLoadSubsetPromise to true because that breaks truncate handling (must-refetch scenarios). The truncate buffering logic depends on the existing status behavior.

Instead, the fix:

  1. Tracks loading at the collection level (collection-subscriber.ts): After subscribing, checks if the source collection's isLoadingSubset changed from false to true. If so, listens for the loadingSubset:change event and tracks a promise on the live query collection for isReady. Includes cleanup registration to prevent memory leaks if unsubscribed early.

  2. Calls onFirstReady callbacks during cleanup (lifecycle.ts): Ensures preload() promises resolve instead of hanging when cleanup occurs before the collection becomes ready. Includes try-catch error handling to ensure all callbacks are attempted.

  3. Adds QueryClient cache fallbacks (query.ts): Handles edge cases where observer state may be stale after unsubscribe/resubscribe cycles.

Key Invariants

  1. isReady must return false while loadSubset is in progress
  2. preload() must always resolve (either when ready OR during cleanup)
  3. Truncate handling must continue to work correctly (buffering events during must-refetch)
  4. Event listeners must be cleaned up on early unsubscribe to prevent memory leaks

Non-goals

  • Didn't change the subscription's internal trackLoadSubsetPromise behavior
  • Didn't change the behavior for ordered queries (already working correctly)

Verification

pnpm --filter @tanstack/db exec vitest run --pool-options.threads.maxThreads=2
pnpm --filter @tanstack/query-db-collection exec vitest run --pool-options.threads.maxThreads=2
pnpm --filter @tanstack/electric-db-collection exec vitest run

All tests pass:

  • db: 1932 passed (3 skipped)
  • query-db-collection: 187 passed
  • electric-db-collection: 404 passed

Files Changed

  • packages/db/src/query/live/collection-subscriber.ts - Track collection loading state with cleanup
  • packages/db/src/collection/lifecycle.ts - Call callbacks during cleanup with error handling
  • packages/query-db-collection/src/query.ts - Add QueryClient cache fallback checks

🤖 Generated with Claude Code

KyleAMathews and others added 2 commits January 27, 2026 09:52
When a live query without orderBy/limit subscribes to an on-demand
collection, the subscription was passing includeInitialState: true to
subscribeChanges, which internally called requestSnapshot with
trackLoadSubsetPromise: false. This prevented the subscription status
from transitioning to 'loadingSubset', causing the live query to be
marked ready before data actually loaded.

The fix changes subscribeToMatchingChanges to manually call
requestSnapshot() after creating the subscription (with default
tracking enabled), ensuring the loadSubset promise is properly
tracked and the live query waits for data before becoming ready.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This follow-up fix addresses edge cases that caused tests to fail after
the initial isReady fix:

1. lifecycle.ts: Call pending onFirstReady callbacks during cleanup
   - Prevents preload() promises from hanging when cleanup happens
   - Ensures clean termination of pending preload operations

2. query.ts: Add fallback cache checks in createQueryFromOpts
   - Check QueryClient cache when observer state is out of sync
   - Handle cases where observer was deleted but data is still cached
   - Prevents hangs during cleanup/recreate cycles

3. Test updates:
   - Updated where clause tests to use synchronous loadSubset
   - These tests verify where clause passing, not async loading behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Jan 27, 2026

🦋 Changeset detected

Latest commit: 3d6e48b

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

This PR includes changesets to release 12 packages
Name Type
@tanstack/db Patch
@tanstack/query-db-collection Patch
@tanstack/angular-db Patch
@tanstack/electric-db-collection Patch
@tanstack/offline-transactions Patch
@tanstack/powersync-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 27, 2026

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 3d6e48b

@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

Size Change: +258 B (+0.28%)

Total Size: 91.2 kB

Filename Size Change
./packages/db/dist/esm/collection/lifecycle.js 1.75 kB +70 B (+4.16%)
./packages/db/dist/esm/query/live/collection-subscriber.js 2.12 kB +188 B (+9.73%) ⚠️
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.19 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.49 kB
./packages/db/dist/esm/collection/subscription.js 3.62 kB
./packages/db/dist/esm/collection/sync.js 2.41 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.7 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.69 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.93 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 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 733 B
./packages/db/dist/esm/query/builder/index.js 4.09 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.42 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.81 kB
./packages/db/dist/esm/query/compiler/index.js 2.02 kB
./packages/db/dist/esm/query/compiler/joins.js 2.07 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.45 kB
./packages/db/dist/esm/query/compiler/select.js 1.06 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.42 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 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 2.9 kB
./packages/db/dist/esm/utils.js 924 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 852 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

Size Change: 0 B

Total Size: 3.7 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
autofix-ci bot and others added 3 commits January 27, 2026 18:49
- Fix potential memory leak in trackCollectionLoading by registering
  cleanup callback for early unsubscribe scenarios
- Add error handling for onFirstReady callbacks during cleanup to
  ensure all callbacks are attempted even if one throws

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, when multiple live queries subscribed to the same source
collection, only the first would correctly track loading state due to
the `!wasLoadingBefore` guard. This caused subsequent live queries to
incorrectly report `isReady` before data finished loading.

The fix removes this guard since each live query needs its own loading
state tracking regardless of whether another query already triggered
loading.

Also adds a regression test for this scenario.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chicojasl
Copy link

Verified this locally. Working great! 👍

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.

3 participants