UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 28.1 kB
{"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import {\n normalizeExpressionPaths,\n normalizeOrderByPaths,\n} from '../compiler/expressions.js'\nimport {\n computeOrderedLoadCursor,\n computeSubscriptionOrderByHints,\n filterDuplicateInserts,\n sendChangesToInput,\n splitUpdates,\n trackBiggestSentValue,\n} from './utils.js'\nimport { SubsetDemandController } from './subset-demand-controller.js'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n ChangeMessage,\n SubscriptionLoadSubsetErrorEvent,\n SubscriptionStatusChangeEvent,\n} from '../../types.js'\nimport type { Context, GetResult } from '../builder/types.js'\nimport type { BasicExpression } from '../ir.js'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\nimport type { CollectionConfigBuilder } from './collection-config-builder.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type { LazyDemandPlan } from '../compiler/joins.js'\n\nconst loadMoreCallbackSymbol = Symbol.for(\n `@tanstack/db.collection-config-builder`,\n)\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Keep track of the biggest value we've sent so far (needed for orderBy optimization)\n private biggest: any = undefined\n\n // Track the most recent ordered load request key (cursor + window).\n // This avoids infinite loops from cached data re-writes while still allowing\n // window moves or new keys at the same cursor value to trigger new requests.\n private lastLoadRequestKey: string | undefined\n\n // Track deferred promises for subscription loading states\n private subscriptionLoadingPromises = new Map<\n CollectionSubscription,\n { resolve: () => void }\n >()\n\n // Track keys that have been sent to the D2 pipeline to prevent duplicate inserts\n // This is necessary because different code paths (initial load, change events)\n // can potentially send the same item to D2 multiple times.\n private sentToD2Keys = new Set<string | number>()\n\n // Direct load tracking callback for ordered path (set during subscribeToOrderedChanges,\n // used by loadNextItems for subsequent requestLimitedSnapshot calls)\n private orderedLoadSubsetResult?: (result: Promise<void> | true) => void\n private pendingOrderedLoadPromise: Promise<void> | undefined\n private readonly demand = new SubsetDemandController()\n\n constructor(\n private sourceId: string,\n private alias: string,\n private collection: Collection,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>,\n ) {}\n\n subscribe(): CollectionSubscription {\n const whereClause = this.getWhereClause()\n\n if (whereClause) {\n const whereExpression = normalizeExpressionPaths(whereClause, this.alias)\n return this.subscribeToChanges(whereExpression)\n }\n\n return this.subscribeToChanges()\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n const orderByInfo = this.getOrderByInfo()\n let initialSubsetPending = !this.collectionConfigBuilder.isLazySource(\n this.sourceId,\n )\n\n // Direct load promise tracking: pipes loadSubset results straight to the\n // live query collection, avoiding the multi-hop deferred promise chain that\n // can break under microtask timing (e.g., queueMicrotask in TanStack Query).\n const trackLoadResult = (result: Promise<void> | true) => {\n if (result instanceof Promise) {\n // Defer the tracked rejection by one microtask so the subscription's\n // error event can put an initial live query in error before loading\n // state would otherwise let it become ready.\n const trackedResult = result.catch(async (error: unknown) => {\n await Promise.resolve()\n throw error\n })\n this.collectionConfigBuilder.trackSubsetLoadPromise(trackedResult)\n if (initialSubsetPending) {\n void result.then(\n () => {\n initialSubsetPending = false\n },\n () => {},\n )\n }\n } else {\n initialSubsetPending = false\n }\n }\n\n // Status change handler - passed to subscribeChanges so it's registered\n // BEFORE any snapshot is requested, preventing race conditions.\n // Used as a fallback for status transitions not covered by direct tracking\n // (e.g., truncate-triggered reloads that call trackLoadSubsetPromise directly).\n const onStatusChange = (event: SubscriptionStatusChangeEvent) => {\n if (this.collectionConfigBuilder.isLazySource(this.sourceId)) return\n const subscription = event.subscription as CollectionSubscription\n if (event.status === `loadingSubset`) {\n this.ensureLoadingPromise(subscription)\n } else {\n // status is 'ready'\n const deferred = this.subscriptionLoadingPromises.get(subscription)\n if (deferred) {\n this.subscriptionLoadingPromises.delete(subscription)\n deferred.resolve()\n }\n }\n }\n const onLoadSubsetError = (event: SubscriptionLoadSubsetErrorEvent) => {\n this.collectionConfigBuilder.recordSubsetError(\n event.error,\n initialSubsetPending,\n )\n }\n\n // Create subscription with onStatusChange - listener is registered before any async work\n let subscription: CollectionSubscription\n if (orderByInfo) {\n subscription = this.subscribeToOrderedChanges(\n whereExpression,\n orderByInfo,\n onStatusChange,\n trackLoadResult,\n onLoadSubsetError,\n )\n } else {\n // Lazy sources load only the subsets demanded by the compiled graph.\n const includeInitialState = !this.collectionConfigBuilder.isLazySource(\n this.sourceId,\n )\n\n subscription = this.subscribeToMatchingChanges(\n whereExpression,\n includeInitialState,\n onStatusChange,\n trackLoadResult,\n onLoadSubsetError,\n )\n this.registerSubscriptionCleanup(subscription)\n }\n\n // Check current status after subscribing - if status is 'loadingSubset', track it.\n // The onStatusChange listener will catch the transition to 'ready'.\n if (\n !this.collectionConfigBuilder.isLazySource(this.sourceId) &&\n subscription.status === `loadingSubset`\n ) {\n this.ensureLoadingPromise(subscription)\n }\n\n return subscription\n }\n\n private registerSubscriptionCleanup(\n subscription: CollectionSubscription,\n ): void {\n const unsubscribe = () => {\n // If subscription has a pending promise, resolve it before unsubscribing\n const deferred = this.subscriptionLoadingPromises.get(subscription)\n if (deferred) {\n this.subscriptionLoadingPromises.delete(subscription)\n deferred.resolve()\n }\n\n this.demand.clear()\n subscription.unsubscribe()\n }\n // currentSyncState is always defined when subscribe() is called\n // (called during sync session setup)\n this.collectionConfigBuilder.currentSyncState!.unsubscribeCallbacks.add(\n unsubscribe,\n )\n }\n\n setDemand(\n subscription: CollectionSubscription,\n plan: LazyDemandPlan,\n keys: Set<unknown>,\n ): void {\n let update\n try {\n update = this.demand.setDemand(subscription, plan, keys)\n } catch (error) {\n // CollectionSubscription reports adapter failures before rethrowing.\n // Convert that synchronous form to the same query-local fatal demand\n // state as a rejected load, without letting it escape the source commit.\n // Preserve unrelated graph/programming errors as throws.\n if (subscription.lastError !== error) throw error\n const isInitialSync =\n this.collectionConfigBuilder.liveQueryCollection?.status === `loading`\n const generation = this.collectionConfigBuilder.beginDemand(plan.id)\n this.collectionConfigBuilder.failDemand(plan.id, generation, error)\n if (isInitialSync) throw error\n return\n }\n if (!update.changed) return\n\n if (update.empty) {\n this.collectionConfigBuilder.retireDemand(plan.id)\n return\n }\n\n const generation = this.collectionConfigBuilder.beginDemand(plan.id)\n if (update.ready instanceof Promise) {\n this.collectionConfigBuilder.trackSubsetLoadOperationPromise(update.ready)\n void update.ready.then(\n () => this.collectionConfigBuilder.settleDemand(plan.id, generation),\n (error) =>\n this.collectionConfigBuilder.failDemand(plan.id, generation, error),\n )\n } else {\n this.collectionConfigBuilder.settleDemand(plan.id, generation)\n }\n }\n\n private sendChangesToPipeline(\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean,\n ) {\n const changesArray = Array.isArray(changes) ? changes : [...changes]\n const filteredChanges = filterDuplicateInserts(\n changesArray,\n this.sentToD2Keys,\n )\n\n // currentSyncState and input are always defined when this method is called\n // (only called from active subscriptions during a sync session)\n const input =\n this.collectionConfigBuilder.currentSyncState!.inputs[this.sourceId]!\n const sentChanges = sendChangesToInput(input, filteredChanges)\n\n // Do not provide the callback that loads more data\n // if there's no more data to load\n // otherwise we end up in an infinite loop trying to load more data\n const dataLoader = sentChanges > 0 ? callback : undefined\n\n // We need to schedule a graph run even if there's no data to load\n // because we need to mark the collection as ready if it's not already\n // and that's only done in `scheduleGraphRun`\n this.collectionConfigBuilder.scheduleGraphRun(dataLoader, {\n sourceId: this.sourceId,\n })\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean,\n onStatusChange: (event: SubscriptionStatusChangeEvent) => void,\n onLoadSubsetResult: (result: Promise<void> | true) => void,\n onLoadSubsetError: (event: SubscriptionLoadSubsetErrorEvent) => void,\n ): CollectionSubscription {\n const sendChanges = (\n changes: Array<ChangeMessage<any, string | number>>,\n ) => {\n this.sendChangesToPipeline(changes)\n }\n\n // Get the query's orderBy and limit to pass to loadSubset.\n const hints = computeSubscriptionOrderByHints(\n this.collectionConfigBuilder.query,\n this.alias,\n )\n\n // Track loading via the loadSubset promise directly.\n // requestSnapshot uses trackLoadSubsetPromise: false (needed for truncate handling),\n // so we use onLoadSubsetResult to get the promise and track it ourselves.\n const subscription = this.collection.subscribeChanges(sendChanges, {\n ...(includeInitialState && { includeInitialState }),\n whereExpression,\n onStatusChange,\n onLoadSubsetError,\n orderBy: hints.orderBy,\n limit: hints.limit,\n onLoadSubsetResult: includeInitialState ? onLoadSubsetResult : undefined,\n })\n\n return subscription\n }\n\n private subscribeToOrderedChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n orderByInfo: OrderByOptimizationInfo,\n onStatusChange: (event: SubscriptionStatusChangeEvent) => void,\n onLoadSubsetResult: (result: Promise<void> | true) => void,\n onLoadSubsetError: (event: SubscriptionLoadSubsetErrorEvent) => void,\n ): CollectionSubscription {\n const { orderBy, offset, limit, index } = orderByInfo\n\n // Store the callback so loadNextItems can also use direct tracking.\n // Track in-flight ordered loads to avoid issuing redundant requests while\n // a previous snapshot is still pending.\n const handleLoadSubsetResult = (result: Promise<void> | true) => {\n if (result instanceof Promise) {\n this.pendingOrderedLoadPromise = result\n const finish = () => {\n if (this.pendingOrderedLoadPromise === result) {\n this.pendingOrderedLoadPromise = undefined\n }\n }\n void result.then(finish, finish)\n }\n onLoadSubsetResult(result)\n }\n\n this.orderedLoadSubsetResult = handleLoadSubsetResult\n\n // Use a holder to forward-reference subscription in the callback\n const subscriptionHolder: { current?: CollectionSubscription } = {}\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>,\n ) => {\n const changesArray = Array.isArray(changes) ? changes : [...changes]\n\n this.trackSentValues(changesArray, orderByInfo.comparator)\n\n // Split live updates into a delete of the old value and an insert of the new value\n const splittedChanges = splitUpdates(changesArray)\n this.sendChangesToPipelineWithTracking(\n splittedChanges,\n subscriptionHolder.current!,\n )\n }\n\n // Subscribe to changes with onStatusChange - listener is registered before any snapshot\n // values bigger than what we've sent don't need to be sent because they can't affect the topK\n const subscription = this.collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n onStatusChange,\n onLoadSubsetError,\n })\n subscriptionHolder.current = subscription\n this.registerSubscriptionCleanup(subscription)\n\n // Listen for truncate events to reset cursor tracking state and sentToD2Keys\n // This ensures that after a must-refetch/truncate, we don't use stale cursor data\n // and allow re-inserts of previously sent keys\n const truncateUnsubscribe = this.collection.on(`truncate`, () => {\n this.biggest = undefined\n this.lastLoadRequestKey = undefined\n this.pendingOrderedLoadPromise = undefined\n this.sentToD2Keys.clear()\n })\n\n // Clean up truncate listener when subscription is unsubscribed\n subscription.on(`unsubscribed`, () => {\n truncateUnsubscribe()\n })\n\n // Normalize the orderBy clauses such that the references are relative to the collection\n const normalizedOrderBy = normalizeOrderByPaths(orderBy, this.alias)\n\n // Trigger the snapshot request — use direct load tracking (trackLoadSubsetPromise: false)\n // to pipe the loadSubset result straight to the live query collection. This bypasses\n // the subscription status → onStatusChange → deferred promise chain which is fragile\n // under microtask timing (e.g., queueMicrotask delays in TanStack Query observers).\n if (index) {\n // We have an index on the first orderBy column - use lazy loading optimization\n subscription.setOrderByIndex(index)\n\n subscription.requestLimitedSnapshot({\n limit: offset + limit,\n orderBy: normalizedOrderBy,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: handleLoadSubsetResult,\n })\n } else {\n // No index available (e.g., non-ref expression): pass orderBy/limit to loadSubset\n subscription.requestSnapshot({\n orderBy: normalizedOrderBy,\n limit: offset + limit,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: handleLoadSubsetResult,\n })\n }\n\n return subscription\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n loadMoreIfNeeded(subscription: CollectionSubscription) {\n const orderByInfo = this.getOrderByInfo()\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load\n return true\n }\n\n const { dataNeeded, index } = orderByInfo\n\n if (!dataNeeded || !index) {\n // dataNeeded is not set when there's no index (e.g., non-ref expression\n // or auto-indexing is disabled). Without an index, lazy loading can't work —\n // all data was already loaded eagerly via requestSnapshot.\n return true\n }\n\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n if (this.pendingOrderedLoadPromise) {\n // The current window still needs the in-flight coverage. Attach it to\n // this operation without making an unrelated or superseded request a\n // dependency of every window change.\n this.collectionConfigBuilder.trackSubsetLoadOperationPromise(\n this.pendingOrderedLoadPromise,\n )\n return true\n }\n try {\n this.loadNextItems(n, subscription)\n } catch (error) {\n if (subscription.lastError !== error) throw error\n // The subscription already reported the failure. Automatic refills\n // must not make the source transaction that exposed the gap fail.\n }\n }\n return true\n }\n\n private sendChangesToPipelineWithTracking(\n changes: Iterable<ChangeMessage<any, string | number>>,\n subscription: CollectionSubscription,\n ) {\n const orderByInfo = this.getOrderByInfo()\n if (!orderByInfo) {\n this.sendChangesToPipeline(changes)\n return\n }\n\n // Cache the loadMoreIfNeeded callback on the subscription using a symbol property.\n // This ensures we pass the same function instance to the scheduler each time,\n // allowing it to deduplicate callbacks when multiple changes arrive during a transaction.\n type SubscriptionWithLoader = CollectionSubscription & {\n [loadMoreCallbackSymbol]?: () => boolean\n }\n\n const subscriptionWithLoader = subscription as SubscriptionWithLoader\n\n subscriptionWithLoader[loadMoreCallbackSymbol] ??=\n this.loadMoreIfNeeded.bind(this, subscription)\n\n this.sendChangesToPipeline(\n changes,\n subscriptionWithLoader[loadMoreCallbackSymbol],\n )\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n private loadNextItems(n: number, subscription: CollectionSubscription) {\n const orderByInfo = this.getOrderByInfo()\n if (!orderByInfo) {\n return\n }\n\n const cursor = computeOrderedLoadCursor(\n orderByInfo,\n this.biggest,\n this.lastLoadRequestKey,\n this.alias,\n n,\n )\n if (!cursor) return // Duplicate request — skip\n\n const loadRequestKey = cursor.loadRequestKey\n this.lastLoadRequestKey = loadRequestKey\n\n // Take the `n` items after the biggest sent value\n // Omit offset so requestLimitedSnapshot can advance based on\n // the number of rows already loaded (supports offset-based backends).\n try {\n subscription.requestLimitedSnapshot({\n orderBy: cursor.normalizedOrderBy,\n limit: n,\n minValues: cursor.minValues,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: (result) => {\n if (result instanceof Promise) {\n void result.then(undefined, () => {\n if (this.lastLoadRequestKey === loadRequestKey) {\n this.lastLoadRequestKey = undefined\n }\n })\n }\n this.orderedLoadSubsetResult?.(result)\n },\n })\n } catch (error) {\n if (this.lastLoadRequestKey === loadRequestKey) {\n this.lastLoadRequestKey = undefined\n }\n throw error\n }\n }\n\n private getWhereClause(): BasicExpression<boolean> | undefined {\n const sourceWhereClausesCache =\n this.collectionConfigBuilder.sourceWhereClausesCache\n if (!sourceWhereClausesCache) {\n return undefined\n }\n return sourceWhereClausesCache.get(this.sourceId)\n }\n\n private getOrderByInfo(): OrderByOptimizationInfo | undefined {\n const info =\n this.collectionConfigBuilder.optimizableOrderByCollections[this.sourceId]\n if (info?.sourceId === this.sourceId) {\n return info\n }\n return undefined\n }\n\n private trackSentValues(\n changes: Array<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n ): void {\n const result = trackBiggestSentValue(\n changes,\n this.biggest,\n this.sentToD2Keys,\n comparator,\n )\n this.biggest = result.biggest\n if (result.shouldResetLoadKey) {\n this.lastLoadRequestKey = undefined\n }\n }\n\n private ensureLoadingPromise(subscription: CollectionSubscription) {\n if (this.subscriptionLoadingPromises.has(subscription)) {\n return\n }\n\n let resolve: () => void\n const promise = new Promise<void>((res) => {\n resolve = res\n })\n\n this.subscriptionLoadingPromises.set(subscription, {\n resolve: resolve!,\n })\n this.collectionConfigBuilder.trackSubsetLoadPromise(promise)\n }\n}\n"],"names":["SubsetDemandController","normalizeExpressionPaths","subscription","generation","filterDuplicateInserts","sendChangesToInput","computeSubscriptionOrderByHints","splitUpdates","normalizeOrderByPaths","computeOrderedLoadCursor","trackBiggestSentValue"],"mappings":"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"}