@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 58.8 kB
Source Map (JSON)
{"version":3,"file":"collection-config-builder.cjs","sources":["../../../../src/query/live/collection-config-builder.ts"],"sourcesContent":["import { D2, output } from '@tanstack/db-ivm'\nimport { compileQuery } from '../compiler/index.js'\nimport {\n MissingAliasInputsError,\n SetWindowRequiresOrderByError,\n} from '../../errors.js'\nimport {\n getActivePublicationContext,\n transactionScopedScheduler,\n} from '../../scheduler.js'\nimport { getActiveTransaction } from '../../transactions.js'\nimport { deepEquals } from '../../utils.js'\nimport { CollectionSubscriber } from './collection-subscriber.js'\nimport { getCollectionBuilder } from './collection-registry.js'\nimport { LIVE_QUERY_INTERNAL } from './internal.js'\nimport { materializeCompilation } from './materialized-pipeline.js'\nimport { BucketFacadeAdapter } from './bucket-facade-adapter.js'\nimport {\n buildQueryFromConfig,\n extractCollectionFromSource,\n extractCollectionSources,\n extractCollectionsFromQuery,\n} from './utils.js'\nimport type { LiveQueryInternalUtils } from './internal.js'\nimport type { WindowOptions } from '../compiler/index.js'\nimport type { SchedulerContextId } from '../../scheduler.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type { RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n CollectionConfigSingleRowOption,\n KeyedStream,\n ResultStream,\n StringCollationConfig,\n SyncConfig,\n UtilsRecord,\n} from '../../types.js'\nimport type { Context, GetResult } from '../builder/types.js'\nimport type { BasicExpression, QueryIR } from '../ir.js'\nimport type { LazyCollectionCallbacks } from '../compiler/joins.js'\nimport type {\n Changes,\n FullSyncState,\n LiveQueryCollectionConfig,\n SyncState,\n} from './types.js'\nimport type { AllCollectionEvents } from '../../collection/events.js'\n\nexport type LiveQueryCollectionUtils = UtilsRecord & {\n getRunCount: () => number\n /**\n * Sets the offset and limit of an ordered query.\n * Is a no-op if the query is not ordered.\n *\n * @returns `true` if no subset loading was triggered, or `Promise<void>` that resolves when the subset has been loaded\n */\n setWindow: (options: WindowOptions) => true | Promise<void>\n /**\n * Gets the current window (offset and limit) for an ordered query.\n *\n * @returns The current window settings, or `undefined` if the query is not windowed\n */\n getWindow: () => { offset: number; limit: number } | undefined\n [LIVE_QUERY_INTERNAL]: LiveQueryInternalUtils\n}\n\ntype PendingGraphRun = {\n loadCallbacks: Set<() => boolean>\n}\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\ntype SyncMethods<TResult extends object> = Parameters<\n SyncConfig<TResult>[`sync`]\n>[0]\n\nexport class CollectionConfigBuilder<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n private readonly id: string\n readonly query: QueryIR\n private readonly collections: Record<string, Collection<any, any, any>>\n private readonly collectionSources: ReturnType<\n typeof extractCollectionSources\n >\n private readonly collectionByAlias: Record<string, Collection<any, any, any>>\n // Populated during compilation with all aliases (including subquery inner aliases)\n private compiledAliasToCollectionId: Record<string, string> = {}\n\n // WeakMap to store the keys of the results\n // so that we can retrieve them in the getKey function\n private readonly resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n private readonly orderByIndices = new WeakMap<object, string>()\n\n private readonly compare?: (val1: TResult, val2: TResult) => number\n private readonly compareOptions?: StringCollationConfig\n\n private isGraphRunning = false\n private runCount = 0\n\n // Current sync session state (set when sync starts, cleared when it stops)\n // Public for testing purposes (CollectionConfigBuilder is internal, not public API)\n public currentSyncConfig:\n | Parameters<SyncConfig<TResult>[`sync`]>[0]\n | undefined\n public currentSyncState: FullSyncState | undefined\n\n // Error state tracking\n private isInErrorState = false\n private fatalQueryError = false\n private readonly erroredSourceIds = new Set<string>()\n\n // Reference to the live query collection for error state transitions\n public liveQueryCollection?: Collection<TResult, any, any>\n\n private windowFn: ((options: WindowOptions) => void) | undefined\n private readonly initialWindow: WindowOptions | undefined\n private currentWindow: WindowOptions | undefined\n\n private maybeRunGraphFn: (() => void) | undefined\n\n private readonly sourceDependencies: Record<\n string,\n Array<CollectionConfigBuilder<any, any>>\n > = {}\n\n private readonly builderDependencies = new Set<\n CollectionConfigBuilder<any, any>\n >()\n\n // Pending graph runs per scheduler context (e.g., per transaction)\n // The builder manages its own state; the scheduler just orchestrates execution order\n // Only stores callbacks - if sync ends, pending jobs gracefully no-op\n private readonly pendingGraphRuns = new Map<\n SchedulerContextId,\n PendingGraphRun\n >()\n\n // Unsubscribe function for scheduler's onClear listener\n // Registered when sync starts, unregistered when sync stops\n // Prevents memory leaks by releasing the scheduler's reference to this builder\n private unsubscribeFromSchedulerClears?: () => void\n\n private graphCache: D2 | undefined\n private inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n private pipelineCache: ResultStream | undefined\n public sourceWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n private bucketFacadesCache:\n | ReturnType<typeof materializeCompilation>[`facades`]\n | undefined\n\n // Map of opaque source ID to subscription\n readonly subscriptions: Record<string, CollectionSubscription> = {}\n // Map of opaque source ID to demand callbacks for that lazy source\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of opaque source IDs that are lazy (don't load initial state)\n readonly lazySources = new Set<string>()\n private readonly activeDemands = new Map<\n string,\n { generation: number; settled: boolean }\n >()\n private readonly demandGenerations = new Map<string, number>()\n // Map of lexical source IDs to optimizable ORDER BY state\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> = {}\n\n constructor(\n private readonly config: LiveQueryCollectionConfig<TContext, TResult>,\n ) {\n // Generate a unique ID if not provided\n this.id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n this.query = buildQueryFromConfig({\n query: config.query,\n requireObjectResult: true,\n })\n this.initialWindow = this.query.orderBy?.length\n ? {\n offset: this.query.offset ?? 0,\n limit: this.query.limit ?? Infinity,\n }\n : undefined\n this.collections = extractCollectionsFromQuery(this.query)\n this.collectionSources = extractCollectionSources(this.query)\n this.collectionByAlias = Object.fromEntries(\n this.collectionSources.map(({ alias, collection }) => [\n alias,\n collection,\n ]),\n )\n\n // Create compare function for ordering if the query has orderBy\n if (this.query.orderBy && this.query.orderBy.length > 0) {\n this.compare = createOrderByComparator<TResult>(this.orderByIndices)\n }\n\n // Use explicitly provided compareOptions if available, otherwise inherit from FROM collection\n this.compareOptions =\n this.config.defaultStringCollation ??\n extractCollectionFromSource(this.query).compareOptions\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n this.compileBasePipeline()\n }\n\n /**\n * Recursively checks if a query or any of its subqueries contains joins\n */\n private hasJoins(query: QueryIR): boolean {\n // Check if this query has joins\n if (query.join && query.join.length > 0) {\n return true\n }\n\n // Recursively check subqueries in the from clause\n if (query.from.type === `queryRef`) {\n if (this.hasJoins(query.from.query)) {\n return true\n }\n } else if (query.from.type === `unionFrom`) {\n for (const source of query.from.sources) {\n if (source.type === `queryRef` && this.hasJoins(source.query)) {\n return true\n }\n }\n } else if (query.from.type === `unionAll`) {\n for (const branch of query.from.queries) {\n if (this.hasJoins(branch)) {\n return true\n }\n }\n }\n\n return false\n }\n\n getConfig(): CollectionConfigSingleRowOption<TResult> & {\n utils: LiveQueryCollectionUtils\n } {\n return {\n id: this.id,\n getKey:\n this.config.getKey ||\n ((item: any) =>\n (this.resultKeys.get(item) ?? item.$key) as string | number),\n sync: this.getSyncConfig(),\n compare: this.compare,\n defaultStringCollation: this.compareOptions,\n gcTime: this.config.gcTime ?? 5000, // 5 seconds by default for live queries\n schema: this.config.schema,\n onInsert: this.config.onInsert,\n onUpdate: this.config.onUpdate,\n onDelete: this.config.onDelete,\n startSync: this.config.startSync,\n singleResult: this.query.singleResult,\n utils: {\n getRunCount: this.getRunCount.bind(this),\n setWindow: this.setWindow.bind(this),\n getWindow: this.getWindow.bind(this),\n [LIVE_QUERY_INTERNAL]: {\n getBuilder: () => this,\n hasCustomGetKey: !!this.config.getKey,\n hasJoins: this.hasJoins(this.query),\n hasDistinct: !!this.query.distinct,\n },\n },\n }\n }\n\n setWindow(options: WindowOptions): true | Promise<void> {\n if (!this.windowFn) {\n throw new SetWindowRequiresOrderByError()\n }\n\n const previousWindow = this.currentWindow ?? this.initialWindow\n try {\n this.windowFn(options)\n this.maybeRunGraphFn?.()\n this.currentWindow = options\n } catch (error) {\n if (previousWindow) {\n try {\n this.windowFn(previousWindow)\n this.maybeRunGraphFn?.()\n } catch {\n // Recovery is best-effort; preserve the error from the requested\n // window rather than replacing it with a rollback failure.\n }\n }\n throw error\n }\n\n return this.liveQueryCollection?._sync.waitForCurrentLoadSubset() ?? true\n }\n\n getWindow(): { offset: number; limit: number } | undefined {\n // Only return window if this is a windowed query (has orderBy and windowFn)\n const window = this.currentWindow ?? this.initialWindow\n if (!this.windowFn || !window) {\n return undefined\n }\n return {\n offset: window.offset ?? 0,\n limit: window.limit ?? 0,\n }\n }\n\n /**\n * Resolves a collection alias to its collection ID.\n *\n * Uses a two-tier lookup strategy:\n * 1. First checks compiled aliases (includes subquery inner aliases)\n * 2. Falls back to declared aliases from the query's from/join clauses\n *\n * @param alias - The alias to resolve (e.g., \"employee\", \"manager\")\n * @returns The collection ID that the alias references\n * @throws {Error} If the alias is not found in either lookup\n */\n getCollectionIdForAlias(alias: string): string {\n const compiled = this.compiledAliasToCollectionId[alias]\n if (compiled) {\n return compiled\n }\n const collection = this.collectionByAlias[alias]\n if (collection) {\n return collection.id\n }\n throw new Error(`Unknown source alias \"${alias}\"`)\n }\n\n isLazySource(sourceId: string): boolean {\n return this.lazySources.has(sourceId)\n }\n\n beginDemand(planId: string): number {\n const generation = (this.demandGenerations.get(planId) ?? 0) + 1\n this.demandGenerations.set(planId, generation)\n this.activeDemands.set(planId, { generation, settled: false })\n return generation\n }\n\n settleDemand(planId: string, generation: number): void {\n const demand = this.activeDemands.get(planId)\n if (!demand || demand.generation !== generation || demand.settled) return\n demand.settled = true\n this.maybeRunGraphFn?.()\n }\n\n failDemand(planId: string, generation: number, error: unknown): void {\n const demand = this.activeDemands.get(planId)\n if (!demand || demand.generation !== generation) return\n const message = error instanceof Error ? error.message : String(error)\n this.transitionToError(`Subset demand '${planId}' failed: ${message}`)\n }\n\n retireDemand(planId: string): void {\n this.activeDemands.delete(planId)\n }\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happen because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n maybeRunGraph(callback?: () => boolean) {\n if (this.isGraphRunning) {\n // no nested runs of the graph\n // which is possible if the `callback`\n // would call `maybeRunGraph` e.g. after it has loaded some more data\n return\n }\n\n // Should only be called when sync is active\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `maybeRunGraph called without active sync session. This should not happen.`,\n )\n }\n\n this.isGraphRunning = true\n\n try {\n const { begin, commit } = this.currentSyncConfig\n const syncState = this.currentSyncState\n\n // Don't run if the live query is in an error state\n if (this.isInErrorState) {\n return\n }\n\n // Always run the graph if subscribed (eager execution)\n if (syncState.subscribedToAllCollections) {\n let callbackCalled = false\n while (syncState.graph.pendingWork()) {\n syncState.graph.run()\n callback?.()\n callbackCalled = true\n }\n\n // Publish only after every operator has reached quiescence. A source\n // change can reach sibling materializations in different graph steps;\n // flushing between those steps would expose a mixed root snapshot.\n syncState.flushPendingChanges?.()\n\n // Ensure the callback runs at least once even when the graph has no pending work.\n // This handles lazy loading scenarios where setWindow() increases the limit or\n // an async loadSubset completes and we need to re-check if more data is needed.\n if (!callbackCalled) {\n callback?.()\n }\n\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (syncState.messagesCount === 0) {\n begin()\n commit()\n }\n\n // After graph processing completes, check if we should mark ready.\n // This is the canonical place to transition to ready state because:\n // 1. All data has been processed through the graph\n // 2. All source collections have had a chance to send their initial data\n // This prevents marking ready before data is processed (fixes isReady=true with empty data)\n this.updateLiveQueryStatus(this.currentSyncConfig)\n }\n } finally {\n this.isGraphRunning = false\n }\n }\n\n /**\n * Schedules a graph run with the transaction-scoped scheduler.\n * Ensures each builder runs at most once per transaction, with automatic dependency tracking\n * to run parent queries before child queries. Outside a transaction, runs immediately.\n *\n * Multiple calls during a transaction are coalesced into a single execution.\n * Dependencies are auto-discovered from subscribed live queries, or can be overridden.\n * Load callbacks are combined when entries merge.\n *\n * Uses the current sync session's config and syncState from instance properties.\n *\n * @param callback - Optional callback to load more data if needed (returns true when done)\n * @param options - Optional scheduling configuration\n * @param options.contextId - Transaction ID to group work; defaults to active transaction\n * @param options.jobId - Unique identifier for this job; defaults to this builder instance\n * @param options.sourceId - Source that triggered this schedule; adds its dependencies\n * @param options.dependencies - Explicit dependency list; overrides auto-discovered dependencies\n */\n scheduleGraphRun(\n callback?: () => boolean,\n options?: {\n contextId?: SchedulerContextId\n jobId?: unknown\n sourceId?: string\n dependencies?: Array<CollectionConfigBuilder<any, any>>\n },\n ) {\n const contextId =\n options?.contextId ??\n getActiveTransaction()?.id ??\n getActivePublicationContext()\n // Use the builder instance as the job ID for deduplication. This is memory-safe\n // because the scheduler's context Map is deleted after flushing (no long-term retention).\n const jobId = options?.jobId ?? this\n const dependentBuilders = (() => {\n if (options?.dependencies) {\n return options.dependencies\n }\n\n const deps = new Set(this.builderDependencies)\n if (options?.sourceId) {\n const sourceDeps = this.sourceDependencies[options.sourceId]\n if (sourceDeps) {\n for (const dep of sourceDeps) {\n deps.add(dep)\n }\n }\n }\n\n deps.delete(this)\n\n return Array.from(deps)\n })()\n\n // Ensure dependent builders are actually scheduled in this context so that\n // dependency edges always point to a real job (or a deduped no-op if already scheduled).\n if (contextId) {\n for (const dep of dependentBuilders) {\n if (typeof dep.scheduleGraphRun === `function`) {\n dep.scheduleGraphRun(undefined, { contextId })\n }\n }\n }\n\n // We intentionally scope deduplication to the builder instance. Each instance\n // owns caches and compiled pipelines, so sharing work across instances that\n // merely reuse the same string id would execute the wrong builder's graph.\n\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `scheduleGraphRun called without active sync session. This should not happen.`,\n )\n }\n\n // Manage our own state - get or create pending callbacks for this context\n let pending = contextId ? this.pendingGraphRuns.get(contextId) : undefined\n if (!pending) {\n pending = {\n loadCallbacks: new Set(),\n }\n if (contextId) {\n this.pendingGraphRuns.set(contextId, pending)\n }\n }\n\n // Add callback if provided (this is what accumulates between schedules)\n if (callback) {\n pending.loadCallbacks.add(callback)\n }\n\n // Schedule execution (scheduler just orchestrates order, we manage state)\n // For immediate execution (no contextId), pass pending directly since it won't be in the map\n const pendingToPass = contextId ? undefined : pending\n transactionScopedScheduler.schedule({\n contextId,\n jobId,\n dependencies: dependentBuilders,\n run: () => this.executeGraphRun(contextId, pendingToPass),\n })\n }\n\n /**\n * Clears pending graph run state for a specific context.\n * Called when the scheduler clears a context (e.g., transaction rollback/abort).\n */\n clearPendingGraphRun(contextId: SchedulerContextId): void {\n this.pendingGraphRuns.delete(contextId)\n }\n\n /**\n * Returns true if this builder has a pending graph run for the given context.\n */\n hasPendingGraphRun(contextId: SchedulerContextId): boolean {\n return this.pendingGraphRuns.has(contextId)\n }\n\n /**\n * Executes a pending graph run. Called by the scheduler when dependencies are satisfied.\n * Clears the pending state BEFORE execution so that any re-schedules during the run\n * create fresh state and don't interfere with the current execution.\n * Uses instance sync state - if sync has ended, gracefully returns without executing.\n *\n * @param contextId - Optional context ID to look up pending state\n * @param pendingParam - For immediate execution (no context), pending state is passed directly\n */\n private executeGraphRun(\n contextId?: SchedulerContextId,\n pendingParam?: PendingGraphRun,\n ): void {\n // Get pending state: either from parameter (no context) or from map (with context)\n // Remove from map BEFORE checking sync state to prevent leaking entries when sync ends\n // before the transaction flushes (e.g., unsubscribe during in-flight transaction)\n const pending =\n pendingParam ??\n (contextId ? this.pendingGraphRuns.get(contextId) : undefined)\n if (contextId) {\n this.pendingGraphRuns.delete(contextId)\n }\n\n // If no pending state, nothing to execute (context was cleared)\n if (!pending) {\n return\n }\n\n // If sync session has ended, don't execute (graph is finalized, subscriptions cleared)\n if (!this.currentSyncConfig || !this.currentSyncState) {\n return\n }\n\n this.incrementRunCount()\n\n const combinedLoader = () => {\n let allDone = true\n let firstError: unknown\n pending.loadCallbacks.forEach((loader) => {\n try {\n allDone = loader() && allDone\n } catch (error) {\n allDone = false\n firstError ??= error\n }\n })\n if (firstError) {\n throw firstError\n }\n // Returning false signals that callers should schedule another pass.\n return allDone\n }\n\n this.maybeRunGraph(combinedLoader)\n }\n\n private getSyncConfig(): SyncConfig<TResult> {\n return {\n rowUpdateMode: `full`,\n sync: this.syncFn.bind(this),\n }\n }\n\n incrementRunCount() {\n this.runCount++\n }\n\n getRunCount() {\n return this.runCount\n }\n\n private syncFn(config: SyncMethods<TResult>) {\n // Store reference to the live query collection for error state transitions\n this.liveQueryCollection = config.collection\n // Reset error state from any previous sync session so a restarted sync can become ready again.\n this.isInErrorState = false\n this.fatalQueryError = false\n this.erroredSourceIds.clear()\n // Store config and syncState as instance properties for the duration of this sync session\n this.currentSyncConfig = config\n\n const syncState: SyncState = {\n messagesCount: 0,\n subscribedToAllCollections: false,\n unsubscribeCallbacks: new Set<() => void>(),\n }\n\n // Extend the pipeline such that it applies the incoming changes to the collection\n const fullSyncState = this.extendPipelineWithChangeProcessing(\n config,\n syncState,\n )\n this.currentSyncState = fullSyncState\n\n // Listen for scheduler context clears to clean up our pending state\n // Re-register on each sync start so the listener is active for the sync session's lifetime\n this.unsubscribeFromSchedulerClears = transactionScopedScheduler.onClear(\n (contextId) => {\n this.clearPendingGraphRun(contextId)\n },\n )\n\n // Listen for loadingSubset changes on the live query collection BEFORE subscribing.\n // This ensures we don't miss the event if subset loading completes synchronously.\n // When isLoadingSubset becomes false, we may need to mark the collection as ready\n // (if all source collections are already ready but we were waiting for subset load to complete)\n const loadingSubsetUnsubscribe = config.collection.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n // Subset loading finished, check if we can now mark ready\n this.updateLiveQueryStatus(config)\n }\n },\n )\n syncState.unsubscribeCallbacks.add(loadingSubsetUnsubscribe)\n\n const loadSubsetDataCallbacks = this.subscribeToAllCollections(\n config,\n fullSyncState,\n )\n\n this.maybeRunGraphFn = () => this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Initial run with callback to load more data if needed\n this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Return the unsubscribe function\n return () => {\n syncState.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n\n // Clear current sync session state\n this.currentSyncConfig = undefined\n this.currentSyncState = undefined\n this.maybeRunGraphFn = undefined\n this.currentWindow = undefined\n this.isInErrorState = false\n this.fatalQueryError = false\n this.erroredSourceIds.clear()\n\n // Clear all pending graph runs to prevent memory leaks from in-flight transactions\n // that may flush after the sync session ends\n this.pendingGraphRuns.clear()\n\n // Reset caches so a fresh graph/pipeline is compiled on next start\n // This avoids reusing a finalized D2 graph across GC restarts\n this.graphCache = undefined\n this.inputsCache = undefined\n this.pipelineCache = undefined\n this.sourceWhereClausesCache = undefined\n this.bucketFacadesCache = undefined\n\n // Reset lazy source alias state\n this.lazySources.clear()\n this.demandGenerations.clear()\n this.activeDemands.clear()\n this.optimizableOrderByCollections = {}\n this.lazySourcesCallbacks = {}\n\n // Clear subscription references to prevent memory leaks\n // Note: Individual subscriptions are already unsubscribed via unsubscribeCallbacks\n Object.keys(this.subscriptions).forEach(\n (key) => delete this.subscriptions[key],\n )\n this.compiledAliasToCollectionId = {}\n\n // Unregister from scheduler's onClear listener to prevent memory leaks\n // The scheduler's listener Set would otherwise keep a strong reference to this builder\n this.unsubscribeFromSchedulerClears?.()\n this.unsubscribeFromSchedulerClears = undefined\n }\n }\n\n /**\n * Compiles the query pipeline with all declared aliases.\n */\n private compileBasePipeline() {\n this.graphCache = new D2()\n this.inputsCache = Object.fromEntries(\n this.collectionSources.map((source) => [\n source.sourceId,\n this.graphCache!.newInput<any>(),\n ]),\n )\n\n const compilation = compileQuery(\n this.query,\n this.inputsCache as Record<string, KeyedStream>,\n this.collections,\n this.subscriptions,\n this.lazySourcesCallbacks,\n this.lazySources,\n this.optimizableOrderByCollections,\n (windowFn: (options: WindowOptions) => void) => {\n this.windowFn = windowFn\n // `setWindow` mutates the compiled top-K operator, which is replaced\n // whenever a cleaned-up live query compiles a fresh pipeline. Keep the\n // desired window on the builder and replay it into each new operator.\n if (this.currentWindow) {\n windowFn(this.currentWindow)\n }\n },\n )\n\n const materialized = materializeCompilation(\n compilation,\n this.config.getKey,\n this.hasJoins(this.query),\n )\n this.pipelineCache = materialized.pipeline\n this.sourceWhereClausesCache = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n this.bucketFacadesCache = materialized.facades\n\n const missingSources = this.collectionSources\n .map((source) => source.sourceId)\n .filter((sourceId) => !Object.hasOwn(this.inputsCache!, sourceId))\n if (missingSources.length > 0) {\n throw new MissingAliasInputsError(missingSources)\n }\n }\n\n private maybeCompileBasePipeline() {\n if (!this.graphCache || !this.inputsCache || !this.pipelineCache) {\n this.compileBasePipeline()\n }\n return {\n graph: this.graphCache!,\n inputs: this.inputsCache!,\n pipeline: this.pipelineCache!,\n }\n }\n\n private extendPipelineWithChangeProcessing(\n config: SyncMethods<TResult>,\n syncState: SyncState,\n ): FullSyncState {\n const { begin, commit } = config\n const { graph, inputs, pipeline } = this.maybeCompileBasePipeline()\n\n // Accumulator for changes across all output callbacks within a single graph run.\n // This allows us to batch all changes from intermediate join states into a single\n // transaction, avoiding duplicate key errors when joins produce multiple outputs\n // for the same key (e.g., first output with null, then output with joined data).\n let pendingChanges: Map<unknown, Changes<TResult>> = new Map()\n\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n // Accumulate changes from this output callback into the pending changes map.\n // Changes for the same key are merged (inserts/deletes are added together).\n messages.reduce(accumulateChanges<TResult>, pendingChanges)\n }),\n )\n\n const bucketFacades = new BucketFacadeAdapter(\n this.id,\n this.bucketFacadesCache ?? [],\n (count) => {\n syncState.messagesCount += count\n },\n )\n syncState.unsubscribeCallbacks.add(() => bucketFacades.cleanup())\n\n // Flush pending changes and reset the accumulator.\n // Called at the end of each graph run to commit all accumulated changes.\n syncState.flushPendingChanges = () => {\n const hasParentChanges = pendingChanges.size > 0\n const hasChildChanges = bucketFacades.hasPendingChanges()\n\n if (!hasParentChanges && !hasChildChanges) {\n return\n }\n\n let facadePublication:\n | ReturnType<BucketFacadeAdapter[`flush`]>\n | undefined\n let rootPublication:\n | ReturnType<Collection[`_deferPublication`]>\n | undefined\n try {\n facadePublication = bucketFacades.flush()\n rootPublication = hasParentChanges\n ? config.collection._deferPublication()\n : undefined\n const changesToApply: Map<unknown, Changes<TResult>> = new Map(\n [...pendingChanges].map(([key, changes]) => {\n const resolved: Changes<TResult> = {\n ...changes,\n value: bucketFacades.resolve(changes.value),\n }\n if (changes.previousValue !== undefined) {\n resolved.previousValue = bucketFacades.resolve(\n changes.previousValue,\n )\n }\n return [key, resolved]\n }),\n )\n\n if (hasParentChanges) {\n begin()\n changesToApply.forEach(this.applyChanges.bind(this, config))\n if (hasOrderOnlyMove(changesToApply)) {\n markLayoutChange(config.collection)\n }\n commit()\n }\n } catch (error) {\n pendingChanges = new Map()\n rootPublication?.discard()\n facadePublication?.rollback()\n throw error\n }\n pendingChanges = new Map()\n\n let publicationError: unknown\n for (const publish of [\n rootPublication?.publish,\n facadePublication.publish,\n ]) {\n if (!publish) continue\n try {\n publish()\n } catch (error) {\n publicationError ??= error\n }\n }\n if (publicationError !== undefined) throw publicationError\n }\n\n graph.finalize()\n\n // Extend the sync state with the graph, inputs, and pipeline\n syncState.graph = graph\n syncState.inputs = inputs\n syncState.pipeline = pipeline\n\n return syncState as FullSyncState\n }\n\n private applyChanges(\n config: SyncMethods<TResult>,\n changes: {\n deletes: number\n inserts: number\n value: TResult\n orderByIndex: string | undefined\n },\n key: unknown,\n ) {\n const { write, collection } = config\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n this.resultKeys.set(value, key)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n this.orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes && collection.has(collection.getKeyFromItem(value)))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `Could not apply changes: ${JSON.stringify(changes)}. This should never happen.`,\n )\n }\n }\n\n /**\n * Handle status changes from source collections\n */\n private handleSourceStatusChange(\n config: SyncMethods<TResult>,\n sourceId: string,\n collectionId: string,\n event: AllCollectionEvents[`status:change`],\n ) {\n const { status } = event\n\n // Handle error state - any source collection in error puts live query in error\n if (status === `error`) {\n this.erroredSourceIds.add(sourceId)\n this.setErrorState(\n `Source collection '${collectionId}' entered error state`,\n )\n return\n }\n\n // Handle manual cleanup - this should not happen due to GC prevention,\n // but could happen if user manually calls cleanup()\n if (status === `cleaned-up`) {\n this.transitionToError(\n `Source collection '${collectionId}' was manually cleaned up while live query '${this.id}' depends on it. ` +\n `Live queries prevent automatic GC, so this was likely a manual cleanup() call.`,\n )\n return\n }\n\n if (status === `ready`) {\n const recovered = this.erroredSourceIds.delete(sourceId)\n if (\n recovered &&\n !this.fatalQueryError &&\n this.erroredSourceIds.size === 0\n ) {\n this.isInErrorState = false\n this.maybeRunGraphFn?.()\n }\n }\n\n // Update ready status based on all source collections\n this.updateLiveQueryStatus(config)\n }\n\n /**\n * Update the live query status based on source collection statuses\n */\n private updateLiveQueryStatus(config: SyncMethods<TResult>) {\n const { markReady } = config\n\n // Don't update status if already in error\n if (this.isInErrorState) {\n return\n }\n\n const subscribedToAll = this.currentSyncState?.subscribedToAllCollections\n const allReady = this.allRequiredSourcesReady()\n const allDemandsSettled = [...this.activeDemands.values()].every(\n (demand) => demand.settled,\n )\n const isLoading = this.liveQueryCollection?.isLoadingSubset\n // Mark ready when:\n // 1. All subscriptions are set up (subscribedToAllCollections)\n // 2. All source collections are ready\n // 3. Every active route demand has settled\n // 4. The live query collection is not loading subset data\n // This prevents marking the live query ready before its data is processed\n // (fixes issue where useLiveQuery returns isReady=true with empty data)\n if (subscribedToAll && allReady && allDemandsSettled && !isLoading) {\n markReady()\n }\n }\n\n /**\n * Transition the live query to error state\n */\n private transitionToError(message: string) {\n this.fatalQueryError = true\n this.setErrorState(message)\n }\n\n private setErrorState(message: string) {\n this.isInErrorState = true\n\n // Log error to console for debugging\n console.error(`[Live Query Error] ${message}`)\n\n // Transition live query collection to error state\n this.liveQueryCollection?._lifecycle.setStatus(`error`)\n }\n\n private allRequiredSourcesReady() {\n return this.collectionSources.every(\n (source) =>\n // Only on-demand sources settle through route demand. Eager\n // loadSubset calls return immediately, so they must reach ready.\n (this.lazySources.has(source.sourceId) &&\n source.collection.config.syncMode === `on-demand`) ||\n source.collection.isReady(),\n )\n }\n\n /**\n * Creates one subscription per lexical collection source.\n * Each source gets independent filters, even when aliases or collections repeat.\n * Example: `{ employee: col, manager: col }` creates two separate subscriptions.\n */\n private subscribeToAllCollections(\n config: SyncMethods<TResult>,\n syncState: FullSyncState,\n ) {\n if (this.collectionSources.length === 0) {\n throw new Error(\n `Query '${this.id}' has no collection sources. This should not happen; please report.`,\n )\n }\n\n const loaders = this.collectionSources.map((source) => {\n const { sourceId, alias, collection } = source\n const collectionId = collection.id\n\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder && dependencyBuilder !== this) {\n this.sourceDependencies[sourceId] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.sourceDependencies[sourceId] = []\n }\n\n // CollectionSubscriber handles the actual subscription to the source collection\n // and feeds data into the D2 graph inputs for this specific alias\n const collectionSubscriber = new CollectionSubscriber(\n sourceId,\n alias,\n collection,\n this,\n )\n\n // Subscribe to status changes for status flow\n const statusUnsubscribe = collection.on(`status:change`, (event) => {\n this.handleSourceStatusChange(config, sourceId, collectionId, event)\n })\n syncState.unsubscribeCallbacks.add(statusUnsubscribe)\n\n // The source may have failed before this live query subscribed. Register\n // the listener first, then reconcile that current state so no transition\n // can be missed between observation and subscription.\n if (collection.status === `error`) {\n this.handleSourceStatusChange(config, sourceId, collectionId, {\n type: `status:change`,\n collection,\n status: `error`,\n previousStatus: `error`,\n })\n }\n\n const subscription = collectionSubscriber.subscribe()\n this.subscriptions[sourceId] = subscription\n\n const lazyCallbacks = this.lazySourcesCallbacks[sourceId]\n if (lazyCallbacks) {\n lazyCallbacks.setDemand = (plan, keys) =>\n collectionSubscriber.setDemand(subscription, plan, keys)\n for (const plan of lazyCallbacks.plans ?? []) {\n if (plan.initialKeys.size > 0) {\n lazyCallbacks.setDemand(plan, plan.initialKeys)\n }\n }\n }\n\n // Create a callback for loading more data if needed (used by OrderBy optimization)\n const loadMore = collectionSubscriber.loadMoreIfNeeded.bind(\n collectionSubscriber,\n subscription,\n )\n\n return loadMore\n })\n\n // Combine all loaders into a single callback that initiates loading more data\n // from any source that needs it. Returns true once all loaders have been called,\n // but the actual async loading may still be in progress.\n const loadSubsetDataCallbacks = () => {\n loaders.map((loader) => loader())\n return true\n }\n\n // Mark as subscribed so the graph can start running\n // (graph only runs when all collections are subscribed)\n syncState.subscribedToAllCollections = true\n\n // Note: We intentionally don't call updateLiveQueryStatus() here.\n // The graph hasn't run yet, so marking ready would be premature.\n // The canonical place to mark ready is after the graph processes data\n // in maybeRunGraph(), which ensures data has been processed first.\n\n return loadSubsetDataCallbacks\n }\n}\n\nfunction createOrderByComparator<T extends object>(\n orderByIndices: WeakMap<object, string>,\n) {\n return (val1: T, val2: T): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n}\n\nfunction accumulateChanges<T>(\n acc: Map<unknown, Changes<T>>,\n [[key, tupleData], multiplicity]: [\n [unknown, [any, string | undefined]],\n number,\n ],\n) {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [T, string | undefined]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n // Remember the retracted (old) value + position so the flush can tell an\n // order-only move apart from a real value change.\n changes.previousValue = value\n changes.previousOrderByIndex = orderByIndex\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n // Update value to the latest version for this key\n changes.value = value\n if (orderByIndex !== undefined) {\n changes.orderByIndex = orderByIndex\n }\n }\n acc.set(key, changes)\n return acc\n}\n\n/**\n * Decide whether a flush contains an order-only move.\n *\n * An \"order-only move\" — a row updated in place whose `orderByIndex` moved but\n * whose projected value is deep-equal to before — is swallowed by the value-diff\n * and needs an explicit layout notification. The collection coalesces that\n * signal with any ordinary row publication per subscriber.\n */\nfunction hasOrderOnlyMove<T>(\n changesToApply: Map<unknown, Changes<T>>,\n): boolean {\n for (const changes of changesToApply.values()) {\n const isUpdate = changes.inserts > 0 && changes.deletes > 0\n if (\n isUpdate &&\n changes.previousValue !== undefined &&\n deepEquals(changes.previousValue, changes.value) &&\n changes.orderByIndex !== changes.previousOrderByIndex\n ) {\n return true\n }\n }\n return false\n}\n\n/** Mark the collection's next commit as layout-changing. */\nfunction markLayoutChange(collection: { _markLayoutChange: () => void }): void {\n collection._markLayoutChange()\n}\n"],"names":["buildQueryFromConfig","extractCollectionsFromQuery","extractCollectionSources","extractCollectionFromSource","LIVE_QUERY_INTERNAL","SetWindowRequiresOrderByError","getActiveTransaction","getActivePublicationContext","transactionScopedScheduler","D2","compileQuery","materializeCompilation","MissingAliasInputsError","output","BucketFacadeAdapter","getCollectionBuilder","collectionSubscriber","CollectionSubscriber","deepEquals"],"mappings":"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"}