UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

221 lines (220 loc) • 9.51 kB
import { StandardSchemaV1 } from '@standard-schema/spec'; import { CollectionConfig, CollectionErrorInfo, CollectionStatus, HealthProblem } from '../types.js'; import { CollectionEventsManager } from './events.js'; import { CollectionIndexesManager } from './indexes.js'; import { CollectionChangesManager } from './changes.js'; import { CollectionSyncManager } from './sync.js'; import { CollectionStateManager } from './state.js'; /** * 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;