@tanstack/db 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/SortedMap.cjs +40 -26
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -15
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs +2 -0
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +12 -4
- package/dist/cjs/collection/index.cjs +2 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +5 -2
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +6 -5
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +4 -1
- package/dist/cjs/collection/subscription.cjs +91 -57
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +26 -4
- package/dist/cjs/collection/sync.cjs +11 -6
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +9 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/event-emitter.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.cjs +8 -6
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/paced-mutations.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +91 -38
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +6 -2
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/expression-helpers.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/internal.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +19 -2
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.d.cts +32 -1
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/scheduler.cjs.map +1 -1
- package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +53 -8
- package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/cursor.cjs +39 -0
- package/dist/cjs/utils/cursor.cjs.map +1 -0
- package/dist/cjs/utils/cursor.d.cts +18 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/SortedMap.d.ts +10 -15
- package/dist/esm/SortedMap.js +40 -26
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js +2 -0
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.d.ts +12 -4
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.js +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +6 -3
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +4 -1
- package/dist/esm/collection/state.js +6 -5
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +26 -4
- package/dist/esm/collection/subscription.js +92 -58
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js +11 -6
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.js +8 -6
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.js.map +1 -1
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/paced-mutations.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +6 -2
- package/dist/esm/query/compiler/order-by.js +91 -38
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/expression-helpers.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +30 -15
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/internal.js.map +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/query/predicate-utils.d.ts +32 -1
- package/dist/esm/query/predicate-utils.js +19 -2
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/scheduler.js.map +1 -1
- package/dist/esm/strategies/debounceStrategy.js.map +1 -1
- package/dist/esm/strategies/queueStrategy.js.map +1 -1
- package/dist/esm/strategies/throttleStrategy.js.map +1 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +53 -8
- package/dist/esm/utils/browser-polyfills.js.map +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/cursor.d.ts +18 -0
- package/dist/esm/utils/cursor.js +39 -0
- package/dist/esm/utils/cursor.js.map +1 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +30 -28
- package/src/SortedMap.ts +50 -31
- package/src/collection/change-events.ts +20 -20
- package/src/collection/changes.ts +16 -12
- package/src/collection/events.ts +20 -10
- package/src/collection/index.ts +47 -46
- package/src/collection/indexes.ts +14 -14
- package/src/collection/lifecycle.ts +16 -16
- package/src/collection/mutations.ts +25 -20
- package/src/collection/state.ts +43 -36
- package/src/collection/subscription.ts +171 -90
- package/src/collection/sync.ts +34 -22
- package/src/duplicate-instance-check.ts +1 -1
- package/src/errors.ts +49 -40
- package/src/event-emitter.ts +5 -5
- package/src/index.ts +21 -21
- package/src/indexes/auto-index.ts +11 -11
- package/src/indexes/base-index.ts +13 -13
- package/src/indexes/btree-index.ts +21 -17
- package/src/indexes/index-options.ts +3 -3
- package/src/indexes/lazy-index.ts +8 -8
- package/src/indexes/reverse-index.ts +5 -5
- package/src/local-only.ts +12 -12
- package/src/local-storage.ts +17 -17
- package/src/optimistic-action.ts +5 -5
- package/src/paced-mutations.ts +6 -6
- package/src/proxy.ts +43 -43
- package/src/query/builder/functions.ts +28 -28
- package/src/query/builder/index.ts +22 -22
- package/src/query/builder/ref-proxy.ts +4 -4
- package/src/query/builder/types.ts +8 -8
- package/src/query/compiler/evaluators.ts +9 -9
- package/src/query/compiler/expressions.ts +6 -6
- package/src/query/compiler/group-by.ts +24 -24
- package/src/query/compiler/index.ts +44 -44
- package/src/query/compiler/joins.ts +37 -37
- package/src/query/compiler/order-by.ts +170 -77
- package/src/query/compiler/select.ts +13 -13
- package/src/query/compiler/types.ts +2 -2
- package/src/query/expression-helpers.ts +16 -16
- package/src/query/index.ts +10 -9
- package/src/query/ir.ts +13 -13
- package/src/query/live/collection-config-builder.ts +53 -53
- package/src/query/live/collection-registry.ts +6 -6
- package/src/query/live/collection-subscriber.ts +87 -48
- package/src/query/live/internal.ts +1 -1
- package/src/query/live/types.ts +4 -4
- package/src/query/live-query-collection.ts +15 -15
- package/src/query/optimizer.ts +29 -29
- package/src/query/predicate-utils.ts +105 -50
- package/src/query/subset-dedupe.ts +6 -6
- package/src/scheduler.ts +3 -3
- package/src/strategies/debounceStrategy.ts +6 -6
- package/src/strategies/index.ts +4 -4
- package/src/strategies/queueStrategy.ts +5 -5
- package/src/strategies/throttleStrategy.ts +6 -6
- package/src/strategies/types.ts +2 -2
- package/src/transactions.ts +9 -9
- package/src/types.ts +76 -18
- package/src/utils/array-utils.ts +1 -1
- package/src/utils/browser-polyfills.ts +2 -2
- package/src/utils/btree.ts +22 -22
- package/src/utils/comparison.ts +3 -3
- package/src/utils/cursor.ts +78 -0
- package/src/utils/index-optimization.ts +14 -14
- package/src/utils.ts +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sources":["../../../src/collection/state.ts"],"sourcesContent":["import { deepEquals } from \"../utils\"\nimport { SortedMap } from \"../SortedMap\"\nimport type { Transaction } from \"../transactions\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n OptimisticChangeMessage,\n} from \"../types\"\nimport type { CollectionImpl } from \"./index.js\"\nimport type { CollectionLifecycleManager } from \"./lifecycle\"\nimport type { CollectionChangesManager } from \"./changes\"\nimport type { CollectionIndexesManager } from \"./indexes\"\n\ninterface PendingSyncedTransaction<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n truncate?: boolean\n deletedKeys: Set<string | number>\n optimisticSnapshot?: {\n upserts: Map<TKey, T>\n deletes: Set<TKey>\n }\n}\n\nexport class CollectionStateManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n public config!: CollectionConfig<TOutput, TKey, TSchema>\n public collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n public lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n public changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n public indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n\n // Core state - make public for testing\n public transactions: SortedMap<string, Transaction<any>>\n public pendingSyncedTransactions: Array<\n PendingSyncedTransaction<TOutput, TKey>\n > = []\n public syncedData: Map<TKey, TOutput> | SortedMap<TKey, TOutput>\n public syncedMetadata = new Map<TKey, unknown>()\n\n // Optimistic state tracking - make public for testing\n public optimisticUpserts = new Map<TKey, TOutput>()\n public optimisticDeletes = new Set<TKey>()\n\n // Cached size for performance\n public size = 0\n\n // State used for computing the change events\n public syncedKeys = new Set<TKey>()\n public preSyncVisibleState = new Map<TKey, TOutput>()\n public recentlySyncedKeys = new Set<TKey>()\n public hasReceivedFirstCommit = false\n public isCommittingSyncTransactions = false\n\n /**\n * Creates a new CollectionState manager\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>) {\n this.config = config\n this.transactions = new SortedMap<string, Transaction<any>>((a, b) =>\n a.compareCreatedAt(b)\n )\n\n // Set up data storage with optional comparison function\n if (config.compare) {\n this.syncedData = new SortedMap<TKey, TOutput>(config.compare)\n } else {\n this.syncedData = new Map<TKey, TOutput>()\n }\n }\n\n setDeps(deps: {\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.collection = deps.collection\n this.lifecycle = deps.lifecycle\n this.changes = deps.changes\n this.indexes = deps.indexes\n }\n\n /**\n * Get the current value for a key (virtual derived state)\n */\n public get(key: TKey): TOutput | undefined {\n const { optimisticDeletes, optimisticUpserts, syncedData } = this\n // Check if optimistically deleted\n if (optimisticDeletes.has(key)) {\n return undefined\n }\n\n // Check optimistic upserts first\n if (optimisticUpserts.has(key)) {\n return optimisticUpserts.get(key)\n }\n\n // Fall back to synced data\n return syncedData.get(key)\n }\n\n /**\n * Check if a key exists in the collection (virtual derived state)\n */\n public has(key: TKey): boolean {\n const { optimisticDeletes, optimisticUpserts, syncedData } = this\n // Check if optimistically deleted\n if (optimisticDeletes.has(key)) {\n return false\n }\n\n // Check optimistic upserts first\n if (optimisticUpserts.has(key)) {\n return true\n }\n\n // Fall back to synced data\n return syncedData.has(key)\n }\n\n /**\n * Get all keys (virtual derived state)\n */\n public *keys(): IterableIterator<TKey> {\n const { syncedData, optimisticDeletes, optimisticUpserts } = this\n // Yield keys from synced data, skipping any that are deleted.\n for (const key of syncedData.keys()) {\n if (!optimisticDeletes.has(key)) {\n yield key\n }\n }\n // Yield keys from upserts that were not already in synced data.\n for (const key of optimisticUpserts.keys()) {\n if (!syncedData.has(key) && !optimisticDeletes.has(key)) {\n // The optimisticDeletes check is technically redundant if inserts/updates always remove from deletes,\n // but it's safer to keep it.\n yield key\n }\n }\n }\n\n /**\n * Get all values (virtual derived state)\n */\n public *values(): IterableIterator<TOutput> {\n for (const key of this.keys()) {\n const value = this.get(key)\n if (value !== undefined) {\n yield value\n }\n }\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *entries(): IterableIterator<[TKey, TOutput]> {\n for (const key of this.keys()) {\n const value = this.get(key)\n if (value !== undefined) {\n yield [key, value]\n }\n }\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *[Symbol.iterator](): IterableIterator<[TKey, TOutput]> {\n for (const [key, value] of this.entries()) {\n yield [key, value]\n }\n }\n\n /**\n * Execute a callback for each entry in the collection\n */\n public forEach(\n callbackfn: (value: TOutput, key: TKey, index: number) => void\n ): void {\n let index = 0\n for (const [key, value] of this.entries()) {\n callbackfn(value, key, index++)\n }\n }\n\n /**\n * Create a new array with the results of calling a function for each entry in the collection\n */\n public map<U>(\n callbackfn: (value: TOutput, key: TKey, index: number) => U\n ): Array<U> {\n const result: Array<U> = []\n let index = 0\n for (const [key, value] of this.entries()) {\n result.push(callbackfn(value, key, index++))\n }\n return result\n }\n\n /**\n * Check if the given collection is this collection\n * @param collection The collection to check\n * @returns True if the given collection is this collection, false otherwise\n */\n private isThisCollection(\n collection: CollectionImpl<any, any, any, any, any>\n ): boolean {\n return collection === this.collection\n }\n\n /**\n * Recompute optimistic state from active transactions\n */\n public recomputeOptimisticState(\n triggeredByUserAction: boolean = false\n ): void {\n // Skip redundant recalculations when we're in the middle of committing sync transactions\n // While the sync pipeline is replaying a large batch we still want to honour\n // fresh optimistic mutations from the UI. Only skip recompute for the\n // internal sync-driven redraws; user-triggered work (triggeredByUserAction)\n // must run so live queries stay responsive during long commits.\n if (this.isCommittingSyncTransactions && !triggeredByUserAction) {\n return\n }\n\n const previousState = new Map(this.optimisticUpserts)\n const previousDeletes = new Set(this.optimisticDeletes)\n\n // Clear current optimistic state\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n\n const activeTransactions: Array<Transaction<any>> = []\n\n for (const transaction of this.transactions.values()) {\n if (![`completed`, `failed`].includes(transaction.state)) {\n activeTransactions.push(transaction)\n }\n }\n\n // Apply active transactions only (completed transactions are handled by sync operations)\n for (const transaction of activeTransactions) {\n for (const mutation of transaction.mutations) {\n if (this.isThisCollection(mutation.collection) && mutation.optimistic) {\n switch (mutation.type) {\n case `insert`:\n case `update`:\n this.optimisticUpserts.set(\n mutation.key,\n mutation.modified as TOutput\n )\n this.optimisticDeletes.delete(mutation.key)\n break\n case `delete`:\n this.optimisticUpserts.delete(mutation.key)\n this.optimisticDeletes.add(mutation.key)\n break\n }\n }\n }\n }\n\n // Update cached size\n this.size = this.calculateSize()\n\n // Collect events for changes\n const events: Array<ChangeMessage<TOutput, TKey>> = []\n this.collectOptimisticChanges(previousState, previousDeletes, events)\n\n // Filter out events for recently synced keys to prevent duplicates\n // BUT: Only filter out events that are actually from sync operations\n // New user transactions should NOT be filtered even if the key was recently synced\n const filteredEventsBySyncStatus = events.filter((event) => {\n if (!this.recentlySyncedKeys.has(event.key)) {\n return true // Key not recently synced, allow event through\n }\n\n // Key was recently synced - allow if this is a user-triggered action\n if (triggeredByUserAction) {\n return true\n }\n\n // Otherwise filter out duplicate sync events\n return false\n })\n\n // Filter out redundant delete events if there are pending sync transactions\n // that will immediately restore the same data, but only for completed transactions\n // IMPORTANT: Skip complex filtering for user-triggered actions to prevent UI blocking\n if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {\n const pendingSyncKeys = new Set<TKey>()\n\n // Collect keys from pending sync operations\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n pendingSyncKeys.add(operation.key as TKey)\n }\n }\n\n // Only filter out delete events for keys that:\n // 1. Have pending sync operations AND\n // 2. Are from completed transactions (being cleaned up)\n const filteredEvents = filteredEventsBySyncStatus.filter((event) => {\n if (event.type === `delete` && pendingSyncKeys.has(event.key)) {\n // Check if this delete is from clearing optimistic state of completed transactions\n // We can infer this by checking if we have no remaining optimistic mutations for this key\n const hasActiveOptimisticMutation = activeTransactions.some((tx) =>\n tx.mutations.some(\n (m) => this.isThisCollection(m.collection) && m.key === event.key\n )\n )\n\n if (!hasActiveOptimisticMutation) {\n return false // Skip this delete event as sync will restore the data\n }\n }\n return true\n })\n\n // Update indexes for the filtered events\n if (filteredEvents.length > 0) {\n this.indexes.updateIndexes(filteredEvents)\n }\n this.changes.emitEvents(filteredEvents, triggeredByUserAction)\n } else {\n // Update indexes for all events\n if (filteredEventsBySyncStatus.length > 0) {\n this.indexes.updateIndexes(filteredEventsBySyncStatus)\n }\n // Emit all events if no pending sync transactions\n this.changes.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction)\n }\n }\n\n /**\n * Calculate the current size based on synced data and optimistic changes\n */\n private calculateSize(): number {\n const syncedSize = this.syncedData.size\n const deletesFromSynced = Array.from(this.optimisticDeletes).filter(\n (key) => this.syncedData.has(key) && !this.optimisticUpserts.has(key)\n ).length\n const upsertsNotInSynced = Array.from(this.optimisticUpserts.keys()).filter(\n (key) => !this.syncedData.has(key)\n ).length\n\n return syncedSize - deletesFromSynced + upsertsNotInSynced\n }\n\n /**\n * Collect events for optimistic changes\n */\n private collectOptimisticChanges(\n previousUpserts: Map<TKey, TOutput>,\n previousDeletes: Set<TKey>,\n events: Array<ChangeMessage<TOutput, TKey>>\n ): void {\n const allKeys = new Set([\n ...previousUpserts.keys(),\n ...this.optimisticUpserts.keys(),\n ...previousDeletes,\n ...this.optimisticDeletes,\n ])\n\n for (const key of allKeys) {\n const currentValue = this.get(key)\n const previousValue = this.getPreviousValue(\n key,\n previousUpserts,\n previousDeletes\n )\n\n if (previousValue !== undefined && currentValue === undefined) {\n events.push({ type: `delete`, key, value: previousValue })\n } else if (previousValue === undefined && currentValue !== undefined) {\n events.push({ type: `insert`, key, value: currentValue })\n } else if (\n previousValue !== undefined &&\n currentValue !== undefined &&\n previousValue !== currentValue\n ) {\n events.push({\n type: `update`,\n key,\n value: currentValue,\n previousValue,\n })\n }\n }\n }\n\n /**\n * Get the previous value for a key given previous optimistic state\n */\n private getPreviousValue(\n key: TKey,\n previousUpserts: Map<TKey, TOutput>,\n previousDeletes: Set<TKey>\n ): TOutput | undefined {\n if (previousDeletes.has(key)) {\n return undefined\n }\n if (previousUpserts.has(key)) {\n return previousUpserts.get(key)\n }\n return this.syncedData.get(key)\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n // Check if there are any persisting transaction\n let hasPersistingTransaction = false\n for (const transaction of this.transactions.values()) {\n if (transaction.state === `persisting`) {\n hasPersistingTransaction = true\n break\n }\n }\n\n // pending synced transactions could be either `committed` or still open.\n // we only want to process `committed` transactions here\n const {\n committedSyncedTransactions,\n uncommittedSyncedTransactions,\n hasTruncateSync,\n } = this.pendingSyncedTransactions.reduce(\n (acc, t) => {\n if (t.committed) {\n acc.committedSyncedTransactions.push(t)\n if (t.truncate === true) {\n acc.hasTruncateSync = true\n }\n } else {\n acc.uncommittedSyncedTransactions.push(t)\n }\n return acc\n },\n {\n committedSyncedTransactions: [] as Array<\n PendingSyncedTransaction<TOutput, TKey>\n >,\n uncommittedSyncedTransactions: [] as Array<\n PendingSyncedTransaction<TOutput, TKey>\n >,\n hasTruncateSync: false,\n }\n )\n\n if (!hasPersistingTransaction || hasTruncateSync) {\n // Set flag to prevent redundant optimistic state recalculations\n this.isCommittingSyncTransactions = true\n\n // Get the optimistic snapshot from the truncate transaction (captured when truncate() was called)\n const truncateOptimisticSnapshot = hasTruncateSync\n ? committedSyncedTransactions.find((t) => t.truncate)\n ?.optimisticSnapshot\n : null\n\n // First collect all keys that will be affected by sync operations\n const changedKeys = new Set<TKey>()\n for (const transaction of committedSyncedTransactions) {\n for (const operation of transaction.operations) {\n changedKeys.add(operation.key as TKey)\n }\n }\n\n // Use pre-captured state if available (from optimistic scenarios),\n // otherwise capture current state (for pure sync scenarios)\n let currentVisibleState = this.preSyncVisibleState\n if (currentVisibleState.size === 0) {\n // No pre-captured state, capture it now for pure sync operations\n currentVisibleState = new Map<TKey, TOutput>()\n for (const key of changedKeys) {\n const currentValue = this.get(key)\n if (currentValue !== undefined) {\n currentVisibleState.set(key, currentValue)\n }\n }\n }\n\n const events: Array<ChangeMessage<TOutput, TKey>> = []\n const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`\n\n for (const transaction of committedSyncedTransactions) {\n // Handle truncate operations first\n if (transaction.truncate) {\n // TRUNCATE PHASE\n // 1) Emit a delete for every visible key (synced + optimistic) so downstream listeners/indexes\n // observe a clear-before-rebuild. We intentionally skip keys already in\n // optimisticDeletes because their delete was previously emitted by the user.\n // Use the snapshot to ensure we emit deletes for all items that existed at truncate start.\n const visibleKeys = new Set([\n ...this.syncedData.keys(),\n ...(truncateOptimisticSnapshot?.upserts.keys() || []),\n ])\n for (const key of visibleKeys) {\n if (truncateOptimisticSnapshot?.deletes.has(key)) continue\n const previousValue =\n truncateOptimisticSnapshot?.upserts.get(key) ||\n this.syncedData.get(key)\n if (previousValue !== undefined) {\n events.push({ type: `delete`, key, value: previousValue })\n }\n }\n\n // 2) Clear the authoritative synced base. Subsequent server ops in this\n // same commit will rebuild the base atomically.\n this.syncedData.clear()\n this.syncedMetadata.clear()\n this.syncedKeys.clear()\n\n // 3) Clear currentVisibleState for truncated keys to ensure subsequent operations\n // are compared against the post-truncate state (undefined) rather than pre-truncate state\n // This ensures that re-inserted keys are emitted as INSERT events, not UPDATE events\n for (const key of changedKeys) {\n currentVisibleState.delete(key)\n }\n }\n\n for (const operation of transaction.operations) {\n const key = operation.key as TKey\n this.syncedKeys.add(key)\n\n // Update metadata\n switch (operation.type) {\n case `insert`:\n this.syncedMetadata.set(key, operation.metadata)\n break\n case `update`:\n this.syncedMetadata.set(\n key,\n Object.assign(\n {},\n this.syncedMetadata.get(key),\n operation.metadata\n )\n )\n break\n case `delete`:\n this.syncedMetadata.delete(key)\n break\n }\n\n // Update synced data\n switch (operation.type) {\n case `insert`:\n this.syncedData.set(key, operation.value)\n break\n case `update`: {\n if (rowUpdateMode === `partial`) {\n const updatedValue = Object.assign(\n {},\n this.syncedData.get(key),\n operation.value\n )\n this.syncedData.set(key, updatedValue)\n } else {\n this.syncedData.set(key, operation.value)\n }\n break\n }\n case `delete`:\n this.syncedData.delete(key)\n break\n }\n }\n }\n\n // After applying synced operations, if this commit included a truncate,\n // re-apply optimistic mutations on top of the fresh synced base. This ensures\n // the UI preserves local intent while respecting server rebuild semantics.\n // Ordering: deletes (above) -> server ops (just applied) -> optimistic upserts.\n if (hasTruncateSync) {\n // Avoid duplicating keys that were inserted/updated by synced operations in this commit\n const syncedInsertedOrUpdatedKeys = new Set<TKey>()\n for (const t of committedSyncedTransactions) {\n for (const op of t.operations) {\n if (op.type === `insert` || op.type === `update`) {\n syncedInsertedOrUpdatedKeys.add(op.key as TKey)\n }\n }\n }\n\n // Build re-apply sets from the snapshot taken at the start of this function.\n // This prevents losing optimistic state if transactions complete during truncate processing.\n const reapplyUpserts = new Map<TKey, TOutput>(\n truncateOptimisticSnapshot!.upserts\n )\n const reapplyDeletes = new Set<TKey>(\n truncateOptimisticSnapshot!.deletes\n )\n\n // Emit inserts for re-applied upserts, skipping any keys that have an optimistic delete.\n // If the server also inserted/updated the same key in this batch, override that value\n // with the optimistic value to preserve local intent.\n for (const [key, value] of reapplyUpserts) {\n if (reapplyDeletes.has(key)) continue\n if (syncedInsertedOrUpdatedKeys.has(key)) {\n let foundInsert = false\n for (let i = events.length - 1; i >= 0; i--) {\n const evt = events[i]!\n if (evt.key === key && evt.type === `insert`) {\n evt.value = value\n foundInsert = true\n break\n }\n }\n if (!foundInsert) {\n events.push({ type: `insert`, key, value })\n }\n } else {\n events.push({ type: `insert`, key, value })\n }\n }\n\n // Finally, ensure we do NOT insert keys that have an outstanding optimistic delete.\n if (events.length > 0 && reapplyDeletes.size > 0) {\n const filtered: Array<ChangeMessage<TOutput, TKey>> = []\n for (const evt of events) {\n if (evt.type === `insert` && reapplyDeletes.has(evt.key)) {\n continue\n }\n filtered.push(evt)\n }\n events.length = 0\n events.push(...filtered)\n }\n\n // Ensure listeners are active before emitting this critical batch\n if (this.lifecycle.status !== `ready`) {\n this.lifecycle.markReady()\n }\n }\n\n // Maintain optimistic state appropriately\n // Clear optimistic state since sync operations will now provide the authoritative data.\n // Any still-active user transactions will be re-applied below in recompute.\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n\n // Reset flag and recompute optimistic state for any remaining active transactions\n this.isCommittingSyncTransactions = false\n\n // If we had a truncate, restore the preserved optimistic state from the snapshot\n // This includes items from transactions that may have completed during processing\n if (hasTruncateSync && truncateOptimisticSnapshot) {\n for (const [key, value] of truncateOptimisticSnapshot.upserts) {\n this.optimisticUpserts.set(key, value)\n }\n for (const key of truncateOptimisticSnapshot.deletes) {\n this.optimisticDeletes.add(key)\n }\n }\n\n // Always overlay any still-active optimistic transactions so mutations that started\n // after the truncate snapshot are preserved.\n for (const transaction of this.transactions.values()) {\n if (![`completed`, `failed`].includes(transaction.state)) {\n for (const mutation of transaction.mutations) {\n if (\n this.isThisCollection(mutation.collection) &&\n mutation.optimistic\n ) {\n switch (mutation.type) {\n case `insert`:\n case `update`:\n this.optimisticUpserts.set(\n mutation.key,\n mutation.modified as TOutput\n )\n this.optimisticDeletes.delete(mutation.key)\n break\n case `delete`:\n this.optimisticUpserts.delete(mutation.key)\n this.optimisticDeletes.add(mutation.key)\n break\n }\n }\n }\n }\n }\n\n // Check for redundant sync operations that match completed optimistic operations\n const completedOptimisticOps = new Map<TKey, any>()\n\n for (const transaction of this.transactions.values()) {\n if (transaction.state === `completed`) {\n for (const mutation of transaction.mutations) {\n if (\n mutation.optimistic &&\n this.isThisCollection(mutation.collection) &&\n changedKeys.has(mutation.key)\n ) {\n completedOptimisticOps.set(mutation.key, {\n type: mutation.type,\n value: mutation.modified,\n })\n }\n }\n }\n }\n\n // Now check what actually changed in the final visible state\n for (const key of changedKeys) {\n const previousVisibleValue = currentVisibleState.get(key)\n const newVisibleValue = this.get(key) // This returns the new derived state\n\n // Check if this sync operation is redundant with a completed optimistic operation\n const completedOp = completedOptimisticOps.get(key)\n let isRedundantSync = false\n\n if (completedOp) {\n if (\n completedOp.type === `delete` &&\n previousVisibleValue !== undefined &&\n newVisibleValue === undefined &&\n deepEquals(completedOp.value, previousVisibleValue)\n ) {\n isRedundantSync = true\n } else if (\n newVisibleValue !== undefined &&\n deepEquals(completedOp.value, newVisibleValue)\n ) {\n isRedundantSync = true\n }\n }\n\n if (!isRedundantSync) {\n if (\n previousVisibleValue === undefined &&\n newVisibleValue !== undefined\n ) {\n events.push({\n type: `insert`,\n key,\n value: newVisibleValue,\n })\n } else if (\n previousVisibleValue !== undefined &&\n newVisibleValue === undefined\n ) {\n events.push({\n type: `delete`,\n key,\n value: previousVisibleValue,\n })\n } else if (\n previousVisibleValue !== undefined &&\n newVisibleValue !== undefined &&\n !deepEquals(previousVisibleValue, newVisibleValue)\n ) {\n events.push({\n type: `update`,\n key,\n value: newVisibleValue,\n previousValue: previousVisibleValue,\n })\n }\n }\n }\n\n // Update cached size after synced data changes\n this.size = this.calculateSize()\n\n // Update indexes for all events before emitting\n if (events.length > 0) {\n this.indexes.updateIndexes(events)\n }\n\n // End batching and emit all events (combines any batched events with sync events)\n this.changes.emitEvents(events, true)\n\n this.pendingSyncedTransactions = uncommittedSyncedTransactions\n\n // Clear the pre-sync state since sync operations are complete\n this.preSyncVisibleState.clear()\n\n // Clear recently synced keys after a microtask to allow recomputeOptimisticState to see them\n Promise.resolve().then(() => {\n this.recentlySyncedKeys.clear()\n })\n\n // Mark that we've received the first commit (for tracking purposes)\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n }\n }\n\n /**\n * Schedule cleanup of a transaction when it completes\n */\n public scheduleTransactionCleanup(transaction: Transaction<any>): void {\n // Only schedule cleanup for transactions that aren't already completed\n if (transaction.state === `completed`) {\n this.transactions.delete(transaction.id)\n return\n }\n\n // Schedule cleanup when the transaction completes\n transaction.isPersisted.promise\n .then(() => {\n // Transaction completed successfully, remove it immediately\n this.transactions.delete(transaction.id)\n })\n .catch(() => {\n // Transaction failed, but we want to keep failed transactions for reference\n // so don't remove it.\n // This empty catch block is necessary to prevent unhandled promise rejections.\n })\n }\n\n /**\n * Capture visible state for keys that will be affected by pending sync operations\n * This must be called BEFORE onTransactionStateChange clears optimistic state\n */\n public capturePreSyncVisibleState(): void {\n if (this.pendingSyncedTransactions.length === 0) return\n\n // Get all keys that will be affected by sync operations\n const syncedKeys = new Set<TKey>()\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n syncedKeys.add(operation.key as TKey)\n }\n }\n\n // Mark keys as about to be synced to suppress intermediate events from recomputeOptimisticState\n for (const key of syncedKeys) {\n this.recentlySyncedKeys.add(key)\n }\n\n // Only capture current visible state for keys that will be affected by sync operations\n // This is much more efficient than capturing the entire collection state\n // Only capture keys that haven't been captured yet to preserve earlier captures\n for (const key of syncedKeys) {\n if (!this.preSyncVisibleState.has(key)) {\n const currentValue = this.get(key)\n if (currentValue !== undefined) {\n this.preSyncVisibleState.set(key, currentValue)\n }\n }\n }\n }\n\n /**\n * Trigger a recomputation when transactions change\n * This method should be called by the Transaction class when state changes\n */\n public onTransactionStateChange(): void {\n // Check if commitPendingTransactions will be called after this\n // by checking if there are pending sync transactions (same logic as in transactions.ts)\n this.changes.shouldBatchEvents = this.pendingSyncedTransactions.length > 0\n\n // CRITICAL: Capture visible state BEFORE clearing optimistic state\n this.capturePreSyncVisibleState()\n\n this.recomputeOptimisticState(false)\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.syncedData.clear()\n this.syncedMetadata.clear()\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n this.size = 0\n this.pendingSyncedTransactions = []\n this.syncedKeys.clear()\n this.hasReceivedFirstCommit = false\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.js","sources":["../../../src/collection/state.ts"],"sourcesContent":["import { deepEquals } from '../utils'\nimport { SortedMap } from '../SortedMap'\nimport type { Transaction } from '../transactions'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ChangeMessage,\n CollectionConfig,\n OptimisticChangeMessage,\n} from '../types'\nimport type { CollectionImpl } from './index.js'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionEventsManager } from './events'\n\ninterface PendingSyncedTransaction<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n truncate?: boolean\n deletedKeys: Set<string | number>\n optimisticSnapshot?: {\n upserts: Map<TKey, T>\n deletes: Set<TKey>\n }\n}\n\nexport class CollectionStateManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n public config!: CollectionConfig<TOutput, TKey, TSchema>\n public collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n public lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n public changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n public indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private _events!: CollectionEventsManager\n\n // Core state - make public for testing\n public transactions: SortedMap<string, Transaction<any>>\n public pendingSyncedTransactions: Array<\n PendingSyncedTransaction<TOutput, TKey>\n > = []\n public syncedData: SortedMap<TKey, TOutput>\n public syncedMetadata = new Map<TKey, unknown>()\n\n // Optimistic state tracking - make public for testing\n public optimisticUpserts = new Map<TKey, TOutput>()\n public optimisticDeletes = new Set<TKey>()\n\n // Cached size for performance\n public size = 0\n\n // State used for computing the change events\n public syncedKeys = new Set<TKey>()\n public preSyncVisibleState = new Map<TKey, TOutput>()\n public recentlySyncedKeys = new Set<TKey>()\n public hasReceivedFirstCommit = false\n public isCommittingSyncTransactions = false\n\n /**\n * Creates a new CollectionState manager\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>) {\n this.config = config\n this.transactions = new SortedMap<string, Transaction<any>>((a, b) =>\n a.compareCreatedAt(b),\n )\n\n // Set up data storage - always use SortedMap for deterministic iteration.\n // If a custom compare function is provided, use it; otherwise entries are sorted by key only.\n this.syncedData = new SortedMap<TKey, TOutput>(config.compare)\n }\n\n setDeps(deps: {\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n }) {\n this.collection = deps.collection\n this.lifecycle = deps.lifecycle\n this.changes = deps.changes\n this.indexes = deps.indexes\n this._events = deps.events\n }\n\n /**\n * Get the current value for a key (virtual derived state)\n */\n public get(key: TKey): TOutput | undefined {\n const { optimisticDeletes, optimisticUpserts, syncedData } = this\n // Check if optimistically deleted\n if (optimisticDeletes.has(key)) {\n return undefined\n }\n\n // Check optimistic upserts first\n if (optimisticUpserts.has(key)) {\n return optimisticUpserts.get(key)\n }\n\n // Fall back to synced data\n return syncedData.get(key)\n }\n\n /**\n * Check if a key exists in the collection (virtual derived state)\n */\n public has(key: TKey): boolean {\n const { optimisticDeletes, optimisticUpserts, syncedData } = this\n // Check if optimistically deleted\n if (optimisticDeletes.has(key)) {\n return false\n }\n\n // Check optimistic upserts first\n if (optimisticUpserts.has(key)) {\n return true\n }\n\n // Fall back to synced data\n return syncedData.has(key)\n }\n\n /**\n * Get all keys (virtual derived state)\n */\n public *keys(): IterableIterator<TKey> {\n const { syncedData, optimisticDeletes, optimisticUpserts } = this\n // Yield keys from synced data, skipping any that are deleted.\n for (const key of syncedData.keys()) {\n if (!optimisticDeletes.has(key)) {\n yield key\n }\n }\n // Yield keys from upserts that were not already in synced data.\n for (const key of optimisticUpserts.keys()) {\n if (!syncedData.has(key) && !optimisticDeletes.has(key)) {\n // The optimisticDeletes check is technically redundant if inserts/updates always remove from deletes,\n // but it's safer to keep it.\n yield key\n }\n }\n }\n\n /**\n * Get all values (virtual derived state)\n */\n public *values(): IterableIterator<TOutput> {\n for (const key of this.keys()) {\n const value = this.get(key)\n if (value !== undefined) {\n yield value\n }\n }\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *entries(): IterableIterator<[TKey, TOutput]> {\n for (const key of this.keys()) {\n const value = this.get(key)\n if (value !== undefined) {\n yield [key, value]\n }\n }\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *[Symbol.iterator](): IterableIterator<[TKey, TOutput]> {\n for (const [key, value] of this.entries()) {\n yield [key, value]\n }\n }\n\n /**\n * Execute a callback for each entry in the collection\n */\n public forEach(\n callbackfn: (value: TOutput, key: TKey, index: number) => void,\n ): void {\n let index = 0\n for (const [key, value] of this.entries()) {\n callbackfn(value, key, index++)\n }\n }\n\n /**\n * Create a new array with the results of calling a function for each entry in the collection\n */\n public map<U>(\n callbackfn: (value: TOutput, key: TKey, index: number) => U,\n ): Array<U> {\n const result: Array<U> = []\n let index = 0\n for (const [key, value] of this.entries()) {\n result.push(callbackfn(value, key, index++))\n }\n return result\n }\n\n /**\n * Check if the given collection is this collection\n * @param collection The collection to check\n * @returns True if the given collection is this collection, false otherwise\n */\n private isThisCollection(\n collection: CollectionImpl<any, any, any, any, any>,\n ): boolean {\n return collection === this.collection\n }\n\n /**\n * Recompute optimistic state from active transactions\n */\n public recomputeOptimisticState(\n triggeredByUserAction: boolean = false,\n ): void {\n // Skip redundant recalculations when we're in the middle of committing sync transactions\n // While the sync pipeline is replaying a large batch we still want to honour\n // fresh optimistic mutations from the UI. Only skip recompute for the\n // internal sync-driven redraws; user-triggered work (triggeredByUserAction)\n // must run so live queries stay responsive during long commits.\n if (this.isCommittingSyncTransactions && !triggeredByUserAction) {\n return\n }\n\n const previousState = new Map(this.optimisticUpserts)\n const previousDeletes = new Set(this.optimisticDeletes)\n\n // Clear current optimistic state\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n\n const activeTransactions: Array<Transaction<any>> = []\n\n for (const transaction of this.transactions.values()) {\n if (![`completed`, `failed`].includes(transaction.state)) {\n activeTransactions.push(transaction)\n }\n }\n\n // Apply active transactions only (completed transactions are handled by sync operations)\n for (const transaction of activeTransactions) {\n for (const mutation of transaction.mutations) {\n if (this.isThisCollection(mutation.collection) && mutation.optimistic) {\n switch (mutation.type) {\n case `insert`:\n case `update`:\n this.optimisticUpserts.set(\n mutation.key,\n mutation.modified as TOutput,\n )\n this.optimisticDeletes.delete(mutation.key)\n break\n case `delete`:\n this.optimisticUpserts.delete(mutation.key)\n this.optimisticDeletes.add(mutation.key)\n break\n }\n }\n }\n }\n\n // Update cached size\n this.size = this.calculateSize()\n\n // Collect events for changes\n const events: Array<ChangeMessage<TOutput, TKey>> = []\n this.collectOptimisticChanges(previousState, previousDeletes, events)\n\n // Filter out events for recently synced keys to prevent duplicates\n // BUT: Only filter out events that are actually from sync operations\n // New user transactions should NOT be filtered even if the key was recently synced\n const filteredEventsBySyncStatus = events.filter((event) => {\n if (!this.recentlySyncedKeys.has(event.key)) {\n return true // Key not recently synced, allow event through\n }\n\n // Key was recently synced - allow if this is a user-triggered action\n if (triggeredByUserAction) {\n return true\n }\n\n // Otherwise filter out duplicate sync events\n return false\n })\n\n // Filter out redundant delete events if there are pending sync transactions\n // that will immediately restore the same data, but only for completed transactions\n // IMPORTANT: Skip complex filtering for user-triggered actions to prevent UI blocking\n if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {\n const pendingSyncKeys = new Set<TKey>()\n\n // Collect keys from pending sync operations\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n pendingSyncKeys.add(operation.key as TKey)\n }\n }\n\n // Only filter out delete events for keys that:\n // 1. Have pending sync operations AND\n // 2. Are from completed transactions (being cleaned up)\n const filteredEvents = filteredEventsBySyncStatus.filter((event) => {\n if (event.type === `delete` && pendingSyncKeys.has(event.key)) {\n // Check if this delete is from clearing optimistic state of completed transactions\n // We can infer this by checking if we have no remaining optimistic mutations for this key\n const hasActiveOptimisticMutation = activeTransactions.some((tx) =>\n tx.mutations.some(\n (m) => this.isThisCollection(m.collection) && m.key === event.key,\n ),\n )\n\n if (!hasActiveOptimisticMutation) {\n return false // Skip this delete event as sync will restore the data\n }\n }\n return true\n })\n\n // Update indexes for the filtered events\n if (filteredEvents.length > 0) {\n this.indexes.updateIndexes(filteredEvents)\n }\n this.changes.emitEvents(filteredEvents, triggeredByUserAction)\n } else {\n // Update indexes for all events\n if (filteredEventsBySyncStatus.length > 0) {\n this.indexes.updateIndexes(filteredEventsBySyncStatus)\n }\n // Emit all events if no pending sync transactions\n this.changes.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction)\n }\n }\n\n /**\n * Calculate the current size based on synced data and optimistic changes\n */\n private calculateSize(): number {\n const syncedSize = this.syncedData.size\n const deletesFromSynced = Array.from(this.optimisticDeletes).filter(\n (key) => this.syncedData.has(key) && !this.optimisticUpserts.has(key),\n ).length\n const upsertsNotInSynced = Array.from(this.optimisticUpserts.keys()).filter(\n (key) => !this.syncedData.has(key),\n ).length\n\n return syncedSize - deletesFromSynced + upsertsNotInSynced\n }\n\n /**\n * Collect events for optimistic changes\n */\n private collectOptimisticChanges(\n previousUpserts: Map<TKey, TOutput>,\n previousDeletes: Set<TKey>,\n events: Array<ChangeMessage<TOutput, TKey>>,\n ): void {\n const allKeys = new Set([\n ...previousUpserts.keys(),\n ...this.optimisticUpserts.keys(),\n ...previousDeletes,\n ...this.optimisticDeletes,\n ])\n\n for (const key of allKeys) {\n const currentValue = this.get(key)\n const previousValue = this.getPreviousValue(\n key,\n previousUpserts,\n previousDeletes,\n )\n\n if (previousValue !== undefined && currentValue === undefined) {\n events.push({ type: `delete`, key, value: previousValue })\n } else if (previousValue === undefined && currentValue !== undefined) {\n events.push({ type: `insert`, key, value: currentValue })\n } else if (\n previousValue !== undefined &&\n currentValue !== undefined &&\n previousValue !== currentValue\n ) {\n events.push({\n type: `update`,\n key,\n value: currentValue,\n previousValue,\n })\n }\n }\n }\n\n /**\n * Get the previous value for a key given previous optimistic state\n */\n private getPreviousValue(\n key: TKey,\n previousUpserts: Map<TKey, TOutput>,\n previousDeletes: Set<TKey>,\n ): TOutput | undefined {\n if (previousDeletes.has(key)) {\n return undefined\n }\n if (previousUpserts.has(key)) {\n return previousUpserts.get(key)\n }\n return this.syncedData.get(key)\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n // Check if there are any persisting transaction\n let hasPersistingTransaction = false\n for (const transaction of this.transactions.values()) {\n if (transaction.state === `persisting`) {\n hasPersistingTransaction = true\n break\n }\n }\n\n // pending synced transactions could be either `committed` or still open.\n // we only want to process `committed` transactions here\n const {\n committedSyncedTransactions,\n uncommittedSyncedTransactions,\n hasTruncateSync,\n } = this.pendingSyncedTransactions.reduce(\n (acc, t) => {\n if (t.committed) {\n acc.committedSyncedTransactions.push(t)\n if (t.truncate === true) {\n acc.hasTruncateSync = true\n }\n } else {\n acc.uncommittedSyncedTransactions.push(t)\n }\n return acc\n },\n {\n committedSyncedTransactions: [] as Array<\n PendingSyncedTransaction<TOutput, TKey>\n >,\n uncommittedSyncedTransactions: [] as Array<\n PendingSyncedTransaction<TOutput, TKey>\n >,\n hasTruncateSync: false,\n },\n )\n\n if (!hasPersistingTransaction || hasTruncateSync) {\n // Set flag to prevent redundant optimistic state recalculations\n this.isCommittingSyncTransactions = true\n\n // Get the optimistic snapshot from the truncate transaction (captured when truncate() was called)\n const truncateOptimisticSnapshot = hasTruncateSync\n ? committedSyncedTransactions.find((t) => t.truncate)\n ?.optimisticSnapshot\n : null\n\n // First collect all keys that will be affected by sync operations\n const changedKeys = new Set<TKey>()\n for (const transaction of committedSyncedTransactions) {\n for (const operation of transaction.operations) {\n changedKeys.add(operation.key as TKey)\n }\n }\n\n // Use pre-captured state if available (from optimistic scenarios),\n // otherwise capture current state (for pure sync scenarios)\n let currentVisibleState = this.preSyncVisibleState\n if (currentVisibleState.size === 0) {\n // No pre-captured state, capture it now for pure sync operations\n currentVisibleState = new Map<TKey, TOutput>()\n for (const key of changedKeys) {\n const currentValue = this.get(key)\n if (currentValue !== undefined) {\n currentVisibleState.set(key, currentValue)\n }\n }\n }\n\n const events: Array<ChangeMessage<TOutput, TKey>> = []\n const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`\n\n for (const transaction of committedSyncedTransactions) {\n // Handle truncate operations first\n if (transaction.truncate) {\n // TRUNCATE PHASE\n // 1) Emit a delete for every visible key (synced + optimistic) so downstream listeners/indexes\n // observe a clear-before-rebuild. We intentionally skip keys already in\n // optimisticDeletes because their delete was previously emitted by the user.\n // Use the snapshot to ensure we emit deletes for all items that existed at truncate start.\n const visibleKeys = new Set([\n ...this.syncedData.keys(),\n ...(truncateOptimisticSnapshot?.upserts.keys() || []),\n ])\n for (const key of visibleKeys) {\n if (truncateOptimisticSnapshot?.deletes.has(key)) continue\n const previousValue =\n truncateOptimisticSnapshot?.upserts.get(key) ||\n this.syncedData.get(key)\n if (previousValue !== undefined) {\n events.push({ type: `delete`, key, value: previousValue })\n }\n }\n\n // 2) Clear the authoritative synced base. Subsequent server ops in this\n // same commit will rebuild the base atomically.\n this.syncedData.clear()\n this.syncedMetadata.clear()\n this.syncedKeys.clear()\n\n // 3) Clear currentVisibleState for truncated keys to ensure subsequent operations\n // are compared against the post-truncate state (undefined) rather than pre-truncate state\n // This ensures that re-inserted keys are emitted as INSERT events, not UPDATE events\n for (const key of changedKeys) {\n currentVisibleState.delete(key)\n }\n\n // 4) Emit truncate event so subscriptions can reset their cursor tracking state\n this._events.emit(`truncate`, {\n type: `truncate`,\n collection: this.collection,\n })\n }\n\n for (const operation of transaction.operations) {\n const key = operation.key as TKey\n this.syncedKeys.add(key)\n\n // Update metadata\n switch (operation.type) {\n case `insert`:\n this.syncedMetadata.set(key, operation.metadata)\n break\n case `update`:\n this.syncedMetadata.set(\n key,\n Object.assign(\n {},\n this.syncedMetadata.get(key),\n operation.metadata,\n ),\n )\n break\n case `delete`:\n this.syncedMetadata.delete(key)\n break\n }\n\n // Update synced data\n switch (operation.type) {\n case `insert`:\n this.syncedData.set(key, operation.value)\n break\n case `update`: {\n if (rowUpdateMode === `partial`) {\n const updatedValue = Object.assign(\n {},\n this.syncedData.get(key),\n operation.value,\n )\n this.syncedData.set(key, updatedValue)\n } else {\n this.syncedData.set(key, operation.value)\n }\n break\n }\n case `delete`:\n this.syncedData.delete(key)\n break\n }\n }\n }\n\n // After applying synced operations, if this commit included a truncate,\n // re-apply optimistic mutations on top of the fresh synced base. This ensures\n // the UI preserves local intent while respecting server rebuild semantics.\n // Ordering: deletes (above) -> server ops (just applied) -> optimistic upserts.\n if (hasTruncateSync) {\n // Avoid duplicating keys that were inserted/updated by synced operations in this commit\n const syncedInsertedOrUpdatedKeys = new Set<TKey>()\n for (const t of committedSyncedTransactions) {\n for (const op of t.operations) {\n if (op.type === `insert` || op.type === `update`) {\n syncedInsertedOrUpdatedKeys.add(op.key as TKey)\n }\n }\n }\n\n // Build re-apply sets from the snapshot taken at the start of this function.\n // This prevents losing optimistic state if transactions complete during truncate processing.\n const reapplyUpserts = new Map<TKey, TOutput>(\n truncateOptimisticSnapshot!.upserts,\n )\n const reapplyDeletes = new Set<TKey>(\n truncateOptimisticSnapshot!.deletes,\n )\n\n // Emit inserts for re-applied upserts, skipping any keys that have an optimistic delete.\n // If the server also inserted/updated the same key in this batch, override that value\n // with the optimistic value to preserve local intent.\n for (const [key, value] of reapplyUpserts) {\n if (reapplyDeletes.has(key)) continue\n if (syncedInsertedOrUpdatedKeys.has(key)) {\n let foundInsert = false\n for (let i = events.length - 1; i >= 0; i--) {\n const evt = events[i]!\n if (evt.key === key && evt.type === `insert`) {\n evt.value = value\n foundInsert = true\n break\n }\n }\n if (!foundInsert) {\n events.push({ type: `insert`, key, value })\n }\n } else {\n events.push({ type: `insert`, key, value })\n }\n }\n\n // Finally, ensure we do NOT insert keys that have an outstanding optimistic delete.\n if (events.length > 0 && reapplyDeletes.size > 0) {\n const filtered: Array<ChangeMessage<TOutput, TKey>> = []\n for (const evt of events) {\n if (evt.type === `insert` && reapplyDeletes.has(evt.key)) {\n continue\n }\n filtered.push(evt)\n }\n events.length = 0\n events.push(...filtered)\n }\n\n // Ensure listeners are active before emitting this critical batch\n if (this.lifecycle.status !== `ready`) {\n this.lifecycle.markReady()\n }\n }\n\n // Maintain optimistic state appropriately\n // Clear optimistic state since sync operations will now provide the authoritative data.\n // Any still-active user transactions will be re-applied below in recompute.\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n\n // Reset flag and recompute optimistic state for any remaining active transactions\n this.isCommittingSyncTransactions = false\n\n // If we had a truncate, restore the preserved optimistic state from the snapshot\n // This includes items from transactions that may have completed during processing\n if (hasTruncateSync && truncateOptimisticSnapshot) {\n for (const [key, value] of truncateOptimisticSnapshot.upserts) {\n this.optimisticUpserts.set(key, value)\n }\n for (const key of truncateOptimisticSnapshot.deletes) {\n this.optimisticDeletes.add(key)\n }\n }\n\n // Always overlay any still-active optimistic transactions so mutations that started\n // after the truncate snapshot are preserved.\n for (const transaction of this.transactions.values()) {\n if (![`completed`, `failed`].includes(transaction.state)) {\n for (const mutation of transaction.mutations) {\n if (\n this.isThisCollection(mutation.collection) &&\n mutation.optimistic\n ) {\n switch (mutation.type) {\n case `insert`:\n case `update`:\n this.optimisticUpserts.set(\n mutation.key,\n mutation.modified as TOutput,\n )\n this.optimisticDeletes.delete(mutation.key)\n break\n case `delete`:\n this.optimisticUpserts.delete(mutation.key)\n this.optimisticDeletes.add(mutation.key)\n break\n }\n }\n }\n }\n }\n\n // Check for redundant sync operations that match completed optimistic operations\n const completedOptimisticOps = new Map<TKey, any>()\n\n for (const transaction of this.transactions.values()) {\n if (transaction.state === `completed`) {\n for (const mutation of transaction.mutations) {\n if (\n mutation.optimistic &&\n this.isThisCollection(mutation.collection) &&\n changedKeys.has(mutation.key)\n ) {\n completedOptimisticOps.set(mutation.key, {\n type: mutation.type,\n value: mutation.modified,\n })\n }\n }\n }\n }\n\n // Now check what actually changed in the final visible state\n for (const key of changedKeys) {\n const previousVisibleValue = currentVisibleState.get(key)\n const newVisibleValue = this.get(key) // This returns the new derived state\n\n // Check if this sync operation is redundant with a completed optimistic operation\n const completedOp = completedOptimisticOps.get(key)\n let isRedundantSync = false\n\n if (completedOp) {\n if (\n completedOp.type === `delete` &&\n previousVisibleValue !== undefined &&\n newVisibleValue === undefined &&\n deepEquals(completedOp.value, previousVisibleValue)\n ) {\n isRedundantSync = true\n } else if (\n newVisibleValue !== undefined &&\n deepEquals(completedOp.value, newVisibleValue)\n ) {\n isRedundantSync = true\n }\n }\n\n if (!isRedundantSync) {\n if (\n previousVisibleValue === undefined &&\n newVisibleValue !== undefined\n ) {\n events.push({\n type: `insert`,\n key,\n value: newVisibleValue,\n })\n } else if (\n previousVisibleValue !== undefined &&\n newVisibleValue === undefined\n ) {\n events.push({\n type: `delete`,\n key,\n value: previousVisibleValue,\n })\n } else if (\n previousVisibleValue !== undefined &&\n newVisibleValue !== undefined &&\n !deepEquals(previousVisibleValue, newVisibleValue)\n ) {\n events.push({\n type: `update`,\n key,\n value: newVisibleValue,\n previousValue: previousVisibleValue,\n })\n }\n }\n }\n\n // Update cached size after synced data changes\n this.size = this.calculateSize()\n\n // Update indexes for all events before emitting\n if (events.length > 0) {\n this.indexes.updateIndexes(events)\n }\n\n // End batching and emit all events (combines any batched events with sync events)\n this.changes.emitEvents(events, true)\n\n this.pendingSyncedTransactions = uncommittedSyncedTransactions\n\n // Clear the pre-sync state since sync operations are complete\n this.preSyncVisibleState.clear()\n\n // Clear recently synced keys after a microtask to allow recomputeOptimisticState to see them\n Promise.resolve().then(() => {\n this.recentlySyncedKeys.clear()\n })\n\n // Mark that we've received the first commit (for tracking purposes)\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n }\n }\n\n /**\n * Schedule cleanup of a transaction when it completes\n */\n public scheduleTransactionCleanup(transaction: Transaction<any>): void {\n // Only schedule cleanup for transactions that aren't already completed\n if (transaction.state === `completed`) {\n this.transactions.delete(transaction.id)\n return\n }\n\n // Schedule cleanup when the transaction completes\n transaction.isPersisted.promise\n .then(() => {\n // Transaction completed successfully, remove it immediately\n this.transactions.delete(transaction.id)\n })\n .catch(() => {\n // Transaction failed, but we want to keep failed transactions for reference\n // so don't remove it.\n // This empty catch block is necessary to prevent unhandled promise rejections.\n })\n }\n\n /**\n * Capture visible state for keys that will be affected by pending sync operations\n * This must be called BEFORE onTransactionStateChange clears optimistic state\n */\n public capturePreSyncVisibleState(): void {\n if (this.pendingSyncedTransactions.length === 0) return\n\n // Get all keys that will be affected by sync operations\n const syncedKeys = new Set<TKey>()\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n syncedKeys.add(operation.key as TKey)\n }\n }\n\n // Mark keys as about to be synced to suppress intermediate events from recomputeOptimisticState\n for (const key of syncedKeys) {\n this.recentlySyncedKeys.add(key)\n }\n\n // Only capture current visible state for keys that will be affected by sync operations\n // This is much more efficient than capturing the entire collection state\n // Only capture keys that haven't been captured yet to preserve earlier captures\n for (const key of syncedKeys) {\n if (!this.preSyncVisibleState.has(key)) {\n const currentValue = this.get(key)\n if (currentValue !== undefined) {\n this.preSyncVisibleState.set(key, currentValue)\n }\n }\n }\n }\n\n /**\n * Trigger a recomputation when transactions change\n * This method should be called by the Transaction class when state changes\n */\n public onTransactionStateChange(): void {\n // Check if commitPendingTransactions will be called after this\n // by checking if there are pending sync transactions (same logic as in transactions.ts)\n this.changes.shouldBatchEvents = this.pendingSyncedTransactions.length > 0\n\n // CRITICAL: Capture visible state BEFORE clearing optimistic state\n this.capturePreSyncVisibleState()\n\n this.recomputeOptimisticState(false)\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.syncedData.clear()\n this.syncedMetadata.clear()\n this.optimisticUpserts.clear()\n this.optimisticDeletes.clear()\n this.size = 0\n this.pendingSyncedTransactions = []\n this.syncedKeys.clear()\n this.hasReceivedFirstCommit = false\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -7,11 +7,18 @@ type RequestSnapshotOptions = {
|
|
|
7
7
|
where?: BasicExpression<boolean>;
|
|
8
8
|
optimizedOnly?: boolean;
|
|
9
9
|
trackLoadSubsetPromise?: boolean;
|
|
10
|
+
/** Optional orderBy to pass to loadSubset for backend optimization */
|
|
11
|
+
orderBy?: OrderBy;
|
|
12
|
+
/** Optional limit to pass to loadSubset for backend optimization */
|
|
13
|
+
limit?: number;
|
|
10
14
|
};
|
|
11
15
|
type RequestLimitedSnapshotOptions = {
|
|
12
16
|
orderBy: OrderBy;
|
|
13
17
|
limit: number;
|
|
14
|
-
|
|
18
|
+
/** All column values for cursor (first value used for local index, all values for sync layer) */
|
|
19
|
+
minValues?: Array<unknown>;
|
|
20
|
+
/** Row offset for offset-based pagination (passed to sync layer) */
|
|
21
|
+
offset?: number;
|
|
15
22
|
};
|
|
16
23
|
type CollectionSubscriptionOptions = {
|
|
17
24
|
includeInitialState?: boolean;
|
|
@@ -25,6 +32,7 @@ export declare class CollectionSubscription extends EventEmitter<SubscriptionEve
|
|
|
25
32
|
private callback;
|
|
26
33
|
private options;
|
|
27
34
|
private loadedInitialState;
|
|
35
|
+
private skipFiltering;
|
|
28
36
|
private snapshotSent;
|
|
29
37
|
/**
|
|
30
38
|
* Track all loadSubset calls made by this subscription so we can unload them on cleanup.
|
|
@@ -32,6 +40,8 @@ export declare class CollectionSubscription extends EventEmitter<SubscriptionEve
|
|
|
32
40
|
*/
|
|
33
41
|
private loadedSubsets;
|
|
34
42
|
private sentKeys;
|
|
43
|
+
private limitedSnapshotRowCount;
|
|
44
|
+
private lastSentKey;
|
|
35
45
|
private filteredCallback;
|
|
36
46
|
private orderByIndex;
|
|
37
47
|
private _status;
|
|
@@ -59,21 +69,33 @@ export declare class CollectionSubscription extends EventEmitter<SubscriptionEve
|
|
|
59
69
|
*/
|
|
60
70
|
requestSnapshot(opts?: RequestSnapshotOptions): boolean;
|
|
61
71
|
/**
|
|
62
|
-
* Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to
|
|
72
|
+
* Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to the cursor.
|
|
63
73
|
* Requires a range index to be set with `setOrderByIndex` prior to calling this method.
|
|
64
74
|
* It uses that range index to load the items in the order of the index.
|
|
65
|
-
*
|
|
75
|
+
*
|
|
76
|
+
* For multi-column orderBy:
|
|
77
|
+
* - Uses first value from `minValues` for LOCAL index operations (wide bounds, ensures no missed rows)
|
|
78
|
+
* - Uses all `minValues` to build a precise composite cursor for SYNC layer loadSubset
|
|
79
|
+
*
|
|
80
|
+
* Note 1: it may load more rows than the provided LIMIT because it loads all values equal to the first cursor value + limit values greater.
|
|
66
81
|
* This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.
|
|
67
82
|
* Note 2: it does not send keys that have already been sent before.
|
|
68
83
|
*/
|
|
69
|
-
requestLimitedSnapshot({ orderBy, limit,
|
|
84
|
+
requestLimitedSnapshot({ orderBy, limit, minValues, offset, }: RequestLimitedSnapshotOptions): void;
|
|
70
85
|
/**
|
|
71
86
|
* Filters and flips changes for keys that have not been sent yet.
|
|
72
87
|
* Deletes are filtered out for keys that have not been sent yet.
|
|
73
88
|
* Updates are flipped into inserts for keys that have not been sent yet.
|
|
89
|
+
* Duplicate inserts are filtered out to prevent D2 multiplicity > 1.
|
|
74
90
|
*/
|
|
75
91
|
private filterAndFlipChanges;
|
|
76
92
|
private trackSentKeys;
|
|
93
|
+
/**
|
|
94
|
+
* Mark that the subscription should not filter any changes.
|
|
95
|
+
* This is used when includeInitialState is explicitly set to false,
|
|
96
|
+
* meaning the caller doesn't want initial state but does want ALL future changes.
|
|
97
|
+
*/
|
|
98
|
+
markAllStateAsSeen(): void;
|
|
77
99
|
unsubscribe(): void;
|
|
78
100
|
}
|
|
79
101
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ensureIndexForExpression } from "../indexes/auto-index.js";
|
|
2
|
-
import { and, eq,
|
|
2
|
+
import { and, eq, lt, gte } from "../query/builder/functions.js";
|
|
3
3
|
import { Value } from "../query/ir.js";
|
|
4
4
|
import { EventEmitter } from "../event-emitter.js";
|
|
5
|
+
import { buildCursor } from "../utils/cursor.js";
|
|
5
6
|
import { createFilteredCallback, createFilterFunctionFromExpression } from "./change-events.js";
|
|
6
7
|
class CollectionSubscription extends EventEmitter {
|
|
7
8
|
constructor(collection, callback, options) {
|
|
@@ -10,9 +11,11 @@ class CollectionSubscription extends EventEmitter {
|
|
|
10
11
|
this.callback = callback;
|
|
11
12
|
this.options = options;
|
|
12
13
|
this.loadedInitialState = false;
|
|
14
|
+
this.skipFiltering = false;
|
|
13
15
|
this.snapshotSent = false;
|
|
14
16
|
this.loadedSubsets = [];
|
|
15
17
|
this.sentKeys = /* @__PURE__ */ new Set();
|
|
18
|
+
this.limitedSnapshotRowCount = 0;
|
|
16
19
|
this._status = `ready`;
|
|
17
20
|
this.pendingLoadSubsetPromises = /* @__PURE__ */ new Set();
|
|
18
21
|
if (options.onUnsubscribe) {
|
|
@@ -113,7 +116,10 @@ class CollectionSubscription extends EventEmitter {
|
|
|
113
116
|
}
|
|
114
117
|
const loadOptions = {
|
|
115
118
|
where: stateOpts.where,
|
|
116
|
-
subscription: this
|
|
119
|
+
subscription: this,
|
|
120
|
+
// Include orderBy and limit if provided so sync layer can optimize the query
|
|
121
|
+
orderBy: opts?.orderBy,
|
|
122
|
+
limit: opts?.limit
|
|
117
123
|
};
|
|
118
124
|
const syncResult = this.collection._sync.loadSubset(loadOptions);
|
|
119
125
|
this.loadedSubsets.push(loadOptions);
|
|
@@ -128,22 +134,31 @@ class CollectionSubscription extends EventEmitter {
|
|
|
128
134
|
const filteredSnapshot = snapshot.filter(
|
|
129
135
|
(change) => !this.sentKeys.has(change.key)
|
|
130
136
|
);
|
|
137
|
+
for (const change of filteredSnapshot) {
|
|
138
|
+
this.sentKeys.add(change.key);
|
|
139
|
+
}
|
|
131
140
|
this.snapshotSent = true;
|
|
132
141
|
this.callback(filteredSnapshot);
|
|
133
142
|
return true;
|
|
134
143
|
}
|
|
135
144
|
/**
|
|
136
|
-
* Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to
|
|
145
|
+
* Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to the cursor.
|
|
137
146
|
* Requires a range index to be set with `setOrderByIndex` prior to calling this method.
|
|
138
147
|
* It uses that range index to load the items in the order of the index.
|
|
139
|
-
*
|
|
148
|
+
*
|
|
149
|
+
* For multi-column orderBy:
|
|
150
|
+
* - Uses first value from `minValues` for LOCAL index operations (wide bounds, ensures no missed rows)
|
|
151
|
+
* - Uses all `minValues` to build a precise composite cursor for SYNC layer loadSubset
|
|
152
|
+
*
|
|
153
|
+
* Note 1: it may load more rows than the provided LIMIT because it loads all values equal to the first cursor value + limit values greater.
|
|
140
154
|
* This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.
|
|
141
155
|
* Note 2: it does not send keys that have already been sent before.
|
|
142
156
|
*/
|
|
143
157
|
requestLimitedSnapshot({
|
|
144
158
|
orderBy,
|
|
145
159
|
limit,
|
|
146
|
-
|
|
160
|
+
minValues,
|
|
161
|
+
offset
|
|
147
162
|
}) {
|
|
148
163
|
if (!limit) throw new Error(`limit is required`);
|
|
149
164
|
if (!this.orderByIndex) {
|
|
@@ -151,6 +166,8 @@ class CollectionSubscription extends EventEmitter {
|
|
|
151
166
|
`Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`
|
|
152
167
|
);
|
|
153
168
|
}
|
|
169
|
+
const minValue = minValues?.[0];
|
|
170
|
+
const minValueForIndex = minValue;
|
|
154
171
|
const index = this.orderByIndex;
|
|
155
172
|
const where = this.options.whereExpression;
|
|
156
173
|
const whereFilterFn = where ? createFilterFunctionFromExpression(where) : void 0;
|
|
@@ -164,28 +181,28 @@ class CollectionSubscription extends EventEmitter {
|
|
|
164
181
|
}
|
|
165
182
|
return whereFilterFn?.(value) ?? true;
|
|
166
183
|
};
|
|
167
|
-
let biggestObservedValue =
|
|
184
|
+
let biggestObservedValue = minValueForIndex;
|
|
168
185
|
const changes = [];
|
|
169
186
|
let keys = [];
|
|
170
|
-
if (
|
|
187
|
+
if (minValueForIndex !== void 0) {
|
|
171
188
|
const { expression } = orderBy[0];
|
|
172
189
|
const allRowsWithMinValue = this.collection.currentStateAsChanges({
|
|
173
|
-
where: eq(expression, new Value(
|
|
190
|
+
where: eq(expression, new Value(minValueForIndex))
|
|
174
191
|
});
|
|
175
192
|
if (allRowsWithMinValue) {
|
|
176
193
|
const keysWithMinValue = allRowsWithMinValue.map((change) => change.key).filter((key) => !this.sentKeys.has(key) && filterFn(key));
|
|
177
194
|
keys.push(...keysWithMinValue);
|
|
178
195
|
const keysGreaterThanMin = index.take(
|
|
179
196
|
limit - keys.length,
|
|
180
|
-
|
|
197
|
+
minValueForIndex,
|
|
181
198
|
filterFn
|
|
182
199
|
);
|
|
183
200
|
keys.push(...keysGreaterThanMin);
|
|
184
201
|
} else {
|
|
185
|
-
keys = index.take(limit,
|
|
202
|
+
keys = index.take(limit, minValueForIndex, filterFn);
|
|
186
203
|
}
|
|
187
204
|
} else {
|
|
188
|
-
keys = index.take(limit,
|
|
205
|
+
keys = index.take(limit, minValueForIndex, filterFn);
|
|
189
206
|
}
|
|
190
207
|
const valuesNeeded = () => Math.max(limit - changes.length, 0);
|
|
191
208
|
const collectionExhausted = () => keys.length === 0;
|
|
@@ -203,55 +220,52 @@ class CollectionSubscription extends EventEmitter {
|
|
|
203
220
|
}
|
|
204
221
|
keys = index.take(valuesNeeded(), biggestObservedValue, filterFn);
|
|
205
222
|
}
|
|
223
|
+
const currentOffset = this.limitedSnapshotRowCount;
|
|
224
|
+
for (const change of changes) {
|
|
225
|
+
this.sentKeys.add(change.key);
|
|
226
|
+
}
|
|
206
227
|
this.callback(changes);
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
228
|
+
this.limitedSnapshotRowCount += changes.length;
|
|
229
|
+
if (changes.length > 0) {
|
|
230
|
+
this.lastSentKey = changes[changes.length - 1].key;
|
|
231
|
+
}
|
|
232
|
+
let cursorExpressions;
|
|
233
|
+
if (minValues !== void 0 && minValues.length > 0) {
|
|
234
|
+
const whereFromCursor = buildCursor(orderBy, minValues);
|
|
235
|
+
if (whereFromCursor) {
|
|
236
|
+
const { expression } = orderBy[0];
|
|
237
|
+
const minValue2 = minValues[0];
|
|
238
|
+
let whereCurrentCursor;
|
|
239
|
+
if (minValue2 instanceof Date) {
|
|
240
|
+
const minValuePlus1ms = new Date(minValue2.getTime() + 1);
|
|
241
|
+
whereCurrentCursor = and(
|
|
242
|
+
gte(expression, new Value(minValue2)),
|
|
243
|
+
lt(expression, new Value(minValuePlus1ms))
|
|
244
|
+
);
|
|
245
|
+
} else {
|
|
246
|
+
whereCurrentCursor = eq(expression, new Value(minValue2));
|
|
247
|
+
}
|
|
248
|
+
cursorExpressions = {
|
|
249
|
+
whereFrom: whereFromCursor,
|
|
250
|
+
whereCurrent: whereCurrentCursor,
|
|
251
|
+
lastKey: this.lastSentKey
|
|
252
|
+
};
|
|
253
|
+
}
|
|
213
254
|
}
|
|
214
|
-
const
|
|
215
|
-
where
|
|
255
|
+
const loadOptions = {
|
|
256
|
+
where,
|
|
257
|
+
// Main filter only, no cursor
|
|
216
258
|
limit,
|
|
217
259
|
orderBy,
|
|
260
|
+
cursor: cursorExpressions,
|
|
261
|
+
// Cursor expressions passed separately
|
|
262
|
+
offset: offset ?? currentOffset,
|
|
263
|
+
// Use provided offset, or auto-tracked offset
|
|
218
264
|
subscription: this
|
|
219
265
|
};
|
|
220
|
-
const syncResult = this.collection._sync.loadSubset(
|
|
221
|
-
this.loadedSubsets.push(
|
|
222
|
-
|
|
223
|
-
if (typeof minValue !== `undefined`) {
|
|
224
|
-
const { expression } = orderBy[0];
|
|
225
|
-
let exactValueFilter;
|
|
226
|
-
if (minValue instanceof Date) {
|
|
227
|
-
const minValuePlus1ms = new Date(minValue.getTime() + 1);
|
|
228
|
-
exactValueFilter = and(
|
|
229
|
-
gte(expression, new Value(minValue)),
|
|
230
|
-
lt(expression, new Value(minValuePlus1ms))
|
|
231
|
-
);
|
|
232
|
-
} else {
|
|
233
|
-
exactValueFilter = eq(expression, new Value(minValue));
|
|
234
|
-
}
|
|
235
|
-
const loadOptions2 = {
|
|
236
|
-
where: exactValueFilter,
|
|
237
|
-
subscription: this
|
|
238
|
-
};
|
|
239
|
-
const equalValueResult = this.collection._sync.loadSubset(loadOptions2);
|
|
240
|
-
this.loadedSubsets.push(loadOptions2);
|
|
241
|
-
if (equalValueResult instanceof Promise) {
|
|
242
|
-
promises.push(equalValueResult);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (syncResult instanceof Promise) {
|
|
246
|
-
promises.push(syncResult);
|
|
247
|
-
}
|
|
248
|
-
if (promises.length > 0) {
|
|
249
|
-
const combinedPromise = Promise.all(promises).then(() => {
|
|
250
|
-
});
|
|
251
|
-
this.trackLoadSubsetPromise(combinedPromise);
|
|
252
|
-
} else {
|
|
253
|
-
this.trackLoadSubsetPromise(syncResult);
|
|
254
|
-
}
|
|
266
|
+
const syncResult = this.collection._sync.loadSubset(loadOptions);
|
|
267
|
+
this.loadedSubsets.push(loadOptions);
|
|
268
|
+
this.trackLoadSubsetPromise(syncResult);
|
|
255
269
|
}
|
|
256
270
|
// TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function
|
|
257
271
|
// and that that also works properly (i.e. does not skip duplicate values)
|
|
@@ -259,34 +273,54 @@ class CollectionSubscription extends EventEmitter {
|
|
|
259
273
|
* Filters and flips changes for keys that have not been sent yet.
|
|
260
274
|
* Deletes are filtered out for keys that have not been sent yet.
|
|
261
275
|
* Updates are flipped into inserts for keys that have not been sent yet.
|
|
276
|
+
* Duplicate inserts are filtered out to prevent D2 multiplicity > 1.
|
|
262
277
|
*/
|
|
263
278
|
filterAndFlipChanges(changes) {
|
|
264
|
-
if (this.loadedInitialState) {
|
|
279
|
+
if (this.loadedInitialState || this.skipFiltering) {
|
|
265
280
|
return changes;
|
|
266
281
|
}
|
|
267
282
|
const newChanges = [];
|
|
268
283
|
for (const change of changes) {
|
|
269
284
|
let newChange = change;
|
|
270
|
-
|
|
285
|
+
const keyInSentKeys = this.sentKeys.has(change.key);
|
|
286
|
+
if (!keyInSentKeys) {
|
|
271
287
|
if (change.type === `update`) {
|
|
272
288
|
newChange = { ...change, type: `insert`, previousValue: void 0 };
|
|
273
289
|
} else if (change.type === `delete`) {
|
|
274
290
|
continue;
|
|
275
291
|
}
|
|
276
292
|
this.sentKeys.add(change.key);
|
|
293
|
+
} else {
|
|
294
|
+
if (change.type === `insert`) {
|
|
295
|
+
continue;
|
|
296
|
+
} else if (change.type === `delete`) {
|
|
297
|
+
this.sentKeys.delete(change.key);
|
|
298
|
+
}
|
|
277
299
|
}
|
|
278
300
|
newChanges.push(newChange);
|
|
279
301
|
}
|
|
280
302
|
return newChanges;
|
|
281
303
|
}
|
|
282
304
|
trackSentKeys(changes) {
|
|
283
|
-
if (this.loadedInitialState) {
|
|
305
|
+
if (this.loadedInitialState || this.skipFiltering) {
|
|
284
306
|
return;
|
|
285
307
|
}
|
|
286
308
|
for (const change of changes) {
|
|
287
|
-
|
|
309
|
+
if (change.type === `delete`) {
|
|
310
|
+
this.sentKeys.delete(change.key);
|
|
311
|
+
} else {
|
|
312
|
+
this.sentKeys.add(change.key);
|
|
313
|
+
}
|
|
288
314
|
}
|
|
289
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Mark that the subscription should not filter any changes.
|
|
318
|
+
* This is used when includeInitialState is explicitly set to false,
|
|
319
|
+
* meaning the caller doesn't want initial state but does want ALL future changes.
|
|
320
|
+
*/
|
|
321
|
+
markAllStateAsSeen() {
|
|
322
|
+
this.skipFiltering = true;
|
|
323
|
+
}
|
|
290
324
|
unsubscribe() {
|
|
291
325
|
for (const options of this.loadedSubsets) {
|
|
292
326
|
this.collection._sync.unloadSubset(options);
|