UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

274 lines (273 loc) • 12 kB
import { SortedMap } from '../SortedMap.js'; import { VirtualOrigin, VirtualRowProps, WithVirtualProps } from '../virtual-props.js'; import { Transaction } from '../transactions.js'; import { StandardSchemaV1 } from '@standard-schema/spec'; import { ChangeMessage, CollectionConfig, OptimisticChangeMessage, PendingMutation } from '../types.js'; import { CollectionImpl } from './index.js'; import { CollectionLifecycleManager } from './lifecycle.js'; import { CollectionChangesManager } from './changes.js'; import { CollectionIndexesManager } from './indexes.js'; import { CollectionEventsManager } from './events.js'; import { Deferred } from '../deferred.js'; import { DuplicateKeySyncError } from '../errors.js'; type PendingSyncOperation<T extends object, TKey extends string | number> = OptimisticChangeMessage<T, TKey> & { /** Preserve adapter intent when queue changes require reclassification. */ originalSyncType?: `insert`; /** A partial update admitted against a row must not become an upsert. */ admittedAgainstExistingRow?: boolean; }; type PendingInsertDisposition = `insert` | `update` | `duplicate`; interface PendingSyncedTransaction<T extends object = Record<string, unknown>, TKey extends string | number = string | number> { committed: boolean; applicationStarted: boolean; layoutChanged: boolean; operations: Array<PendingSyncOperation<T, TKey>>; truncate?: boolean; rowMetadataWrites: Map<TKey, PendingMetadataWrite>; explicitRowMetadataWriteKeys?: Set<TKey>; collectionMetadataWrites: Map<string, PendingMetadataWrite>; /** Resolves after application and rejects if canceled before application. */ applied: Deferred<void>; optimisticSnapshot?: { upserts: Map<TKey, T>; deletes: Set<TKey>; ownership: Map<TKey, Array<{ type: `upsert`; value: T; transaction?: Transaction<any>; } | { type: `delete`; transaction?: Transaction<any>; }>>; }; preserveHydrationSeedKeys?: boolean; /** Present on adapter transactions that can be revalidated after cancel. */ duplicateKeyError?: (key: TKey) => DuplicateKeySyncError; /** Retained until commit so an invalidated active transaction stays addressable. */ invalidationError?: Error; /** * When true, this transaction should be processed immediately even if there * are persisting user transactions. Used by manual write operations (writeInsert, * writeUpdate, writeDelete, writeUpsert) which need synchronous updates to syncedData. */ immediate?: boolean; } type PendingMetadataWrite = { type: `set`; value: unknown; } | { type: `delete`; }; type InsertDependency = { mutation: object; transaction: Transaction<any>; }; type OptimisticUpsert<T extends object> = Pick<PendingMutation<T>, `key` | `modified`> & { insertDependency?: InsertDependency; }; export declare class CollectionStateManager<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> { config: CollectionConfig<TOutput, TKey, TSchema, any>; collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>; lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>; changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>; indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>; private _events; transactions: SortedMap<string, Transaction<any>>; pendingSyncedTransactions: Array<PendingSyncedTransaction<TOutput, TKey>>; private pendingSyncedProjection; syncedData: SortedMap<TKey, TOutput>; syncedMetadata: Map<TKey, unknown>; syncedCollectionMetadata: Map<string, unknown>; hydrationSeedKeys: Set<TKey>; hydratedKeys: Set<TKey>; private appliedAdapterDeletedKeys?; private hasAppliedAdapterTruncate; optimisticUpserts: Map<TKey, TOutput>; optimisticDeletes: Set<TKey>; pendingOptimisticUpserts: Map<TKey, OptimisticUpsert<TOutput>>; pendingOptimisticDeletes: Set<TKey>; pendingOptimisticDirectUpserts: Set<TKey>; pendingOptimisticDirectDeletes: Set<TKey>; private acknowledgedInserts; /** * Tracks the origin of confirmed changes for each row. * 'local' = change originated from this client * 'remote' = change was received via sync * * This is used for the $origin virtual property. * Note: This only tracks *confirmed* changes, not optimistic ones. * Optimistic changes are always considered 'local' for $origin. */ rowOrigins: Map<TKey, VirtualOrigin>; /** * Tracks keys that have pending local changes. * Used to determine whether sync-confirmed data should have 'local' or 'remote' origin. * When sync confirms data for a key with pending local changes, it keeps 'local' origin. */ pendingLocalChanges: Set<TKey>; pendingLocalOrigins: Set<TKey>; private virtualPropsCache; size: number; preSyncVisibleState: Map<TKey, TOutput | undefined>; preSyncVirtualState: Map<TKey, VirtualRowProps<TKey>>; recentlySyncedKeys: Set<TKey>; hasReceivedFirstCommit: boolean; isCommittingSyncTransactions: boolean; private isDrainingSyncTransactions; private syncRunGeneration; isLocalOnly: boolean; /** * Creates a new CollectionState manager */ constructor(config: CollectionConfig<TOutput, TKey, TSchema, any>); setDeps(deps: { collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>; lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>; changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>; indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>; events: CollectionEventsManager; }): void; /** * Checks whether this row currently has no pending local optimistic writes. * * This is local mutation status, not backend confirmation: `true` means the * row is not currently affected by an optimistic transaction in this * collection's visible state. * * Used to compute the $synced virtual property. */ isRowSynced(key: TKey): boolean; /** * Gets the origin of the last confirmed change to a row. * Returns 'local' if the row has optimistic mutations (optimistic changes are local). * Used to compute the $origin virtual property. */ getRowOrigin(key: TKey): VirtualOrigin; private createVirtualPropsSnapshot; private getVirtualPropsSnapshotForState; private snapshotRowOriginsForKeys; private enrichWithVirtualPropsSnapshot; private clearOriginTrackingState; /** * Enriches a row with virtual properties using the "add-if-missing" pattern. * If the row already has virtual properties (from an upstream collection), * they are preserved. Otherwise, new values are computed. */ enrichWithVirtualProps(row: TOutput, key: TKey): WithVirtualProps<TOutput, TKey>; /** * Creates a change message with virtual properties. * Uses the "add-if-missing" pattern so that pass-through from upstream * collections works correctly. */ enrichChangeMessage(change: ChangeMessage<TOutput, TKey>): ChangeMessage<WithVirtualProps<TOutput, TKey>, TKey>; /** * Get the current value for a key enriched with virtual properties. */ getWithVirtualProps(key: TKey): WithVirtualProps<TOutput, TKey> | undefined; /** * Get the current value for a key (virtual derived state) */ get(key: TKey): TOutput | undefined; /** * Check if a key exists in the collection (virtual derived state) */ has(key: TKey): boolean; /** * Get all keys (virtual derived state) */ keys(): IterableIterator<TKey>; /** * Get all values (virtual derived state) */ values(): IterableIterator<TOutput>; /** * Get all entries (virtual derived state) */ entries(): IterableIterator<[TKey, TOutput]>; /** * Get all entries (virtual derived state) */ [Symbol.iterator](): IterableIterator<[TKey, TOutput]>; /** * Execute a callback for each entry in the collection */ forEach(callbackfn: (value: TOutput, key: TKey, index: number) => void): void; /** * Create a new array with the results of calling a function for each entry in the collection */ map<U>(callbackfn: (value: TOutput, key: TKey, index: number) => U): Array<U>; /** * Check if the given collection is this collection * @param collection The collection to check * @returns True if the given collection is this collection, false otherwise */ private isThisCollection; /** * Recompute optimistic state from active transactions */ recomputeOptimisticState(triggeredByUserAction?: boolean): void; /** * Calculate the current size based on synced data and optimistic changes */ private calculateSize; /** * Collect events for optimistic changes */ private collectOptimisticChanges; private resolveOptimisticUpsert; /** * Capture the optimistic layers that exist when a truncate is authored. * Keeping their exact owners lets application discard a later rollback * without reviving a failed same-key winner or retaining post-capture work. */ captureTruncateOptimisticSnapshot(): NonNullable<PendingSyncedTransaction<TOutput, TKey>[`optimisticSnapshot`]>; /** Build once per output flush; queued membership excludes optimistic edits. */ createSyncedKeyLookup(): (key: TKey) => boolean; enableHydrationAuthorityTracking(): void; /** A late hydration seed cannot supersede committed adapter work. */ createAdapterAuthorityLookup(): (key: TKey) => boolean; private getProjectedSyncedKeyState; private classifyProjectedInsert; /** Classify an adapter insert against retained and queued source state. */ classifyPendingSyncedInsert(key: TKey, value: TOutput): PendingInsertDisposition; private applyPendingSyncOperation; /** Extend the queued projection after admitting one sync operation. */ stagePendingSyncOperation(operation: PendingSyncOperation<TOutput, TKey>): void; /** * Get the previous value for a key given previous optimistic state */ private getPreviousValue; private rebuildAutomaticRowMetadataWrites; private rebuildPendingSyncedProjection; private rejectInvalidCommittedTransactions; /** Rebuild after truncate or application changes queue history. */ refreshPendingSyncedProjection(): void; /** * Attempts to commit pending synced transactions if there are no active transactions * This method processes operations from pending transactions and applies them to the synced data */ commitPendingTransactions: () => void; private commitNextPendingTransactionBatch; /** Abandons a queued transaction and invalidated dependents before visibility. */ cancelPendingSyncedTransaction(transaction: PendingSyncedTransaction<TOutput, TKey>, reason?: Error): void; /** * Schedule cleanup of a transaction when it completes */ scheduleTransactionCleanup(transaction: Transaction<any>): void; /** * Capture visible state for keys that will be affected by pending sync operations * This must be called BEFORE onTransactionStateChange clears optimistic state */ capturePreSyncVisibleState(): void; /** * Trigger a recomputation when transactions change * This method should be called by the Transaction class when state changes */ onTransactionStateChange(): void; /** * Clean up the collection by stopping sync and clearing data * This can be called manually or automatically by garbage collection */ cleanup(): void; } export {};