@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 22.2 kB
Source Map (JSON)
{"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import { normalizeExpressionPaths } from '../compiler/expressions.js'\nimport { OrderedSourceLoader } from './ordered-source-loader.js'\nimport {\n computeSubscriptionOrderByHints,\n reconcileChangesForD2,\n sendChangesToInput,\n splitUpdates,\n} from './utils.js'\nimport { SubsetDemandController } from './subset-demand-controller.js'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n ChangeMessage,\n LoadSubsetRequestResult,\n SubscribeChangesOptions,\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\ntype TruncateReplayPublicationControl = NonNullable<\n SubscribeChangesOptions[`truncateReplayPublication`]\n>\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Track deferred promises for subscription loading states\n private subscriptionLoadingPromises = new Map<\n CollectionSubscription,\n { resolve: () => void }\n >()\n\n // Exact row last contributed to D2 for each source key.\n private sentToD2Rows = new Map<string | number, Record<string, unknown>>()\n\n // Direct load tracking callback for ordered path (set during subscribeToOrderedChanges,\n // used by loadNextItems for subsequent requestLimitedSnapshot calls)\n private orderedLoader: OrderedSourceLoader | 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 =\n this.collectionConfigBuilder.sourceWhereClausesCache?.get(this.sourceId)\n return this.subscribeToChanges(\n whereClause\n ? normalizeExpressionPaths(whereClause, this.alias)\n : undefined,\n )\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n const orderByInfo =\n this.collectionConfigBuilder.optimizableOrderByCollections[this.sourceId]\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: LoadSubsetRequestResult) => {\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 }\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 // Lazy demand owns its fatal-error path. For eager sources, one\n // successful page does not finish initial ordered refinement.\n !this.collectionConfigBuilder.isLazySource(this.sourceId) &&\n this.collectionConfigBuilder.liveQueryCollection?.status ===\n `loading`,\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 =\n (this.collection.config.syncMode !== `on-demand` ||\n this.collectionConfigBuilder.query.limit !== 0) &&\n !this.collectionConfigBuilder.isLazySource(this.sourceId)\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 try {\n this.demand.clear()\n } finally {\n subscription.unsubscribe()\n }\n }\n // currentSyncState is always defined when subscribe() is called\n // (called during sync run 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 (!Object.is(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(\n plan.id,\n update.ready instanceof Promise,\n )\n if (update.ready instanceof Promise) {\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 ): void {\n const changesArray = Array.isArray(changes) ? changes : [...changes]\n const reconciledChanges = reconcileChangesForD2(\n changesArray,\n this.sentToD2Rows,\n )\n // currentSyncState and input are always defined when this method is called\n // (only called from active subscriptions during a sync run)\n const input =\n this.collectionConfigBuilder.currentSyncState!.inputs[this.sourceId]!\n if (sendChangesToInput(input, reconciledChanges) > 0) {\n this.collectionConfigBuilder.advanceGraphInputRevision()\n }\n // A zero-change source settlement can still release ordered demand.\n this.collectionConfigBuilder.scheduleGraphRun()\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean,\n onStatusChange: (event: SubscriptionStatusChangeEvent) => void,\n onLoadSubsetResult: (result: LoadSubsetRequestResult) => 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 this.collectionConfigBuilder.queryCompareOptions,\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 truncateReplayPublication: this.truncateReplayPublicationControl(),\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: LoadSubsetRequestResult) => void,\n onLoadSubsetError: (event: SubscriptionLoadSubsetErrorEvent) => void,\n ): CollectionSubscription {\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 subscription = subscriptionHolder.current\n if (!subscription) return\n const changesArray = Array.isArray(changes) ? changes : [...changes]\n\n this.orderedLoader?.onSourceChanges(changesArray, this.sentToD2Rows)\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.sendChangesToPipeline(splittedChanges)\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 truncateReplayPublication: this.truncateReplayPublicationControl(() => {\n // Recovery favors a simple, authoritative rebuild over resuming a\n // fragile cursor. The retained full-source demand is replayed on later\n // truncates, so this adds at most one demand per subscription.\n // Queue startup inside the publication barrier too: a synchronous\n // throw establishes no acquisition for the replay to wait on.\n const loader = this.orderedLoader\n this.collectionConfigBuilder.trackOrderedLoadPromise(\n Promise.resolve().then(() => loader?.loadFullSource()),\n true,\n )\n }),\n })\n subscriptionHolder.current = subscription\n this.registerSubscriptionCleanup(subscription)\n\n // Reset ordered-load state on truncate. Keep exact D2 rows until the\n // replacement publication retracts or replaces them.\n const truncateUnsubscribe = this.collection.on(`truncate`, () => {\n this.orderedLoader?.resetCursor()\n })\n\n // Clean up truncate listener when subscription is unsubscribed\n subscription.on(`unsubscribed`, () => {\n truncateUnsubscribe()\n subscriptionHolder.current = undefined\n this.orderedLoader?.dispose()\n this.orderedLoader = undefined\n })\n\n this.orderedLoader = new OrderedSourceLoader(\n orderByInfo,\n subscription,\n this.alias,\n (result, holdPublication, settlesAsync) => {\n if (result instanceof Promise) {\n this.collectionConfigBuilder.trackOrderedLoadPromise(\n result,\n holdPublication &&\n settlesAsync &&\n !subscription.hasPendingTruncateReplacement,\n )\n }\n onLoadSubsetResult(result)\n },\n () =>\n this.collectionConfigBuilder.liveQueryCollection?.status === `ready` &&\n !this.collectionConfigBuilder.hasActiveWindowOperation(),\n () => this.collectionConfigBuilder.getGraphInputRevision(),\n () =>\n orderByInfo.joinedFilterSourceId !== undefined &&\n this.collectionConfigBuilder.hasPendingJoinedWork(\n orderByInfo.joinedFilterSourceId,\n ),\n )\n this.orderedLoader.start()\n\n return subscription\n }\n\n private truncateReplayPublicationControl(\n onStart?: () => void,\n ): TruncateReplayPublicationControl {\n const syncRunGeneration =\n this.collectionConfigBuilder.getSyncRunGeneration()\n return {\n start: () => {\n onStart?.()\n },\n succeed: () => {\n if (\n syncRunGeneration !==\n this.collectionConfigBuilder.getSyncRunGeneration()\n ) {\n return\n }\n this.orderedLoader?.settleFullSourceReplay()\n this.collectionConfigBuilder.scheduleGraphRunIfSyncRunCurrent(\n syncRunGeneration,\n )\n },\n }\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): void {\n if (\n subscription.hasPendingTruncateReplacement &&\n !this.collectionConfigBuilder.hasActiveWindowOperation()\n ) {\n return\n }\n\n const orderByInfo =\n this.collectionConfigBuilder.optimizableOrderByCollections[this.sourceId]\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load\n return\n }\n\n try {\n const pending = this.orderedLoader?.loadMore(\n this.collectionConfigBuilder.getActiveWindowOperationGeneration(),\n )\n if (pending) {\n this.collectionConfigBuilder.trackSubsetLoadOperationPromise(pending)\n }\n } catch (error) {\n if (!Object.is(subscription.lastError, error)) throw error\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","reconcileChangesForD2","sendChangesToInput","computeSubscriptionOrderByHints","splitUpdates","OrderedSourceLoader"],"mappings":"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"}