-
Notifications
You must be signed in to change notification settings - Fork 102
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
base: main
Are you sure you want to change the base?
Conversation
…ubclass the same event emiiter implimetation
🦋 Changeset detectedLatest commit: 53fa027 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: +1.66 kB (+2.04%) Total Size: 83.2 kB
ℹ️ View Unchanged
|
Size Change: 0 B Total Size: 1.46 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.
🚀
if (this.syncLoadSubsetFn) { | ||
const result = this.syncLoadSubsetFn(options) | ||
|
||
// If the result is void (synchronous), wrap in Promise.resolve() |
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.
We talked about collections needing to be able to return true
or a promise — true if the data is loaded already. We need a sync response as otherwise useLiveQuery can't run sync
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.
hmm need to check why it's wrapping it there...
Returning void/undefined (synchronicity) makes it easy to check if a promise was returned as it's thruthy. Returning undefined if there is noting to do makes the code simpler than true - no typeof checks - but happy to change.
|
||
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)
Still to do:
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 consumersisLoadingMore
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
isLoadingMore
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' | 'loadingMore'
(readonly getter with private mutable field)pendingLoadSubsetPromises: Set<Promise<void>>
status:change
- Emitted when status transitionsstatus:ready
- Emitted when entering ready statestatus:loadingMore
- Emitted when entering loading stateunsubscribed
- New event emitted when subscription is destroyedready
even on promise rejectionunsubscribe()
emitsunsubscribed
event before clearing listeners5. Generic
isLoadingMore
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:
isLoadingMore
(boolean getter, not a method)pendingLoadSubsetPromises: Set<Promise<void>>
inCollectionSyncManager
loadingMore: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
eventsloadingMore
stateliveQueryCollection.trackLoadPromise()
isLoadingMore
reflects subscription loading statesLoading State Isolation: Each live query's subscriptions are independent. Query A triggering
loadSubset
doesn't affect Query B'sisLoadingMore
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'
API
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 -
isLoadingMore
is automatically available.For Sync Implementers
onLoadMore
toloadSubset
:subscription
parameter for advanced use cases:syncMode
for on-demand loading: