-
Notifications
You must be signed in to change notification settings - Fork 103
Add loadSubset State Tracking and On-Demand Sync Mode #669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ubclass the same event emiiter implimetation
🦋 Changeset detectedLatest commit: f31a67e 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 |
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-ivm
@tanstack/electric-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
Size Change: 0 B Total Size: 83.6 kB ℹ️ View Unchanged
|
Size Change: 0 B Total Size: 2.36 kB ℹ️ View Unchanged
|
…an unsunbscribed event to it
… event to the subscription, fix types
4d186d9
to
68dc938
Compare
68dc938
to
b1aeceb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
|
||
await liveQuery.preload() | ||
|
||
// Calling loadSubset directly on source collection sets its own isLoadingMore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the plan here was isLoadingMore is true only if a live query calls updatePredicate? Otherwise a live query would be see there isLoadingMore
set to true when e.g. a joined collection needs to grab an object, etc. So any UI set to this would be flickering on and off seemingly randomly w/o any way to control it by the dev.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit of the code all dates to when I was working over the weekend. Will update...
I do wander if we need to expose that it is grabbing data from the server though, joins lazy appearing without the dev being able to put a placeholder/spinner does feel messy.
Maybe we need to separate flags?
Also not that this version from the weekend made the isLoadingMore prop a standard on all collections. For base collections it's true when their own loadSubset is pending, for live query collections it's when a subscription trigger a loadSubset that returns a promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we need an isLoadingMore and a isLoadingSubset, the latter whenever any subset triggered by the collection is loading, and isLoadingMore for when it's triggered by an offset/limit change (which do not isn't complete yet, and not in this PR)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fully implement this as only triggered when the offset/limit is changed we will have to rebase this PR on #663 or wait till thats merged. I suggest we leave the implementation as is now (setting isLoading on any subscription triggering a loadSubset
) and fix in a future PR.
db/packages/db/src/query/live/collection-subscriber.ts
Lines 77 to 84 in 9ad1169
const statusUnsubscribe = subscription.on(`status:change`, (event) => { | |
// TODO: For now we are setting this loading state whenever the subscription | |
// status changes to 'loadingMore'. But we have discussed it only happening | |
// when the the live query has it's offset/limit changed, and that triggers the | |
// subscription to request a snapshot. This will require more work to implement, | |
// and builds on https://github.com/TanStack/db/pull/663 which this PR | |
// does not yet depend on. | |
if (event.status === `loadingMore`) { |
The question is if collection. isLoadingMore
should be collection.isLoadingSubset
and that isLoadingMore
is a live query only thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, left a few minor comments.
* Tracks a load promise for isLoadingMore state. | ||
* @internal This is for internal coordination (e.g., live-query glue code), not for general use. | ||
*/ | ||
public syncMore(options: OnLoadMoreOptions): void | Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather keep this method such that we can keep the _sync
property private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree, syncMore
is an internal method that shouldering be presented on the front he public api. The managers having the underscore marks them as "internal implementation but exposed for debugging and internal communication". I would prefer to not present syncMore
on the prompts when people are interacting with a collection.
}) | ||
|
||
// Track the promise if it's actually a promise (async work) | ||
if (syncResult instanceof Promise) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also check that the promise is not yet resolved? Because if it is already resolved then there is no need to do the below as that may lead to some flickering in the UI if one would e.g. show a spinner when more is loading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately there is no api to do this, thats why loadSubset can return true
to indicate that it has the data synchronously.
* feat: implement useLiveInfiniteQuery hook for React * use the new utils.setWindow to page through the results improve types add test that checks that we detect new pages on more rows syncing changeset tweaks * isFetchingNextPage set by promise from setWindow --------- Co-authored-by: Sam Willis <sam.willis@gmail.com>
Updated changeset to correctly describe: - isLoadingSubset property (not isLoadingMore) - loadingSubset:change events - syncMode configuration options - Comprehensive loading state tracking - Enhanced setWindow utility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…features (#679) Fix changeset for PR #669 to accurately describe features Updated changeset to correctly describe: - isLoadingSubset property (not isLoadingMore) - loadingSubset:change events - syncMode configuration options - Comprehensive loading state tracking - Enhanced setWindow utility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
🎉 This PR has been released! Thank you for your contribution! |
Overview
This PR adds comprehensive loading status tracking to collections and live queries, enabling UIs to display loading indicators when more data is being fetched. It also introduces a new
syncMode
configuration option to control when collections load data - either eagerly on initial sync or on-demand as queries request it.Problem
No Loading Indicators: When live queries pushed predicates down to source collections via
syncMore
, there was no indication that data was being loaded. This made it impossible to show proper loading states in the UI.Always-Eager Loading: Collections always loaded all data immediately during initial sync, even when using predicate pushdown. There was no way to configure collections to only load data as it was requested by queries.
Solution
This PR implements a multi-layered loading state tracking system with configurable sync modes:
Loading State Tracking
Subscription
interface for external consumersisLoadingSubset
propertyKey Isolation Property: Each live query maintains its own loading state based on its own subscriptions. When a live query triggers loading via predicate pushdown, only that live query's
isLoadingSubset
becomestrue
. Other live queries that share the same source collection but don't need that specific snapshot remain unaffected.Sync Modes
Collections can now be configured with two sync modes:
eager
(default): Loads all data immediately during initial sync.loadSubset
calls are bypassed.on-demand
: Only loads data as it's requested vialoadSubset
calls. Requires aloadSubset
handler.Changes
1. Renamed
syncMore
/loadMore
toloadSubset
Files:
packages/db/src/collection/sync.ts
,packages/db/src/types.ts
,packages/db/src/collection/subscription.ts
syncMore
→loadSubset
(collection method)onLoadMore
→loadSubset
(sync config property)OnLoadMoreOptions
→LoadSubsetOptions
(type)syncOnLoadMoreFn
→syncLoadSubsetFn
(internal)pendingLoadMorePromises
→pendingLoadSubsetPromises
(internal)Rationale: "loadSubset" better reflects that this operation loads a filtered/limited subset of data, not just "more" data.
2. Added
syncMode
ConfigurationFiles:
packages/db/src/types.ts
,packages/db/src/collection/sync.ts
New
syncMode
property on collection config:Behavior:
eager
(default):loadSubset
is bypassed and returnsundefined
. All data is loaded during initial sync.on-demand
:loadSubset
calls proceed normally. Data is only loaded as requested.Validation:
syncMode: 'on-demand'
is set but noloadSubset
handler is provided, throwsCollectionConfigurationError
with a helpful message3. Standardized
loadSubset
Return TypeFiles:
packages/db/src/collection/sync.ts
,packages/db/src/types.ts
Promise<void> | undefined
undefined
whensyncMode
is'eager'
or no sync implementation is configuredloadSubset
results inPromise.resolve()
SyncConfigRes['loadSubset']
type signature4. CollectionSubscription Status Tracking & Events
File:
packages/db/src/collection/subscription.ts
Added comprehensive status tracking:
status: 'ready' | 'loadingSubset'
(readonly getter with private mutable field)pendingLoadSubsetPromises: Set<Promise<void>>
status:change
- Emitted when status transitionsstatus:ready
- Emitted when entering ready statestatus:loadingSubset
- Emitted when entering loading stateunsubscribed
- New event emitted when subscription is destroyedready
even on promise rejectionunsubscribe()
emitsunsubscribed
event before clearing listeners5. Generic
isLoadingSubset
for All CollectionsFiles:
packages/db/src/collection/sync.ts
,packages/db/src/collection/index.ts
,packages/db/src/collection/events.ts
All collections now track their loading state:
isLoadingSubset
(boolean getter, not a method)pendingLoadSubsetPromises: Set<Promise<void>>
inCollectionSyncManager
loadingSubset:change
event when state transitionstrackLoadPromise(promise: Promise<void>)
method for internal coordination_sync
public onCollection
for internal use6. Live Query Integration
Files:
packages/db/src/query/live/collection-subscriber.ts
,packages/db/src/query/live/collection-config-builder.ts
Live queries reflect loading state from their subscriptions:
CollectionSubscriber
subscribes to subscriptionstatus:change
eventsloadingSubset
stateliveQueryCollection.trackLoadPromise()
isLoadingSubset
reflects subscription loading statesLoading State Isolation: Each live query's subscriptions are independent. Query A triggering
loadSubset
doesn't affect Query B'sisLoadingSubset
status.7. Subscription Interface for External Consumers
File:
packages/db/src/types.ts
New public
Subscription
interface:Purpose: Allows sync implementations to:
loadSubset
callunsubscribed
)8. Reusable Event Emitter
File:
packages/db/src/event-emitter.ts
(new)Extracted event emitter logic into a reusable base class:
EventEmitter<TEvents>
with full type safetyon
,once
,off
,waitFor
emitInner
(for subclass use),clearListeners
queueMicrotask
CollectionEventsManager
extends it and wrapsemitInner
with publicemit
CollectionSubscription
extends it and usesemitInner
internally9. Fixed Local-Only Collection Types
File:
packages/db/src/local-only.ts
Fixed mutation function typing issues:
UtilsRecord
type parameters10. Comprehensive Test Coverage
Files:
packages/db/tests/collection-subscription.test.ts
,packages/db/tests/collection.test.ts
,packages/db/tests/query/live-query-collection.test.ts
Added extensive test suites:
loadSubset
: Updated to usesyncMode: 'on-demand'
11. Enhanced
setWindow
with Loading AwarenessFile:
packages/db/src/query/live/collection-config-builder.ts
The
setWindow
utility function now returns a value that indicates whether subset loading was triggered, allowing callers to wait for data loading to complete:Return Type:
true | Promise<void>
true
whenisLoadingSubset
isfalse
after calling the window function - no loading was triggeredPromise<void>
whenisLoadingSubset
istrue
- loading was triggered and the promise resolves when loading completesImplementation Details:
windowFn()
andmaybeRunGraphFn()
, checksthis.liveQueryCollection?.isLoadingSubset
loadingSubset:change
eventisLoadingSubset
becomesfalse
Usage Pattern:
Test Coverage:
setWindow
returnstrue
when no loading is triggeredPromise<void>
is returned when loading is triggeredisLoadingSubset
becomestrue
during loadingisLoadingSubset
returns tofalse
after resolutionAPI
Sync Mode Configuration
Collection Subscription
Collection
Live Query
Breaking Changes
Renamed Methods and Types
syncMore
→loadSubset
(collection method)onLoadMore
→loadSubset
(sync config property)OnLoadMoreOptions
→LoadSubsetOptions
(type)Migration:
Note:
loadSubset
is now called viacollection._sync.loadSubset()
as it's an internal coordination API, not for general public use.Migration Guide
For Collection Users
No changes required if you're just using collections -
isLoadingSubset
is automatically available.For Sync Implementers
onLoadMore
toloadSubset
:subscription
parameter for advanced use cases:syncMode
for on-demand loading: