UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

929 lines (928 loc) 34.5 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const utils = require("../utils.cjs"); const SortedMap = require("../SortedMap.cjs"); const virtualProps = require("../virtual-props.cjs"); const transactionMetadata = require("./transaction-metadata.cjs"); class CollectionStateManager { /** * Creates a new CollectionState manager */ constructor(config) { this.pendingSyncedTransactions = []; this.syncedMetadata = /* @__PURE__ */ new Map(); this.syncedCollectionMetadata = /* @__PURE__ */ new Map(); this.optimisticUpserts = /* @__PURE__ */ new Map(); this.optimisticDeletes = /* @__PURE__ */ new Set(); this.pendingOptimisticUpserts = /* @__PURE__ */ new Map(); this.pendingOptimisticDeletes = /* @__PURE__ */ new Set(); this.pendingOptimisticDirectUpserts = /* @__PURE__ */ new Set(); this.pendingOptimisticDirectDeletes = /* @__PURE__ */ new Set(); this.rowOrigins = /* @__PURE__ */ new Map(); this.pendingLocalChanges = /* @__PURE__ */ new Set(); this.pendingLocalOrigins = /* @__PURE__ */ new Set(); this.virtualPropsCache = /* @__PURE__ */ new WeakMap(); this.size = 0; this.syncedKeys = /* @__PURE__ */ new Set(); this.preSyncVisibleState = /* @__PURE__ */ new Map(); this.recentlySyncedKeys = /* @__PURE__ */ new Set(); this.hasReceivedFirstCommit = false; this.isCommittingSyncTransactions = false; this.isLocalOnly = false; this.commitPendingTransactions = () => { let hasPersistingTransaction = false; for (const transaction of this.transactions.values()) { if (transaction.state === `persisting`) { hasPersistingTransaction = true; break; } } const { committedSyncedTransactions, uncommittedSyncedTransactions, hasTruncateSync, hasImmediateSync } = this.pendingSyncedTransactions.reduce( (acc, t) => { if (t.committed) { acc.committedSyncedTransactions.push(t); if (t.truncate) { acc.hasTruncateSync = true; } if (t.immediate) { acc.hasImmediateSync = true; } } else { acc.uncommittedSyncedTransactions.push(t); } return acc; }, { committedSyncedTransactions: [], uncommittedSyncedTransactions: [], hasTruncateSync: false, hasImmediateSync: false } ); if (!hasPersistingTransaction || hasTruncateSync || hasImmediateSync) { this.isCommittingSyncTransactions = true; const previousRowOrigins = new Map(this.rowOrigins); const previousOptimisticUpserts = new Map(this.optimisticUpserts); const previousOptimisticDeletes = new Set(this.optimisticDeletes); const truncateOptimisticSnapshot = hasTruncateSync ? committedSyncedTransactions.find((t) => t.truncate)?.optimisticSnapshot : null; let truncatePendingLocalChanges; let truncatePendingLocalOrigins; const changedKeys = /* @__PURE__ */ new Set(); for (const transaction of committedSyncedTransactions) { for (const operation of transaction.operations) { changedKeys.add(operation.key); } for (const [key] of transaction.rowMetadataWrites) { changedKeys.add(key); } } let currentVisibleState = this.preSyncVisibleState; if (currentVisibleState.size === 0) { currentVisibleState = /* @__PURE__ */ new Map(); for (const key of changedKeys) { const currentValue = this.get(key); if (currentValue !== void 0) { currentVisibleState.set(key, currentValue); } } } const events = []; const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`; const completedOptimisticOps = /* @__PURE__ */ new Map(); for (const transaction of this.transactions.values()) { if (transaction.state === `completed`) { for (const mutation of transaction.mutations) { if (this.isThisCollection(mutation.collection)) { if (mutation.optimistic) { completedOptimisticOps.set(mutation.key, { type: mutation.type, value: mutation.modified }); } } } } } for (const transaction of committedSyncedTransactions) { if (transaction.truncate) { const visibleKeys = /* @__PURE__ */ new Set([ ...this.syncedData.keys(), ...truncateOptimisticSnapshot?.upserts.keys() || [] ]); for (const key of visibleKeys) { if (truncateOptimisticSnapshot?.deletes.has(key)) continue; const previousValue = truncateOptimisticSnapshot?.upserts.get(key) || this.syncedData.get(key); if (previousValue !== void 0) { events.push({ type: `delete`, key, value: previousValue }); } } truncatePendingLocalChanges = new Set(this.pendingLocalChanges); truncatePendingLocalOrigins = new Set(this.pendingLocalOrigins); this.syncedData.clear(); this.syncedMetadata.clear(); this.syncedKeys.clear(); this.clearOriginTrackingState(); for (const key of changedKeys) { currentVisibleState.delete(key); } this._events.emit(`truncate`, { type: `truncate`, collection: this.collection }); } for (const operation of transaction.operations) { const key = operation.key; this.syncedKeys.add(key); const origin = this.isLocalOnly || this.pendingLocalChanges.has(key) || this.pendingLocalOrigins.has(key) || truncatePendingLocalChanges?.has(key) === true || truncatePendingLocalOrigins?.has(key) === true ? "local" : "remote"; switch (operation.type) { case `insert`: this.syncedData.set(key, operation.value); this.rowOrigins.set(key, origin); this.pendingLocalChanges.delete(key); this.pendingLocalOrigins.delete(key); this.pendingOptimisticUpserts.delete(key); this.pendingOptimisticDeletes.delete(key); this.pendingOptimisticDirectUpserts.delete(key); this.pendingOptimisticDirectDeletes.delete(key); break; case `update`: { if (rowUpdateMode === `partial`) { const updatedValue = Object.assign( {}, this.syncedData.get(key), operation.value ); this.syncedData.set(key, updatedValue); } else { this.syncedData.set(key, operation.value); } this.rowOrigins.set(key, origin); this.pendingLocalChanges.delete(key); this.pendingLocalOrigins.delete(key); this.pendingOptimisticUpserts.delete(key); this.pendingOptimisticDeletes.delete(key); this.pendingOptimisticDirectUpserts.delete(key); this.pendingOptimisticDirectDeletes.delete(key); break; } case `delete`: this.syncedData.delete(key); this.syncedMetadata.delete(key); this.rowOrigins.delete(key); this.pendingLocalChanges.delete(key); this.pendingLocalOrigins.delete(key); this.pendingOptimisticUpserts.delete(key); this.pendingOptimisticDeletes.delete(key); this.pendingOptimisticDirectUpserts.delete(key); this.pendingOptimisticDirectDeletes.delete(key); break; } } for (const [key, metadataWrite] of transaction.rowMetadataWrites) { if (metadataWrite.type === `delete`) { this.syncedMetadata.delete(key); continue; } this.syncedMetadata.set(key, metadataWrite.value); } for (const [ key, metadataWrite ] of transaction.collectionMetadataWrites) { if (metadataWrite.type === `delete`) { this.syncedCollectionMetadata.delete(key); continue; } this.syncedCollectionMetadata.set(key, metadataWrite.value); } } if (hasTruncateSync) { const syncedInsertedOrUpdatedKeys = /* @__PURE__ */ new Set(); for (const t of committedSyncedTransactions) { for (const op of t.operations) { if (op.type === `insert` || op.type === `update`) { syncedInsertedOrUpdatedKeys.add(op.key); } } } const reapplyUpserts = new Map( truncateOptimisticSnapshot.upserts ); const reapplyDeletes = new Set( truncateOptimisticSnapshot.deletes ); for (const [key, value] of reapplyUpserts) { if (reapplyDeletes.has(key)) continue; if (syncedInsertedOrUpdatedKeys.has(key)) { let foundInsert = false; for (let i = events.length - 1; i >= 0; i--) { const evt = events[i]; if (evt.key === key && evt.type === `insert`) { evt.value = value; foundInsert = true; break; } } if (!foundInsert) { events.push({ type: `insert`, key, value }); } } else { events.push({ type: `insert`, key, value }); } } if (events.length > 0 && reapplyDeletes.size > 0) { const filtered = []; for (const evt of events) { if (evt.type === `insert` && reapplyDeletes.has(evt.key)) { continue; } filtered.push(evt); } events.length = 0; events.push(...filtered); } if (this.lifecycle.status !== `ready`) { this.lifecycle.markReady(); } } this.optimisticUpserts.clear(); this.optimisticDeletes.clear(); this.isCommittingSyncTransactions = false; if (hasTruncateSync && truncateOptimisticSnapshot) { for (const [key, value] of truncateOptimisticSnapshot.upserts) { this.optimisticUpserts.set(key, value); } for (const key of truncateOptimisticSnapshot.deletes) { this.optimisticDeletes.add(key); } } for (const transaction of this.transactions.values()) { if (![`completed`, `failed`].includes(transaction.state)) { for (const mutation of transaction.mutations) { if (this.isThisCollection(mutation.collection) && mutation.optimistic) { switch (mutation.type) { case `insert`: case `update`: this.optimisticUpserts.set( mutation.key, mutation.modified ); this.optimisticDeletes.delete(mutation.key); break; case `delete`: this.optimisticUpserts.delete(mutation.key); this.optimisticDeletes.add(mutation.key); break; } } } } } for (const key of changedKeys) { const previousVisibleValue = currentVisibleState.get(key); const newVisibleValue = this.get(key); const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, { rowOrigins: previousRowOrigins, optimisticUpserts: previousOptimisticUpserts, optimisticDeletes: previousOptimisticDeletes, completedOptimisticKeys: completedOptimisticOps }); const nextVirtualProps = this.getVirtualPropsSnapshotForState(key); const virtualChanged = previousVirtualProps.$synced !== nextVirtualProps.$synced || previousVirtualProps.$origin !== nextVirtualProps.$origin; const previousValueWithVirtual = previousVisibleValue !== void 0 ? virtualProps.enrichRowWithVirtualProps( previousVisibleValue, key, this.collection.id, () => previousVirtualProps.$synced, () => previousVirtualProps.$origin ) : void 0; const completedOp = completedOptimisticOps.get(key); let isRedundantSync = false; if (completedOp) { if (completedOp.type === `delete` && previousVisibleValue !== void 0 && newVisibleValue === void 0 && utils.deepEquals(completedOp.value, previousVisibleValue)) { isRedundantSync = true; } else if (newVisibleValue !== void 0 && utils.deepEquals(completedOp.value, newVisibleValue)) { isRedundantSync = true; } } const shouldEmitVirtualUpdate = virtualChanged && previousVisibleValue !== void 0 && newVisibleValue !== void 0 && utils.deepEquals(previousVisibleValue, newVisibleValue); if (isRedundantSync && !shouldEmitVirtualUpdate) { continue; } if (previousVisibleValue === void 0 && newVisibleValue !== void 0) { const completedOptimisticOp = completedOptimisticOps.get(key); if (completedOptimisticOp) { const previousValueFromCompleted = completedOptimisticOp.value; const previousValueWithVirtualFromCompleted = virtualProps.enrichRowWithVirtualProps( previousValueFromCompleted, key, this.collection.id, () => previousVirtualProps.$synced, () => previousVirtualProps.$origin ); events.push({ type: `update`, key, value: newVisibleValue, previousValue: previousValueWithVirtualFromCompleted }); } else { events.push({ type: `insert`, key, value: newVisibleValue }); } } else if (previousVisibleValue !== void 0 && newVisibleValue === void 0) { events.push({ type: `delete`, key, value: previousValueWithVirtual ?? previousVisibleValue }); } else if (previousVisibleValue !== void 0 && newVisibleValue !== void 0 && (!utils.deepEquals(previousVisibleValue, newVisibleValue) || shouldEmitVirtualUpdate)) { events.push({ type: `update`, key, value: newVisibleValue, previousValue: previousValueWithVirtual ?? previousVisibleValue }); } } this.size = this.calculateSize(); if (events.length > 0) { this.indexes.updateIndexes(events); } this.changes.emitEvents(events, true); this.pendingSyncedTransactions = uncommittedSyncedTransactions; this.preSyncVisibleState.clear(); Promise.resolve().then(() => { this.recentlySyncedKeys.clear(); }); if (!this.hasReceivedFirstCommit) { this.hasReceivedFirstCommit = true; } } }; this.config = config; this.transactions = new SortedMap.SortedMap( (a, b) => a.compareCreatedAt(b) ); this.syncedData = new SortedMap.SortedMap(config.compare); } setDeps(deps) { this.collection = deps.collection; this.lifecycle = deps.lifecycle; this.changes = deps.changes; this.indexes = deps.indexes; this._events = deps.events; } /** * Checks if a row has pending optimistic mutations (not yet confirmed by sync). * Used to compute the $synced virtual property. */ isRowSynced(key) { if (this.isLocalOnly) { return true; } return !this.optimisticUpserts.has(key) && !this.optimisticDeletes.has(key); } /** * 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) { if (this.isLocalOnly) { return "local"; } if (this.optimisticUpserts.has(key) || this.optimisticDeletes.has(key)) { return "local"; } return this.rowOrigins.get(key) ?? "remote"; } createVirtualPropsSnapshot(key, overrides) { return { $synced: overrides?.$synced ?? this.isRowSynced(key), $origin: overrides?.$origin ?? this.getRowOrigin(key), $key: overrides?.$key ?? key, $collectionId: overrides?.$collectionId ?? this.collection.id }; } getVirtualPropsSnapshotForState(key, options) { if (this.isLocalOnly) { return this.createVirtualPropsSnapshot(key, { $synced: true, $origin: "local" }); } const optimisticUpserts = options?.optimisticUpserts ?? this.optimisticUpserts; const optimisticDeletes = options?.optimisticDeletes ?? this.optimisticDeletes; const hasOptimisticChange = optimisticUpserts.has(key) || optimisticDeletes.has(key) || options?.completedOptimisticKeys?.has(key) === true; return this.createVirtualPropsSnapshot(key, { $synced: !hasOptimisticChange, $origin: hasOptimisticChange ? "local" : (options?.rowOrigins ?? this.rowOrigins).get(key) ?? "remote" }); } enrichWithVirtualPropsSnapshot(row, virtualProps2) { const existingRow = row; const synced = existingRow.$synced ?? virtualProps2.$synced; const origin = existingRow.$origin ?? virtualProps2.$origin; const resolvedKey = existingRow.$key ?? virtualProps2.$key; const collectionId = existingRow.$collectionId ?? virtualProps2.$collectionId; const cached = this.virtualPropsCache.get(row); if (cached && cached.synced === synced && cached.origin === origin && cached.key === resolvedKey && cached.collectionId === collectionId) { return cached.enriched; } const enriched = { ...row, $synced: synced, $origin: origin, $key: resolvedKey, $collectionId: collectionId }; this.virtualPropsCache.set(row, { synced, origin, key: resolvedKey, collectionId, enriched }); return enriched; } clearOriginTrackingState() { this.rowOrigins.clear(); this.pendingLocalChanges.clear(); this.pendingLocalOrigins.clear(); } /** * 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, key) { return this.enrichWithVirtualPropsSnapshot( row, this.createVirtualPropsSnapshot(key) ); } /** * Creates a change message with virtual properties. * Uses the "add-if-missing" pattern so that pass-through from upstream * collections works correctly. */ enrichChangeMessage(change) { const { __virtualProps } = change; const enrichedValue = __virtualProps?.value ? this.enrichWithVirtualPropsSnapshot(change.value, __virtualProps.value) : this.enrichWithVirtualProps(change.value, change.key); const enrichedPreviousValue = change.previousValue ? __virtualProps?.previousValue ? this.enrichWithVirtualPropsSnapshot( change.previousValue, __virtualProps.previousValue ) : this.enrichWithVirtualProps(change.previousValue, change.key) : void 0; return { key: change.key, type: change.type, value: enrichedValue, previousValue: enrichedPreviousValue, metadata: change.metadata }; } /** * Get the current value for a key enriched with virtual properties. */ getWithVirtualProps(key) { const value = this.get(key); if (value === void 0) { return void 0; } return this.enrichWithVirtualProps(value, key); } /** * Get the current value for a key (virtual derived state) */ get(key) { const { optimisticDeletes, optimisticUpserts, syncedData } = this; if (optimisticDeletes.has(key)) { return void 0; } if (optimisticUpserts.has(key)) { return optimisticUpserts.get(key); } return syncedData.get(key); } /** * Check if a key exists in the collection (virtual derived state) */ has(key) { const { optimisticDeletes, optimisticUpserts, syncedData } = this; if (optimisticDeletes.has(key)) { return false; } if (optimisticUpserts.has(key)) { return true; } return syncedData.has(key); } /** * Get all keys (virtual derived state) */ *keys() { const { syncedData, optimisticDeletes, optimisticUpserts } = this; for (const key of syncedData.keys()) { if (!optimisticDeletes.has(key)) { yield key; } } for (const key of optimisticUpserts.keys()) { if (!syncedData.has(key) && !optimisticDeletes.has(key)) { yield key; } } } /** * Get all values (virtual derived state) */ *values() { for (const key of this.keys()) { const value = this.get(key); if (value !== void 0) { yield value; } } } /** * Get all entries (virtual derived state) */ *entries() { for (const key of this.keys()) { const value = this.get(key); if (value !== void 0) { yield [key, value]; } } } /** * Get all entries (virtual derived state) */ *[Symbol.iterator]() { for (const [key, value] of this.entries()) { yield [key, value]; } } /** * Execute a callback for each entry in the collection */ forEach(callbackfn) { let index = 0; for (const [key, value] of this.entries()) { callbackfn(value, key, index++); } } /** * Create a new array with the results of calling a function for each entry in the collection */ map(callbackfn) { const result = []; let index = 0; for (const [key, value] of this.entries()) { result.push(callbackfn(value, key, index++)); } return result; } /** * 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 */ isThisCollection(collection) { return collection === this.collection; } /** * Recompute optimistic state from active transactions */ recomputeOptimisticState(triggeredByUserAction = false) { if (this.isCommittingSyncTransactions && !triggeredByUserAction) { return; } const previousState = new Map(this.optimisticUpserts); const previousDeletes = new Set(this.optimisticDeletes); const previousRowOrigins = new Map(this.rowOrigins); for (const transaction of this.transactions.values()) { const isDirectTransaction = transaction.metadata[transactionMetadata.DIRECT_TRANSACTION_METADATA_KEY] === true; if (transaction.state === `completed`) { for (const mutation of transaction.mutations) { if (!this.isThisCollection(mutation.collection)) { continue; } this.pendingLocalOrigins.add(mutation.key); if (!mutation.optimistic) { continue; } switch (mutation.type) { case `insert`: case `update`: this.pendingOptimisticUpserts.set( mutation.key, mutation.modified ); this.pendingOptimisticDeletes.delete(mutation.key); if (isDirectTransaction) { this.pendingOptimisticDirectUpserts.add(mutation.key); this.pendingOptimisticDirectDeletes.delete(mutation.key); } else { this.pendingOptimisticDirectUpserts.delete(mutation.key); this.pendingOptimisticDirectDeletes.delete(mutation.key); } break; case `delete`: this.pendingOptimisticUpserts.delete(mutation.key); this.pendingOptimisticDeletes.add(mutation.key); if (isDirectTransaction) { this.pendingOptimisticDirectUpserts.delete(mutation.key); this.pendingOptimisticDirectDeletes.add(mutation.key); } else { this.pendingOptimisticDirectUpserts.delete(mutation.key); this.pendingOptimisticDirectDeletes.delete(mutation.key); } break; } } } else if (transaction.state === `failed`) { for (const mutation of transaction.mutations) { if (!this.isThisCollection(mutation.collection)) { continue; } this.pendingLocalOrigins.delete(mutation.key); if (mutation.optimistic) { this.pendingOptimisticUpserts.delete(mutation.key); this.pendingOptimisticDeletes.delete(mutation.key); this.pendingOptimisticDirectUpserts.delete(mutation.key); this.pendingOptimisticDirectDeletes.delete(mutation.key); } } } } this.optimisticUpserts.clear(); this.optimisticDeletes.clear(); this.pendingLocalChanges.clear(); const pendingSyncKeys = /* @__PURE__ */ new Set(); for (const transaction of this.pendingSyncedTransactions) { for (const operation of transaction.operations) { pendingSyncKeys.add(operation.key); } } const staleOptimisticUpserts = []; for (const [key, value] of this.pendingOptimisticUpserts) { if (pendingSyncKeys.has(key) || this.pendingOptimisticDirectUpserts.has(key)) { this.optimisticUpserts.set(key, value); } else { staleOptimisticUpserts.push(key); } } for (const key of staleOptimisticUpserts) { this.pendingOptimisticUpserts.delete(key); this.pendingLocalOrigins.delete(key); } const staleOptimisticDeletes = []; for (const key of this.pendingOptimisticDeletes) { if (pendingSyncKeys.has(key) || this.pendingOptimisticDirectDeletes.has(key)) { this.optimisticDeletes.add(key); } else { staleOptimisticDeletes.push(key); } } for (const key of staleOptimisticDeletes) { this.pendingOptimisticDeletes.delete(key); this.pendingLocalOrigins.delete(key); } const activeTransactions = []; for (const transaction of this.transactions.values()) { if (![`completed`, `failed`].includes(transaction.state)) { activeTransactions.push(transaction); } } for (const transaction of activeTransactions) { for (const mutation of transaction.mutations) { if (!this.isThisCollection(mutation.collection)) { continue; } this.pendingLocalChanges.add(mutation.key); if (mutation.optimistic) { switch (mutation.type) { case `insert`: case `update`: this.optimisticUpserts.set( mutation.key, mutation.modified ); this.optimisticDeletes.delete(mutation.key); break; case `delete`: this.optimisticUpserts.delete(mutation.key); this.optimisticDeletes.add(mutation.key); break; } } } } this.size = this.calculateSize(); const events = []; this.collectOptimisticChanges( previousState, previousDeletes, previousRowOrigins, events ); const filteredEventsBySyncStatus = events.filter((event) => { if (!this.recentlySyncedKeys.has(event.key)) { return true; } if (triggeredByUserAction) { return true; } return false; }); if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) { const pendingSyncKeysForFilter = /* @__PURE__ */ new Set(); for (const transaction of this.pendingSyncedTransactions) { for (const operation of transaction.operations) { pendingSyncKeysForFilter.add(operation.key); } } const filteredEvents = filteredEventsBySyncStatus.filter((event) => { if (event.type === `delete` && pendingSyncKeysForFilter.has(event.key)) { const hasActiveOptimisticMutation = activeTransactions.some( (tx) => tx.mutations.some( (m) => this.isThisCollection(m.collection) && m.key === event.key ) ); if (!hasActiveOptimisticMutation) { return false; } } return true; }); if (filteredEvents.length > 0) { this.indexes.updateIndexes(filteredEvents); } this.changes.emitEvents(filteredEvents, triggeredByUserAction); } else { if (filteredEventsBySyncStatus.length > 0) { this.indexes.updateIndexes(filteredEventsBySyncStatus); } this.changes.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction); } } /** * Calculate the current size based on synced data and optimistic changes */ calculateSize() { const syncedSize = this.syncedData.size; const deletesFromSynced = Array.from(this.optimisticDeletes).filter( (key) => this.syncedData.has(key) && !this.optimisticUpserts.has(key) ).length; const upsertsNotInSynced = Array.from(this.optimisticUpserts.keys()).filter( (key) => !this.syncedData.has(key) ).length; return syncedSize - deletesFromSynced + upsertsNotInSynced; } /** * Collect events for optimistic changes */ collectOptimisticChanges(previousUpserts, previousDeletes, previousRowOrigins, events) { const allKeys = /* @__PURE__ */ new Set([ ...previousUpserts.keys(), ...this.optimisticUpserts.keys(), ...previousDeletes, ...this.optimisticDeletes ]); for (const key of allKeys) { const currentValue = this.get(key); const previousValue = this.getPreviousValue( key, previousUpserts, previousDeletes ); const previousVirtualProps = this.getVirtualPropsSnapshotForState(key, { rowOrigins: previousRowOrigins, optimisticUpserts: previousUpserts, optimisticDeletes: previousDeletes }); const nextVirtualProps = this.getVirtualPropsSnapshotForState(key); if (previousValue !== void 0 && currentValue === void 0) { events.push({ type: `delete`, key, value: previousValue, __virtualProps: { value: previousVirtualProps } }); } else if (previousValue === void 0 && currentValue !== void 0) { events.push({ type: `insert`, key, value: currentValue, __virtualProps: { value: nextVirtualProps } }); } else if (previousValue !== void 0 && currentValue !== void 0 && previousValue !== currentValue) { events.push({ type: `update`, key, value: currentValue, previousValue, __virtualProps: { value: nextVirtualProps, previousValue: previousVirtualProps } }); } } } /** * Get the previous value for a key given previous optimistic state */ getPreviousValue(key, previousUpserts, previousDeletes) { if (previousDeletes.has(key)) { return void 0; } if (previousUpserts.has(key)) { return previousUpserts.get(key); } return this.syncedData.get(key); } /** * Schedule cleanup of a transaction when it completes */ scheduleTransactionCleanup(transaction) { if (transaction.state === `completed`) { this.transactions.delete(transaction.id); return; } transaction.isPersisted.promise.then(() => { this.transactions.delete(transaction.id); }).catch(() => { }); } /** * Capture visible state for keys that will be affected by pending sync operations * This must be called BEFORE onTransactionStateChange clears optimistic state */ capturePreSyncVisibleState() { if (this.pendingSyncedTransactions.length === 0) return; const syncedKeys = /* @__PURE__ */ new Set(); for (const transaction of this.pendingSyncedTransactions) { for (const operation of transaction.operations) { syncedKeys.add(operation.key); } } for (const key of syncedKeys) { this.recentlySyncedKeys.add(key); } for (const key of syncedKeys) { if (!this.preSyncVisibleState.has(key)) { const currentValue = this.get(key); if (currentValue !== void 0) { this.preSyncVisibleState.set(key, currentValue); } } } } /** * Trigger a recomputation when transactions change * This method should be called by the Transaction class when state changes */ onTransactionStateChange() { this.changes.shouldBatchEvents = this.pendingSyncedTransactions.length > 0; this.capturePreSyncVisibleState(); this.recomputeOptimisticState(false); } /** * Clean up the collection by stopping sync and clearing data * This can be called manually or automatically by garbage collection */ cleanup() { this.syncedData.clear(); this.syncedMetadata.clear(); this.syncedCollectionMetadata.clear(); this.optimisticUpserts.clear(); this.optimisticDeletes.clear(); this.pendingOptimisticUpserts.clear(); this.pendingOptimisticDeletes.clear(); this.pendingOptimisticDirectUpserts.clear(); this.pendingOptimisticDirectDeletes.clear(); this.clearOriginTrackingState(); this.isLocalOnly = false; this.size = 0; this.pendingSyncedTransactions = []; this.syncedKeys.clear(); this.hasReceivedFirstCommit = false; } } exports.CollectionStateManager = CollectionStateManager; //# sourceMappingURL=state.cjs.map