@tanstack/db
Version:
A reactive client store for building super fast apps on sync
221 lines (220 loc) • 9.51 kB
text/typescript
import { StandardSchemaV1 } from '@standard-schema/spec';
import { CollectionConfig, CollectionErrorInfo, CollectionStatus, HealthProblem } from '../types.cjs';
import { CollectionEventsManager } from './events.cjs';
import { CollectionIndexesManager } from './indexes.cjs';
import { CollectionChangesManager } from './changes.cjs';
import { CollectionSyncManager } from './sync.cjs';
import { CollectionStateManager } from './state.cjs';
/**
* Floor applied to the GC delay of a collection that started syncing before
* anything subscribed. Adapters build their live query while rendering and
* subscribe when that render commits. This grace period reduces cleanup
* during that gap; a later subscriber can still restart sync. Adapters pass
* a near-zero `gcTime` to make teardown on unmount immediate. Does not apply
* to the timer armed when the last subscriber leaves, which still honours
* `gcTime` exactly.
*/
export declare const UNSUBSCRIBED_GC_FLOOR_MS = 50;
/** A Collection's gcTime when its config names none (5 minutes). */
export declare const DEFAULT_GC_TIME_MS = 300000;
export declare class CollectionLifecycleManager<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> {
private config;
private id;
private indexes;
private events;
private changes;
private sync;
private state;
status: CollectionStatus;
hasBeenReady: boolean;
hasReceivedFirstCommit: boolean;
private readyFailureSink;
onFirstReadyCallbacks: Array<() => void>;
private idleCallbackId;
private failureReason;
private failureReasonFromReload;
private stoppedError;
private statusError;
private requestErrors;
private requestErrorsFromReload;
private adapterStopped;
private reloadRun;
private recovering;
private gcGraceUntil;
private cleanupConfig;
private statusRevision;
private cleaningUp;
private cleanupPromise;
private readonly cleanupStartCallbacks;
/**
* Creates a new CollectionLifecycleManager instance
*/
constructor(config: CollectionConfig<TOutput, TKey, TSchema, any>, id: string, cleanupConfig?: () => void);
setDeps(deps: {
indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>;
events: CollectionEventsManager;
changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>;
sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>;
state: CollectionStateManager<TOutput, TKey, TSchema, TInput>;
}): void;
/**
* Validates state transitions to prevent invalid status changes
*/
validateStatusTransition(from: CollectionStatus, to: CollectionStatus): void;
/**
* Safely update the collection status with validation
* @private
*/
setStatus(newStatus: CollectionStatus, allowReady?: boolean, beforeEmit?: () => void): void;
/**
* Validates that the collection is in a usable state for data operations
* @private
*/
validateCollectionUsable(operation: string): void;
/**
* Mark the collection as ready for use
* This is called by sync implementations to explicitly signal that the collection is ready,
* providing a more intuitive alternative to using commits for readiness signaling
* @private - Should only be called by sync implementations
*
* `beforeEffects` runs after the status reads `ready` and before status
* listeners and ready callbacks, so their writes follow it.
*/
markReady(beforeEffects?: () => void): void;
/**
* @internal Hold ready-effect failures while a sync function runs, whether
* it calls markReady or commits a truncate. The returned function stops
* holding them and returns the first one.
*/
deferReadyFailures(): () => {
error: unknown;
} | undefined;
private applyReadyTransition;
/**
* The sync adapter stopped. Before the first ready this fails the sync
* run's first load; after it, the rows stay and the error shows until a
* reload. A second fail() in the same sync run changes nothing: the adapter
* already stopped, and the first error stays.
*/
fail(error: unknown): void;
/** Whether the current sync run's adapter called `fail()`. */
get stoppedThisRun(): boolean;
/**
* Whether the current sync run is in its first load: `loading`, or a reload
* of a failed first load that is not ready yet. A failure now fails that
* first load.
*/
get inFirstLoad(): boolean;
/** Whether a reload's new sync run is in flight. */
get reloading(): boolean;
/**
* A reload starts a new sync run while everything visible stays: the rows,
* the status, and the current problem. The node shows `reloading`.
*/
beginReload(): Promise<void>;
/**
* The reload's new run replaced the rows with a snapshot or a truncate, or
* marked ready: the retired run's problems clear, the new run's stay, and a
* failed first load becomes ready.
*/
completeReload(beforeEffects?: () => void): void;
/** A reload retired the sync run; subscriptions detach its demand. */
retireSubscriptionDemand(): void;
/** The reload ended without a commit, for example by cleanup. */
abandonReload(error: unknown): void;
private endReload;
/**
* Local storage or an internal invariant broke. The Collection can no
* longer serve reads or accept writes, from any active status. Nothing
* recovers a crash in place: only cleanup, which discards all state, leaves
* it.
*/
crash(error?: unknown): void;
/** The Collection's own problems, as health tree detail. */
get problems(): {
failureReason: HealthProblem | null;
error: HealthProblem | null;
};
/** The cause of status `error` or `crashed`, as health tree detail. */
get statusProblem(): HealthProblem | null;
/** The sync adapter still tries after a problem. Status and rows do not change. */
reportError(error: unknown): void;
/**
* The source synced successfully again. With no error it clears the
* failure reason; with one it removes the failure whose cause is that
* value, and the failure reason clears once none is left. With nothing to
* remove it does nothing. `undefined` is no error: it clears every
* failure, also one that `reportError(undefined)` listed.
*/
reportHealthy(error?: unknown): void;
/**
* @internal A live query's request failed after initial-query readiness,
* with each of its failures in failure order.
*/
reportRequestError(key: string, errors: Array<unknown>): void;
/** @internal The failed request settled or was released. */
clearRequestError(key: string): void;
/** fail() ends the sync adapter's reporting for its sync run. */
private assertAdapterActive;
private assertReady;
/** Return the cause of status `error` or `crashed` in this sync run, if any. */
getSyncError(): unknown;
/** Cleanup discards the current projection until a new sync run starts. */
hasStartedCleanup(): boolean;
assertCanStartSync(): void;
isCleaningUp(): boolean;
/**
* Observe the synchronous start of cleanup without treating it as terminal
* resource settlement. Internal dependents use this to retire work before
* an asynchronous adapter cleanup publishes `cleaned-up`.
*/
onCleanupStart(callback: () => void): () => void;
/**
* Start the garbage collection timer for a collection with no subscribers
* Called when sync starts outside a subscription
*/
startGCTimerIfUnsubscribed(): void;
private canGarbageCollect;
/**
* Renew inactive retention. Settlement keeps the remaining startup/preload
* handoff; a real last unsubscribe clears it and uses gcTime exactly.
*/
startGCTimer(preserveHandoff?: boolean): void;
/**
* Cancel the garbage collection timer
* Called when the collection becomes active again
*/
cancelGCTimer(): void;
/**
* Schedule cleanup to run during browser idle time
* This prevents blocking the UI thread during cleanup operations
*/
private scheduleIdleCleanup;
private beginCleanup;
/**
* Register a callback to be executed when the collection first becomes ready
* Useful for preloading collections
* @param callback Function to call when the collection first becomes ready
*/
onFirstReady(callback: () => void): () => void;
cleanup(): Promise<void>;
}
/** @internal Requests that failed after initial-query readiness, by issuer. */
export declare class RequestErrors {
private readonly failed;
private readonly open;
problem: HealthProblem | null;
/** Returns whether the problem changed. */
fail(key: string, errors: Array<unknown>): boolean;
/** Returns whether the problem changed. */
settle(key: string): boolean;
private release;
private show;
}
/**
* @internal A health problem: its newest failure's fields, with every
* failure of its frame listed, oldest first.
*/
export declare function problemOf(errors: ReadonlyArray<CollectionErrorInfo>): HealthProblem;
/** @internal Health tree detail for a problem value. */
export declare function describeProblem(error: unknown): CollectionErrorInfo;