-
Notifications
You must be signed in to change notification settings - Fork 107
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 <[email protected]>
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 <[email protected]>
…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 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
|
🎉 This PR has been released! Thank you for your contribution! |
…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 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
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
syncModeconfiguration 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
Subscriptioninterface for external consumersisLoadingSubsetpropertyKey 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
isLoadingSubsetbecomestrue. 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.loadSubsetcalls are bypassed.on-demand: Only loads data as it's requested vialoadSubsetcalls. Requires aloadSubsethandler.Changes
1. Renamed
syncMore/loadMoretoloadSubsetFiles:
packages/db/src/collection/sync.ts,packages/db/src/types.ts,packages/db/src/collection/subscription.tssyncMore→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
syncModeConfigurationFiles:
packages/db/src/types.ts,packages/db/src/collection/sync.tsNew
syncModeproperty on collection config:Behavior:
eager(default):loadSubsetis bypassed and returnsundefined. All data is loaded during initial sync.on-demand:loadSubsetcalls proceed normally. Data is only loaded as requested.Validation:
syncMode: 'on-demand'is set but noloadSubsethandler is provided, throwsCollectionConfigurationErrorwith a helpful message3. Standardized
loadSubsetReturn TypeFiles:
packages/db/src/collection/sync.ts,packages/db/src/types.tsPromise<void> | undefinedundefinedwhensyncModeis'eager'or no sync implementation is configuredloadSubsetresults inPromise.resolve()SyncConfigRes['loadSubset']type signature4. CollectionSubscription Status Tracking & Events
File:
packages/db/src/collection/subscription.tsAdded 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 destroyedreadyeven on promise rejectionunsubscribe()emitsunsubscribedevent before clearing listeners5. Generic
isLoadingSubsetfor All CollectionsFiles:
packages/db/src/collection/sync.ts,packages/db/src/collection/index.ts,packages/db/src/collection/events.tsAll collections now track their loading state:
isLoadingSubset(boolean getter, not a method)pendingLoadSubsetPromises: Set<Promise<void>>inCollectionSyncManagerloadingSubset:changeevent when state transitionstrackLoadPromise(promise: Promise<void>)method for internal coordination_syncpublic onCollectionfor internal use6. Live Query Integration
Files:
packages/db/src/query/live/collection-subscriber.ts,packages/db/src/query/live/collection-config-builder.tsLive queries reflect loading state from their subscriptions:
CollectionSubscribersubscribes to subscriptionstatus:changeeventsloadingSubsetstateliveQueryCollection.trackLoadPromise()isLoadingSubsetreflects subscription loading statesLoading State Isolation: Each live query's subscriptions are independent. Query A triggering
loadSubsetdoesn't affect Query B'sisLoadingSubsetstatus.7. Subscription Interface for External Consumers
File:
packages/db/src/types.tsNew public
Subscriptioninterface:Purpose: Allows sync implementations to:
loadSubsetcallunsubscribed)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,waitForemitInner(for subclass use),clearListenersqueueMicrotaskCollectionEventsManagerextends it and wrapsemitInnerwith publicemitCollectionSubscriptionextends it and usesemitInnerinternally9. Fixed Local-Only Collection Types
File:
packages/db/src/local-only.tsFixed mutation function typing issues:
UtilsRecordtype 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.tsAdded extensive test suites:
loadSubset: Updated to usesyncMode: 'on-demand'11. Enhanced
setWindowwith Loading AwarenessFile:
packages/db/src/query/live/collection-config-builder.tsThe
setWindowutility 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>truewhenisLoadingSubsetisfalseafter calling the window function - no loading was triggeredPromise<void>whenisLoadingSubsetistrue- loading was triggered and the promise resolves when loading completesImplementation Details:
windowFn()andmaybeRunGraphFn(), checksthis.liveQueryCollection?.isLoadingSubsetloadingSubset:changeeventisLoadingSubsetbecomesfalseUsage Pattern:
Test Coverage:
setWindowreturnstruewhen no loading is triggeredPromise<void>is returned when loading is triggeredisLoadingSubsetbecomestrueduring loadingisLoadingSubsetreturns tofalseafter 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:
loadSubsetis 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 -
isLoadingSubsetis automatically available.For Sync Implementers
onLoadMoretoloadSubset:subscriptionparameter for advanced use cases:syncModefor on-demand loading: