@tanstack/db 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/query/builder/functions.cjs +2 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +2 -0
- package/dist/cjs/query/builder/ref-proxy.cjs +6 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +4 -2
- package/dist/cjs/query/compiler/joins.cjs +5 -0
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +14 -5
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/effect.cjs +1 -1
- package/dist/cjs/query/effect.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +22 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +2 -2
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +2 -0
- package/dist/esm/query/builder/functions.js +2 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +4 -2
- package/dist/esm/query/builder/ref-proxy.js +6 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +5 -0
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js +14 -5
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/effect.js +1 -1
- package/dist/esm/query/effect.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js +22 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +2 -2
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/package.json +1 -1
- package/skills/db-core/live-queries/SKILL.md +2 -1
- package/src/query/builder/functions.ts +2 -0
- package/src/query/builder/ref-proxy.ts +18 -2
- package/src/query/compiler/joins.ts +8 -0
- package/src/query/compiler/order-by.ts +21 -6
- package/src/query/effect.ts +1 -1
- package/src/query/live/collection-config-builder.ts +29 -1
- package/src/query/live/collection-subscriber.ts +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-config-builder.js","sources":["../../../../src/query/live/collection-config-builder.ts"],"sourcesContent":["import { D2, output, serializeValue } from '@tanstack/db-ivm'\nimport { INCLUDES_ROUTING, compileQuery } from '../compiler/index.js'\nimport { createCollection } from '../../collection/index.js'\nimport {\n MissingAliasInputsError,\n SetWindowRequiresOrderByError,\n} from '../../errors.js'\nimport { transactionScopedScheduler } from '../../scheduler.js'\nimport { getActiveTransaction } from '../../transactions.js'\nimport { CollectionSubscriber } from './collection-subscriber.js'\nimport { getCollectionBuilder } from './collection-registry.js'\nimport { LIVE_QUERY_INTERNAL } from './internal.js'\nimport {\n buildQueryFromConfig,\n extractCollectionAliases,\n extractCollectionFromSource,\n extractCollectionsFromQuery,\n} from './utils.js'\nimport type { LiveQueryInternalUtils } from './internal.js'\nimport type {\n IncludesCompilationResult,\n WindowOptions,\n} 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 ChangeMessage,\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 {\n BasicExpression,\n IncludesMaterialization,\n PropRef,\n QueryIR,\n} 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 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\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 currentWindow: WindowOptions | undefined\n\n private maybeRunGraphFn: (() => void) | undefined\n\n private readonly aliasDependencies: 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 includesCache: Array<IncludesCompilationResult> | undefined\n\n // Map of source alias to subscription\n readonly subscriptions: Record<string, CollectionSubscription> = {}\n // Map of source aliases to functions that load keys for that lazy source\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of source aliases that are lazy (don't load initial state)\n readonly lazySources = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\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.collections = extractCollectionsFromQuery(this.query)\n const collectionAliasesById = extractCollectionAliases(this.query)\n\n // Build a reverse lookup map from alias to collection instance.\n // This enables self-join support where the same collection can be referenced\n // multiple times with different aliases (e.g., { employee: col, manager: col })\n this.collectionByAlias = {}\n for (const [collectionId, aliases] of collectionAliasesById.entries()) {\n const collection = this.collections[collectionId]\n if (!collection) continue\n for (const alias of aliases) {\n this.collectionByAlias[alias] = 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 }\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 this.currentWindow = options\n this.windowFn(options)\n this.maybeRunGraphFn?.()\n\n // Check if loading a subset was triggered\n if (this.liveQueryCollection?.isLoadingSubset) {\n // Loading was triggered, return a promise that resolves when it completes\n return new Promise<void>((resolve) => {\n const unsubscribe = this.liveQueryCollection!.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n unsubscribe()\n resolve()\n }\n },\n )\n })\n }\n\n // No loading was triggered\n return 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 if (!this.windowFn || !this.currentWindow) {\n return undefined\n }\n return {\n offset: this.currentWindow.offset ?? 0,\n limit: this.currentWindow.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 isLazyAlias(alias: string): boolean {\n return this.lazySources.has(alias)\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 // Flush accumulated changes after each graph step to commit them as one transaction.\n // This ensures intermediate join states (like null on one side) don't cause\n // duplicate key errors when the full join result arrives in the same step.\n syncState.flushPendingChanges?.()\n callback?.()\n callbackCalled = true\n }\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.alias - Source alias that triggered this schedule; adds alias-specific 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 alias?: string\n dependencies?: Array<CollectionConfigBuilder<any, any>>\n },\n ) {\n const contextId = options?.contextId ?? getActiveTransaction()?.id\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?.alias) {\n const aliasDeps = this.aliasDependencies[options.alias]\n if (aliasDeps) {\n for (const dep of aliasDeps) {\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 // 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\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.includesCache = undefined\n\n // Reset lazy source alias state\n this.lazySources.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 Object.keys(this.collectionByAlias).map((alias) => [\n alias,\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 },\n )\n\n this.pipelineCache = compilation.pipeline\n this.sourceWhereClausesCache = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n this.includesCache = compilation.includes\n\n // Defensive check: verify all compiled aliases have corresponding inputs\n // This should never happen since all aliases come from user declarations,\n // but catch it early if the assumption is violated in the future.\n const missingAliases = Object.keys(this.compiledAliasToCollectionId).filter(\n (alias) => !Object.hasOwn(this.inputsCache!, alias),\n )\n if (missingAliases.length > 0) {\n throw new MissingAliasInputsError(missingAliases)\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 // Set up includes output routing and child collection lifecycle\n const includesState = this.setupIncludesOutput(\n this.includesCache,\n syncState,\n )\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 = hasPendingIncludesChanges(includesState)\n\n if (!hasParentChanges && !hasChildChanges) {\n return\n }\n\n let changesToApply = pendingChanges\n\n // When a custom getKey is provided, multiple D2 internal keys may map\n // to the same user-visible key. Re-accumulate by custom key so that a\n // retract + insert for the same logical row merges into an UPDATE\n // instead of a separate DELETE and INSERT that can race.\n if (this.config.getKey) {\n const merged = new Map<unknown, Changes<TResult>>()\n for (const [, changes] of pendingChanges) {\n const customKey = this.config.getKey(changes.value)\n const existing = merged.get(customKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n // Keep the value from the insert side (the new value)\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n merged.set(customKey, { ...changes })\n }\n }\n changesToApply = merged\n }\n\n // 1. Flush parent changes\n if (hasParentChanges) {\n begin()\n changesToApply.forEach(this.applyChanges.bind(this, config))\n commit()\n }\n pendingChanges = new Map()\n\n // 2. Process includes: create/dispose child Collections, route child changes\n flushIncludesState(\n includesState,\n config.collection,\n this.id,\n hasParentChanges ? changesToApply : null,\n config,\n )\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 /**\n * Sets up output callbacks for includes child pipelines.\n * Each includes entry gets its own output callback that accumulates child changes,\n * and a child registry that maps correlation key → child Collection.\n */\n private setupIncludesOutput(\n includesEntries: Array<IncludesCompilationResult> | undefined,\n syncState: SyncState,\n ): Array<IncludesOutputState> {\n if (!includesEntries || includesEntries.length === 0) {\n return []\n }\n\n return includesEntries.map((entry) => {\n const state: IncludesOutputState = {\n fieldName: entry.fieldName,\n childCorrelationField: entry.childCorrelationField,\n hasOrderBy: entry.hasOrderBy,\n materialization: entry.materialization,\n scalarField: entry.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n // Attach output callback on the child pipeline\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n // Accumulate by [routingKey, childKey]\n let byChild = state.pendingChildChanges.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n state.pendingChildChanges.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n // Set up shared buffers for nested includes (e.g., comments inside issues)\n if (entry.childCompilationResult.includes) {\n state.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\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 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.transitionToError(\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 // 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.allCollectionsReady()\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. 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 && !isLoading) {\n markReady()\n }\n }\n\n /**\n * Transition the live query to error state\n */\n private transitionToError(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 allCollectionsReady() {\n return Object.values(this.collections).every((collection) =>\n collection.isReady(),\n )\n }\n\n /**\n * Creates per-alias subscriptions enabling self-join support.\n * Each alias gets its own subscription with independent filters, even for the same collection.\n * Example: `{ employee: col, manager: col }` creates two separate subscriptions.\n */\n private subscribeToAllCollections(\n config: SyncMethods<TResult>,\n syncState: FullSyncState,\n ) {\n // Use compiled aliases as the source of truth - these include all aliases from the query\n // including those from subqueries, which may not be in collectionByAlias\n const compiledAliases = Object.entries(this.compiledAliasToCollectionId)\n if (compiledAliases.length === 0) {\n throw new Error(\n `Compiler returned no alias metadata for query '${this.id}'. This should not happen; please report.`,\n )\n }\n\n // Create a separate subscription for each alias, enabling self-joins where the same\n // collection can be used multiple times with different filters and subscriptions\n const loaders = compiledAliases.map(([alias, collectionId]) => {\n // Try collectionByAlias first (for declared aliases), fall back to collections (for subquery aliases)\n const collection =\n this.collectionByAlias[alias] ?? this.collections[collectionId]!\n\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder && dependencyBuilder !== this) {\n this.aliasDependencies[alias] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.aliasDependencies[alias] = []\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 alias,\n collectionId,\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, collectionId, event)\n })\n syncState.unsubscribeCallbacks.add(statusUnsubscribe)\n\n const subscription = collectionSubscriber.subscribe()\n // Store subscription by alias (not collection ID) to support lazy loading\n // which needs to look up subscriptions by their query alias\n this.subscriptions[alias] = subscription\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\n/**\n * Shared buffer setup for a single nested includes level.\n * Pipeline output writes into the buffer; during flush the buffer is drained\n * into per-entry states via the routing index.\n */\ntype NestedIncludesSetup = {\n compilationResult: IncludesCompilationResult\n /** Shared buffer: nestedCorrelationKey → Map<childKey, Changes> */\n buffer: Map<unknown, Map<unknown, Changes<any>>>\n /** For 3+ levels of nesting */\n nestedSetups?: Array<NestedIncludesSetup>\n}\n\n/**\n * State tracked per includes entry for output routing and child lifecycle\n */\ntype IncludesOutputState = {\n fieldName: string\n childCorrelationField: PropRef\n /** Whether the child query has an ORDER BY clause */\n hasOrderBy: boolean\n /** How the child result is materialized on the parent row */\n materialization: IncludesMaterialization\n /** Internal field used to unwrap scalar child selects */\n scalarField?: string\n /** Maps correlation key value → child Collection entry */\n childRegistry: Map<unknown, ChildCollectionEntry>\n /** Pending child changes: correlationKey → Map<childKey, Changes> */\n pendingChildChanges: Map<unknown, Map<unknown, Changes<any>>>\n /** Reverse index: correlation key → Set of parent collection keys */\n correlationToParentKeys: Map<unknown, Set<unknown>>\n /** Shared nested pipeline setups (one per nested includes level) */\n nestedSetups?: Array<NestedIncludesSetup>\n /** nestedCorrelationKey → parentCorrelationKey */\n nestedRoutingIndex?: Map<unknown, unknown>\n /** parentCorrelationKey → Set<nestedCorrelationKeys> */\n nestedRoutingReverseIndex?: Map<unknown, Set<unknown>>\n}\n\ntype ChildCollectionEntry = {\n collection: Collection<any, any, any>\n syncMethods: SyncMethods<any> | null\n resultKeys: WeakMap<object, unknown>\n orderByIndices: WeakMap<object, string> | null\n /** Per-entry nested includes states (one per nested includes level) */\n includesStates?: Array<IncludesOutputState>\n}\n\nfunction materializesInline(state: IncludesOutputState): boolean {\n return state.materialization !== `collection`\n}\n\nfunction materializeIncludedValue(\n state: IncludesOutputState,\n entry: ChildCollectionEntry | undefined,\n): unknown {\n if (!entry) {\n if (state.materialization === `array`) {\n return []\n }\n if (state.materialization === `concat`) {\n return ``\n }\n return undefined\n }\n\n if (state.materialization === `collection`) {\n return entry.collection\n }\n\n const rows = [...entry.collection.toArray]\n const values = state.scalarField\n ? rows.map((row) => row?.[state.scalarField!])\n : rows\n\n if (state.materialization === `array`) {\n return values\n }\n\n return values.map((value) => String(value ?? ``)).join(``)\n}\n\n/**\n * Sets up shared buffers for nested includes pipelines.\n * Instead of writing directly into a single shared IncludesOutputState,\n * each nested pipeline writes into a buffer that is later drained per-entry.\n */\nfunction setupNestedPipelines(\n includes: Array<IncludesCompilationResult>,\n syncState: SyncState,\n): Array<NestedIncludesSetup> {\n return includes.map((entry) => {\n const buffer: Map<unknown, Map<unknown, Changes<any>>> = new Map()\n\n // Attach output callback that writes into the shared buffer\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n let byChild = buffer.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n buffer.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n const setup: NestedIncludesSetup = {\n compilationResult: entry,\n buffer,\n }\n\n // Recursively set up deeper levels\n if (entry.childCompilationResult.includes) {\n setup.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n }\n\n return setup\n })\n}\n\n/**\n * Creates fresh per-entry IncludesOutputState array from NestedIncludesSetup array.\n * Each entry gets its own isolated state for nested includes.\n */\nfunction createPerEntryIncludesStates(\n setups: Array<NestedIncludesSetup>,\n): Array<IncludesOutputState> {\n return setups.map((setup) => {\n const state: IncludesOutputState = {\n fieldName: setup.compilationResult.fieldName,\n childCorrelationField: setup.compilationResult.childCorrelationField,\n hasOrderBy: setup.compilationResult.hasOrderBy,\n materialization: setup.compilationResult.materialization,\n scalarField: setup.compilationResult.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n if (setup.nestedSetups) {\n state.nestedSetups = setup.nestedSetups\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\n}\n\n/**\n * Drains shared buffers into per-entry states using the routing index.\n * Returns the set of parent correlation keys that had changes routed to them.\n */\nfunction drainNestedBuffers(state: IncludesOutputState): Set<unknown> {\n const dirtyCorrelationKeys = new Set<unknown>()\n\n if (!state.nestedSetups) return dirtyCorrelationKeys\n\n for (let i = 0; i < state.nestedSetups.length; i++) {\n const setup = state.nestedSetups[i]!\n const toDelete: Array<unknown> = []\n\n for (const [nestedCorrelationKey, childChanges] of setup.buffer) {\n const parentCorrelationKey =\n state.nestedRoutingIndex!.get(nestedCorrelationKey)\n if (parentCorrelationKey === undefined) {\n // Unroutable — parent not yet seen; keep in buffer\n continue\n }\n\n const entry = state.childRegistry.get(parentCorrelationKey)\n if (!entry || !entry.includesStates) {\n continue\n }\n\n // Route changes into this entry's per-entry state at position i\n const entryState = entry.includesStates[i]!\n for (const [childKey, changes] of childChanges) {\n let byChild = entryState.pendingChildChanges.get(nestedCorrelationKey)\n if (!byChild) {\n byChild = new Map()\n entryState.pendingChildChanges.set(nestedCorrelationKey, byChild)\n }\n const existing = byChild.get(childKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n byChild.set(childKey, { ...changes })\n }\n }\n\n dirtyCorrelationKeys.add(parentCorrelationKey)\n toDelete.push(nestedCorrelationKey)\n }\n\n for (const key of toDelete) {\n setup.buffer.delete(key)\n }\n }\n\n return dirtyCorrelationKeys\n}\n\n/**\n * Updates the routing index after processing child changes.\n * Maps nested correlation keys to parent correlation keys so that\n * grandchild changes can be routed to the correct per-entry state.\n */\nfunction updateRoutingIndex(\n state: IncludesOutputState,\n correlationKey: unknown,\n childChanges: Map<unknown, Changes<any>>,\n): void {\n if (!state.nestedSetups) return\n\n for (const setup of state.nestedSetups) {\n for (const [, change] of childChanges) {\n if (change.inserts > 0) {\n // Read the nested routing key from the INCLUDES_ROUTING stamp.\n // Must use the composite routing key (not raw correlationKey) to match\n // how nested buffers are keyed by computeRoutingKey.\n const nestedRouting =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting?.correlationKey\n const nestedParentContext = nestedRouting?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.set(nestedRoutingKey, correlationKey)\n let reverseSet = state.nestedRoutingReverseIndex!.get(correlationKey)\n if (!reverseSet) {\n reverseSet = new Set()\n state.nestedRoutingReverseIndex!.set(correlationKey, reverseSet)\n }\n reverseSet.add(nestedRoutingKey)\n }\n } else if (change.deletes > 0 && change.inserts === 0) {\n // Remove from routing index\n const nestedRouting2 =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting2?.correlationKey\n const nestedParentContext2 = nestedRouting2?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext2,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.delete(nestedRoutingKey)\n const reverseSet =\n state.nestedRoutingReverseIndex!.get(correlationKey)\n if (reverseSet) {\n reverseSet.delete(nestedRoutingKey)\n if (reverseSet.size === 0) {\n state.nestedRoutingReverseIndex!.delete(correlationKey)\n }\n }\n }\n }\n }\n }\n}\n\n/**\n * Cleans routing index entries when a parent is deleted.\n * Uses the reverse index to find and remove all nested routing entries.\n */\nfunction cleanRoutingIndexOnDelete(\n state: IncludesOutputState,\n correlationKey: unknown,\n): void {\n if (!state.nestedRoutingReverseIndex) return\n\n const nestedKeys = state.nestedRoutingReverseIndex.get(correlationKey)\n if (nestedKeys) {\n for (const nestedKey of nestedKeys) {\n state.nestedRoutingIndex!.delete(nestedKey)\n }\n state.nestedRoutingReverseIndex.delete(correlationKey)\n }\n}\n\n/**\n * Recursively checks whether any nested buffer has pending changes.\n */\nfunction hasNestedBufferChanges(setups: Array<NestedIncludesSetup>): boolean {\n for (const setup of setups) {\n if (setup.buffer.size > 0) return true\n if (setup.nestedSetups && hasNestedBufferChanges(setup.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Computes a composite routing key from correlation key and parent context.\n * When parentContext is null (no parent filters), returns the raw correlationKey\n * for zero behavioral change on existing queries.\n */\nfunction computeRoutingKey(\n correlationKey: unknown,\n parentContext: Record<string, any> | null,\n): unknown {\n if (parentContext == null) return correlationKey\n return JSON.stringify([correlationKey, parentContext])\n}\n\n/**\n * Creates a child Collection entry for includes subqueries.\n * The child Collection is a full-fledged Collection instance that starts syncing immediately.\n */\nfunction createChildCollectionEntry(\n parentId: string,\n fieldName: string,\n correlationKey: unknown,\n hasOrderBy: boolean,\n nestedSetups?: Array<NestedIncludesSetup>,\n): ChildCollectionEntry {\n const resultKeys = new WeakMap<object, unknown>()\n const orderByIndices = hasOrderBy ? new WeakMap<object, string>() : null\n let syncMethods: SyncMethods<any> | null = null\n\n const compare = orderByIndices\n ? createOrderByComparator(orderByIndices)\n : undefined\n\n const collection = createCollection<any, string | number>({\n id: `__child-collection:${parentId}-${fieldName}-${serializeValue(correlationKey)}`,\n getKey: (item: any) => resultKeys.get(item) as string | number,\n compare,\n sync: {\n rowUpdateMode: `full`,\n sync: (methods) => {\n syncMethods = methods\n return () => {\n syncMethods = null\n }\n },\n },\n startSync: true,\n gcTime: 0,\n })\n\n const entry: ChildCollectionEntry = {\n collection,\n get syncMethods() {\n return syncMethods\n },\n resultKeys,\n orderByIndices,\n }\n\n if (nestedSetups) {\n entry.includesStates = createPerEntryIncludesStates(nestedSetups)\n }\n\n return entry\n}\n\n/**\n * Flushes includes state using a bottom-up per-entry approach.\n * Five phases ensure correct ordering:\n * 1. Parent INSERTs — create child entries with per-entry nested states\n * 2. Child changes — apply to child Collections, update routing index\n * 3. Drain nested buffers — route buffered grandchild changes to per-entry states\n * 4. Flush per-entry states — recursively flush nested includes on each entry\n * 5. Parent DELETEs — clean up child entries and routing index\n */\nfunction flushIncludesState(\n includesState: Array<IncludesOutputState>,\n parentCollection: Collection<any, any, any>,\n parentId: string,\n parentChanges: Map<unknown, Changes<any>> | null,\n parentSyncMethods: SyncMethods<any> | null,\n): void {\n for (const state of includesState) {\n // Phase 1: Parent INSERTs — ensure a child Collection exists for every parent\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.inserts > 0) {\n const parentResult = changes.value\n // Extract routing info from INCLUDES_ROUTING symbol (set by compiler)\n const routing = parentResult[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n if (correlationKey != null) {\n // Ensure child Collection exists for this routing key\n if (!state.childRegistry.has(routingKey)) {\n const entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n routingKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(routingKey, entry)\n }\n // Update reverse index: routing key → parent keys\n let parentKeys = state.correlationToParentKeys.get(routingKey)\n if (!parentKeys) {\n parentKeys = new Set()\n state.correlationToParentKeys.set(routingKey, parentKeys)\n }\n parentKeys.add(parentKey)\n\n const childValue = materializeIncludedValue(\n state,\n state.childRegistry.get(routingKey),\n )\n parentResult[state.fieldName] = childValue\n\n // Parent rows may already be materialized in the live collection by the\n // time includes state is flushed, so update the stored row as well.\n const storedParent = parentCollection.get(parentKey as any)\n if (storedParent && storedParent !== parentResult) {\n storedParent[state.fieldName] = childValue\n }\n }\n }\n }\n }\n\n // Track affected correlation keys for inline materializations before clearing child changes.\n const affectedCorrelationKeys = materializesInline(state)\n ? new Set<unknown>(state.pendingChildChanges.keys())\n : null\n\n // Phase 2: Child changes — apply to child Collections\n // Track which entries had child changes and capture their childChanges maps\n const entriesWithChildChanges = new Map<\n unknown,\n { entry: ChildCollectionEntry; childChanges: Map<unknown, Changes<any>> }\n >()\n if (state.pendingChildChanges.size > 0) {\n for (const [correlationKey, childChanges] of state.pendingChildChanges) {\n // Ensure child Collection exists for this correlation key\n let entry = state.childRegistry.get(correlationKey)\n if (!entry) {\n entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n correlationKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(correlationKey, entry)\n }\n\n if (state.materialization === `collection`) {\n attachChildCollectionToParent(\n parentCollection,\n state.fieldName,\n correlationKey,\n state.correlationToParentKeys,\n entry.collection,\n )\n }\n\n // Apply child changes to the child Collection\n if (entry.syncMethods) {\n entry.syncMethods.begin()\n for (const [childKey, change] of childChanges) {\n entry.resultKeys.set(change.value, childKey)\n if (entry.orderByIndices && change.orderByIndex !== undefined) {\n entry.orderByIndices.set(change.value, change.orderByIndex)\n }\n if (change.inserts > 0 && change.deletes === 0) {\n entry.syncMethods.write({ value: change.value, type: `insert` })\n } else if (\n change.inserts > change.deletes ||\n (change.inserts === change.deletes &&\n entry.syncMethods.collection.has(\n entry.syncMethods.collection.getKeyFromItem(change.value),\n ))\n ) {\n entry.syncMethods.write({ value: change.value, type: `update` })\n } else if (change.deletes > 0) {\n entry.syncMethods.write({ value: change.value, type: `delete` })\n }\n }\n entry.syncMethods.commit()\n }\n\n // Update routing index for nested includes\n updateRoutingIndex(state, correlationKey, childChanges)\n\n entriesWithChildChanges.set(correlationKey, { entry, childChanges })\n }\n state.pendingChildChanges.clear()\n }\n\n // Phase 3: Drain nested buffers — route buffered grandchild changes to per-entry states\n const dirtyFromBuffers = drainNestedBuffers(state)\n\n // Phase 4: Flush per-entry states\n // First: entries that had child changes in Phase 2\n for (const [, { entry, childChanges }] of entriesWithChildChanges) {\n if (entry.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n childChanges,\n entry.syncMethods,\n )\n }\n }\n // Then: entries that only had buffer-routed changes (no child changes at this level)\n for (const correlationKey of dirtyFromBuffers) {\n if (entriesWithChildChanges.has(correlationKey)) continue\n const entry = state.childRegistry.get(correlationKey)\n if (entry?.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n null,\n entry.syncMethods,\n )\n }\n }\n\n // For inline materializations: re-emit affected parents with updated snapshots.\n // We mutate items in-place (so collection.get() reflects changes immediately)\n // and emit UPDATE events directly. We bypass the sync methods because\n // commitPendingTransactions compares previous vs new visible state using\n // deepEquals, but in-place mutation means both sides reference the same\n // object, so the comparison always returns true and suppresses the event.\n const inlineReEmitKeys = materializesInline(state)\n ? new Set([...(affectedCorrelationKeys || []), ...dirtyFromBuffers])\n : null\n if (parentSyncMethods && inlineReEmitKeys && inlineReEmitKeys.size > 0) {\n const events: Array<ChangeMessage<any>> = []\n for (const correlationKey of inlineReEmitKeys) {\n const parentKeys = state.correlationToParentKeys.get(correlationKey)\n if (!parentKeys) continue\n const entry = state.childRegistry.get(correlationKey)\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n const key = parentSyncMethods.collection.getKeyFromItem(item)\n // Capture previous value before in-place mutation\n const previousValue = { ...item }\n item[state.fieldName] = materializeIncludedValue(state, entry)\n events.push({\n type: `update`,\n key,\n value: item,\n previousValue,\n })\n }\n }\n }\n if (events.length > 0) {\n // Emit directly — the in-place mutation already updated the data in\n // syncedData, so we only need to notify subscribers.\n const changesManager = (parentCollection as any)._changes as {\n emitEvents: (\n changes: Array<ChangeMessage<any>>,\n forceEmit?: boolean,\n ) => void\n }\n changesManager.emitEvents(events, true)\n }\n }\n\n // Phase 5: Parent DELETEs — dispose child Collections and clean up\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.deletes > 0 && changes.inserts === 0) {\n const routing = changes.value[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n if (correlationKey != null) {\n // Clean up reverse index first, only delete child collection\n // when the last parent referencing it is removed\n const parentKeys = state.correlationToParentKeys.get(routingKey)\n if (parentKeys) {\n parentKeys.delete(parentKey)\n if (parentKeys.size === 0) {\n cleanRoutingIndexOnDelete(state, routingKey)\n state.childRegistry.delete(routingKey)\n state.correlationToParentKeys.delete(routingKey)\n }\n }\n }\n }\n }\n }\n }\n\n // Clean up the internal routing stamp from parent/child results\n if (parentChanges) {\n for (const [, changes] of parentChanges) {\n delete changes.value[INCLUDES_ROUTING]\n }\n }\n}\n\n/**\n * Checks whether any includes state has pending changes that need to be flushed.\n * Checks direct pending child changes and shared nested buffers.\n */\nfunction hasPendingIncludesChanges(\n states: Array<IncludesOutputState>,\n): boolean {\n for (const state of states) {\n if (state.pendingChildChanges.size > 0) return true\n if (state.nestedSetups && hasNestedBufferChanges(state.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Attaches a child Collection to parent rows that match a given correlation key.\n * Uses the reverse index to look up parent keys directly instead of scanning.\n */\nfunction attachChildCollectionToParent(\n parentCollection: Collection<any, any, any>,\n fieldName: string,\n correlationKey: unknown,\n correlationToParentKeys: Map<unknown, Set<unknown>>,\n childCollection: Collection<any, any, any>,\n): void {\n const parentKeys = correlationToParentKeys.get(correlationKey)\n if (!parentKeys) return\n\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n item[fieldName] = childCollection\n }\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 } 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"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection-config-builder.js","sources":["../../../../src/query/live/collection-config-builder.ts"],"sourcesContent":["import { D2, output, serializeValue } from '@tanstack/db-ivm'\nimport { INCLUDES_ROUTING, compileQuery } from '../compiler/index.js'\nimport { createCollection } from '../../collection/index.js'\nimport {\n MissingAliasInputsError,\n SetWindowRequiresOrderByError,\n} from '../../errors.js'\nimport { transactionScopedScheduler } from '../../scheduler.js'\nimport { getActiveTransaction } from '../../transactions.js'\nimport { CollectionSubscriber } from './collection-subscriber.js'\nimport { getCollectionBuilder } from './collection-registry.js'\nimport { LIVE_QUERY_INTERNAL } from './internal.js'\nimport {\n buildQueryFromConfig,\n extractCollectionAliases,\n extractCollectionFromSource,\n extractCollectionsFromQuery,\n} from './utils.js'\nimport type { LiveQueryInternalUtils } from './internal.js'\nimport type {\n IncludesCompilationResult,\n WindowOptions,\n} 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 ChangeMessage,\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 {\n BasicExpression,\n IncludesMaterialization,\n PropRef,\n QueryIR,\n} 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 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\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 currentWindow: WindowOptions | undefined\n\n private maybeRunGraphFn: (() => void) | undefined\n\n private readonly aliasDependencies: 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 includesCache: Array<IncludesCompilationResult> | undefined\n\n // Map of source alias to subscription\n readonly subscriptions: Record<string, CollectionSubscription> = {}\n // Map of source aliases to functions that load keys for that lazy source\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of source aliases that are lazy (don't load initial state)\n readonly lazySources = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\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.collections = extractCollectionsFromQuery(this.query)\n const collectionAliasesById = extractCollectionAliases(this.query)\n\n // Build a reverse lookup map from alias to collection instance.\n // This enables self-join support where the same collection can be referenced\n // multiple times with different aliases (e.g., { employee: col, manager: col })\n this.collectionByAlias = {}\n for (const [collectionId, aliases] of collectionAliasesById.entries()) {\n const collection = this.collections[collectionId]\n if (!collection) continue\n for (const alias of aliases) {\n this.collectionByAlias[alias] = 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 }\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 this.currentWindow = options\n this.windowFn(options)\n this.maybeRunGraphFn?.()\n\n // Check if loading a subset was triggered\n if (this.liveQueryCollection?.isLoadingSubset) {\n // Loading was triggered, return a promise that resolves when it completes\n return new Promise<void>((resolve) => {\n const unsubscribe = this.liveQueryCollection!.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n unsubscribe()\n resolve()\n }\n },\n )\n })\n }\n\n // No loading was triggered\n return 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 if (!this.windowFn || !this.currentWindow) {\n return undefined\n }\n return {\n offset: this.currentWindow.offset ?? 0,\n limit: this.currentWindow.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 isLazyAlias(alias: string): boolean {\n return this.lazySources.has(alias)\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 // Flush accumulated changes after each graph step to commit them as one transaction.\n // This ensures intermediate join states (like null on one side) don't cause\n // duplicate key errors when the full join result arrives in the same step.\n syncState.flushPendingChanges?.()\n callback?.()\n callbackCalled = true\n }\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.alias - Source alias that triggered this schedule; adds alias-specific 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 alias?: string\n dependencies?: Array<CollectionConfigBuilder<any, any>>\n },\n ) {\n const contextId = options?.contextId ?? getActiveTransaction()?.id\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?.alias) {\n const aliasDeps = this.aliasDependencies[options.alias]\n if (aliasDeps) {\n for (const dep of aliasDeps) {\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 // 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\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.includesCache = undefined\n\n // Reset lazy source alias state\n this.lazySources.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 Object.keys(this.collectionByAlias).map((alias) => [\n alias,\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 },\n )\n\n this.pipelineCache = compilation.pipeline\n this.sourceWhereClausesCache = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n this.includesCache = compilation.includes\n\n // Defensive check: verify all compiled aliases have corresponding inputs\n // This should never happen since all aliases come from user declarations,\n // but catch it early if the assumption is violated in the future.\n const missingAliases = Object.keys(this.compiledAliasToCollectionId).filter(\n (alias) => !Object.hasOwn(this.inputsCache!, alias),\n )\n if (missingAliases.length > 0) {\n throw new MissingAliasInputsError(missingAliases)\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 // Set up includes output routing and child collection lifecycle\n const includesState = this.setupIncludesOutput(\n this.includesCache,\n syncState,\n )\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 = hasPendingIncludesChanges(includesState)\n\n if (!hasParentChanges && !hasChildChanges) {\n return\n }\n\n let changesToApply = pendingChanges\n\n // When a custom getKey is provided, multiple D2 internal keys may map\n // to the same user-visible key. Re-accumulate by custom key so that a\n // retract + insert for the same logical row merges into an UPDATE\n // instead of a separate DELETE and INSERT that can race.\n if (this.config.getKey) {\n const merged = new Map<unknown, Changes<TResult>>()\n for (const [, changes] of pendingChanges) {\n const customKey = this.config.getKey(changes.value)\n const existing = merged.get(customKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n // Keep the value from the insert side (the new value)\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n merged.set(customKey, { ...changes })\n }\n }\n changesToApply = merged\n }\n\n // 1. Flush parent changes\n if (hasParentChanges) {\n begin()\n changesToApply.forEach(this.applyChanges.bind(this, config))\n commit()\n }\n pendingChanges = new Map()\n\n // 2. Process includes: create/dispose child Collections, route child changes\n flushIncludesState(\n includesState,\n config.collection,\n this.id,\n hasParentChanges ? changesToApply : null,\n config,\n )\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 /**\n * Sets up output callbacks for includes child pipelines.\n * Each includes entry gets its own output callback that accumulates child changes,\n * and a child registry that maps correlation key → child Collection.\n */\n private setupIncludesOutput(\n includesEntries: Array<IncludesCompilationResult> | undefined,\n syncState: SyncState,\n ): Array<IncludesOutputState> {\n if (!includesEntries || includesEntries.length === 0) {\n return []\n }\n\n return includesEntries.map((entry) => {\n const state: IncludesOutputState = {\n fieldName: entry.fieldName,\n childCorrelationField: entry.childCorrelationField,\n hasOrderBy: entry.hasOrderBy,\n materialization: entry.materialization,\n scalarField: entry.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n // Attach output callback on the child pipeline\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n // Accumulate by [routingKey, childKey]\n let byChild = state.pendingChildChanges.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n state.pendingChildChanges.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n // Set up shared buffers for nested includes (e.g., comments inside issues)\n if (entry.childCompilationResult.includes) {\n state.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\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 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.transitionToError(\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 // 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.allCollectionsReady()\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. 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 && !isLoading) {\n markReady()\n }\n }\n\n /**\n * Transition the live query to error state\n */\n private transitionToError(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 allCollectionsReady() {\n return Object.values(this.collections).every((collection) =>\n collection.isReady(),\n )\n }\n\n /**\n * Creates per-alias subscriptions enabling self-join support.\n * Each alias gets its own subscription with independent filters, even for the same collection.\n * Example: `{ employee: col, manager: col }` creates two separate subscriptions.\n */\n private subscribeToAllCollections(\n config: SyncMethods<TResult>,\n syncState: FullSyncState,\n ) {\n // Use compiled aliases as the source of truth - these include all aliases from the query\n // including those from subqueries, which may not be in collectionByAlias\n const compiledAliases = Object.entries(this.compiledAliasToCollectionId)\n if (compiledAliases.length === 0) {\n throw new Error(\n `Compiler returned no alias metadata for query '${this.id}'. This should not happen; please report.`,\n )\n }\n\n // Create a separate subscription for each alias, enabling self-joins where the same\n // collection can be used multiple times with different filters and subscriptions\n const loaders = compiledAliases.map(([alias, collectionId]) => {\n // Try collectionByAlias first (for declared aliases), fall back to collections (for subquery aliases)\n const collection =\n this.collectionByAlias[alias] ?? this.collections[collectionId]!\n\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder && dependencyBuilder !== this) {\n this.aliasDependencies[alias] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.aliasDependencies[alias] = []\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 alias,\n collectionId,\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, collectionId, event)\n })\n syncState.unsubscribeCallbacks.add(statusUnsubscribe)\n\n const subscription = collectionSubscriber.subscribe()\n // Store subscription by alias (not collection ID) to support lazy loading\n // which needs to look up subscriptions by their query alias\n this.subscriptions[alias] = subscription\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\n/**\n * Shared buffer setup for a single nested includes level.\n * Pipeline output writes into the buffer; during flush the buffer is drained\n * into per-entry states via the routing index.\n */\ntype NestedIncludesSetup = {\n compilationResult: IncludesCompilationResult\n /** Shared buffer: nestedCorrelationKey → Map<childKey, Changes> */\n buffer: Map<unknown, Map<unknown, Changes<any>>>\n /** For 3+ levels of nesting */\n nestedSetups?: Array<NestedIncludesSetup>\n}\n\n/**\n * State tracked per includes entry for output routing and child lifecycle\n */\ntype IncludesOutputState = {\n fieldName: string\n childCorrelationField: PropRef\n /** Whether the child query has an ORDER BY clause */\n hasOrderBy: boolean\n /** How the child result is materialized on the parent row */\n materialization: IncludesMaterialization\n /** Internal field used to unwrap scalar child selects */\n scalarField?: string\n /** Maps correlation key value → child Collection entry */\n childRegistry: Map<unknown, ChildCollectionEntry>\n /** Pending child changes: correlationKey → Map<childKey, Changes> */\n pendingChildChanges: Map<unknown, Map<unknown, Changes<any>>>\n /** Reverse index: correlation key → Set of parent collection keys */\n correlationToParentKeys: Map<unknown, Set<unknown>>\n /** Shared nested pipeline setups (one per nested includes level) */\n nestedSetups?: Array<NestedIncludesSetup>\n /** nestedCorrelationKey → parentCorrelationKey */\n nestedRoutingIndex?: Map<unknown, unknown>\n /** parentCorrelationKey → Set<nestedCorrelationKeys> */\n nestedRoutingReverseIndex?: Map<unknown, Set<unknown>>\n}\n\ntype ChildCollectionEntry = {\n collection: Collection<any, any, any>\n syncMethods: SyncMethods<any> | null\n resultKeys: WeakMap<object, unknown>\n orderByIndices: WeakMap<object, string> | null\n /** Per-entry nested includes states (one per nested includes level) */\n includesStates?: Array<IncludesOutputState>\n}\n\nfunction materializesInline(state: IncludesOutputState): boolean {\n return state.materialization !== `collection`\n}\n\nfunction materializeIncludedValue(\n state: IncludesOutputState,\n entry: ChildCollectionEntry | undefined,\n): unknown {\n if (!entry) {\n if (state.materialization === `array`) {\n return []\n }\n if (state.materialization === `concat`) {\n return ``\n }\n return undefined\n }\n\n if (state.materialization === `collection`) {\n return entry.collection\n }\n\n const rows = [...entry.collection.toArray]\n const values = state.scalarField\n ? rows.map((row) => row?.[state.scalarField!])\n : rows\n\n if (state.materialization === `array`) {\n return values\n }\n\n return values.map((value) => String(value ?? ``)).join(``)\n}\n\n/**\n * Sets up shared buffers for nested includes pipelines.\n * Instead of writing directly into a single shared IncludesOutputState,\n * each nested pipeline writes into a buffer that is later drained per-entry.\n */\nfunction setupNestedPipelines(\n includes: Array<IncludesCompilationResult>,\n syncState: SyncState,\n): Array<NestedIncludesSetup> {\n return includes.map((entry) => {\n const buffer: Map<unknown, Map<unknown, Changes<any>>> = new Map()\n\n // Attach output callback that writes into the shared buffer\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n let byChild = buffer.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n buffer.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n const setup: NestedIncludesSetup = {\n compilationResult: entry,\n buffer,\n }\n\n // Recursively set up deeper levels\n if (entry.childCompilationResult.includes) {\n setup.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n }\n\n return setup\n })\n}\n\n/**\n * Creates fresh per-entry IncludesOutputState array from NestedIncludesSetup array.\n * Each entry gets its own isolated state for nested includes.\n */\nfunction createPerEntryIncludesStates(\n setups: Array<NestedIncludesSetup>,\n): Array<IncludesOutputState> {\n return setups.map((setup) => {\n const state: IncludesOutputState = {\n fieldName: setup.compilationResult.fieldName,\n childCorrelationField: setup.compilationResult.childCorrelationField,\n hasOrderBy: setup.compilationResult.hasOrderBy,\n materialization: setup.compilationResult.materialization,\n scalarField: setup.compilationResult.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n if (setup.nestedSetups) {\n state.nestedSetups = setup.nestedSetups\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\n}\n\n/**\n * Drains shared buffers into per-entry states using the routing index.\n * Returns the set of parent correlation keys that had changes routed to them.\n */\nfunction drainNestedBuffers(state: IncludesOutputState): Set<unknown> {\n const dirtyCorrelationKeys = new Set<unknown>()\n\n if (!state.nestedSetups) return dirtyCorrelationKeys\n\n for (let i = 0; i < state.nestedSetups.length; i++) {\n const setup = state.nestedSetups[i]!\n const toDelete: Array<unknown> = []\n\n for (const [nestedCorrelationKey, childChanges] of setup.buffer) {\n const parentCorrelationKey =\n state.nestedRoutingIndex!.get(nestedCorrelationKey)\n if (parentCorrelationKey === undefined) {\n // Unroutable — parent not yet seen; keep in buffer\n continue\n }\n\n const entry = state.childRegistry.get(parentCorrelationKey)\n if (!entry || !entry.includesStates) {\n continue\n }\n\n // Route changes into this entry's per-entry state at position i\n const entryState = entry.includesStates[i]!\n for (const [childKey, changes] of childChanges) {\n let byChild = entryState.pendingChildChanges.get(nestedCorrelationKey)\n if (!byChild) {\n byChild = new Map()\n entryState.pendingChildChanges.set(nestedCorrelationKey, byChild)\n }\n const existing = byChild.get(childKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n byChild.set(childKey, { ...changes })\n }\n }\n\n dirtyCorrelationKeys.add(parentCorrelationKey)\n toDelete.push(nestedCorrelationKey)\n }\n\n for (const key of toDelete) {\n setup.buffer.delete(key)\n }\n }\n\n return dirtyCorrelationKeys\n}\n\n/**\n * Updates the routing index after processing child changes.\n * Maps nested correlation keys to parent correlation keys so that\n * grandchild changes can be routed to the correct per-entry state.\n */\nfunction updateRoutingIndex(\n state: IncludesOutputState,\n correlationKey: unknown,\n childChanges: Map<unknown, Changes<any>>,\n): void {\n if (!state.nestedSetups) return\n\n for (const setup of state.nestedSetups) {\n for (const [, change] of childChanges) {\n if (change.inserts > 0) {\n // Read the nested routing key from the INCLUDES_ROUTING stamp.\n // Must use the composite routing key (not raw correlationKey) to match\n // how nested buffers are keyed by computeRoutingKey.\n const nestedRouting =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting?.correlationKey\n const nestedParentContext = nestedRouting?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.set(nestedRoutingKey, correlationKey)\n let reverseSet = state.nestedRoutingReverseIndex!.get(correlationKey)\n if (!reverseSet) {\n reverseSet = new Set()\n state.nestedRoutingReverseIndex!.set(correlationKey, reverseSet)\n }\n reverseSet.add(nestedRoutingKey)\n }\n } else if (change.deletes > 0 && change.inserts === 0) {\n // Remove from routing index\n const nestedRouting2 =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting2?.correlationKey\n const nestedParentContext2 = nestedRouting2?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext2,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.delete(nestedRoutingKey)\n const reverseSet =\n state.nestedRoutingReverseIndex!.get(correlationKey)\n if (reverseSet) {\n reverseSet.delete(nestedRoutingKey)\n if (reverseSet.size === 0) {\n state.nestedRoutingReverseIndex!.delete(correlationKey)\n }\n }\n }\n }\n }\n }\n}\n\n/**\n * Cleans routing index entries when a parent is deleted.\n * Uses the reverse index to find and remove all nested routing entries.\n */\nfunction cleanRoutingIndexOnDelete(\n state: IncludesOutputState,\n correlationKey: unknown,\n): void {\n if (!state.nestedRoutingReverseIndex) return\n\n const nestedKeys = state.nestedRoutingReverseIndex.get(correlationKey)\n if (nestedKeys) {\n for (const nestedKey of nestedKeys) {\n state.nestedRoutingIndex!.delete(nestedKey)\n }\n state.nestedRoutingReverseIndex.delete(correlationKey)\n }\n}\n\n/**\n * Recursively checks whether any nested buffer has pending changes.\n */\nfunction hasNestedBufferChanges(setups: Array<NestedIncludesSetup>): boolean {\n for (const setup of setups) {\n if (setup.buffer.size > 0) return true\n if (setup.nestedSetups && hasNestedBufferChanges(setup.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Computes a composite routing key from correlation key and parent context.\n * When parentContext is null (no parent filters), returns the raw correlationKey\n * for zero behavioral change on existing queries.\n */\nfunction computeRoutingKey(\n correlationKey: unknown,\n parentContext: Record<string, any> | null,\n): unknown {\n if (parentContext == null) return correlationKey\n return JSON.stringify([correlationKey, parentContext])\n}\n\n/**\n * Creates a child Collection entry for includes subqueries.\n * The child Collection is a full-fledged Collection instance that starts syncing immediately.\n */\nfunction createChildCollectionEntry(\n parentId: string,\n fieldName: string,\n correlationKey: unknown,\n hasOrderBy: boolean,\n nestedSetups?: Array<NestedIncludesSetup>,\n): ChildCollectionEntry {\n const resultKeys = new WeakMap<object, unknown>()\n const orderByIndices = hasOrderBy ? new WeakMap<object, string>() : null\n let syncMethods: SyncMethods<any> | null = null\n\n const compare = orderByIndices\n ? createOrderByComparator(orderByIndices)\n : undefined\n\n const collection = createCollection<any, string | number>({\n id: `__child-collection:${parentId}-${fieldName}-${serializeValue(correlationKey)}`,\n getKey: (item: any) => resultKeys.get(item) as string | number,\n compare,\n sync: {\n rowUpdateMode: `full`,\n sync: (methods) => {\n syncMethods = methods\n return () => {\n syncMethods = null\n }\n },\n },\n startSync: true,\n gcTime: 0,\n })\n\n const entry: ChildCollectionEntry = {\n collection,\n get syncMethods() {\n return syncMethods\n },\n resultKeys,\n orderByIndices,\n }\n\n if (nestedSetups) {\n entry.includesStates = createPerEntryIncludesStates(nestedSetups)\n }\n\n return entry\n}\n\n/**\n * Flushes includes state using a bottom-up per-entry approach.\n * Five phases ensure correct ordering:\n * 1. Parent INSERTs — create child entries with per-entry nested states\n * 2. Child changes — apply to child Collections, update routing index\n * 3. Drain nested buffers — route buffered grandchild changes to per-entry states\n * 4. Flush per-entry states — recursively flush nested includes on each entry\n * 5. Parent DELETEs — clean up child entries and routing index\n */\nfunction flushIncludesState(\n includesState: Array<IncludesOutputState>,\n parentCollection: Collection<any, any, any>,\n parentId: string,\n parentChanges: Map<unknown, Changes<any>> | null,\n parentSyncMethods: SyncMethods<any> | null,\n): void {\n for (const state of includesState) {\n // Phase 1: Parent INSERTs — ensure a child Collection exists for every parent\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.inserts > 0) {\n const parentResult = changes.value\n // Extract routing info from INCLUDES_ROUTING symbol (set by compiler)\n const routing = parentResult[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n if (correlationKey != null) {\n // Ensure child Collection exists for this routing key\n if (!state.childRegistry.has(routingKey)) {\n const entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n routingKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(routingKey, entry)\n }\n // Update reverse index: routing key → parent keys\n let parentKeys = state.correlationToParentKeys.get(routingKey)\n if (!parentKeys) {\n parentKeys = new Set()\n state.correlationToParentKeys.set(routingKey, parentKeys)\n }\n parentKeys.add(parentKey)\n\n const childValue = materializeIncludedValue(\n state,\n state.childRegistry.get(routingKey),\n )\n parentResult[state.fieldName] = childValue\n\n // Parent rows may already be materialized in the live collection by the\n // time includes state is flushed, so update the stored row as well.\n const storedParent = parentCollection.get(parentKey as any)\n if (storedParent && storedParent !== parentResult) {\n storedParent[state.fieldName] = childValue\n }\n }\n }\n }\n }\n\n // Track affected correlation keys for inline materializations before clearing child changes.\n const affectedCorrelationKeys = materializesInline(state)\n ? new Set<unknown>(state.pendingChildChanges.keys())\n : null\n\n // Phase 2: Child changes — apply to child Collections\n // Track which entries had child changes and capture their childChanges maps\n const entriesWithChildChanges = new Map<\n unknown,\n { entry: ChildCollectionEntry; childChanges: Map<unknown, Changes<any>> }\n >()\n if (state.pendingChildChanges.size > 0) {\n for (const [correlationKey, childChanges] of state.pendingChildChanges) {\n // Ensure child Collection exists for this correlation key\n let entry = state.childRegistry.get(correlationKey)\n if (!entry) {\n entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n correlationKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(correlationKey, entry)\n }\n\n if (state.materialization === `collection`) {\n attachChildCollectionToParent(\n parentCollection,\n state.fieldName,\n correlationKey,\n state.correlationToParentKeys,\n entry.collection,\n )\n }\n\n // Apply child changes to the child Collection\n if (entry.syncMethods) {\n entry.syncMethods.begin()\n for (const [childKey, change] of childChanges) {\n entry.resultKeys.set(change.value, childKey)\n if (entry.orderByIndices && change.orderByIndex !== undefined) {\n entry.orderByIndices.set(change.value, change.orderByIndex)\n }\n if (change.inserts > 0 && change.deletes === 0) {\n entry.syncMethods.write({ value: change.value, type: `insert` })\n } else if (\n change.inserts > change.deletes ||\n (change.inserts === change.deletes &&\n entry.syncMethods.collection.has(\n entry.syncMethods.collection.getKeyFromItem(change.value),\n ))\n ) {\n entry.syncMethods.write({ value: change.value, type: `update` })\n } else if (change.deletes > 0) {\n entry.syncMethods.write({ value: change.value, type: `delete` })\n }\n }\n entry.syncMethods.commit()\n }\n\n // Update routing index for nested includes\n updateRoutingIndex(state, correlationKey, childChanges)\n\n entriesWithChildChanges.set(correlationKey, { entry, childChanges })\n }\n state.pendingChildChanges.clear()\n }\n\n // Phase 3: Drain nested buffers — route buffered grandchild changes to per-entry states\n const dirtyFromBuffers = drainNestedBuffers(state)\n\n // Phase 4: Flush per-entry states\n // First: entries that had child changes in Phase 2\n for (const [, { entry, childChanges }] of entriesWithChildChanges) {\n if (entry.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n childChanges,\n entry.syncMethods,\n )\n }\n }\n // Then: entries that only had buffer-routed changes (no child changes at this level)\n for (const correlationKey of dirtyFromBuffers) {\n if (entriesWithChildChanges.has(correlationKey)) continue\n const entry = state.childRegistry.get(correlationKey)\n if (entry?.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n null,\n entry.syncMethods,\n )\n }\n }\n // Finally: entries with deep nested buffer changes (grandchild-or-deeper buffers\n // have pending data, but neither this level nor the immediate child level changed).\n // Without this pass, changes at depth 3+ are stranded because drainNestedBuffers\n // only drains one level and Phase 4 only flushes entries dirty from Phase 2/3.\n const deepBufferDirty = new Set<unknown>()\n if (state.nestedSetups) {\n for (const [correlationKey, entry] of state.childRegistry) {\n if (entriesWithChildChanges.has(correlationKey)) continue\n if (dirtyFromBuffers.has(correlationKey)) continue\n if (\n entry.includesStates &&\n hasPendingIncludesChanges(entry.includesStates)\n ) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n null,\n entry.syncMethods,\n )\n deepBufferDirty.add(correlationKey)\n }\n }\n }\n\n // For inline materializations: re-emit affected parents with updated snapshots.\n // We mutate items in-place (so collection.get() reflects changes immediately)\n // and emit UPDATE events directly. We bypass the sync methods because\n // commitPendingTransactions compares previous vs new visible state using\n // deepEquals, but in-place mutation means both sides reference the same\n // object, so the comparison always returns true and suppresses the event.\n const inlineReEmitKeys = materializesInline(state)\n ? new Set([\n ...(affectedCorrelationKeys || []),\n ...dirtyFromBuffers,\n ...deepBufferDirty,\n ])\n : null\n if (parentSyncMethods && inlineReEmitKeys && inlineReEmitKeys.size > 0) {\n const events: Array<ChangeMessage<any>> = []\n for (const correlationKey of inlineReEmitKeys) {\n const parentKeys = state.correlationToParentKeys.get(correlationKey)\n if (!parentKeys) continue\n const entry = state.childRegistry.get(correlationKey)\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n const key = parentSyncMethods.collection.getKeyFromItem(item)\n // Capture previous value before in-place mutation\n const previousValue = { ...item }\n item[state.fieldName] = materializeIncludedValue(state, entry)\n events.push({\n type: `update`,\n key,\n value: item,\n previousValue,\n })\n }\n }\n }\n if (events.length > 0) {\n // Emit directly — the in-place mutation already updated the data in\n // syncedData, so we only need to notify subscribers.\n const changesManager = (parentCollection as any)._changes as {\n emitEvents: (\n changes: Array<ChangeMessage<any>>,\n forceEmit?: boolean,\n ) => void\n }\n changesManager.emitEvents(events, true)\n }\n }\n\n // Phase 5: Parent DELETEs — dispose child Collections and clean up\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.deletes > 0 && changes.inserts === 0) {\n const routing = changes.value[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n if (correlationKey != null) {\n // Clean up reverse index first, only delete child collection\n // when the last parent referencing it is removed\n const parentKeys = state.correlationToParentKeys.get(routingKey)\n if (parentKeys) {\n parentKeys.delete(parentKey)\n if (parentKeys.size === 0) {\n cleanRoutingIndexOnDelete(state, routingKey)\n state.childRegistry.delete(routingKey)\n state.correlationToParentKeys.delete(routingKey)\n }\n }\n }\n }\n }\n }\n }\n\n // Clean up the internal routing stamp from parent/child results\n if (parentChanges) {\n for (const [, changes] of parentChanges) {\n delete changes.value[INCLUDES_ROUTING]\n }\n }\n}\n\n/**\n * Checks whether any includes state has pending changes that need to be flushed.\n * Checks direct pending child changes and shared nested buffers.\n */\nfunction hasPendingIncludesChanges(\n states: Array<IncludesOutputState>,\n): boolean {\n for (const state of states) {\n if (state.pendingChildChanges.size > 0) return true\n if (state.nestedSetups && hasNestedBufferChanges(state.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Attaches a child Collection to parent rows that match a given correlation key.\n * Uses the reverse index to look up parent keys directly instead of scanning.\n */\nfunction attachChildCollectionToParent(\n parentCollection: Collection<any, any, any>,\n fieldName: string,\n correlationKey: unknown,\n correlationToParentKeys: Map<unknown, Set<unknown>>,\n childCollection: Collection<any, any, any>,\n): void {\n const parentKeys = correlationToParentKeys.get(correlationKey)\n if (!parentKeys) return\n\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n item[fieldName] = childCollection\n }\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 } 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"],"names":[],"mappings":"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"}
|
|
@@ -183,8 +183,8 @@ class CollectionSubscriber {
|
|
|
183
183
|
if (!orderByInfo) {
|
|
184
184
|
return true;
|
|
185
185
|
}
|
|
186
|
-
const { dataNeeded } = orderByInfo;
|
|
187
|
-
if (!dataNeeded) {
|
|
186
|
+
const { dataNeeded, index } = orderByInfo;
|
|
187
|
+
if (!dataNeeded || !index) {
|
|
188
188
|
return true;
|
|
189
189
|
}
|
|
190
190
|
if (this.pendingOrderedLoadPromise) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-subscriber.js","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 type { Collection } from '../../collection/index.js'\nimport type {\n ChangeMessage,\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'\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\n constructor(\n private alias: string,\n private collectionId: string,\n private collection: Collection,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>,\n ) {}\n\n subscribe(): CollectionSubscription {\n const whereClause = this.getWhereClauseForAlias()\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\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 this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(\n result,\n )\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 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\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 )\n } else {\n // If the source alias is lazy then we should not include the initial state\n const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(\n this.alias,\n )\n\n subscription = this.subscribeToMatchingChanges(\n whereExpression,\n includeInitialState,\n onStatusChange,\n )\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 (subscription.status === `loadingSubset`) {\n this.ensureLoadingPromise(subscription)\n }\n\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 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 return subscription\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.alias]!\n const sentChanges = sendChangesToInput(\n input,\n filteredChanges,\n this.collection.config.getKey,\n )\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 alias: this.alias,\n })\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean,\n onStatusChange: (event: SubscriptionStatusChangeEvent) => 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 onLoadSubsetResult = includeInitialState\n ? (result: Promise<void> | true) => {\n if (result instanceof Promise) {\n this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(\n result,\n )\n }\n }\n : undefined\n\n const subscription = this.collection.subscribeChanges(sendChanges, {\n ...(includeInitialState && { includeInitialState }),\n whereExpression,\n onStatusChange,\n orderBy: hints.orderBy,\n limit: hints.limit,\n onLoadSubsetResult,\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 ): 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 result.finally(() => {\n if (this.pendingOrderedLoadPromise === result) {\n this.pendingOrderedLoadPromise = undefined\n }\n })\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 })\n subscriptionHolder.current = 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 } = orderByInfo\n\n if (!dataNeeded) {\n // dataNeeded is not set when there's no index (e.g., non-ref expression).\n // In this case, we've already loaded all data via requestSnapshot\n // and don't need to lazily load more.\n return true\n }\n\n if (this.pendingOrderedLoadPromise) {\n // Wait for in-flight ordered loads to resolve before issuing another request.\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 this.loadNextItems(n, subscription)\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 this.lastLoadRequestKey = cursor.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 subscription.requestLimitedSnapshot({\n orderBy: cursor.normalizedOrderBy,\n limit: n,\n minValues: cursor.minValues,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: this.orderedLoadSubsetResult,\n })\n }\n\n private getWhereClauseForAlias(): BasicExpression<boolean> | undefined {\n const sourceWhereClausesCache =\n this.collectionConfigBuilder.sourceWhereClausesCache\n if (!sourceWhereClausesCache) {\n return undefined\n }\n return sourceWhereClausesCache.get(this.alias)\n }\n\n private getOrderByInfo(): OrderByOptimizationInfo | undefined {\n const info =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n if (info && info.alias === this.alias) {\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.liveQueryCollection!._sync.trackLoadPromise(\n promise,\n )\n }\n}\n"],"names":["subscription"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection-subscriber.js","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 type { Collection } from '../../collection/index.js'\nimport type {\n ChangeMessage,\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'\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\n constructor(\n private alias: string,\n private collectionId: string,\n private collection: Collection,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>,\n ) {}\n\n subscribe(): CollectionSubscription {\n const whereClause = this.getWhereClauseForAlias()\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\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 this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(\n result,\n )\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 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\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 )\n } else {\n // If the source alias is lazy then we should not include the initial state\n const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(\n this.alias,\n )\n\n subscription = this.subscribeToMatchingChanges(\n whereExpression,\n includeInitialState,\n onStatusChange,\n )\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 (subscription.status === `loadingSubset`) {\n this.ensureLoadingPromise(subscription)\n }\n\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 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 return subscription\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.alias]!\n const sentChanges = sendChangesToInput(\n input,\n filteredChanges,\n this.collection.config.getKey,\n )\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 alias: this.alias,\n })\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean,\n onStatusChange: (event: SubscriptionStatusChangeEvent) => 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 onLoadSubsetResult = includeInitialState\n ? (result: Promise<void> | true) => {\n if (result instanceof Promise) {\n this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(\n result,\n )\n }\n }\n : undefined\n\n const subscription = this.collection.subscribeChanges(sendChanges, {\n ...(includeInitialState && { includeInitialState }),\n whereExpression,\n onStatusChange,\n orderBy: hints.orderBy,\n limit: hints.limit,\n onLoadSubsetResult,\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 ): 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 result.finally(() => {\n if (this.pendingOrderedLoadPromise === result) {\n this.pendingOrderedLoadPromise = undefined\n }\n })\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 })\n subscriptionHolder.current = 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 if (this.pendingOrderedLoadPromise) {\n // Wait for in-flight ordered loads to resolve before issuing another request.\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 this.loadNextItems(n, subscription)\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 this.lastLoadRequestKey = cursor.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 subscription.requestLimitedSnapshot({\n orderBy: cursor.normalizedOrderBy,\n limit: n,\n minValues: cursor.minValues,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: this.orderedLoadSubsetResult,\n })\n }\n\n private getWhereClauseForAlias(): BasicExpression<boolean> | undefined {\n const sourceWhereClausesCache =\n this.collectionConfigBuilder.sourceWhereClausesCache\n if (!sourceWhereClausesCache) {\n return undefined\n }\n return sourceWhereClausesCache.get(this.alias)\n }\n\n private getOrderByInfo(): OrderByOptimizationInfo | undefined {\n const info =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n if (info && info.alias === this.alias) {\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.liveQueryCollection!._sync.trackLoadPromise(\n promise,\n )\n }\n}\n"],"names":["subscription"],"mappings":";;AAuBA,MAAM,yBAAyB,uBAAO;AAAA,EACpC;AACF;AAEO,MAAM,qBAGX;AAAA,EAyBA,YACU,OACA,cACA,YACA,yBACR;AAJQ,SAAA,QAAA;AACA,SAAA,eAAA;AACA,SAAA,aAAA;AACA,SAAA,0BAAA;AA3BV,SAAQ,UAAe;AAQvB,SAAQ,kDAAkC,IAAA;AAQ1C,SAAQ,mCAAmB,IAAA;AAAA,EAYxB;AAAA,EAEH,YAAoC;AAClC,UAAM,cAAc,KAAK,uBAAA;AAEzB,QAAI,aAAa;AACf,YAAM,kBAAkB,yBAAyB,aAAa,KAAK,KAAK;AACxE,aAAO,KAAK,mBAAmB,eAAe;AAAA,IAChD;AAEA,WAAO,KAAK,mBAAA;AAAA,EACd;AAAA,EAEQ,mBAAmB,iBAA4C;AACrE,UAAM,cAAc,KAAK,eAAA;AAKzB,UAAM,kBAAkB,CAAC,WAAiC;AACxD,UAAI,kBAAkB,SAAS;AAC7B,aAAK,wBAAwB,oBAAqB,MAAM;AAAA,UACtD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAMA,UAAM,iBAAiB,CAAC,UAAyC;AAC/D,YAAMA,gBAAe,MAAM;AAC3B,UAAI,MAAM,WAAW,iBAAiB;AACpC,aAAK,qBAAqBA,aAAY;AAAA,MACxC,OAAO;AAEL,cAAM,WAAW,KAAK,4BAA4B,IAAIA,aAAY;AAClE,YAAI,UAAU;AACZ,eAAK,4BAA4B,OAAOA,aAAY;AACpD,mBAAS,QAAA;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,aAAa;AACf,qBAAe,KAAK;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ,OAAO;AAEL,YAAM,sBAAsB,CAAC,KAAK,wBAAwB;AAAA,QACxD,KAAK;AAAA,MAAA;AAGP,qBAAe,KAAK;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAIA,QAAI,aAAa,WAAW,iBAAiB;AAC3C,WAAK,qBAAqB,YAAY;AAAA,IACxC;AAEA,UAAM,cAAc,MAAM;AAExB,YAAM,WAAW,KAAK,4BAA4B,IAAI,YAAY;AAClE,UAAI,UAAU;AACZ,aAAK,4BAA4B,OAAO,YAAY;AACpD,iBAAS,QAAA;AAAA,MACX;AAEA,mBAAa,YAAA;AAAA,IACf;AAGA,SAAK,wBAAwB,iBAAkB,qBAAqB;AAAA,MAClE;AAAA,IAAA;AAEF,WAAO;AAAA,EACT;AAAA,EAEQ,sBACN,SACA,UACA;AACA,UAAM,eAAe,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,GAAG,OAAO;AACnE,UAAM,kBAAkB;AAAA,MACtB;AAAA,MACA,KAAK;AAAA,IAAA;AAKP,UAAM,QACJ,KAAK,wBAAwB,iBAAkB,OAAO,KAAK,KAAK;AAClE,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA,KAAK,WAAW,OAAO;AAAA,IAAA;AAMzB,UAAM,aAAa,cAAc,IAAI,WAAW;AAKhD,SAAK,wBAAwB,iBAAiB,YAAY;AAAA,MACxD,OAAO,KAAK;AAAA,IAAA,CACb;AAAA,EACH;AAAA,EAEQ,2BACN,iBACA,qBACA,gBACwB;AACxB,UAAM,cAAc,CAClB,YACG;AACH,WAAK,sBAAsB,OAAO;AAAA,IACpC;AAGA,UAAM,QAAQ;AAAA,MACZ,KAAK,wBAAwB;AAAA,MAC7B,KAAK;AAAA,IAAA;AAMP,UAAM,qBAAqB,sBACvB,CAAC,WAAiC;AAChC,UAAI,kBAAkB,SAAS;AAC7B,aAAK,wBAAwB,oBAAqB,MAAM;AAAA,UACtD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF,IACA;AAEJ,UAAM,eAAe,KAAK,WAAW,iBAAiB,aAAa;AAAA,MACjE,GAAI,uBAAuB,EAAE,oBAAA;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb;AAAA,IAAA,CACD;AAED,WAAO;AAAA,EACT;AAAA,EAEQ,0BACN,iBACA,aACA,gBACA,oBACwB;AACxB,UAAM,EAAE,SAAS,QAAQ,OAAO,UAAU;AAK1C,UAAM,yBAAyB,CAAC,WAAiC;AAC/D,UAAI,kBAAkB,SAAS;AAC7B,aAAK,4BAA4B;AACjC,eAAO,QAAQ,MAAM;AACnB,cAAI,KAAK,8BAA8B,QAAQ;AAC7C,iBAAK,4BAA4B;AAAA,UACnC;AAAA,QACF,CAAC;AAAA,MACH;AACA,yBAAmB,MAAM;AAAA,IAC3B;AAEA,SAAK,0BAA0B;AAG/B,UAAM,qBAA2D,CAAA;AAEjE,UAAM,qBAAqB,CACzB,YACG;AACH,YAAM,eAAe,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,GAAG,OAAO;AAEnE,WAAK,gBAAgB,cAAc,YAAY,UAAU;AAGzD,YAAM,kBAAkB,aAAa,YAAY;AACjD,WAAK;AAAA,QACH;AAAA,QACA,mBAAmB;AAAA,MAAA;AAAA,IAEvB;AAIA,UAAM,eAAe,KAAK,WAAW,iBAAiB,oBAAoB;AAAA,MACxE;AAAA,MACA;AAAA,IAAA,CACD;AACD,uBAAmB,UAAU;AAK7B,UAAM,sBAAsB,KAAK,WAAW,GAAG,YAAY,MAAM;AAC/D,WAAK,UAAU;AACf,WAAK,qBAAqB;AAC1B,WAAK,4BAA4B;AACjC,WAAK,aAAa,MAAA;AAAA,IACpB,CAAC;AAGD,iBAAa,GAAG,gBAAgB,MAAM;AACpC,0BAAA;AAAA,IACF,CAAC;AAGD,UAAM,oBAAoB,sBAAsB,SAAS,KAAK,KAAK;AAMnE,QAAI,OAAO;AAET,mBAAa,gBAAgB,KAAK;AAElC,mBAAa,uBAAuB;AAAA,QAClC,OAAO,SAAS;AAAA,QAChB,SAAS;AAAA,QACT,wBAAwB;AAAA,QACxB,oBAAoB;AAAA,MAAA,CACrB;AAAA,IACH,OAAO;AAEL,mBAAa,gBAAgB;AAAA,QAC3B,SAAS;AAAA,QACT,OAAO,SAAS;AAAA,QAChB,wBAAwB;AAAA,QACxB,oBAAoB;AAAA,MAAA,CACrB;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,cAAsC;AACrD,UAAM,cAAc,KAAK,eAAA;AAEzB,QAAI,CAAC,aAAa;AAGhB,aAAO;AAAA,IACT;AAEA,UAAM,EAAE,YAAY,MAAA,IAAU;AAE9B,QAAI,CAAC,cAAc,CAAC,OAAO;AAIzB,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,2BAA2B;AAElC,aAAO;AAAA,IACT;AAIA,UAAM,IAAI,WAAA;AACV,QAAI,IAAI,GAAG;AACT,WAAK,cAAc,GAAG,YAAY;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kCACN,SACA,cACA;AACA,UAAM,cAAc,KAAK,eAAA;AACzB,QAAI,CAAC,aAAa;AAChB,WAAK,sBAAsB,OAAO;AAClC;AAAA,IACF;AASA,UAAM,yBAAyB;AAE/B,2BAAuB,sBAAsB,MAC3C,KAAK,iBAAiB,KAAK,MAAM,YAAY;AAE/C,SAAK;AAAA,MACH;AAAA,MACA,uBAAuB,sBAAsB;AAAA,IAAA;AAAA,EAEjD;AAAA;AAAA;AAAA,EAIQ,cAAc,GAAW,cAAsC;AACrE,UAAM,cAAc,KAAK,eAAA;AACzB,QAAI,CAAC,aAAa;AAChB;AAAA,IACF;AAEA,UAAM,SAAS;AAAA,MACb;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,IAAA;AAEF,QAAI,CAAC,OAAQ;AAEb,SAAK,qBAAqB,OAAO;AAKjC,iBAAa,uBAAuB;AAAA,MAClC,SAAS,OAAO;AAAA,MAChB,OAAO;AAAA,MACP,WAAW,OAAO;AAAA,MAClB,wBAAwB;AAAA,MACxB,oBAAoB,KAAK;AAAA,IAAA,CAC1B;AAAA,EACH;AAAA,EAEQ,yBAA+D;AACrE,UAAM,0BACJ,KAAK,wBAAwB;AAC/B,QAAI,CAAC,yBAAyB;AAC5B,aAAO;AAAA,IACT;AACA,WAAO,wBAAwB,IAAI,KAAK,KAAK;AAAA,EAC/C;AAAA,EAEQ,iBAAsD;AAC5D,UAAM,OACJ,KAAK,wBAAwB,8BAC3B,KAAK,YACP;AACF,QAAI,QAAQ,KAAK,UAAU,KAAK,OAAO;AACrC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBACN,SACA,YACM;AACN,UAAM,SAAS;AAAA,MACb;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,IAAA;AAEF,SAAK,UAAU,OAAO;AACtB,QAAI,OAAO,oBAAoB;AAC7B,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA,EAEQ,qBAAqB,cAAsC;AACjE,QAAI,KAAK,4BAA4B,IAAI,YAAY,GAAG;AACtD;AAAA,IACF;AAEA,QAAI;AACJ,UAAM,UAAU,IAAI,QAAc,CAAC,QAAQ;AACzC,gBAAU;AAAA,IACZ,CAAC;AAED,SAAK,4BAA4B,IAAI,cAAc;AAAA,MACjD;AAAA,IAAA,CACD;AACD,SAAK,wBAAwB,oBAAqB,MAAM;AAAA,MACtD;AAAA,IAAA;AAAA,EAEJ;AACF;"}
|
package/package.json
CHANGED
|
@@ -286,7 +286,8 @@ const messagesWithContent = createLiveQueryCollection((q) =>
|
|
|
286
286
|
### Includes rules
|
|
287
287
|
|
|
288
288
|
- The subquery **must** have a `where` clause with an `eq()` correlating a parent alias with a child alias. The library extracts this automatically as the join condition.
|
|
289
|
-
- `toArray()`
|
|
289
|
+
- `toArray()` works with both scalar selects (e.g., `select(({ c }) => c.text)` → `string[]`) and object selects (e.g., `select(({ c }) => ({ id: c.id, title: c.title }))` → `Array<{id, title}>`).
|
|
290
|
+
- `concat(toArray())` requires a **scalar** `select` to concatenate into a string.
|
|
290
291
|
- Collection includes (bare subquery) require an **object** `select`.
|
|
291
292
|
- Includes subqueries are compiled into the same incremental pipeline as the parent query -- they are not separate live queries.
|
|
292
293
|
|
|
@@ -437,12 +437,14 @@ export const operators = [
|
|
|
437
437
|
export type OperatorName = (typeof operators)[number]
|
|
438
438
|
|
|
439
439
|
export class ToArrayWrapper<_T = unknown> {
|
|
440
|
+
readonly __brand = `ToArrayWrapper` as const
|
|
440
441
|
declare readonly _type: `toArray`
|
|
441
442
|
declare readonly _result: _T
|
|
442
443
|
constructor(public readonly query: QueryBuilder<any>) {}
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
export class ConcatToArrayWrapper<_T = unknown> {
|
|
447
|
+
readonly __brand = `ConcatToArrayWrapper` as const
|
|
446
448
|
declare readonly _type: `concatToArray`
|
|
447
449
|
declare readonly _result: _T
|
|
448
450
|
constructor(public readonly query: QueryBuilder<any>) {}
|