@tanstack/db
Version:
A reactive client store for building super fast apps on sync
218 lines (217 loc) • 10 kB
text/typescript
import { LIVE_QUERY_INTERNAL, LiveQueryInternalUtils } from "./internal.cjs";
import { WindowOptions } from "../compiler/index.cjs";
import { SchedulerContextId } from "../../scheduler.cjs";
import { CollectionSubscription } from "../../collection/subscription.cjs";
import { OrderByOptimizationInfo } from "../compiler/order-by.cjs";
import { Collection } from "../../collection/index.cjs";
import { HealthSource } from "../../collection/health-tree.cjs";
import { CollectionConfigSingleRowOption, CollectionDependent, StringCollationConfig, SyncConfig, UtilsRecord } from "../../types.cjs";
import { Context, GetResult } from "../builder/types.cjs";
import { BasicExpression, QueryIR } from "../ir.cjs";
import { LazyCollectionCallbacks } from "../compiler/joins.cjs";
import { FullSyncState, LiveQueryCollectionConfig } from "./types.cjs";
export type LiveQueryCollectionUtils = UtilsRecord & {
/** Most recent subset-load failure observed by this live query. */
readonly lastSubsetError: unknown | undefined;
/**
* Sets the offset and limit of the query's window, its ordered view. A
* windowed query has an `orderBy`. A `findOne` keeps its compiled limit (1,
* or 0 for `.limit(0).findOne()`); an ordered `findOne` has a movable window,
* and an unordered one is a fixed one-row result that accepts only its
* compiled limit at offset 0.
*
* @throws `SetWindowRequiresOrderByError` if the query is neither windowed
* nor a `findOne`, or an unordered `findOne` would move its offset.
* @throws `SetWindowFindOneLimitError` if a `findOne` would change its limit.
* @returns `true` if no subset loading was triggered, or `Promise<void>` that resolves when the subset has been loaded
*/
setWindow: (options: WindowOptions) => true | Promise<void>;
/**
* Gets the current window (offset and limit) for an ordered query.
*
* @returns The current window settings, or `undefined` if the query is not windowed
*/
getWindow: () => {
offset: number;
limit: number;
} | undefined;
[LIVE_QUERY_INTERNAL]: LiveQueryInternalUtils;
};
export declare class CollectionConfigBuilder<TContext extends Context, TResult extends object = GetResult<TContext>> {
private readonly config;
private readonly id;
readonly query: QueryIR;
private readonly collections;
private readonly collectionSources;
private readonly resultKeys;
private readonly orderByIndices;
private readonly compare?;
private readonly compareOptions;
readonly queryCompareOptions: StringCollationConfig;
private isGraphRunning;
private graphInputRevision;
currentSyncConfig: Parameters<SyncConfig<TResult>[`sync`]>[0] | undefined;
currentSyncState: FullSyncState | undefined;
private fatalQueryError;
private lastSubsetError;
private reloadSnapshotPending;
liveQueryCollection?: Collection<TResult, any, any>;
private dependentEntry;
/**
* Reload each source with an error anywhere in its tree, then this query if
* its own node shows an error. A nested live query passes the reload down the
* same way. A source's reload keeps this query's status until it commits.
* A query that is not running starts once its sources reloaded, as any
* Collection's reload starts one; `query` is the live-query Collection,
* which a query that never started has not given this builder yet.
*/
reload(query?: Collection<any, any, any> | undefined): Promise<void>;
/** This live query as its sources' `subscribers` list it. */
dependent(): CollectionDependent | undefined;
/**
* Whether this live-query Collection had a subscriber or a preload in its
* current sync run. Until then it reads its source Collections without
* starting provider work.
*/
hasSubscriberOrPreload(): boolean;
private windowFn;
private readonly initialWindow;
private currentWindow;
private settledWindow;
private activeWindowOperation;
private maybeRunGraphFn;
private loadMoreFn;
private readonly builderDependencies;
private graphCache;
private inputsCache;
private pipelineCache;
sourceWhereClausesCache: Map<string, BasicExpression<boolean>> | undefined;
private includesRuntimeCache;
private bucketFacadesCache;
private readonly includeSourceIds;
readonly subscriptions: Record<string, CollectionSubscription>;
lazySourcesCallbacks: Record<string, LazyCollectionCallbacks>;
readonly lazySources: Set<string>;
private readonly activeDemands;
private demandGeneration;
private readonly pendingOrderedLoads;
private orderedLoadFailed;
private windowFailed;
private syncRunGeneration;
private windowOperationGeneration;
private windowRequestGeneration;
private latestWindowRequest;
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>;
constructor(config: LiveQueryCollectionConfig<TContext, TResult>);
/** Direct lexical source Collections, including joins, unions, and includes. */
getSourceCollections(): ReadonlyArray<Collection<any, any, any>>;
/**
* Recursively checks if a query or any of its subqueries contains joins
*/
private hasJoins;
getConfig(): CollectionConfigSingleRowOption<TResult> & {
utils: LiveQueryCollectionUtils;
};
setWindow(options: WindowOptions): true | Promise<void>;
getWindow(): {
offset: number;
limit: number;
} | undefined;
isLazySource(sourceId: string): boolean;
beginDemand(planId: string, pending?: boolean): number;
/** The source aliases of this query, in query order, for its health tree. */
healthSources(): ReadonlyArray<HealthSource>;
settleDemand(planId: string, generation: number): void;
hasPendingJoinedWork(joinedSourceId: string): boolean;
failDemand(subscription: CollectionSubscription, collection: Collection, planId: string, generation: number, error: unknown): void;
recordSubsetError(error: unknown, fatalBeforeReady?: boolean): Error;
trackSubsetLoadPromise(promise: Promise<unknown>): void;
trackSubsetLoadOperationPromise(promise: Promise<unknown>): void;
hasActiveWindowOperation(): boolean;
getActiveWindowOperationGeneration(): number | undefined;
scheduleGraphRunIfSyncRunCurrent(syncRunGeneration: number): void;
trackOrderedLoadPromise(promise: Promise<unknown>, holdPublication?: boolean): void;
retireDemand(planId: string): void;
/** A root load of an eager source failed. */
failRootLoad(sourceId: string, error: Error): void;
/** A later root load of the source succeeded. */
settleRootLoad(sourceId: string): void;
/** A source's truncate replay loaded the query's demand again. */
settleReplayedRequests(sourceId: string): void;
/**
* A source's truncate replay could not load the query's data again: each
* of its failures, in failure order. Before the first ready the earliest
* fails the query.
*/
failReplay(sourceId: string, errors: Array<Error>): void;
reportFailedRequest(key: string, errors: Array<Error>): boolean;
/** A request settled later, or the query no longer needs its data. */
clearFailedRequest(key: string): void;
/** This query's sync run and its sources' sync runs. */
private syncRuns;
/** Replays of other subscriptions to a source's collection that still load. */
siblingReplays(collection: Collection, sourceId: string): Promise<unknown> | undefined;
hasPendingSourceRecovery(): boolean;
private pendingSourceRecovery;
private hasFailedSourceRecovery;
getSyncRunGeneration(): number;
advanceGraphInputRevision(): void;
getGraphInputRevision(): number;
maybeRunGraph(): void;
/**
* Schedules a graph run with the transaction-scoped scheduler.
* Ensures each builder runs at most once per transaction, with automatic dependency tracking
* to run parent queries before child queries. Outside a transaction, runs immediately.
*
* Multiple calls during a transaction are coalesced into a single execution.
* Dependencies are discovered from subscribed live queries.
*
* Uses the current sync run's config and syncState from instance properties.
*
* @param options - Optional scheduling configuration
* @param options.contextId - Transaction ID to group work; defaults to active transaction
*/
scheduleGraphRun(options?: {
contextId?: SchedulerContextId;
}): void;
private getSyncConfig;
private syncFn;
private clearSyncRunState;
/**
* Compiles the query pipeline with all declared aliases.
*/
private compileBasePipeline;
private maybeCompileBasePipeline;
private extendPipelineWithChangeProcessing;
private applyChanges;
/**
* Handle status changes from source collections
*/
private handleSourceStatusChange;
private handleSourceCleanupStart;
/**
* Update the live query status based on source collection statuses
*/
private updateLiveQueryStatus;
/** Whether the current run's graph result is complete. */
private isResultComplete;
/**
* The query's first load failed: status `error`. A sync problem or a
* request error after the first ready never reaches here.
*/
private failQuery;
/**
* The query can no longer serve reads: a crashed source, a graph failure,
* or a contract violation such as a source cleaned up under this query.
*/
private crashQuery;
/** Returns false when the query already ended. */
private enterErrorState;
private allRequiredSourcesReady;
/**
* Creates one subscription per lexical collection source.
* Each source gets independent filters, even when aliases or collections repeat.
* Example: `{ employee: col, manager: col }` creates two separate subscriptions.
*/
private subscribeToAllCollections;
}