@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 50.2 kB
Source Map (JSON)
{"version":3,"file":"effect.cjs","sources":["../../../src/query/effect.ts"],"sourcesContent":["import { D2, output } from '@tanstack/db-ivm'\nimport { transactionScopedScheduler } from '../scheduler.js'\nimport { getActiveTransaction } from '../transactions.js'\nimport { compileQuery } from './compiler/index.js'\nimport {\n normalizeExpressionPaths,\n normalizeOrderByPaths,\n} from './compiler/expressions.js'\nimport { getCollectionBuilder } from './live/collection-registry.js'\nimport {\n buildQueryFromConfig,\n computeOrderedLoadCursor,\n computeSubscriptionOrderByHints,\n extractCollectionAliases,\n extractCollectionsFromQuery,\n filterDuplicateInserts,\n sendChangesToInput,\n splitUpdates,\n trackBiggestSentValue,\n} from './live/utils.js'\nimport type { RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { Collection } from '../collection/index.js'\nimport type { CollectionSubscription } from '../collection/subscription.js'\nimport type { InitialQueryBuilder, QueryBuilder } from './builder/index.js'\nimport type { Context } from './builder/types.js'\nimport type { BasicExpression, QueryIR } from './ir.js'\nimport type { OrderByOptimizationInfo } from './compiler/order-by.js'\nimport type { ChangeMessage, KeyedStream, ResultStream } from '../types.js'\n\n// ---------------------------------------------------------------------------\n// Public Types\n// ---------------------------------------------------------------------------\n\n/** Event types for query result deltas */\nexport type DeltaType = 'enter' | 'exit' | 'update'\n\n/** Delta event emitted when a row enters, exits, or updates within a query result */\nexport type DeltaEvent<\n TRow extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> =\n | {\n type: 'enter'\n key: TKey\n /** Current value for the entering row */\n value: TRow\n metadata?: Record<string, unknown>\n }\n | {\n type: 'exit'\n key: TKey\n /** Current value for the exiting row */\n value: TRow\n metadata?: Record<string, unknown>\n }\n | {\n type: 'update'\n key: TKey\n /** Current value after the update */\n value: TRow\n /** Previous value before the batch */\n previousValue: TRow\n metadata?: Record<string, unknown>\n }\n\n/** Context passed to effect handlers */\nexport interface EffectContext {\n /** ID of this effect (auto-generated if not provided) */\n effectId: string\n /** Aborted when effect.dispose() is called */\n signal: AbortSignal\n}\n\n/** Query input - can be a builder function or a prebuilt query */\nexport type EffectQueryInput<TContext extends Context> =\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\ntype EffectEventHandler<\n TRow extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> = (event: DeltaEvent<TRow, TKey>, ctx: EffectContext) => void | Promise<void>\n\ntype EffectBatchHandler<\n TRow extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> = (\n events: Array<DeltaEvent<TRow, TKey>>,\n ctx: EffectContext,\n) => void | Promise<void>\n\n/** Effect configuration */\nexport interface EffectConfig<\n TRow extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> {\n /** Optional ID for debugging/tracing */\n id?: string\n\n /** Query to watch for deltas */\n query: EffectQueryInput<any>\n\n /** Called once for each row entering the query result */\n onEnter?: EffectEventHandler<TRow, TKey>\n\n /** Called once for each row updating within the query result */\n onUpdate?: EffectEventHandler<TRow, TKey>\n\n /** Called once for each row exiting the query result */\n onExit?: EffectEventHandler<TRow, TKey>\n\n /** Called once per graph run with all delta events from that batch */\n onBatch?: EffectBatchHandler<TRow, TKey>\n\n /** Error handler for exceptions thrown by effect callbacks */\n onError?: (error: Error, event: DeltaEvent<TRow, TKey>) => void\n\n /**\n * Called when a source collection enters an error or cleaned-up state.\n * The effect is automatically disposed after this callback fires.\n * If not provided, the error is logged to console.error.\n */\n onSourceError?: (error: Error) => void\n\n /**\n * Skip deltas during initial collection load.\n * Defaults to false (process all deltas including initial sync).\n * Set to true for effects that should only process new changes.\n */\n skipInitial?: boolean\n}\n\n/** Handle returned by createEffect */\nexport interface Effect {\n /** Dispose the effect. Returns a promise that resolves when in-flight handlers complete. */\n dispose: () => Promise<void>\n /** Whether this effect has been disposed */\n readonly disposed: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types\n// ---------------------------------------------------------------------------\n\n/** Accumulated changes for a single key within a graph run */\ninterface EffectChanges<T> {\n deletes: number\n inserts: number\n /** Value from the latest insert (the newest/current value) */\n insertValue?: T\n /** Value from the first delete (the oldest/previous value before the batch) */\n deleteValue?: T\n}\n\n// ---------------------------------------------------------------------------\n// Global Counter\n// ---------------------------------------------------------------------------\n\nlet effectCounter = 0\n\n// ---------------------------------------------------------------------------\n// createEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive effect that fires handlers when rows enter, exit, or\n * update within a query result. Effects process deltas only — they do not\n * maintain or require the full materialised query result.\n *\n * @example\n * ```typescript\n * const effect = createEffect({\n * query: (q) => q.from({ msg: messagesCollection })\n * .where(({ msg }) => eq(msg.role, 'user')),\n * onEnter: async (event) => {\n * await generateResponse(event.value)\n * },\n * })\n *\n * // Later: stop the effect\n * await effect.dispose()\n * ```\n */\nexport function createEffect<\n TRow extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(config: EffectConfig<TRow, TKey>): Effect {\n const id = config.id ?? `live-query-effect-${++effectCounter}`\n\n // AbortController for signalling disposal to handlers\n const abortController = new AbortController()\n\n const ctx: EffectContext = {\n effectId: id,\n signal: abortController.signal,\n }\n\n // Track in-flight async handler promises so dispose() can await them\n const inFlightHandlers = new Set<Promise<void>>()\n let disposed = false\n\n // Callback invoked by the pipeline runner with each batch of delta events\n const onBatchProcessed = (events: Array<DeltaEvent<TRow, TKey>>) => {\n if (disposed) return\n if (events.length === 0) return\n\n // Batch handler\n if (config.onBatch) {\n try {\n const result = config.onBatch(events, ctx)\n if (result instanceof Promise) {\n const tracked = result.catch((error) => {\n reportError(error, events[0]!, config.onError)\n })\n trackPromise(tracked, inFlightHandlers)\n }\n } catch (error) {\n // For batch handler errors, report with first event as context\n reportError(error, events[0]!, config.onError)\n }\n }\n\n for (const event of events) {\n if (abortController.signal.aborted) break\n\n const handler = getHandlerForEvent(event, config)\n if (!handler) continue\n\n try {\n const result = handler(event, ctx)\n if (result instanceof Promise) {\n const tracked = result.catch((error) => {\n reportError(error, event, config.onError)\n })\n trackPromise(tracked, inFlightHandlers)\n }\n } catch (error) {\n reportError(error, event, config.onError)\n }\n }\n }\n\n // The dispose function is referenced by both the returned Effect object\n // and the onSourceError callback, so we define it first.\n const dispose = async () => {\n if (disposed) return\n disposed = true\n\n // Abort signal for in-flight handlers\n abortController.abort()\n\n // Tear down the pipeline (unsubscribe from sources, etc.)\n runner.dispose()\n\n // Wait for any in-flight async handlers to settle\n if (inFlightHandlers.size > 0) {\n await Promise.allSettled([...inFlightHandlers])\n }\n }\n\n // Create and start the pipeline\n const runner = new EffectPipelineRunner<TRow, TKey>({\n query: config.query,\n skipInitial: config.skipInitial ?? false,\n onBatchProcessed,\n onSourceError: (error: Error) => {\n if (disposed) return\n\n if (config.onSourceError) {\n try {\n config.onSourceError(error)\n } catch (callbackError) {\n console.error(\n `[Effect '${id}'] onSourceError callback threw:`,\n callbackError,\n )\n }\n } else {\n console.error(`[Effect '${id}'] ${error.message}. Disposing effect.`)\n }\n\n // Auto-dispose — the effect can no longer function\n dispose()\n },\n })\n runner.start()\n\n return {\n dispose,\n get disposed() {\n return disposed\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// EffectPipelineRunner\n// ---------------------------------------------------------------------------\n\ninterface EffectPipelineRunnerConfig<\n TRow extends object,\n TKey extends string | number,\n> {\n query: EffectQueryInput<any>\n skipInitial: boolean\n onBatchProcessed: (events: Array<DeltaEvent<TRow, TKey>>) => void\n /** Called when a source collection enters error or cleaned-up state */\n onSourceError: (error: Error) => void\n}\n\n/**\n * Internal class that manages a D2 pipeline for effect delta processing.\n *\n * Sets up the IVM graph, subscribes to source collections, runs the graph\n * when changes arrive, and classifies output multiplicities into DeltaEvents.\n *\n * Unlike CollectionConfigBuilder, this does NOT:\n * - Create or write to a collection (no materialisation)\n * - Manage ordering, windowing, or lazy loading\n */\nclass EffectPipelineRunner<TRow extends object, TKey extends string | number> {\n private readonly query: QueryIR\n private readonly collections: Record<string, Collection<any, any, any>>\n private readonly collectionByAlias: Record<string, Collection<any, any, any>>\n\n private graph: D2 | undefined\n private inputs: Record<string, RootStreamBuilder<unknown>> | undefined\n private pipeline: ResultStream | undefined\n private sourceWhereClauses: Map<string, BasicExpression<boolean>> | undefined\n private compiledAliasToCollectionId: Record<string, string> = {}\n\n // Mutable objects passed to compileQuery by reference.\n // The join compiler captures these references and reads them later when\n // the graph runs, so they must be populated before the first graph run.\n private readonly subscriptions: Record<string, CollectionSubscription> = {}\n private readonly lazySourcesCallbacks: Record<string, any> = {}\n private readonly lazySources = new Set<string>()\n // OrderBy optimization info populated by the compiler when limit is present\n private readonly optimizableOrderByCollections: Record<\n string,\n OrderByOptimizationInfo\n > = {}\n\n // Ordered subscription state for cursor-based loading\n private readonly biggestSentValue = new Map<string, any>()\n private readonly lastLoadRequestKey = new Map<string, string>()\n private pendingOrderedLoadPromise: Promise<void> | undefined\n\n // Subscription management\n private readonly unsubscribeCallbacks = new Set<() => void>()\n // Duplicate insert prevention per alias\n private readonly sentToD2KeysByAlias = new Map<string, Set<string | number>>()\n\n // Output accumulator\n private pendingChanges: Map<unknown, EffectChanges<TRow>> = new Map()\n\n // skipInitial state\n private readonly skipInitial: boolean\n private initialLoadComplete = false\n\n // Scheduler integration\n private subscribedToAllCollections = false\n private readonly builderDependencies = new Set<unknown>()\n private readonly aliasDependencies: Record<string, Array<unknown>> = {}\n\n // Reentrance guard\n private isGraphRunning = false\n private disposed = false\n // When dispose() is called mid-graph-run, defer heavy cleanup until the run completes\n private deferredCleanup = false\n\n private readonly onBatchProcessed: (\n events: Array<DeltaEvent<TRow, TKey>>,\n ) => void\n private readonly onSourceError: (error: Error) => void\n\n constructor(config: EffectPipelineRunnerConfig<TRow, TKey>) {\n this.skipInitial = config.skipInitial\n this.onBatchProcessed = config.onBatchProcessed\n this.onSourceError = config.onSourceError\n\n // Parse query\n this.query = buildQueryFromConfig({ query: config.query })\n\n // Extract source collections\n this.collections = extractCollectionsFromQuery(this.query)\n const aliasesById = extractCollectionAliases(this.query)\n\n // Build alias → collection map\n this.collectionByAlias = {}\n for (const [collectionId, aliases] of aliasesById.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 // Compile the pipeline\n this.compilePipeline()\n }\n\n /** Compile the D2 graph and query pipeline */\n private compilePipeline(): void {\n this.graph = new D2()\n this.inputs = Object.fromEntries(\n Object.keys(this.collectionByAlias).map((alias) => [\n alias,\n this.graph!.newInput<any>(),\n ]),\n )\n\n const compilation = compileQuery(\n this.query,\n this.inputs as Record<string, KeyedStream>,\n this.collections,\n // These mutable objects are captured by reference. The join compiler\n // reads them later when the graph runs, so they must be populated\n // (in start()) before the first graph run.\n this.subscriptions,\n this.lazySourcesCallbacks,\n this.lazySources,\n this.optimizableOrderByCollections,\n () => {}, // setWindowFn (no-op — effects don't paginate)\n )\n\n this.pipeline = compilation.pipeline\n this.sourceWhereClauses = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n\n // Attach the output operator that accumulates changes\n this.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messages.reduce(accumulateEffectChanges<TRow>, this.pendingChanges)\n }),\n )\n\n this.graph.finalize()\n }\n\n /** Subscribe to source collections and start processing */\n start(): void {\n // Use compiled aliases as the source of truth\n const compiledAliases = Object.entries(this.compiledAliasToCollectionId)\n if (compiledAliases.length === 0) {\n // Nothing to subscribe to\n return\n }\n\n // When not skipping initial, we always process events immediately\n if (!this.skipInitial) {\n this.initialLoadComplete = true\n }\n\n // We need to defer initial data processing until ALL subscriptions are\n // created, because join pipelines look up subscriptions by alias during\n // the graph run. If we run the graph while some aliases are still missing,\n // the join tap operator will throw.\n //\n // Strategy: subscribe to each collection but buffer incoming changes.\n // After all subscriptions are in place, flush the buffers and switch to\n // direct processing mode.\n\n const pendingBuffers = new Map<\n string,\n Array<Array<ChangeMessage<any, string | number>>>\n >()\n\n for (const [alias, collectionId] of compiledAliases) {\n const collection =\n this.collectionByAlias[alias] ?? this.collections[collectionId]!\n\n // Initialise per-alias duplicate tracking\n this.sentToD2KeysByAlias.set(alias, new Set())\n\n // Discover dependencies: if source collection is itself a live query\n // collection, its builder must run first during transaction flushes.\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder) {\n this.aliasDependencies[alias] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.aliasDependencies[alias] = []\n }\n\n // Get where clause for this alias (for predicate push-down)\n const whereClause = this.sourceWhereClauses?.get(alias)\n const whereExpression = whereClause\n ? normalizeExpressionPaths(whereClause, alias)\n : undefined\n\n // Initialise buffer for this alias\n const buffer: Array<Array<ChangeMessage<any, string | number>>> = []\n pendingBuffers.set(alias, buffer)\n\n // Lazy aliases (marked by the join compiler) should NOT load initial state\n // eagerly — the join tap operator will load exactly the rows it needs on demand.\n // For on-demand collections, eager loading would trigger a full server fetch\n // for data that should be lazily loaded based on join keys.\n const isLazy = this.lazySources.has(alias)\n\n // Check if this alias has orderBy optimization (cursor-based loading)\n const orderByInfo = this.getOrderByInfoForAlias(alias)\n\n // Build the change callback — for ordered aliases, split updates into\n // delete+insert and track the biggest sent value for cursor positioning.\n const changeCallback = orderByInfo\n ? (changes: Array<ChangeMessage<any, string | number>>) => {\n if (pendingBuffers.has(alias)) {\n pendingBuffers.get(alias)!.push(changes)\n } else {\n this.trackSentValues(alias, changes, orderByInfo.comparator)\n const split = [...splitUpdates(changes)]\n this.handleSourceChanges(alias, split)\n }\n }\n : (changes: Array<ChangeMessage<any, string | number>>) => {\n if (pendingBuffers.has(alias)) {\n pendingBuffers.get(alias)!.push(changes)\n } else {\n this.handleSourceChanges(alias, changes)\n }\n }\n\n // Determine subscription options based on ordered vs unordered path\n const subscriptionOptions = this.buildSubscriptionOptions(\n alias,\n isLazy,\n orderByInfo,\n whereExpression,\n )\n\n // Subscribe to source changes\n const subscription = collection.subscribeChanges(\n changeCallback,\n subscriptionOptions,\n )\n\n // Store subscription immediately so the join compiler can find it\n this.subscriptions[alias] = subscription\n\n // For ordered aliases with an index, trigger the initial limited snapshot.\n // This loads only the top N rows rather than the entire collection.\n if (orderByInfo) {\n this.requestInitialOrderedSnapshot(alias, orderByInfo, subscription)\n }\n\n this.unsubscribeCallbacks.add(() => {\n subscription.unsubscribe()\n delete this.subscriptions[alias]\n })\n\n // Listen for status changes on source collections\n const statusUnsubscribe = collection.on(`status:change`, (event) => {\n if (this.disposed) return\n\n const { status } = event\n\n // Source entered error state — effect can no longer function\n if (status === `error`) {\n this.onSourceError(\n new Error(\n `Source collection '${collectionId}' entered error state`,\n ),\n )\n return\n }\n\n // Source was manually cleaned up — effect can no longer function\n if (status === `cleaned-up`) {\n this.onSourceError(\n new Error(\n `Source collection '${collectionId}' was cleaned up while effect depends on it`,\n ),\n )\n return\n }\n\n // Track source readiness for skipInitial\n if (\n this.skipInitial &&\n !this.initialLoadComplete &&\n this.checkAllCollectionsReady()\n ) {\n this.initialLoadComplete = true\n }\n })\n this.unsubscribeCallbacks.add(statusUnsubscribe)\n }\n\n // Mark as subscribed so the graph can start running\n this.subscribedToAllCollections = true\n\n // All subscriptions are now in place. Flush buffered changes by sending\n // data to D2 inputs first (without running the graph), then run the graph\n // once. This prevents intermediate join states from producing duplicates.\n //\n // We remove each alias from pendingBuffers *before* draining, which\n // switches that alias to direct-processing mode. Any new callbacks that\n // fire during the drain (e.g. from requestLimitedSnapshot) will go\n // through handleSourceChanges directly instead of being lost.\n for (const [alias] of pendingBuffers) {\n const buffer = pendingBuffers.get(alias)!\n pendingBuffers.delete(alias)\n\n const orderByInfo = this.getOrderByInfoForAlias(alias)\n\n // Drain all buffered batches. Since we deleted the alias from\n // pendingBuffers above, any new changes arriving during drain go\n // through handleSourceChanges directly (not back into this buffer).\n for (const changes of buffer) {\n if (orderByInfo) {\n this.trackSentValues(alias, changes, orderByInfo.comparator)\n const split = [...splitUpdates(changes)]\n this.sendChangesToD2(alias, split)\n } else {\n this.sendChangesToD2(alias, changes)\n }\n }\n }\n\n // Initial graph run to process any synchronously-available data.\n // For skipInitial, this run's output is discarded (initialLoadComplete is still false).\n this.runGraph()\n\n // After the initial graph run, if all sources are ready,\n // mark initial load as complete so future events are processed.\n if (this.skipInitial && !this.initialLoadComplete) {\n if (this.checkAllCollectionsReady()) {\n this.initialLoadComplete = true\n }\n }\n }\n\n /** Handle incoming changes from a source collection */\n private handleSourceChanges(\n alias: string,\n changes: Array<ChangeMessage<any, string | number>>,\n ): void {\n this.sendChangesToD2(alias, changes)\n this.scheduleGraphRun(alias)\n }\n\n /**\n * Schedule a graph run via the transaction-scoped scheduler.\n *\n * When called within a transaction, the run is deferred until the\n * transaction flushes, coalescing multiple changes into a single graph\n * execution. Without a transaction, the graph runs immediately.\n *\n * Dependencies are discovered from source collections that are themselves\n * live query collections, ensuring parent queries run before effects.\n */\n private scheduleGraphRun(alias?: string): void {\n const contextId = getActiveTransaction()?.id\n\n // Collect dependencies for this schedule call\n const deps = new Set(this.builderDependencies)\n if (alias) {\n const aliasDeps = this.aliasDependencies[alias]\n if (aliasDeps) {\n for (const dep of aliasDeps) {\n deps.add(dep)\n }\n }\n }\n\n // Ensure dependent builders are scheduled in this context so that\n // dependency edges always point to a real job.\n if (contextId) {\n for (const dep of deps) {\n if (\n typeof dep === `object` &&\n dep !== null &&\n `scheduleGraphRun` in dep &&\n typeof (dep as any).scheduleGraphRun === `function`\n ) {\n ;(dep as any).scheduleGraphRun(undefined, { contextId })\n }\n }\n }\n\n transactionScopedScheduler.schedule({\n contextId,\n jobId: this,\n dependencies: deps,\n run: () => this.executeScheduledGraphRun(),\n })\n }\n\n /**\n * Called by the scheduler when dependencies are satisfied.\n * Checks that the effect is still active before running.\n */\n private executeScheduledGraphRun(): void {\n if (this.disposed || !this.subscribedToAllCollections) return\n this.runGraph()\n }\n\n /**\n * Send changes to the D2 input for the given alias.\n * Returns the number of multiset entries sent.\n */\n private sendChangesToD2(\n alias: string,\n changes: Array<ChangeMessage<any, string | number>>,\n ): number {\n if (this.disposed || !this.inputs || !this.graph) return 0\n\n const input = this.inputs[alias]\n if (!input) return 0\n\n const collection = this.collectionByAlias[alias]\n if (!collection) return 0\n\n // Filter duplicates per alias\n const sentKeys = this.sentToD2KeysByAlias.get(alias)!\n const filtered = filterDuplicateInserts(changes, sentKeys)\n\n return sendChangesToInput(input, filtered, collection.config.getKey)\n }\n\n /**\n * Run the D2 graph until quiescence, then emit accumulated events once.\n *\n * All output across the entire while-loop is accumulated into a single\n * batch so that users see one `onBatchProcessed` invocation per scheduler\n * run, even when ordered loading causes multiple graph steps.\n */\n private runGraph(): void {\n if (this.isGraphRunning || this.disposed || !this.graph) return\n\n this.isGraphRunning = true\n try {\n while (this.graph.pendingWork()) {\n this.graph.run()\n // A handler (via onBatchProcessed) or source error callback may have\n // called dispose() during graph.run(). Stop early to avoid operating\n // on stale state. TS narrows disposed to false from the guard above\n // but it can change during graph.run() via callbacks.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this.disposed) break\n // After each step, check if ordered queries need more data.\n // loadMoreIfNeeded may send data to D2 inputs (via requestLimitedSnapshot),\n // causing pendingWork() to return true for the next iteration.\n this.loadMoreIfNeeded()\n }\n // Emit all accumulated events once the graph reaches quiescence\n this.flushPendingChanges()\n } finally {\n this.isGraphRunning = false\n // If dispose() was called during this graph run, it deferred the heavy\n // cleanup (clearing graph/inputs/pipeline) to avoid nulling references\n // mid-loop. Complete that cleanup now.\n if (this.deferredCleanup) {\n this.deferredCleanup = false\n this.finalCleanup()\n }\n }\n }\n\n /** Classify accumulated changes into DeltaEvents and invoke the callback */\n private flushPendingChanges(): void {\n if (this.pendingChanges.size === 0) return\n\n // If skipInitial and initial load isn't complete yet, discard\n if (this.skipInitial && !this.initialLoadComplete) {\n this.pendingChanges = new Map()\n return\n }\n\n const events: Array<DeltaEvent<TRow, TKey>> = []\n\n for (const [key, changes] of this.pendingChanges) {\n const event = classifyDelta<TRow, TKey>(key as TKey, changes)\n if (event) {\n events.push(event)\n }\n }\n\n this.pendingChanges = new Map()\n\n if (events.length > 0) {\n this.onBatchProcessed(events)\n }\n }\n\n /** Check if all source collections are in the ready state */\n private checkAllCollectionsReady(): boolean {\n return Object.values(this.collections).every((collection) =>\n collection.isReady(),\n )\n }\n\n /**\n * Build subscription options for an alias based on whether it uses ordered\n * loading, is lazy, or should pass orderBy/limit hints.\n */\n private buildSubscriptionOptions(\n alias: string,\n isLazy: boolean,\n orderByInfo: OrderByOptimizationInfo | undefined,\n whereExpression: BasicExpression<boolean> | undefined,\n ): {\n includeInitialState?: boolean\n whereExpression?: BasicExpression<boolean>\n orderBy?: any\n limit?: number\n } {\n // Ordered aliases explicitly disable initial state — data is loaded\n // via requestLimitedSnapshot/requestSnapshot after subscription setup.\n if (orderByInfo) {\n return { includeInitialState: false, whereExpression }\n }\n\n const includeInitialState = !isLazy\n\n // For unordered subscriptions, pass orderBy/limit hints so on-demand\n // collections can optimise server-side fetching.\n const hints = computeSubscriptionOrderByHints(this.query, alias)\n\n return {\n includeInitialState,\n whereExpression,\n ...(hints.orderBy ? { orderBy: hints.orderBy } : {}),\n ...(hints.limit !== undefined ? { limit: hints.limit } : {}),\n }\n }\n\n /**\n * Request the initial ordered snapshot for an alias.\n * Uses requestLimitedSnapshot (index-based cursor) or requestSnapshot\n * (full load with limit) depending on whether an index is available.\n */\n private requestInitialOrderedSnapshot(\n alias: string,\n orderByInfo: OrderByOptimizationInfo,\n subscription: CollectionSubscription,\n ): void {\n const { orderBy, offset, limit, index } = orderByInfo\n const normalizedOrderBy = normalizeOrderByPaths(orderBy, alias)\n\n if (index) {\n subscription.setOrderByIndex(index)\n subscription.requestLimitedSnapshot({\n limit: offset + limit,\n orderBy: normalizedOrderBy,\n trackLoadSubsetPromise: false,\n })\n } else {\n subscription.requestSnapshot({\n orderBy: normalizedOrderBy,\n limit: offset + limit,\n trackLoadSubsetPromise: false,\n })\n }\n }\n\n /**\n * Get orderBy optimization info for a given alias.\n * Returns undefined if no optimization exists for this alias.\n */\n private getOrderByInfoForAlias(\n alias: string,\n ): OrderByOptimizationInfo | undefined {\n // optimizableOrderByCollections is keyed by collection ID\n const collectionId = this.compiledAliasToCollectionId[alias]\n if (!collectionId) return undefined\n\n const info = this.optimizableOrderByCollections[collectionId]\n if (info && info.alias === alias) {\n return info\n }\n return undefined\n }\n\n /**\n * After each graph run step, check if any ordered query's topK operator\n * needs more data. If so, load more rows via requestLimitedSnapshot.\n */\n private loadMoreIfNeeded(): void {\n for (const [, orderByInfo] of Object.entries(\n this.optimizableOrderByCollections,\n )) {\n if (!orderByInfo.dataNeeded) continue\n\n if (this.pendingOrderedLoadPromise) {\n // Wait for in-flight loads to complete before requesting more\n continue\n }\n\n const n = orderByInfo.dataNeeded()\n if (n > 0) {\n this.loadNextItems(orderByInfo, n)\n }\n }\n }\n\n /**\n * Load n more items from the source collection, starting from the cursor\n * position (the biggest value sent so far).\n */\n private loadNextItems(orderByInfo: OrderByOptimizationInfo, n: number): void {\n const { alias } = orderByInfo\n const subscription = this.subscriptions[alias]\n if (!subscription) return\n\n const cursor = computeOrderedLoadCursor(\n orderByInfo,\n this.biggestSentValue.get(alias),\n this.lastLoadRequestKey.get(alias),\n alias,\n n,\n )\n if (!cursor) return // Duplicate request — skip\n\n this.lastLoadRequestKey.set(alias, cursor.loadRequestKey)\n\n subscription.requestLimitedSnapshot({\n orderBy: cursor.normalizedOrderBy,\n limit: n,\n minValues: cursor.minValues,\n trackLoadSubsetPromise: false,\n onLoadSubsetResult: (loadResult: Promise<void> | true) => {\n // Track in-flight load to prevent redundant concurrent requests\n if (loadResult instanceof Promise) {\n this.pendingOrderedLoadPromise = loadResult\n loadResult.finally(() => {\n if (this.pendingOrderedLoadPromise === loadResult) {\n this.pendingOrderedLoadPromise = undefined\n }\n })\n }\n },\n })\n }\n\n /**\n * Track the biggest value sent for a given ordered alias.\n * Used for cursor-based pagination in loadNextItems.\n */\n private trackSentValues(\n alias: string,\n changes: Array<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number,\n ): void {\n const sentKeys = this.sentToD2KeysByAlias.get(alias) ?? new Set()\n const result = trackBiggestSentValue(\n changes,\n this.biggestSentValue.get(alias),\n sentKeys,\n comparator,\n )\n this.biggestSentValue.set(alias, result.biggest)\n if (result.shouldResetLoadKey) {\n this.lastLoadRequestKey.delete(alias)\n }\n }\n\n /** Tear down subscriptions and clear state */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.subscribedToAllCollections = false\n\n // Immediately unsubscribe from sources and clear cheap state\n this.unsubscribeCallbacks.forEach((fn) => fn())\n this.unsubscribeCallbacks.clear()\n this.sentToD2KeysByAlias.clear()\n this.pendingChanges.clear()\n this.lazySources.clear()\n this.builderDependencies.clear()\n this.biggestSentValue.clear()\n this.lastLoadRequestKey.clear()\n this.pendingOrderedLoadPromise = undefined\n\n // Clear mutable objects\n for (const key of Object.keys(this.lazySourcesCallbacks)) {\n delete this.lazySourcesCallbacks[key]\n }\n for (const key of Object.keys(this.aliasDependencies)) {\n delete this.aliasDependencies[key]\n }\n for (const key of Object.keys(this.optimizableOrderByCollections)) {\n delete this.optimizableOrderByCollections[key]\n }\n\n // If the graph is currently running, defer clearing graph/inputs/pipeline\n // until runGraph() completes — otherwise we'd null references mid-loop.\n if (this.isGraphRunning) {\n this.deferredCleanup = true\n } else {\n this.finalCleanup()\n }\n }\n\n /** Clear graph references — called after graph run completes or immediately from dispose */\n private finalCleanup(): void {\n this.graph = undefined\n this.inputs = undefined\n this.pipeline = undefined\n this.sourceWhereClauses = undefined\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getHandlerForEvent<TRow extends object, TKey extends string | number>(\n event: DeltaEvent<TRow, TKey>,\n config: EffectConfig<TRow, TKey>,\n): EffectEventHandler<TRow, TKey> | undefined {\n switch (event.type) {\n case `enter`:\n return config.onEnter\n case `exit`:\n return config.onExit\n case `update`:\n return config.onUpdate\n }\n}\n\n/**\n * Accumulate D2 output multiplicities into per-key effect changes.\n * Tracks both insert values (new) and delete values (old) separately\n * so that update and exit events can include previousValue.\n */\nfunction accumulateEffectChanges<T>(\n acc: Map<unknown, EffectChanges<T>>,\n [[key, tupleData], multiplicity]: [\n [unknown, [any, string | undefined]],\n number,\n ],\n): Map<unknown, EffectChanges<T>> {\n const [value] = tupleData as [T, string | undefined]\n\n const changes: EffectChanges<T> = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n }\n\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n // Keep only the first delete value — this is the pre-batch state\n changes.deleteValue ??= value\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n // Always overwrite with the latest insert — this is the post-batch state\n changes.insertValue = value\n }\n\n acc.set(key, changes)\n return acc\n}\n\n/** Classify accumulated per-key changes into a DeltaEvent */\nfunction classifyDelta<TRow extends object, TKey extends string | number>(\n key: TKey,\n changes: EffectChanges<TRow>,\n): DeltaEvent<TRow, TKey> | undefined {\n const { inserts, deletes, insertValue, deleteValue } = changes\n\n if (inserts > 0 && deletes === 0) {\n // Row entered the query result\n return { type: `enter`, key, value: insertValue! }\n }\n\n if (deletes > 0 && inserts === 0) {\n // Row exited the query result — value is the exiting value,\n // previousValue is omitted (it would be identical to value)\n return { type: `exit`, key, value: deleteValue! }\n }\n\n if (inserts > 0 && deletes > 0) {\n // Row updated within the query result\n return {\n type: `update`,\n key,\n value: insertValue!,\n previousValue: deleteValue!,\n }\n }\n\n // inserts === 0 && deletes === 0 — no net change (should not happen)\n return undefined\n}\n\n/** Track a promise in the in-flight set, automatically removing on settlement */\nfunction trackPromise(\n promise: Promise<void>,\n inFlightHandlers: Set<Promise<void>>,\n): void {\n inFlightHandlers.add(promise)\n promise.finally(() => {\n inFlightHandlers.delete(promise)\n })\n}\n\n/** Report an error to the onError callback or console */\nfunction reportError<TRow extends object, TKey extends string | number>(\n error: unknown,\n event: DeltaEvent<TRow, TKey>,\n onError?: (error: Error, event: DeltaEvent<TRow, TKey>) => void,\n): void {\n const normalised = error instanceof Error ? error : new Error(String(error))\n if (onError) {\n try {\n onError(normalised, event)\n } catch (onErrorError) {\n // Don't let onError errors propagate\n console.error(`[Effect] Error in onError handler:`, onErrorError)\n console.error(`[Effect] Original error:`, normalised)\n }\n } else {\n console.error(`[Effect] Unhandled error in handler:`, normalised)\n }\n}\n"],"names":["buildQueryFromConfig","extractCollectionsFromQuery","extractCollectionAliases","D2","compileQuery","output","getCollectionBuilder","normalizeExpressionPaths","splitUpdates","getActiveTransaction","transactionScopedScheduler","filterDuplicateInserts","sendChangesToInput","computeSubscriptionOrderByHints","index","normalizeOrderByPaths","computeOrderedLoadCursor","trackBiggestSentValue"],"mappings":"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