@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.
Files changed (225) hide show
  1. package/dist/cjs/SortedMap.cjs +40 -26
  2. package/dist/cjs/SortedMap.cjs.map +1 -1
  3. package/dist/cjs/SortedMap.d.cts +10 -15
  4. package/dist/cjs/collection/change-events.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.cjs +2 -0
  6. package/dist/cjs/collection/changes.cjs.map +1 -1
  7. package/dist/cjs/collection/events.cjs.map +1 -1
  8. package/dist/cjs/collection/events.d.cts +12 -4
  9. package/dist/cjs/collection/index.cjs +2 -1
  10. package/dist/cjs/collection/index.cjs.map +1 -1
  11. package/dist/cjs/collection/indexes.cjs.map +1 -1
  12. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  13. package/dist/cjs/collection/mutations.cjs +5 -2
  14. package/dist/cjs/collection/mutations.cjs.map +1 -1
  15. package/dist/cjs/collection/state.cjs +6 -5
  16. package/dist/cjs/collection/state.cjs.map +1 -1
  17. package/dist/cjs/collection/state.d.cts +4 -1
  18. package/dist/cjs/collection/subscription.cjs +91 -57
  19. package/dist/cjs/collection/subscription.cjs.map +1 -1
  20. package/dist/cjs/collection/subscription.d.cts +26 -4
  21. package/dist/cjs/collection/sync.cjs +11 -6
  22. package/dist/cjs/collection/sync.cjs.map +1 -1
  23. package/dist/cjs/errors.cjs +9 -0
  24. package/dist/cjs/errors.cjs.map +1 -1
  25. package/dist/cjs/errors.d.cts +3 -0
  26. package/dist/cjs/event-emitter.cjs.map +1 -1
  27. package/dist/cjs/index.cjs +2 -0
  28. package/dist/cjs/index.cjs.map +1 -1
  29. package/dist/cjs/index.d.cts +1 -1
  30. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  31. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  32. package/dist/cjs/indexes/btree-index.cjs +8 -6
  33. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  34. package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
  35. package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
  36. package/dist/cjs/local-only.cjs.map +1 -1
  37. package/dist/cjs/local-storage.cjs.map +1 -1
  38. package/dist/cjs/optimistic-action.cjs.map +1 -1
  39. package/dist/cjs/paced-mutations.cjs.map +1 -1
  40. package/dist/cjs/proxy.cjs.map +1 -1
  41. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  42. package/dist/cjs/query/builder/index.cjs.map +1 -1
  43. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  47. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  48. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  49. package/dist/cjs/query/compiler/order-by.cjs +91 -38
  50. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  51. package/dist/cjs/query/compiler/order-by.d.cts +6 -2
  52. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  53. package/dist/cjs/query/expression-helpers.cjs.map +1 -1
  54. package/dist/cjs/query/index.d.cts +1 -1
  55. package/dist/cjs/query/ir.cjs.map +1 -1
  56. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  57. package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
  58. package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
  59. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  60. package/dist/cjs/query/live/internal.cjs.map +1 -1
  61. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  62. package/dist/cjs/query/optimizer.cjs.map +1 -1
  63. package/dist/cjs/query/predicate-utils.cjs +19 -2
  64. package/dist/cjs/query/predicate-utils.cjs.map +1 -1
  65. package/dist/cjs/query/predicate-utils.d.cts +32 -1
  66. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  67. package/dist/cjs/scheduler.cjs.map +1 -1
  68. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  69. package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
  70. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  71. package/dist/cjs/transactions.cjs.map +1 -1
  72. package/dist/cjs/types.d.cts +53 -8
  73. package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
  74. package/dist/cjs/utils/btree.cjs.map +1 -1
  75. package/dist/cjs/utils/comparison.cjs.map +1 -1
  76. package/dist/cjs/utils/cursor.cjs +39 -0
  77. package/dist/cjs/utils/cursor.cjs.map +1 -0
  78. package/dist/cjs/utils/cursor.d.cts +18 -0
  79. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  80. package/dist/cjs/utils.cjs.map +1 -1
  81. package/dist/esm/SortedMap.d.ts +10 -15
  82. package/dist/esm/SortedMap.js +40 -26
  83. package/dist/esm/SortedMap.js.map +1 -1
  84. package/dist/esm/collection/change-events.js.map +1 -1
  85. package/dist/esm/collection/changes.js +2 -0
  86. package/dist/esm/collection/changes.js.map +1 -1
  87. package/dist/esm/collection/events.d.ts +12 -4
  88. package/dist/esm/collection/events.js.map +1 -1
  89. package/dist/esm/collection/index.js +2 -1
  90. package/dist/esm/collection/index.js.map +1 -1
  91. package/dist/esm/collection/indexes.js.map +1 -1
  92. package/dist/esm/collection/lifecycle.js.map +1 -1
  93. package/dist/esm/collection/mutations.js +6 -3
  94. package/dist/esm/collection/mutations.js.map +1 -1
  95. package/dist/esm/collection/state.d.ts +4 -1
  96. package/dist/esm/collection/state.js +6 -5
  97. package/dist/esm/collection/state.js.map +1 -1
  98. package/dist/esm/collection/subscription.d.ts +26 -4
  99. package/dist/esm/collection/subscription.js +92 -58
  100. package/dist/esm/collection/subscription.js.map +1 -1
  101. package/dist/esm/collection/sync.js +11 -6
  102. package/dist/esm/collection/sync.js.map +1 -1
  103. package/dist/esm/errors.d.ts +3 -0
  104. package/dist/esm/errors.js +9 -0
  105. package/dist/esm/errors.js.map +1 -1
  106. package/dist/esm/event-emitter.js.map +1 -1
  107. package/dist/esm/index.d.ts +1 -1
  108. package/dist/esm/index.js +4 -2
  109. package/dist/esm/indexes/auto-index.js.map +1 -1
  110. package/dist/esm/indexes/base-index.js.map +1 -1
  111. package/dist/esm/indexes/btree-index.js +8 -6
  112. package/dist/esm/indexes/btree-index.js.map +1 -1
  113. package/dist/esm/indexes/lazy-index.js.map +1 -1
  114. package/dist/esm/indexes/reverse-index.js.map +1 -1
  115. package/dist/esm/local-only.js.map +1 -1
  116. package/dist/esm/local-storage.js.map +1 -1
  117. package/dist/esm/optimistic-action.js.map +1 -1
  118. package/dist/esm/paced-mutations.js.map +1 -1
  119. package/dist/esm/proxy.js.map +1 -1
  120. package/dist/esm/query/builder/functions.js.map +1 -1
  121. package/dist/esm/query/builder/index.js.map +1 -1
  122. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  123. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  124. package/dist/esm/query/compiler/expressions.js.map +1 -1
  125. package/dist/esm/query/compiler/group-by.js.map +1 -1
  126. package/dist/esm/query/compiler/index.js.map +1 -1
  127. package/dist/esm/query/compiler/joins.js.map +1 -1
  128. package/dist/esm/query/compiler/order-by.d.ts +6 -2
  129. package/dist/esm/query/compiler/order-by.js +91 -38
  130. package/dist/esm/query/compiler/order-by.js.map +1 -1
  131. package/dist/esm/query/compiler/select.js.map +1 -1
  132. package/dist/esm/query/expression-helpers.js.map +1 -1
  133. package/dist/esm/query/index.d.ts +1 -1
  134. package/dist/esm/query/ir.js.map +1 -1
  135. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  136. package/dist/esm/query/live/collection-registry.js.map +1 -1
  137. package/dist/esm/query/live/collection-subscriber.js +30 -15
  138. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  139. package/dist/esm/query/live/internal.js.map +1 -1
  140. package/dist/esm/query/live-query-collection.js.map +1 -1
  141. package/dist/esm/query/optimizer.js.map +1 -1
  142. package/dist/esm/query/predicate-utils.d.ts +32 -1
  143. package/dist/esm/query/predicate-utils.js +19 -2
  144. package/dist/esm/query/predicate-utils.js.map +1 -1
  145. package/dist/esm/query/subset-dedupe.js.map +1 -1
  146. package/dist/esm/scheduler.js.map +1 -1
  147. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  148. package/dist/esm/strategies/queueStrategy.js.map +1 -1
  149. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  150. package/dist/esm/transactions.js.map +1 -1
  151. package/dist/esm/types.d.ts +53 -8
  152. package/dist/esm/utils/browser-polyfills.js.map +1 -1
  153. package/dist/esm/utils/btree.js.map +1 -1
  154. package/dist/esm/utils/comparison.js.map +1 -1
  155. package/dist/esm/utils/cursor.d.ts +18 -0
  156. package/dist/esm/utils/cursor.js +39 -0
  157. package/dist/esm/utils/cursor.js.map +1 -0
  158. package/dist/esm/utils/index-optimization.js.map +1 -1
  159. package/dist/esm/utils.js.map +1 -1
  160. package/package.json +30 -28
  161. package/src/SortedMap.ts +50 -31
  162. package/src/collection/change-events.ts +20 -20
  163. package/src/collection/changes.ts +16 -12
  164. package/src/collection/events.ts +20 -10
  165. package/src/collection/index.ts +47 -46
  166. package/src/collection/indexes.ts +14 -14
  167. package/src/collection/lifecycle.ts +16 -16
  168. package/src/collection/mutations.ts +25 -20
  169. package/src/collection/state.ts +43 -36
  170. package/src/collection/subscription.ts +171 -90
  171. package/src/collection/sync.ts +34 -22
  172. package/src/duplicate-instance-check.ts +1 -1
  173. package/src/errors.ts +49 -40
  174. package/src/event-emitter.ts +5 -5
  175. package/src/index.ts +21 -21
  176. package/src/indexes/auto-index.ts +11 -11
  177. package/src/indexes/base-index.ts +13 -13
  178. package/src/indexes/btree-index.ts +21 -17
  179. package/src/indexes/index-options.ts +3 -3
  180. package/src/indexes/lazy-index.ts +8 -8
  181. package/src/indexes/reverse-index.ts +5 -5
  182. package/src/local-only.ts +12 -12
  183. package/src/local-storage.ts +17 -17
  184. package/src/optimistic-action.ts +5 -5
  185. package/src/paced-mutations.ts +6 -6
  186. package/src/proxy.ts +43 -43
  187. package/src/query/builder/functions.ts +28 -28
  188. package/src/query/builder/index.ts +22 -22
  189. package/src/query/builder/ref-proxy.ts +4 -4
  190. package/src/query/builder/types.ts +8 -8
  191. package/src/query/compiler/evaluators.ts +9 -9
  192. package/src/query/compiler/expressions.ts +6 -6
  193. package/src/query/compiler/group-by.ts +24 -24
  194. package/src/query/compiler/index.ts +44 -44
  195. package/src/query/compiler/joins.ts +37 -37
  196. package/src/query/compiler/order-by.ts +170 -77
  197. package/src/query/compiler/select.ts +13 -13
  198. package/src/query/compiler/types.ts +2 -2
  199. package/src/query/expression-helpers.ts +16 -16
  200. package/src/query/index.ts +10 -9
  201. package/src/query/ir.ts +13 -13
  202. package/src/query/live/collection-config-builder.ts +53 -53
  203. package/src/query/live/collection-registry.ts +6 -6
  204. package/src/query/live/collection-subscriber.ts +87 -48
  205. package/src/query/live/internal.ts +1 -1
  206. package/src/query/live/types.ts +4 -4
  207. package/src/query/live-query-collection.ts +15 -15
  208. package/src/query/optimizer.ts +29 -29
  209. package/src/query/predicate-utils.ts +105 -50
  210. package/src/query/subset-dedupe.ts +6 -6
  211. package/src/scheduler.ts +3 -3
  212. package/src/strategies/debounceStrategy.ts +6 -6
  213. package/src/strategies/index.ts +4 -4
  214. package/src/strategies/queueStrategy.ts +5 -5
  215. package/src/strategies/throttleStrategy.ts +6 -6
  216. package/src/strategies/types.ts +2 -2
  217. package/src/transactions.ts +9 -9
  218. package/src/types.ts +76 -18
  219. package/src/utils/array-utils.ts +1 -1
  220. package/src/utils/browser-polyfills.ts +2 -2
  221. package/src/utils/btree.ts +22 -22
  222. package/src/utils/comparison.ts +3 -3
  223. package/src/utils/cursor.ts +78 -0
  224. package/src/utils/index-optimization.ts +14 -14
  225. package/src/utils.ts +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.js","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from \"../indexes/auto-index.js\"\nimport { and, eq, gt, gte, lt } from \"../query/builder/functions.js\"\nimport { Value } from \"../query/ir.js\"\nimport { EventEmitter } from \"../event-emitter.js\"\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from \"./change-events.js\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\nimport type { IndexInterface } from \"../indexes/base-index.js\"\nimport type {\n ChangeMessage,\n LoadSubsetOptions,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from \"../types.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n trackLoadSubsetPromise?: boolean\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n minValue?: any\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n /**\n * Track all loadSubset calls made by this subscription so we can unload them on cleanup.\n * We store the exact LoadSubsetOptions we passed to loadSubset to ensure symmetric unload.\n */\n private loadedSubsets: Array<LoadSubsetOptions> = []\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions: LoadSubsetOptions = {\n where: stateOpts.where,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions)\n\n // Track this loadSubset call so we can unload it later\n this.loadedSubsets.push(loadOptions)\n\n const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true\n if (trackLoadSubsetPromise) {\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key)\n )\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.\n * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.\n * Note 2: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValue,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`\n )\n }\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValue\n const changes: Array<ChangeMessage<any, string | number>> = []\n\n // If we have a minValue we need to handle the case\n // where there might be duplicate values equal to minValue that we need to include\n // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]\n // so if minValue is 3 then the previous snapshot may not have included all 3s\n // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3\n // so we load all rows equal to minValue first, to be sure we don't skip any duplicate values\n let keys: Array<string | number> = []\n if (minValue !== undefined) {\n // First, get all items with the same value as minValue\n const { expression } = orderBy[0]!\n const allRowsWithMinValue = this.collection.currentStateAsChanges({\n where: eq(expression, new Value(minValue)),\n })\n\n if (allRowsWithMinValue) {\n const keysWithMinValue = allRowsWithMinValue\n .map((change) => change.key)\n .filter((key) => !this.sentKeys.has(key) && filterFn(key))\n\n // Add items with the minValue first\n keys.push(...keysWithMinValue)\n\n // Then get items greater than minValue\n const keysGreaterThanMin = index.take(\n limit - keys.length,\n minValue,\n filterFn\n )\n keys.push(...keysGreaterThanMin)\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n const insertedKeys = new Set<string | number>() // Track keys we add to `changes` in this iteration\n\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n insertedKeys.add(key) // Track this key\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n this.callback(changes)\n\n let whereWithValueFilter = where\n if (typeof minValue !== `undefined`) {\n // Only request data that we haven't seen yet (i.e. is bigger than the minValue)\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n const valueFilter = operator(expression, new Value(minValue))\n whereWithValueFilter = where ? and(where, valueFilter) : valueFilter\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions1: LoadSubsetOptions = {\n where: whereWithValueFilter,\n limit,\n orderBy,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions1)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions1)\n\n // Make parallel loadSubset calls for values equal to minValue and values greater than minValue\n const promises: Array<Promise<void>> = []\n\n // First promise: load all values equal to minValue\n if (typeof minValue !== `undefined`) {\n const { expression } = orderBy[0]!\n\n // For Date values, we need to handle precision differences between JS (ms) and backends (μs)\n // A JS Date represents a 1ms range, so we query for all values within that range\n let exactValueFilter\n if (minValue instanceof Date) {\n const minValuePlus1ms = new Date(minValue.getTime() + 1)\n exactValueFilter = and(\n gte(expression, new Value(minValue)),\n lt(expression, new Value(minValuePlus1ms))\n )\n } else {\n exactValueFilter = eq(expression, new Value(minValue))\n }\n\n const loadOptions2: LoadSubsetOptions = {\n where: exactValueFilter,\n subscription: this,\n }\n const equalValueResult = this.collection._sync.loadSubset(loadOptions2)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions2)\n\n if (equalValueResult instanceof Promise) {\n promises.push(equalValueResult)\n }\n }\n\n // Second promise: load values greater than minValue\n if (syncResult instanceof Promise) {\n promises.push(syncResult)\n }\n\n // Track the combined promise\n if (promises.length > 0) {\n const combinedPromise = Promise.all(promises).then(() => {})\n this.trackLoadSubsetPromise(combinedPromise)\n } else {\n this.trackLoadSubsetPromise(syncResult)\n }\n }\n\n // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function\n // and that that also works properly (i.e. does not skip duplicate values)\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState) {\n // We loaded the entire initial state\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState) {\n // No need to track sent keys if we loaded the entire state.\n // Since we sent everything, all keys must have been observed.\n return\n }\n\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n }\n\n unsubscribe() {\n // Unload all subsets that this subscription loaded\n // We pass the exact same LoadSubsetOptions we used for loadSubset\n for (const options of this.loadedSubsets) {\n this.collection._sync.unloadSubset(options)\n }\n this.loadedSubsets = []\n\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscription.js","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from '../indexes/auto-index.js'\nimport { and, eq, gte, lt } from '../query/builder/functions.js'\nimport { Value } from '../query/ir.js'\nimport { EventEmitter } from '../event-emitter.js'\nimport { buildCursor } from '../utils/cursor.js'\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from './change-events.js'\nimport type { BasicExpression, OrderBy } from '../query/ir.js'\nimport type { IndexInterface } from '../indexes/base-index.js'\nimport type {\n ChangeMessage,\n LoadSubsetOptions,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from '../types.js'\nimport type { CollectionImpl } from './index.js'\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n trackLoadSubsetPromise?: boolean\n /** Optional orderBy to pass to loadSubset for backend optimization */\n orderBy?: OrderBy\n /** Optional limit to pass to loadSubset for backend optimization */\n limit?: number\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n /** All column values for cursor (first value used for local index, all values for sync layer) */\n minValues?: Array<unknown>\n /** Row offset for offset-based pagination (passed to sync layer) */\n offset?: number\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to skip filtering in filterAndFlipChanges.\n // This is separate from loadedInitialState because we want to allow\n // requestSnapshot to still work even when filtering is skipped.\n private skipFiltering = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n /**\n * Track all loadSubset calls made by this subscription so we can unload them on cleanup.\n * We store the exact LoadSubsetOptions we passed to loadSubset to ensure symmetric unload.\n */\n private loadedSubsets: Array<LoadSubsetOptions> = []\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n // Track the count of rows sent via requestLimitedSnapshot for offset-based pagination\n private limitedSnapshotRowCount = 0\n\n // Track the last key sent via requestLimitedSnapshot for cursor-based pagination\n private lastSentKey: string | number | undefined\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions,\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>,\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions: LoadSubsetOptions = {\n where: stateOpts.where,\n subscription: this,\n // Include orderBy and limit if provided so sync layer can optimize the query\n orderBy: opts?.orderBy,\n limit: opts?.limit,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions)\n\n // Track this loadSubset call so we can unload it later\n this.loadedSubsets.push(loadOptions)\n\n const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true\n if (trackLoadSubsetPromise) {\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key),\n )\n\n // Add keys to sentKeys BEFORE calling callback to prevent race condition.\n // If a change event arrives while the callback is executing, it will see\n // the keys already in sentKeys and filter out duplicates correctly.\n for (const change of filteredSnapshot) {\n this.sentKeys.add(change.key)\n }\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to the cursor.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n *\n * For multi-column orderBy:\n * - Uses first value from `minValues` for LOCAL index operations (wide bounds, ensures no missed rows)\n * - Uses all `minValues` to build a precise composite cursor for SYNC layer loadSubset\n *\n * 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.\n * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.\n * Note 2: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValues,\n offset,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`,\n )\n }\n\n // Derive first column value from minValues (used for local index operations)\n const minValue = minValues?.[0]\n // Cast for index operations (index expects string | number)\n const minValueForIndex = minValue as string | number | undefined\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValueForIndex\n const changes: Array<ChangeMessage<any, string | number>> = []\n\n // If we have a minValue we need to handle the case\n // where there might be duplicate values equal to minValue that we need to include\n // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]\n // so if minValue is 3 then the previous snapshot may not have included all 3s\n // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3\n // so we load all rows equal to minValue first, to be sure we don't skip any duplicate values\n //\n // For multi-column orderBy, we use the first column value for index operations (wide bounds)\n // This may load some duplicates but ensures we never miss any rows.\n let keys: Array<string | number> = []\n if (minValueForIndex !== undefined) {\n // First, get all items with the same FIRST COLUMN value as minValue\n // This provides wide bounds for the local index\n const { expression } = orderBy[0]!\n const allRowsWithMinValue = this.collection.currentStateAsChanges({\n where: eq(expression, new Value(minValueForIndex)),\n })\n\n if (allRowsWithMinValue) {\n const keysWithMinValue = allRowsWithMinValue\n .map((change) => change.key)\n .filter((key) => !this.sentKeys.has(key) && filterFn(key))\n\n // Add items with the minValue first\n keys.push(...keysWithMinValue)\n\n // Then get items greater than minValue\n const keysGreaterThanMin = index.take(\n limit - keys.length,\n minValueForIndex,\n filterFn,\n )\n keys.push(...keysGreaterThanMin)\n } else {\n keys = index.take(limit, minValueForIndex, filterFn)\n }\n } else {\n keys = index.take(limit, minValueForIndex, filterFn)\n }\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n const insertedKeys = new Set<string | number>() // Track keys we add to `changes` in this iteration\n\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n insertedKeys.add(key) // Track this key\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n // Track row count for offset-based pagination (before sending to callback)\n // Use the current count as the offset for this load\n const currentOffset = this.limitedSnapshotRowCount\n\n // Add keys to sentKeys BEFORE calling callback to prevent race condition.\n // If a change event arrives while the callback is executing, it will see\n // the keys already in sentKeys and filter out duplicates correctly.\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n\n this.callback(changes)\n\n // Update the row count and last key after sending (for next call's offset/cursor)\n this.limitedSnapshotRowCount += changes.length\n if (changes.length > 0) {\n this.lastSentKey = changes[changes.length - 1]!.key\n }\n\n // Build cursor expressions for sync layer loadSubset\n // The cursor expressions are separate from the main where clause\n // so the sync layer can choose cursor-based or offset-based pagination\n let cursorExpressions:\n | {\n whereFrom: BasicExpression<boolean>\n whereCurrent: BasicExpression<boolean>\n lastKey?: string | number\n }\n | undefined\n\n if (minValues !== undefined && minValues.length > 0) {\n const whereFromCursor = buildCursor(orderBy, minValues)\n\n if (whereFromCursor) {\n const { expression } = orderBy[0]!\n const minValue = minValues[0]\n\n // Build the whereCurrent expression for the first orderBy column\n // For Date values, we need to handle precision differences between JS (ms) and backends (μs)\n // A JS Date represents a 1ms range, so we query for all values within that range\n let whereCurrentCursor: BasicExpression<boolean>\n if (minValue instanceof Date) {\n const minValuePlus1ms = new Date(minValue.getTime() + 1)\n whereCurrentCursor = and(\n gte(expression, new Value(minValue)),\n lt(expression, new Value(minValuePlus1ms)),\n )\n } else {\n whereCurrentCursor = eq(expression, new Value(minValue))\n }\n\n cursorExpressions = {\n whereFrom: whereFromCursor,\n whereCurrent: whereCurrentCursor,\n lastKey: this.lastSentKey,\n }\n }\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n // Note: `where` does NOT include cursor expressions - they are passed separately\n // The sync layer can choose to use cursor-based or offset-based pagination\n const loadOptions: LoadSubsetOptions = {\n where, // Main filter only, no cursor\n limit,\n orderBy,\n cursor: cursorExpressions, // Cursor expressions passed separately\n offset: offset ?? currentOffset, // Use provided offset, or auto-tracked offset\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions)\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function\n // and that that also works properly (i.e. does not skip duplicate values)\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n * Duplicate inserts are filtered out to prevent D2 multiplicity > 1.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState || this.skipFiltering) {\n // We loaded the entire initial state or filtering is explicitly skipped\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n const keyInSentKeys = this.sentKeys.has(change.key)\n\n if (!keyInSentKeys) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n } else {\n // Key was already sent - handle based on change type\n if (change.type === `insert`) {\n // Filter out duplicate inserts - the key was already inserted.\n // This prevents D2 multiplicity from going above 1, which would\n // cause deletes to not properly remove items (multiplicity would\n // go from 2 to 1 instead of 1 to 0).\n continue\n } else if (change.type === `delete`) {\n // Remove from sentKeys so future inserts for this key are allowed\n // (e.g., after truncate + reinsert)\n this.sentKeys.delete(change.key)\n }\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState || this.skipFiltering) {\n // No need to track sent keys if we loaded the entire state or filtering is skipped.\n // Since filtering won't be applied, all keys are effectively \"observed\".\n return\n }\n\n for (const change of changes) {\n if (change.type === `delete`) {\n // Remove deleted keys from sentKeys so future re-inserts are allowed\n this.sentKeys.delete(change.key)\n } else {\n // For inserts and updates, track the key as sent\n this.sentKeys.add(change.key)\n }\n }\n }\n\n /**\n * Mark that the subscription should not filter any changes.\n * This is used when includeInitialState is explicitly set to false,\n * meaning the caller doesn't want initial state but does want ALL future changes.\n */\n markAllStateAsSeen() {\n this.skipFiltering = true\n }\n\n unsubscribe() {\n // Unload all subsets that this subscription loaded\n // We pass the exact same LoadSubsetOptions we used for loadSubset\n for (const options of this.loadedSubsets) {\n this.collection._sync.unloadSubset(options)\n }\n this.loadedSubsets = []\n\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":["minValue"],"mappings":"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"}
@@ -41,7 +41,7 @@ class CollectionSyncManager {
41
41
  deletedKeys: /* @__PURE__ */ new Set()
42
42
  });
43
43
  },
44
- write: (messageWithoutKey) => {
44
+ write: (messageWithOptionalKey) => {
45
45
  const pendingTransaction = this.state.pendingSyncedTransactions[this.state.pendingSyncedTransactions.length - 1];
46
46
  if (!pendingTransaction) {
47
47
  throw new NoPendingSyncTransactionWriteError();
@@ -49,15 +49,20 @@ class CollectionSyncManager {
49
49
  if (pendingTransaction.committed) {
50
50
  throw new SyncTransactionAlreadyCommittedWriteError();
51
51
  }
52
- const key = this.config.getKey(messageWithoutKey.value);
53
- let messageType = messageWithoutKey.type;
54
- if (messageWithoutKey.type === `insert`) {
52
+ let key = void 0;
53
+ if (`key` in messageWithOptionalKey) {
54
+ key = messageWithOptionalKey.key;
55
+ } else {
56
+ key = this.config.getKey(messageWithOptionalKey.value);
57
+ }
58
+ let messageType = messageWithOptionalKey.type;
59
+ if (messageWithOptionalKey.type === `insert`) {
55
60
  const insertingIntoExistingSynced = this.state.syncedData.has(key);
56
61
  const hasPendingDeleteForKey = pendingTransaction.deletedKeys.has(key);
57
62
  const isTruncateTransaction = pendingTransaction.truncate === true;
58
63
  if (insertingIntoExistingSynced && !hasPendingDeleteForKey && !isTruncateTransaction) {
59
64
  const existingValue = this.state.syncedData.get(key);
60
- if (existingValue !== void 0 && deepEquals(existingValue, messageWithoutKey.value)) {
65
+ if (existingValue !== void 0 && deepEquals(existingValue, messageWithOptionalKey.value)) {
61
66
  messageType = `update`;
62
67
  } else {
63
68
  const utils = this.config.utils;
@@ -70,7 +75,7 @@ class CollectionSyncManager {
70
75
  }
71
76
  }
72
77
  const message = {
73
- ...messageWithoutKey,
78
+ ...messageWithOptionalKey,
74
79
  type: messageType,
75
80
  key
76
81
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sync.js","sources":["../../../src/collection/sync.ts"],"sourcesContent":["import {\n CollectionConfigurationError,\n CollectionIsInErrorStateError,\n DuplicateKeySyncError,\n NoPendingSyncTransactionCommitError,\n NoPendingSyncTransactionWriteError,\n SyncCleanupError,\n SyncTransactionAlreadyCommittedError,\n SyncTransactionAlreadyCommittedWriteError,\n} from \"../errors\"\nimport { deepEquals } from \"../utils\"\nimport { LIVE_QUERY_INTERNAL } from \"../query/live/internal.js\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type {\n ChangeMessage,\n CleanupFn,\n CollectionConfig,\n LoadSubsetOptions,\n SyncConfigRes,\n} from \"../types\"\nimport type { CollectionImpl } from \"./index.js\"\nimport type { CollectionStateManager } from \"./state\"\nimport type { CollectionLifecycleManager } from \"./lifecycle\"\nimport type { CollectionEventsManager } from \"./events.js\"\nimport type { LiveQueryCollectionUtils } from \"../query/live/collection-config-builder.js\"\n\nexport class CollectionSyncManager<\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 private collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private _events!: CollectionEventsManager\n private config!: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private syncMode: `eager` | `on-demand`\n\n public preloadPromise: Promise<void> | null = null\n public syncCleanupFn: (() => void) | null = null\n public syncLoadSubsetFn:\n | ((options: LoadSubsetOptions) => true | Promise<void>)\n | null = null\n public syncUnloadSubsetFn: ((options: LoadSubsetOptions) => void) | null =\n null\n\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n /**\n * Creates a new CollectionSyncManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n this.syncMode = config.syncMode ?? `eager`\n }\n\n setDeps(deps: {\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n }) {\n this.collection = deps.collection\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n this._events = deps.events\n }\n\n /**\n * Start the sync process for this collection\n * This is called when the collection is first accessed or preloaded\n */\n public startSync(): void {\n if (\n this.lifecycle.status !== `idle` &&\n this.lifecycle.status !== `cleaned-up`\n ) {\n return // Already started or in progress\n }\n\n this.lifecycle.setStatus(`loading`)\n\n try {\n const syncRes = normalizeSyncFnResult(\n this.config.sync.sync({\n collection: this.collection,\n begin: () => {\n this.state.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n deletedKeys: new Set(),\n })\n },\n write: (messageWithoutKey: Omit<ChangeMessage<TOutput>, `key`>) => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionWriteError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedWriteError()\n }\n const key = this.config.getKey(messageWithoutKey.value)\n\n let messageType = messageWithoutKey.type\n\n // Check if an item with this key already exists when inserting\n if (messageWithoutKey.type === `insert`) {\n const insertingIntoExistingSynced = this.state.syncedData.has(key)\n const hasPendingDeleteForKey =\n pendingTransaction.deletedKeys.has(key)\n const isTruncateTransaction = pendingTransaction.truncate === true\n // Allow insert after truncate in the same transaction even if it existed in syncedData\n if (\n insertingIntoExistingSynced &&\n !hasPendingDeleteForKey &&\n !isTruncateTransaction\n ) {\n const existingValue = this.state.syncedData.get(key)\n if (\n existingValue !== undefined &&\n deepEquals(existingValue, messageWithoutKey.value)\n ) {\n // The \"insert\" is an echo of a value we already have locally.\n // Treat it as an update so we preserve optimistic intent without\n // throwing a duplicate-key error during reconciliation.\n messageType = `update`\n } else {\n const utils = this.config\n .utils as Partial<LiveQueryCollectionUtils>\n const internal = utils[LIVE_QUERY_INTERNAL]\n throw new DuplicateKeySyncError(key, this.id, {\n hasCustomGetKey: internal?.hasCustomGetKey ?? false,\n hasJoins: internal?.hasJoins ?? false,\n })\n }\n }\n }\n\n const message: ChangeMessage<TOutput> = {\n ...messageWithoutKey,\n type: messageType,\n key,\n }\n pendingTransaction.operations.push(message)\n\n if (messageType === `delete`) {\n pendingTransaction.deletedKeys.add(key)\n }\n },\n commit: () => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionCommitError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedError()\n }\n\n pendingTransaction.committed = true\n\n this.state.commitPendingTransactions()\n },\n markReady: () => {\n this.lifecycle.markReady()\n },\n truncate: () => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionWriteError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedWriteError()\n }\n\n // Clear all operations from the current transaction\n pendingTransaction.operations = []\n pendingTransaction.deletedKeys.clear()\n\n // Mark the transaction as a truncate operation. During commit, this triggers:\n // - Delete events for all previously synced keys (excluding optimistic-deleted keys)\n // - Clearing of syncedData/syncedMetadata\n // - Subsequent synced ops applied on the fresh base\n // - Finally, optimistic mutations re-applied on top (single batch)\n pendingTransaction.truncate = true\n\n // Capture optimistic state NOW to preserve it even if transactions complete\n // before this truncate transaction is committed\n pendingTransaction.optimisticSnapshot = {\n upserts: new Map(this.state.optimisticUpserts),\n deletes: new Set(this.state.optimisticDeletes),\n }\n },\n })\n )\n\n // Store cleanup function if provided\n this.syncCleanupFn = syncRes?.cleanup ?? null\n\n // Store loadSubset function if provided\n this.syncLoadSubsetFn = syncRes?.loadSubset ?? null\n\n // Store unloadSubset function if provided\n this.syncUnloadSubsetFn = syncRes?.unloadSubset ?? null\n\n // Validate: on-demand mode requires a loadSubset function\n if (this.syncMode === `on-demand` && !this.syncLoadSubsetFn) {\n throw new CollectionConfigurationError(\n `Collection \"${this.id}\" is configured with syncMode \"on-demand\" but the sync function did not return a loadSubset handler. ` +\n `Either provide a loadSubset handler or use syncMode \"eager\".`\n )\n }\n } catch (error) {\n this.lifecycle.setStatus(`error`)\n throw error\n }\n }\n\n /**\n * Preload the collection data by starting sync if not already started\n * Multiple concurrent calls will share the same promise\n */\n public preload(): Promise<void> {\n if (this.preloadPromise) {\n return this.preloadPromise\n }\n\n // Warn when calling preload on an on-demand collection\n if (this.syncMode === `on-demand`) {\n console.warn(\n `${this.id ? `[${this.id}] ` : ``}Calling .preload() on a collection with syncMode \"on-demand\" is a no-op. ` +\n `In on-demand mode, data is only loaded when queries request it. ` +\n `Instead, create a live query and call .preload() on that to load the specific data you need. ` +\n `See https://tanstack.com/blog/tanstack-db-0.5-query-driven-sync for more details.`\n )\n }\n\n this.preloadPromise = new Promise<void>((resolve, reject) => {\n if (this.lifecycle.status === `ready`) {\n resolve()\n return\n }\n\n if (this.lifecycle.status === `error`) {\n reject(new CollectionIsInErrorStateError())\n return\n }\n\n // Register callback BEFORE starting sync to avoid race condition\n this.lifecycle.onFirstReady(() => {\n resolve()\n })\n\n // Start sync if collection hasn't started yet or was cleaned up\n if (\n this.lifecycle.status === `idle` ||\n this.lifecycle.status === `cleaned-up`\n ) {\n try {\n this.startSync()\n } catch (error) {\n reject(error)\n return\n }\n }\n })\n\n return this.preloadPromise\n }\n\n /**\n * Gets whether the collection is currently loading more data\n */\n public get isLoadingSubset(): boolean {\n return this.pendingLoadSubsetPromises.size > 0\n }\n\n /**\n * Tracks a load promise for isLoadingSubset state.\n * @internal This is for internal coordination (e.g., live-query glue code), not for general use.\n */\n public trackLoadPromise(promise: Promise<void>): void {\n const loadingStarting = !this.isLoadingSubset\n this.pendingLoadSubsetPromises.add(promise)\n\n if (loadingStarting) {\n this._events.emit(`loadingSubset:change`, {\n type: `loadingSubset:change`,\n collection: this.collection,\n isLoadingSubset: true,\n previousIsLoadingSubset: false,\n loadingSubsetTransition: `start`,\n })\n }\n\n promise.finally(() => {\n const loadingEnding =\n this.pendingLoadSubsetPromises.size === 1 &&\n this.pendingLoadSubsetPromises.has(promise)\n this.pendingLoadSubsetPromises.delete(promise)\n\n if (loadingEnding) {\n this._events.emit(`loadingSubset:change`, {\n type: `loadingSubset:change`,\n collection: this.collection,\n isLoadingSubset: false,\n previousIsLoadingSubset: true,\n loadingSubsetTransition: `end`,\n })\n }\n })\n }\n\n /**\n * Requests the sync layer to load more data.\n * @param options Options to control what data is being loaded\n * @returns If data loading is asynchronous, this method returns a promise that resolves when the data is loaded.\n * Returns true if no sync function is configured, if syncMode is 'eager', or if there is no work to do.\n */\n public loadSubset(options: LoadSubsetOptions): Promise<void> | true {\n // Bypass loadSubset when syncMode is 'eager'\n if (this.syncMode === `eager`) {\n return true\n }\n\n if (this.syncLoadSubsetFn) {\n const result = this.syncLoadSubsetFn(options)\n // If the result is a promise, track it\n if (result instanceof Promise) {\n this.trackLoadPromise(result)\n return result\n }\n }\n\n return true\n }\n\n /**\n * Notifies the sync layer that a subset is no longer needed.\n * @param options Options that identify what data is being unloaded\n */\n public unloadSubset(options: LoadSubsetOptions): void {\n if (this.syncUnloadSubsetFn) {\n this.syncUnloadSubsetFn(options)\n }\n }\n\n public cleanup(): void {\n try {\n if (this.syncCleanupFn) {\n this.syncCleanupFn()\n this.syncCleanupFn = null\n }\n } catch (error) {\n // Re-throw in a microtask to surface the error after cleanup completes\n queueMicrotask(() => {\n if (error instanceof Error) {\n // Preserve the original error and stack trace\n const wrappedError = new SyncCleanupError(this.id, error)\n wrappedError.cause = error\n wrappedError.stack = error.stack\n throw wrappedError\n } else {\n throw new SyncCleanupError(this.id, error as Error | string)\n }\n })\n }\n this.preloadPromise = null\n }\n}\n\nfunction normalizeSyncFnResult(result: void | CleanupFn | SyncConfigRes) {\n if (typeof result === `function`) {\n return { cleanup: result }\n }\n\n if (typeof result === `object`) {\n return result\n }\n\n return undefined\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.js","sources":["../../../src/collection/sync.ts"],"sourcesContent":["import {\n CollectionConfigurationError,\n CollectionIsInErrorStateError,\n DuplicateKeySyncError,\n NoPendingSyncTransactionCommitError,\n NoPendingSyncTransactionWriteError,\n SyncCleanupError,\n SyncTransactionAlreadyCommittedError,\n SyncTransactionAlreadyCommittedWriteError,\n} from '../errors'\nimport { deepEquals } from '../utils'\nimport { LIVE_QUERY_INTERNAL } from '../query/live/internal.js'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ChangeMessageOrDeleteKeyMessage,\n CleanupFn,\n CollectionConfig,\n LoadSubsetOptions,\n OptimisticChangeMessage,\n SyncConfigRes,\n} from '../types'\nimport type { CollectionImpl } from './index.js'\nimport type { CollectionStateManager } from './state'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionEventsManager } from './events.js'\nimport type { LiveQueryCollectionUtils } from '../query/live/collection-config-builder.js'\n\nexport class CollectionSyncManager<\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 private collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private _events!: CollectionEventsManager\n private config!: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private syncMode: `eager` | `on-demand`\n\n public preloadPromise: Promise<void> | null = null\n public syncCleanupFn: (() => void) | null = null\n public syncLoadSubsetFn:\n | ((options: LoadSubsetOptions) => true | Promise<void>)\n | null = null\n public syncUnloadSubsetFn: ((options: LoadSubsetOptions) => void) | null =\n null\n\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n /**\n * Creates a new CollectionSyncManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n this.syncMode = config.syncMode ?? `eager`\n }\n\n setDeps(deps: {\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n }) {\n this.collection = deps.collection\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n this._events = deps.events\n }\n\n /**\n * Start the sync process for this collection\n * This is called when the collection is first accessed or preloaded\n */\n public startSync(): void {\n if (\n this.lifecycle.status !== `idle` &&\n this.lifecycle.status !== `cleaned-up`\n ) {\n return // Already started or in progress\n }\n\n this.lifecycle.setStatus(`loading`)\n\n try {\n const syncRes = normalizeSyncFnResult(\n this.config.sync.sync({\n collection: this.collection,\n begin: () => {\n this.state.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n deletedKeys: new Set(),\n })\n },\n write: (\n messageWithOptionalKey: ChangeMessageOrDeleteKeyMessage<\n TOutput,\n TKey\n >,\n ) => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionWriteError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedWriteError()\n }\n\n let key: TKey | undefined = undefined\n if (`key` in messageWithOptionalKey) {\n key = messageWithOptionalKey.key\n } else {\n key = this.config.getKey(messageWithOptionalKey.value)\n }\n\n let messageType = messageWithOptionalKey.type\n\n // Check if an item with this key already exists when inserting\n if (messageWithOptionalKey.type === `insert`) {\n const insertingIntoExistingSynced = this.state.syncedData.has(key)\n const hasPendingDeleteForKey =\n pendingTransaction.deletedKeys.has(key)\n const isTruncateTransaction = pendingTransaction.truncate === true\n // Allow insert after truncate in the same transaction even if it existed in syncedData\n if (\n insertingIntoExistingSynced &&\n !hasPendingDeleteForKey &&\n !isTruncateTransaction\n ) {\n const existingValue = this.state.syncedData.get(key)\n if (\n existingValue !== undefined &&\n deepEquals(existingValue, messageWithOptionalKey.value)\n ) {\n // The \"insert\" is an echo of a value we already have locally.\n // Treat it as an update so we preserve optimistic intent without\n // throwing a duplicate-key error during reconciliation.\n messageType = `update`\n } else {\n const utils = this.config\n .utils as Partial<LiveQueryCollectionUtils>\n const internal = utils[LIVE_QUERY_INTERNAL]\n throw new DuplicateKeySyncError(key, this.id, {\n hasCustomGetKey: internal?.hasCustomGetKey ?? false,\n hasJoins: internal?.hasJoins ?? false,\n })\n }\n }\n }\n\n const message = {\n ...messageWithOptionalKey,\n type: messageType,\n key,\n } as OptimisticChangeMessage<TOutput, TKey>\n pendingTransaction.operations.push(message)\n\n if (messageType === `delete`) {\n pendingTransaction.deletedKeys.add(key)\n }\n },\n commit: () => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionCommitError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedError()\n }\n\n pendingTransaction.committed = true\n\n this.state.commitPendingTransactions()\n },\n markReady: () => {\n this.lifecycle.markReady()\n },\n truncate: () => {\n const pendingTransaction =\n this.state.pendingSyncedTransactions[\n this.state.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new NoPendingSyncTransactionWriteError()\n }\n if (pendingTransaction.committed) {\n throw new SyncTransactionAlreadyCommittedWriteError()\n }\n\n // Clear all operations from the current transaction\n pendingTransaction.operations = []\n pendingTransaction.deletedKeys.clear()\n\n // Mark the transaction as a truncate operation. During commit, this triggers:\n // - Delete events for all previously synced keys (excluding optimistic-deleted keys)\n // - Clearing of syncedData/syncedMetadata\n // - Subsequent synced ops applied on the fresh base\n // - Finally, optimistic mutations re-applied on top (single batch)\n pendingTransaction.truncate = true\n\n // Capture optimistic state NOW to preserve it even if transactions complete\n // before this truncate transaction is committed\n pendingTransaction.optimisticSnapshot = {\n upserts: new Map(this.state.optimisticUpserts),\n deletes: new Set(this.state.optimisticDeletes),\n }\n },\n }),\n )\n\n // Store cleanup function if provided\n this.syncCleanupFn = syncRes?.cleanup ?? null\n\n // Store loadSubset function if provided\n this.syncLoadSubsetFn = syncRes?.loadSubset ?? null\n\n // Store unloadSubset function if provided\n this.syncUnloadSubsetFn = syncRes?.unloadSubset ?? null\n\n // Validate: on-demand mode requires a loadSubset function\n if (this.syncMode === `on-demand` && !this.syncLoadSubsetFn) {\n throw new CollectionConfigurationError(\n `Collection \"${this.id}\" is configured with syncMode \"on-demand\" but the sync function did not return a loadSubset handler. ` +\n `Either provide a loadSubset handler or use syncMode \"eager\".`,\n )\n }\n } catch (error) {\n this.lifecycle.setStatus(`error`)\n throw error\n }\n }\n\n /**\n * Preload the collection data by starting sync if not already started\n * Multiple concurrent calls will share the same promise\n */\n public preload(): Promise<void> {\n if (this.preloadPromise) {\n return this.preloadPromise\n }\n\n // Warn when calling preload on an on-demand collection\n if (this.syncMode === `on-demand`) {\n console.warn(\n `${this.id ? `[${this.id}] ` : ``}Calling .preload() on a collection with syncMode \"on-demand\" is a no-op. ` +\n `In on-demand mode, data is only loaded when queries request it. ` +\n `Instead, create a live query and call .preload() on that to load the specific data you need. ` +\n `See https://tanstack.com/blog/tanstack-db-0.5-query-driven-sync for more details.`,\n )\n }\n\n this.preloadPromise = new Promise<void>((resolve, reject) => {\n if (this.lifecycle.status === `ready`) {\n resolve()\n return\n }\n\n if (this.lifecycle.status === `error`) {\n reject(new CollectionIsInErrorStateError())\n return\n }\n\n // Register callback BEFORE starting sync to avoid race condition\n this.lifecycle.onFirstReady(() => {\n resolve()\n })\n\n // Start sync if collection hasn't started yet or was cleaned up\n if (\n this.lifecycle.status === `idle` ||\n this.lifecycle.status === `cleaned-up`\n ) {\n try {\n this.startSync()\n } catch (error) {\n reject(error)\n return\n }\n }\n })\n\n return this.preloadPromise\n }\n\n /**\n * Gets whether the collection is currently loading more data\n */\n public get isLoadingSubset(): boolean {\n return this.pendingLoadSubsetPromises.size > 0\n }\n\n /**\n * Tracks a load promise for isLoadingSubset state.\n * @internal This is for internal coordination (e.g., live-query glue code), not for general use.\n */\n public trackLoadPromise(promise: Promise<void>): void {\n const loadingStarting = !this.isLoadingSubset\n this.pendingLoadSubsetPromises.add(promise)\n\n if (loadingStarting) {\n this._events.emit(`loadingSubset:change`, {\n type: `loadingSubset:change`,\n collection: this.collection,\n isLoadingSubset: true,\n previousIsLoadingSubset: false,\n loadingSubsetTransition: `start`,\n })\n }\n\n promise.finally(() => {\n const loadingEnding =\n this.pendingLoadSubsetPromises.size === 1 &&\n this.pendingLoadSubsetPromises.has(promise)\n this.pendingLoadSubsetPromises.delete(promise)\n\n if (loadingEnding) {\n this._events.emit(`loadingSubset:change`, {\n type: `loadingSubset:change`,\n collection: this.collection,\n isLoadingSubset: false,\n previousIsLoadingSubset: true,\n loadingSubsetTransition: `end`,\n })\n }\n })\n }\n\n /**\n * Requests the sync layer to load more data.\n * @param options Options to control what data is being loaded\n * @returns If data loading is asynchronous, this method returns a promise that resolves when the data is loaded.\n * Returns true if no sync function is configured, if syncMode is 'eager', or if there is no work to do.\n */\n public loadSubset(options: LoadSubsetOptions): Promise<void> | true {\n // Bypass loadSubset when syncMode is 'eager'\n if (this.syncMode === `eager`) {\n return true\n }\n\n if (this.syncLoadSubsetFn) {\n const result = this.syncLoadSubsetFn(options)\n // If the result is a promise, track it\n if (result instanceof Promise) {\n this.trackLoadPromise(result)\n return result\n }\n }\n\n return true\n }\n\n /**\n * Notifies the sync layer that a subset is no longer needed.\n * @param options Options that identify what data is being unloaded\n */\n public unloadSubset(options: LoadSubsetOptions): void {\n if (this.syncUnloadSubsetFn) {\n this.syncUnloadSubsetFn(options)\n }\n }\n\n public cleanup(): void {\n try {\n if (this.syncCleanupFn) {\n this.syncCleanupFn()\n this.syncCleanupFn = null\n }\n } catch (error) {\n // Re-throw in a microtask to surface the error after cleanup completes\n queueMicrotask(() => {\n if (error instanceof Error) {\n // Preserve the original error and stack trace\n const wrappedError = new SyncCleanupError(this.id, error)\n wrappedError.cause = error\n wrappedError.stack = error.stack\n throw wrappedError\n } else {\n throw new SyncCleanupError(this.id, error as Error | string)\n }\n })\n }\n this.preloadPromise = null\n }\n}\n\nfunction normalizeSyncFnResult(result: void | CleanupFn | SyncConfigRes) {\n if (typeof result === `function`) {\n return { cleanup: result }\n }\n\n if (typeof result === `object`) {\n return result\n }\n\n return undefined\n}\n"],"names":[],"mappings":"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"}
@@ -54,6 +54,9 @@ export declare class CollectionOperationError extends TanStackDBError {
54
54
  export declare class UndefinedKeyError extends CollectionOperationError {
55
55
  constructor(item: any);
56
56
  }
57
+ export declare class InvalidKeyError extends CollectionOperationError {
58
+ constructor(key: unknown, item: unknown);
59
+ }
57
60
  export declare class DuplicateKeyError extends CollectionOperationError {
58
61
  constructor(key: string | number);
59
62
  }
@@ -117,6 +117,14 @@ class UndefinedKeyError extends CollectionOperationError {
117
117
  );
118
118
  }
119
119
  }
120
+ class InvalidKeyError extends CollectionOperationError {
121
+ constructor(key, item) {
122
+ const keyType = key === null ? `null` : typeof key;
123
+ super(
124
+ `getKey returned an invalid key type. Expected string or number, but got ${keyType}: ${JSON.stringify(key)}. Item: ${JSON.stringify(item)}`
125
+ );
126
+ }
127
+ }
120
128
  class DuplicateKeyError extends CollectionOperationError {
121
129
  constructor(key) {
122
130
  super(
@@ -562,6 +570,7 @@ export {
562
570
  InvalidJoinConditionRightSourceError,
563
571
  InvalidJoinConditionSameSourceError,
564
572
  InvalidJoinConditionSourceMismatchError,
573
+ InvalidKeyError,
565
574
  InvalidSchemaError,
566
575
  InvalidSourceError,
567
576
  InvalidSourceTypeError,
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sources":["../../src/errors.ts"],"sourcesContent":["// Root error class for all TanStack DB errors\nexport class TanStackDBError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `TanStackDBError`\n }\n}\n\n// Base error classes\nexport class NonRetriableError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n\n// Schema validation error (exported from index for backward compatibility)\nexport class SchemaValidationError extends TanStackDBError {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => `\\n- ${issue.message} - path: ${issue.path}`)\n .join(``)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\n// Module Instance Errors\nexport class DuplicateDbInstanceError extends TanStackDBError {\n constructor() {\n super(\n `Multiple instances of @tanstack/db detected!\\n\\n` +\n `This causes transaction context to be lost because each instance maintains ` +\n `its own transaction stack.\\n\\n` +\n `Common causes:\\n` +\n `1. Different versions of @tanstack/db installed\\n` +\n `2. Incompatible peer dependency versions in packages\\n` +\n `3. Module resolution issues in bundler configuration\\n\\n` +\n `To fix:\\n` +\n `1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)\\n` +\n `2. Force a single version using package manager overrides:\\n` +\n ` - npm: \"overrides\" in package.json\\n` +\n ` - pnpm: \"pnpm.overrides\" in package.json\\n` +\n ` - yarn: \"resolutions\" in package.json\\n` +\n `3. Clear node_modules and lockfile, then reinstall\\n\\n` +\n `To temporarily disable this check (not recommended):\\n` +\n `Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1\\n\\n` +\n `See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`\n )\n this.name = `DuplicateDbInstanceError`\n }\n}\n\n// Collection Configuration Errors\nexport class CollectionConfigurationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionConfigurationError`\n }\n}\n\nexport class CollectionRequiresConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a config`)\n }\n}\n\nexport class CollectionRequiresSyncConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a sync config`)\n }\n}\n\nexport class InvalidSchemaError extends CollectionConfigurationError {\n constructor() {\n super(`Schema must implement the standard-schema interface`)\n }\n}\n\nexport class SchemaMustBeSynchronousError extends CollectionConfigurationError {\n constructor() {\n super(`Schema validation must be synchronous`)\n }\n}\n\n// Collection State Errors\nexport class CollectionStateError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionStateError`\n }\n}\n\nexport class CollectionInErrorStateError extends CollectionStateError {\n constructor(operation: string, collectionId: string) {\n super(\n `Cannot perform ${operation} on collection \"${collectionId}\" - collection is in error state. Try calling cleanup() and restarting the collection.`\n )\n }\n}\n\nexport class InvalidCollectionStatusTransitionError extends CollectionStateError {\n constructor(from: string, to: string, collectionId: string) {\n super(\n `Invalid collection status transition from \"${from}\" to \"${to}\" for collection \"${collectionId}\"`\n )\n }\n}\n\nexport class CollectionIsInErrorStateError extends CollectionStateError {\n constructor() {\n super(`Collection is in error state`)\n }\n}\n\nexport class NegativeActiveSubscribersError extends CollectionStateError {\n constructor() {\n super(`Active subscribers count is negative - this should never happen`)\n }\n}\n\n// Collection Operation Errors\nexport class CollectionOperationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionOperationError`\n }\n}\n\nexport class UndefinedKeyError extends CollectionOperationError {\n constructor(item: any) {\n super(\n `An object was created without a defined key: ${JSON.stringify(item)}`\n )\n }\n}\n\nexport class DuplicateKeyError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Cannot insert document with ID \"${key}\" because it already exists in the collection`\n )\n }\n}\n\nexport class DuplicateKeySyncError extends CollectionOperationError {\n constructor(\n key: string | number,\n collectionId: string,\n options?: { hasCustomGetKey?: boolean; hasJoins?: boolean }\n ) {\n const baseMessage = `Cannot insert document with key \"${key}\" from sync because it already exists in the collection \"${collectionId}\"`\n\n // Provide enhanced guidance when custom getKey is used with joins\n if (options?.hasCustomGetKey && options.hasJoins) {\n super(\n `${baseMessage}. ` +\n `This collection uses a custom getKey with joined queries. ` +\n `Joined queries can produce multiple rows with the same key when relationships are not 1:1. ` +\n `Consider: (1) using a composite key in your getKey function (e.g., \\`\\${item.key1}-\\${item.key2}\\`), ` +\n `(2) ensuring your join produces unique rows per key, or (3) removing the custom getKey ` +\n `to use the default composite key behavior.`\n )\n } else {\n super(baseMessage)\n }\n }\n}\n\nexport class MissingUpdateArgumentError extends CollectionOperationError {\n constructor() {\n super(`The first argument to update is missing`)\n }\n}\n\nexport class NoKeysPassedToUpdateError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to update`)\n }\n}\n\nexport class UpdateKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `The key \"${key}\" was passed to update but an object for this key was not found in the collection`\n )\n }\n}\n\nexport class KeyUpdateNotAllowedError extends CollectionOperationError {\n constructor(originalKey: string | number, newKey: string | number) {\n super(\n `Updating the key of an item is not allowed. Original key: \"${originalKey}\", Attempted new key: \"${newKey}\". Please delete the old item and create a new one if a key change is necessary.`\n )\n }\n}\n\nexport class NoKeysPassedToDeleteError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to delete`)\n }\n}\n\nexport class DeleteKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Collection.delete was called with key '${key}' but there is no item in the collection with this key`\n )\n }\n}\n\n// Collection Handler Errors\nexport class MissingHandlerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `MissingHandlerError`\n }\n}\n\nexport class MissingInsertHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n}\n\nexport class MissingUpdateHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n}\n\nexport class MissingDeleteHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n}\n\n// Transaction Errors\nexport class TransactionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `TransactionError`\n }\n}\n\nexport class MissingMutationFunctionError extends TransactionError {\n constructor() {\n super(`mutationFn is required when creating a transaction`)\n }\n}\n\nexport class OnMutateMustBeSynchronousError extends TransactionError {\n constructor() {\n super(\n `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`\n )\n this.name = `OnMutateMustBeSynchronousError`\n }\n}\n\nexport class TransactionNotPendingMutateError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .mutate() as the transaction is no longer pending`\n )\n }\n}\n\nexport class TransactionAlreadyCompletedRollbackError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .rollback() as the transaction is already completed`\n )\n }\n}\n\nexport class TransactionNotPendingCommitError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .commit() as the transaction is no longer pending`\n )\n }\n}\n\nexport class NoPendingSyncTransactionWriteError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to write to`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedWriteError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n}\n\nexport class NoPendingSyncTransactionCommitError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to commit`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n}\n\n// Query Builder Errors\nexport class QueryBuilderError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryBuilderError`\n }\n}\n\nexport class OnlyOneSourceAllowedError extends QueryBuilderError {\n constructor(context: string) {\n super(`Only one source is allowed in the ${context}`)\n }\n}\n\nexport class SubQueryMustHaveFromClauseError extends QueryBuilderError {\n constructor(context: string) {\n super(`A sub query passed to a ${context} must have a from clause itself`)\n }\n}\n\nexport class InvalidSourceError extends QueryBuilderError {\n constructor(alias: string) {\n super(\n `Invalid source for live query: The value provided for alias \"${alias}\" is not a Collection or subquery. Live queries only accept Collection instances or subqueries. Please ensure you're passing a valid Collection or QueryBuilder, not a plain array or other data type.`\n )\n }\n}\n\nexport class InvalidSourceTypeError extends QueryBuilderError {\n constructor(context: string, type: string) {\n super(\n `Invalid source for ${context}: Expected an object with a single key-value pair like { alias: collection }. ` +\n `For example: .from({ todos: todosCollection }). Got: ${type}`\n )\n }\n}\n\nexport class JoinConditionMustBeEqualityError extends QueryBuilderError {\n constructor() {\n super(`Join condition must be an equality expression`)\n }\n}\n\nexport class QueryMustHaveFromClauseError extends QueryBuilderError {\n constructor() {\n super(`Query must have a from clause`)\n }\n}\n\n// Query Compilation Errors\nexport class QueryCompilationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCompilationError`\n }\n}\n\nexport class DistinctRequiresSelectError extends QueryCompilationError {\n constructor() {\n super(`DISTINCT requires a SELECT clause.`)\n }\n}\n\nexport class HavingRequiresGroupByError extends QueryCompilationError {\n constructor() {\n super(`HAVING clause requires GROUP BY clause`)\n }\n}\n\nexport class LimitOffsetRequireOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n}\n\n/**\n * Error thrown when a collection input stream is not found during query compilation.\n * In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream.\n */\nexport class CollectionInputNotFoundError extends QueryCompilationError {\n constructor(\n alias: string,\n collectionId?: string,\n availableKeys?: Array<string>\n ) {\n const details = collectionId\n ? `alias \"${alias}\" (collection \"${collectionId}\")`\n : `collection \"${alias}\"`\n const availableKeysMsg = availableKeys?.length\n ? `. Available keys: ${availableKeys.join(`, `)}`\n : ``\n super(`Input for ${details} not found in inputs map${availableKeysMsg}`)\n }\n}\n\n/**\n * Error thrown when a subquery uses the same alias as its parent query.\n * This causes issues because parent and subquery would share the same input streams,\n * leading to empty results or incorrect data (aggregation cross-leaking).\n */\nexport class DuplicateAliasInSubqueryError extends QueryCompilationError {\n constructor(alias: string, parentAliases: Array<string>) {\n super(\n `Subquery uses alias \"${alias}\" which is already used in the parent query. ` +\n `Each alias must be unique across parent and subquery contexts. ` +\n `Parent query aliases: ${parentAliases.join(`, `)}. ` +\n `Please rename \"${alias}\" in either the parent query or subquery to avoid conflicts.`\n )\n }\n}\n\nexport class UnsupportedFromTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unsupported FROM type: ${type}`)\n }\n}\n\nexport class UnknownExpressionTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unknown expression type: ${type}`)\n }\n}\n\nexport class EmptyReferencePathError extends QueryCompilationError {\n constructor() {\n super(`Reference path cannot be empty`)\n }\n}\n\nexport class UnknownFunctionError extends QueryCompilationError {\n constructor(functionName: string) {\n super(`Unknown function: ${functionName}`)\n }\n}\n\nexport class JoinCollectionNotFoundError extends QueryCompilationError {\n constructor(collectionId: string) {\n super(`Collection \"${collectionId}\" not found during compilation of join`)\n }\n}\n\n// JOIN Operation Errors\nexport class JoinError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `JoinError`\n }\n}\n\nexport class UnsupportedJoinTypeError extends JoinError {\n constructor(joinType: string) {\n super(`Unsupported join type: ${joinType}`)\n }\n}\n\nexport class InvalidJoinConditionSameSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: both expressions refer to the same source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionSourceMismatchError extends JoinError {\n constructor() {\n super(`Invalid join condition: expressions must reference source aliases`)\n }\n}\n\nexport class InvalidJoinConditionLeftSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: left expression refers to an unavailable source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinConditionRightSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: right expression does not refer to the joined source \"${sourceAlias}\"`\n )\n }\n}\n\nexport class InvalidJoinCondition extends JoinError {\n constructor() {\n super(`Invalid join condition`)\n }\n}\n\nexport class UnsupportedJoinSourceTypeError extends JoinError {\n constructor(type: string) {\n super(`Unsupported join source type: ${type}`)\n }\n}\n\n// GROUP BY and Aggregation Errors\nexport class GroupByError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `GroupByError`\n }\n}\n\nexport class NonAggregateExpressionNotInGroupByError extends GroupByError {\n constructor(alias: string) {\n super(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n}\n\nexport class UnsupportedAggregateFunctionError extends GroupByError {\n constructor(functionName: string) {\n super(`Unsupported aggregate function: ${functionName}`)\n }\n}\n\nexport class AggregateFunctionNotInSelectError extends GroupByError {\n constructor(functionName: string) {\n super(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${functionName}`\n )\n }\n}\n\nexport class UnknownHavingExpressionTypeError extends GroupByError {\n constructor(type: string) {\n super(`Unknown expression type in HAVING clause: ${type}`)\n }\n}\n\n// Storage Errors\nexport class StorageError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `StorageError`\n }\n}\n\nexport class SerializationError extends StorageError {\n constructor(operation: string, originalError: string) {\n super(\n `Cannot ${operation} item because it cannot be JSON serialized: ${originalError}`\n )\n }\n}\n\n// LocalStorage Collection Errors\nexport class LocalStorageCollectionError extends StorageError {\n constructor(message: string) {\n super(message)\n this.name = `LocalStorageCollectionError`\n }\n}\n\nexport class StorageKeyRequiredError extends LocalStorageCollectionError {\n constructor() {\n super(`[LocalStorageCollection] storageKey must be provided.`)\n }\n}\n\nexport class InvalidStorageDataFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string, key: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\" for key \"${key}\".`\n )\n }\n}\n\nexport class InvalidStorageObjectFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\". Expected object format.`\n )\n }\n}\n\n// Sync Cleanup Errors\nexport class SyncCleanupError extends TanStackDBError {\n constructor(collectionId: string, error: Error | string) {\n const message = error instanceof Error ? error.message : String(error)\n super(\n `Collection \"${collectionId}\" sync cleanup function threw an error: ${message}`\n )\n this.name = `SyncCleanupError`\n }\n}\n\n// Query Optimizer Errors\nexport class QueryOptimizerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryOptimizerError`\n }\n}\n\nexport class CannotCombineEmptyExpressionListError extends QueryOptimizerError {\n constructor() {\n super(`Cannot combine empty expression list`)\n }\n}\n\n/**\n * Internal error when the query optimizer fails to convert a WHERE clause to a collection filter.\n */\nexport class WhereClauseConversionError extends QueryOptimizerError {\n constructor(collectionId: string, alias: string) {\n super(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}' alias '${alias}'. This indicates a bug in the query optimization logic.`\n )\n }\n}\n\n/**\n * Error when a subscription cannot be found during lazy join processing.\n * For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user').\n */\nexport class SubscriptionNotFoundError extends QueryCompilationError {\n constructor(\n resolvedAlias: string,\n originalAlias: string,\n collectionId: string,\n availableAliases: Array<string>\n ) {\n super(\n `Internal error: subscription for alias '${resolvedAlias}' (remapped from '${originalAlias}', collection '${collectionId}') is missing in join pipeline. Available aliases: ${availableAliases.join(`, `)}. This indicates a bug in alias tracking.`\n )\n }\n}\n\n/**\n * Error thrown when aggregate expressions are used outside of a GROUP BY context.\n */\nexport class AggregateNotSupportedError extends QueryCompilationError {\n constructor() {\n super(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`\n )\n }\n}\n\n/**\n * Internal error when the compiler returns aliases that don't have corresponding input streams.\n * This should never happen since all aliases come from user declarations.\n */\nexport class MissingAliasInputsError extends QueryCompilationError {\n constructor(missingAliases: Array<string>) {\n super(\n `Internal error: compiler returned aliases without inputs: ${missingAliases.join(`, `)}. ` +\n `This indicates a bug in query compilation. Please report this issue.`\n )\n }\n}\n\n/**\n * Error thrown when setWindow is called on a collection without an ORDER BY clause.\n */\nexport class SetWindowRequiresOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `setWindow() can only be called on collections with an ORDER BY clause. ` +\n `Add .orderBy() to your query to enable window movement.`\n )\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sources":["../../src/errors.ts"],"sourcesContent":["// Root error class for all TanStack DB errors\nexport class TanStackDBError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `TanStackDBError`\n }\n}\n\n// Base error classes\nexport class NonRetriableError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n\n// Schema validation error (exported from index for backward compatibility)\nexport class SchemaValidationError extends TanStackDBError {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string,\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => `\\n- ${issue.message} - path: ${issue.path}`)\n .join(``)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\n// Module Instance Errors\nexport class DuplicateDbInstanceError extends TanStackDBError {\n constructor() {\n super(\n `Multiple instances of @tanstack/db detected!\\n\\n` +\n `This causes transaction context to be lost because each instance maintains ` +\n `its own transaction stack.\\n\\n` +\n `Common causes:\\n` +\n `1. Different versions of @tanstack/db installed\\n` +\n `2. Incompatible peer dependency versions in packages\\n` +\n `3. Module resolution issues in bundler configuration\\n\\n` +\n `To fix:\\n` +\n `1. Check installed versions: npm list @tanstack/db (or pnpm/yarn list)\\n` +\n `2. Force a single version using package manager overrides:\\n` +\n ` - npm: \"overrides\" in package.json\\n` +\n ` - pnpm: \"pnpm.overrides\" in package.json\\n` +\n ` - yarn: \"resolutions\" in package.json\\n` +\n `3. Clear node_modules and lockfile, then reinstall\\n\\n` +\n `To temporarily disable this check (not recommended):\\n` +\n `Set environment variable: TANSTACK_DB_DISABLE_DUP_CHECK=1\\n\\n` +\n `See: https://tanstack.com/db/latest/docs/troubleshooting#duplicate-instances`,\n )\n this.name = `DuplicateDbInstanceError`\n }\n}\n\n// Collection Configuration Errors\nexport class CollectionConfigurationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionConfigurationError`\n }\n}\n\nexport class CollectionRequiresConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a config`)\n }\n}\n\nexport class CollectionRequiresSyncConfigError extends CollectionConfigurationError {\n constructor() {\n super(`Collection requires a sync config`)\n }\n}\n\nexport class InvalidSchemaError extends CollectionConfigurationError {\n constructor() {\n super(`Schema must implement the standard-schema interface`)\n }\n}\n\nexport class SchemaMustBeSynchronousError extends CollectionConfigurationError {\n constructor() {\n super(`Schema validation must be synchronous`)\n }\n}\n\n// Collection State Errors\nexport class CollectionStateError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionStateError`\n }\n}\n\nexport class CollectionInErrorStateError extends CollectionStateError {\n constructor(operation: string, collectionId: string) {\n super(\n `Cannot perform ${operation} on collection \"${collectionId}\" - collection is in error state. Try calling cleanup() and restarting the collection.`,\n )\n }\n}\n\nexport class InvalidCollectionStatusTransitionError extends CollectionStateError {\n constructor(from: string, to: string, collectionId: string) {\n super(\n `Invalid collection status transition from \"${from}\" to \"${to}\" for collection \"${collectionId}\"`,\n )\n }\n}\n\nexport class CollectionIsInErrorStateError extends CollectionStateError {\n constructor() {\n super(`Collection is in error state`)\n }\n}\n\nexport class NegativeActiveSubscribersError extends CollectionStateError {\n constructor() {\n super(`Active subscribers count is negative - this should never happen`)\n }\n}\n\n// Collection Operation Errors\nexport class CollectionOperationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `CollectionOperationError`\n }\n}\n\nexport class UndefinedKeyError extends CollectionOperationError {\n constructor(item: any) {\n super(\n `An object was created without a defined key: ${JSON.stringify(item)}`,\n )\n }\n}\n\nexport class InvalidKeyError extends CollectionOperationError {\n constructor(key: unknown, item: unknown) {\n const keyType = key === null ? `null` : typeof key\n super(\n `getKey returned an invalid key type. Expected string or number, but got ${keyType}: ${JSON.stringify(key)}. Item: ${JSON.stringify(item)}`,\n )\n }\n}\n\nexport class DuplicateKeyError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Cannot insert document with ID \"${key}\" because it already exists in the collection`,\n )\n }\n}\n\nexport class DuplicateKeySyncError extends CollectionOperationError {\n constructor(\n key: string | number,\n collectionId: string,\n options?: { hasCustomGetKey?: boolean; hasJoins?: boolean },\n ) {\n const baseMessage = `Cannot insert document with key \"${key}\" from sync because it already exists in the collection \"${collectionId}\"`\n\n // Provide enhanced guidance when custom getKey is used with joins\n if (options?.hasCustomGetKey && options.hasJoins) {\n super(\n `${baseMessage}. ` +\n `This collection uses a custom getKey with joined queries. ` +\n `Joined queries can produce multiple rows with the same key when relationships are not 1:1. ` +\n `Consider: (1) using a composite key in your getKey function (e.g., \\`\\${item.key1}-\\${item.key2}\\`), ` +\n `(2) ensuring your join produces unique rows per key, or (3) removing the custom getKey ` +\n `to use the default composite key behavior.`,\n )\n } else {\n super(baseMessage)\n }\n }\n}\n\nexport class MissingUpdateArgumentError extends CollectionOperationError {\n constructor() {\n super(`The first argument to update is missing`)\n }\n}\n\nexport class NoKeysPassedToUpdateError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to update`)\n }\n}\n\nexport class UpdateKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `The key \"${key}\" was passed to update but an object for this key was not found in the collection`,\n )\n }\n}\n\nexport class KeyUpdateNotAllowedError extends CollectionOperationError {\n constructor(originalKey: string | number, newKey: string | number) {\n super(\n `Updating the key of an item is not allowed. Original key: \"${originalKey}\", Attempted new key: \"${newKey}\". Please delete the old item and create a new one if a key change is necessary.`,\n )\n }\n}\n\nexport class NoKeysPassedToDeleteError extends CollectionOperationError {\n constructor() {\n super(`No keys were passed to delete`)\n }\n}\n\nexport class DeleteKeyNotFoundError extends CollectionOperationError {\n constructor(key: string | number) {\n super(\n `Collection.delete was called with key '${key}' but there is no item in the collection with this key`,\n )\n }\n}\n\n// Collection Handler Errors\nexport class MissingHandlerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `MissingHandlerError`\n }\n}\n\nexport class MissingInsertHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`,\n )\n }\n}\n\nexport class MissingUpdateHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`,\n )\n }\n}\n\nexport class MissingDeleteHandlerError extends MissingHandlerError {\n constructor() {\n super(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`,\n )\n }\n}\n\n// Transaction Errors\nexport class TransactionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `TransactionError`\n }\n}\n\nexport class MissingMutationFunctionError extends TransactionError {\n constructor() {\n super(`mutationFn is required when creating a transaction`)\n }\n}\n\nexport class OnMutateMustBeSynchronousError extends TransactionError {\n constructor() {\n super(\n `onMutate must be synchronous and cannot return a promise. Remove async/await or returned promises from onMutate.`,\n )\n this.name = `OnMutateMustBeSynchronousError`\n }\n}\n\nexport class TransactionNotPendingMutateError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .mutate() as the transaction is no longer pending`,\n )\n }\n}\n\nexport class TransactionAlreadyCompletedRollbackError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .rollback() as the transaction is already completed`,\n )\n }\n}\n\nexport class TransactionNotPendingCommitError extends TransactionError {\n constructor() {\n super(\n `You can no longer call .commit() as the transaction is no longer pending`,\n )\n }\n}\n\nexport class NoPendingSyncTransactionWriteError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to write to`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedWriteError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't still write to it.`,\n )\n }\n}\n\nexport class NoPendingSyncTransactionCommitError extends TransactionError {\n constructor() {\n super(`No pending sync transaction to commit`)\n }\n}\n\nexport class SyncTransactionAlreadyCommittedError extends TransactionError {\n constructor() {\n super(\n `The pending sync transaction is already committed, you can't commit it again.`,\n )\n }\n}\n\n// Query Builder Errors\nexport class QueryBuilderError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryBuilderError`\n }\n}\n\nexport class OnlyOneSourceAllowedError extends QueryBuilderError {\n constructor(context: string) {\n super(`Only one source is allowed in the ${context}`)\n }\n}\n\nexport class SubQueryMustHaveFromClauseError extends QueryBuilderError {\n constructor(context: string) {\n super(`A sub query passed to a ${context} must have a from clause itself`)\n }\n}\n\nexport class InvalidSourceError extends QueryBuilderError {\n constructor(alias: string) {\n super(\n `Invalid source for live query: The value provided for alias \"${alias}\" is not a Collection or subquery. Live queries only accept Collection instances or subqueries. Please ensure you're passing a valid Collection or QueryBuilder, not a plain array or other data type.`,\n )\n }\n}\n\nexport class InvalidSourceTypeError extends QueryBuilderError {\n constructor(context: string, type: string) {\n super(\n `Invalid source for ${context}: Expected an object with a single key-value pair like { alias: collection }. ` +\n `For example: .from({ todos: todosCollection }). Got: ${type}`,\n )\n }\n}\n\nexport class JoinConditionMustBeEqualityError extends QueryBuilderError {\n constructor() {\n super(`Join condition must be an equality expression`)\n }\n}\n\nexport class QueryMustHaveFromClauseError extends QueryBuilderError {\n constructor() {\n super(`Query must have a from clause`)\n }\n}\n\n// Query Compilation Errors\nexport class QueryCompilationError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCompilationError`\n }\n}\n\nexport class DistinctRequiresSelectError extends QueryCompilationError {\n constructor() {\n super(`DISTINCT requires a SELECT clause.`)\n }\n}\n\nexport class HavingRequiresGroupByError extends QueryCompilationError {\n constructor() {\n super(`HAVING clause requires GROUP BY clause`)\n }\n}\n\nexport class LimitOffsetRequireOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`,\n )\n }\n}\n\n/**\n * Error thrown when a collection input stream is not found during query compilation.\n * In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream.\n */\nexport class CollectionInputNotFoundError extends QueryCompilationError {\n constructor(\n alias: string,\n collectionId?: string,\n availableKeys?: Array<string>,\n ) {\n const details = collectionId\n ? `alias \"${alias}\" (collection \"${collectionId}\")`\n : `collection \"${alias}\"`\n const availableKeysMsg = availableKeys?.length\n ? `. Available keys: ${availableKeys.join(`, `)}`\n : ``\n super(`Input for ${details} not found in inputs map${availableKeysMsg}`)\n }\n}\n\n/**\n * Error thrown when a subquery uses the same alias as its parent query.\n * This causes issues because parent and subquery would share the same input streams,\n * leading to empty results or incorrect data (aggregation cross-leaking).\n */\nexport class DuplicateAliasInSubqueryError extends QueryCompilationError {\n constructor(alias: string, parentAliases: Array<string>) {\n super(\n `Subquery uses alias \"${alias}\" which is already used in the parent query. ` +\n `Each alias must be unique across parent and subquery contexts. ` +\n `Parent query aliases: ${parentAliases.join(`, `)}. ` +\n `Please rename \"${alias}\" in either the parent query or subquery to avoid conflicts.`,\n )\n }\n}\n\nexport class UnsupportedFromTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unsupported FROM type: ${type}`)\n }\n}\n\nexport class UnknownExpressionTypeError extends QueryCompilationError {\n constructor(type: string) {\n super(`Unknown expression type: ${type}`)\n }\n}\n\nexport class EmptyReferencePathError extends QueryCompilationError {\n constructor() {\n super(`Reference path cannot be empty`)\n }\n}\n\nexport class UnknownFunctionError extends QueryCompilationError {\n constructor(functionName: string) {\n super(`Unknown function: ${functionName}`)\n }\n}\n\nexport class JoinCollectionNotFoundError extends QueryCompilationError {\n constructor(collectionId: string) {\n super(`Collection \"${collectionId}\" not found during compilation of join`)\n }\n}\n\n// JOIN Operation Errors\nexport class JoinError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `JoinError`\n }\n}\n\nexport class UnsupportedJoinTypeError extends JoinError {\n constructor(joinType: string) {\n super(`Unsupported join type: ${joinType}`)\n }\n}\n\nexport class InvalidJoinConditionSameSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: both expressions refer to the same source \"${sourceAlias}\"`,\n )\n }\n}\n\nexport class InvalidJoinConditionSourceMismatchError extends JoinError {\n constructor() {\n super(`Invalid join condition: expressions must reference source aliases`)\n }\n}\n\nexport class InvalidJoinConditionLeftSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: left expression refers to an unavailable source \"${sourceAlias}\"`,\n )\n }\n}\n\nexport class InvalidJoinConditionRightSourceError extends JoinError {\n constructor(sourceAlias: string) {\n super(\n `Invalid join condition: right expression does not refer to the joined source \"${sourceAlias}\"`,\n )\n }\n}\n\nexport class InvalidJoinCondition extends JoinError {\n constructor() {\n super(`Invalid join condition`)\n }\n}\n\nexport class UnsupportedJoinSourceTypeError extends JoinError {\n constructor(type: string) {\n super(`Unsupported join source type: ${type}`)\n }\n}\n\n// GROUP BY and Aggregation Errors\nexport class GroupByError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `GroupByError`\n }\n}\n\nexport class NonAggregateExpressionNotInGroupByError extends GroupByError {\n constructor(alias: string) {\n super(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`,\n )\n }\n}\n\nexport class UnsupportedAggregateFunctionError extends GroupByError {\n constructor(functionName: string) {\n super(`Unsupported aggregate function: ${functionName}`)\n }\n}\n\nexport class AggregateFunctionNotInSelectError extends GroupByError {\n constructor(functionName: string) {\n super(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${functionName}`,\n )\n }\n}\n\nexport class UnknownHavingExpressionTypeError extends GroupByError {\n constructor(type: string) {\n super(`Unknown expression type in HAVING clause: ${type}`)\n }\n}\n\n// Storage Errors\nexport class StorageError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `StorageError`\n }\n}\n\nexport class SerializationError extends StorageError {\n constructor(operation: string, originalError: string) {\n super(\n `Cannot ${operation} item because it cannot be JSON serialized: ${originalError}`,\n )\n }\n}\n\n// LocalStorage Collection Errors\nexport class LocalStorageCollectionError extends StorageError {\n constructor(message: string) {\n super(message)\n this.name = `LocalStorageCollectionError`\n }\n}\n\nexport class StorageKeyRequiredError extends LocalStorageCollectionError {\n constructor() {\n super(`[LocalStorageCollection] storageKey must be provided.`)\n }\n}\n\nexport class InvalidStorageDataFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string, key: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\" for key \"${key}\".`,\n )\n }\n}\n\nexport class InvalidStorageObjectFormatError extends LocalStorageCollectionError {\n constructor(storageKey: string) {\n super(\n `[LocalStorageCollection] Invalid data format in storage key \"${storageKey}\". Expected object format.`,\n )\n }\n}\n\n// Sync Cleanup Errors\nexport class SyncCleanupError extends TanStackDBError {\n constructor(collectionId: string, error: Error | string) {\n const message = error instanceof Error ? error.message : String(error)\n super(\n `Collection \"${collectionId}\" sync cleanup function threw an error: ${message}`,\n )\n this.name = `SyncCleanupError`\n }\n}\n\n// Query Optimizer Errors\nexport class QueryOptimizerError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryOptimizerError`\n }\n}\n\nexport class CannotCombineEmptyExpressionListError extends QueryOptimizerError {\n constructor() {\n super(`Cannot combine empty expression list`)\n }\n}\n\n/**\n * Internal error when the query optimizer fails to convert a WHERE clause to a collection filter.\n */\nexport class WhereClauseConversionError extends QueryOptimizerError {\n constructor(collectionId: string, alias: string) {\n super(\n `Failed to convert WHERE clause to collection filter for collection '${collectionId}' alias '${alias}'. This indicates a bug in the query optimization logic.`,\n )\n }\n}\n\n/**\n * Error when a subscription cannot be found during lazy join processing.\n * For subqueries, aliases may be remapped (e.g., 'activeUser' → 'user').\n */\nexport class SubscriptionNotFoundError extends QueryCompilationError {\n constructor(\n resolvedAlias: string,\n originalAlias: string,\n collectionId: string,\n availableAliases: Array<string>,\n ) {\n super(\n `Internal error: subscription for alias '${resolvedAlias}' (remapped from '${originalAlias}', collection '${collectionId}') is missing in join pipeline. Available aliases: ${availableAliases.join(`, `)}. This indicates a bug in alias tracking.`,\n )\n }\n}\n\n/**\n * Error thrown when aggregate expressions are used outside of a GROUP BY context.\n */\nexport class AggregateNotSupportedError extends QueryCompilationError {\n constructor() {\n super(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`,\n )\n }\n}\n\n/**\n * Internal error when the compiler returns aliases that don't have corresponding input streams.\n * This should never happen since all aliases come from user declarations.\n */\nexport class MissingAliasInputsError extends QueryCompilationError {\n constructor(missingAliases: Array<string>) {\n super(\n `Internal error: compiler returned aliases without inputs: ${missingAliases.join(`, `)}. ` +\n `This indicates a bug in query compilation. Please report this issue.`,\n )\n }\n}\n\n/**\n * Error thrown when setWindow is called on a collection without an ORDER BY clause.\n */\nexport class SetWindowRequiresOrderByError extends QueryCompilationError {\n constructor() {\n super(\n `setWindow() can only be called on collections with an ORDER BY clause. ` +\n `Add .orderBy() to your query to enable window movement.`,\n )\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"event-emitter.js","sources":["../../src/event-emitter.ts"],"sourcesContent":["/**\n * Generic type-safe event emitter\n * @template TEvents - Record of event names to event payload types\n */\nexport class EventEmitter<TEvents extends Record<string, any>> {\n private listeners = new Map<\n keyof TEvents,\n Set<(event: TEvents[keyof TEvents]) => void>\n >()\n\n /**\n * Subscribe to an event\n * @param event - Event name to listen for\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n on<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback as (event: any) => void)\n\n return () => {\n this.listeners.get(event)?.delete(callback as (event: any) => void)\n }\n }\n\n /**\n * Subscribe to an event once (automatically unsubscribes after first emission)\n * @param event - Event name to listen for\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n once<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void\n ): () => void {\n const unsubscribe = this.on(event, (eventPayload) => {\n callback(eventPayload)\n unsubscribe()\n })\n return unsubscribe\n }\n\n /**\n * Unsubscribe from an event\n * @param event - Event name to stop listening for\n * @param callback - Function to remove\n */\n off<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void\n ): void {\n this.listeners.get(event)?.delete(callback as (event: any) => void)\n }\n\n /**\n * Wait for an event to be emitted\n * @param event - Event name to wait for\n * @param timeout - Optional timeout in milliseconds\n * @returns Promise that resolves with the event payload\n */\n waitFor<T extends keyof TEvents>(\n event: T,\n timeout?: number\n ): Promise<TEvents[T]> {\n return new Promise((resolve, reject) => {\n let timeoutId: NodeJS.Timeout | undefined\n const unsubscribe = this.on(event, (eventPayload) => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n resolve(eventPayload)\n unsubscribe()\n })\n if (timeout) {\n timeoutId = setTimeout(() => {\n timeoutId = undefined\n unsubscribe()\n reject(new Error(`Timeout waiting for event ${String(event)}`))\n }, timeout)\n }\n })\n }\n\n /**\n * Emit an event to all listeners\n * @param event - Event name to emit\n * @param eventPayload - Event payload\n * @internal For use by subclasses - subclasses should wrap this with a public emit if needed\n */\n protected emitInner<T extends keyof TEvents>(\n event: T,\n eventPayload: TEvents[T]\n ): void {\n this.listeners.get(event)?.forEach((listener) => {\n try {\n listener(eventPayload)\n } catch (error) {\n // Re-throw in a microtask to surface the error\n queueMicrotask(() => {\n throw error\n })\n }\n })\n }\n\n /**\n * Clear all listeners\n */\n protected clearListeners(): void {\n this.listeners.clear()\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,aAAkD;AAAA,EAAxD,cAAA;AACL,SAAQ,gCAAgB,IAAA;AAAA,EAGtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQF,GACE,OACA,UACY;AACZ,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,KAAK;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAgC;AAE/D,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAgC;AAAA,IACpE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KACE,OACA,UACY;AACZ,UAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,eAAS,YAAY;AACrB,kBAAA;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IACE,OACA,UACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAgC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QACE,OACA,SACqB;AACrB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACJ,YAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,YAAI,WAAW;AACb,uBAAa,SAAS;AACtB,sBAAY;AAAA,QACd;AACA,gBAAQ,YAAY;AACpB,oBAAA;AAAA,MACF,CAAC;AACD,UAAI,SAAS;AACX,oBAAY,WAAW,MAAM;AAC3B,sBAAY;AACZ,sBAAA;AACA,iBAAO,IAAI,MAAM,6BAA6B,OAAO,KAAK,CAAC,EAAE,CAAC;AAAA,QAChE,GAAG,OAAO;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,UACR,OACA,cACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa;AAC/C,UAAI;AACF,iBAAS,YAAY;AAAA,MACvB,SAAS,OAAO;AAEd,uBAAe,MAAM;AACnB,gBAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKU,iBAAuB;AAC/B,SAAK,UAAU,MAAA;AAAA,EACjB;AACF;"}
1
+ {"version":3,"file":"event-emitter.js","sources":["../../src/event-emitter.ts"],"sourcesContent":["/**\n * Generic type-safe event emitter\n * @template TEvents - Record of event names to event payload types\n */\nexport class EventEmitter<TEvents extends Record<string, any>> {\n private listeners = new Map<\n keyof TEvents,\n Set<(event: TEvents[keyof TEvents]) => void>\n >()\n\n /**\n * Subscribe to an event\n * @param event - Event name to listen for\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n on<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback as (event: any) => void)\n\n return () => {\n this.listeners.get(event)?.delete(callback as (event: any) => void)\n }\n }\n\n /**\n * Subscribe to an event once (automatically unsubscribes after first emission)\n * @param event - Event name to listen for\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n once<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void,\n ): () => void {\n const unsubscribe = this.on(event, (eventPayload) => {\n callback(eventPayload)\n unsubscribe()\n })\n return unsubscribe\n }\n\n /**\n * Unsubscribe from an event\n * @param event - Event name to stop listening for\n * @param callback - Function to remove\n */\n off<T extends keyof TEvents>(\n event: T,\n callback: (event: TEvents[T]) => void,\n ): void {\n this.listeners.get(event)?.delete(callback as (event: any) => void)\n }\n\n /**\n * Wait for an event to be emitted\n * @param event - Event name to wait for\n * @param timeout - Optional timeout in milliseconds\n * @returns Promise that resolves with the event payload\n */\n waitFor<T extends keyof TEvents>(\n event: T,\n timeout?: number,\n ): Promise<TEvents[T]> {\n return new Promise((resolve, reject) => {\n let timeoutId: NodeJS.Timeout | undefined\n const unsubscribe = this.on(event, (eventPayload) => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n timeoutId = undefined\n }\n resolve(eventPayload)\n unsubscribe()\n })\n if (timeout) {\n timeoutId = setTimeout(() => {\n timeoutId = undefined\n unsubscribe()\n reject(new Error(`Timeout waiting for event ${String(event)}`))\n }, timeout)\n }\n })\n }\n\n /**\n * Emit an event to all listeners\n * @param event - Event name to emit\n * @param eventPayload - Event payload\n * @internal For use by subclasses - subclasses should wrap this with a public emit if needed\n */\n protected emitInner<T extends keyof TEvents>(\n event: T,\n eventPayload: TEvents[T],\n ): void {\n this.listeners.get(event)?.forEach((listener) => {\n try {\n listener(eventPayload)\n } catch (error) {\n // Re-throw in a microtask to surface the error\n queueMicrotask(() => {\n throw error\n })\n }\n })\n }\n\n /**\n * Clear all listeners\n */\n protected clearListeners(): void {\n this.listeners.clear()\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,aAAkD;AAAA,EAAxD,cAAA;AACL,SAAQ,gCAAgB,IAAA;AAAA,EAGtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQF,GACE,OACA,UACY;AACZ,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,KAAK;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAgC;AAE/D,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAgC;AAAA,IACpE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KACE,OACA,UACY;AACZ,UAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,eAAS,YAAY;AACrB,kBAAA;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IACE,OACA,UACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAgC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QACE,OACA,SACqB;AACrB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACJ,YAAM,cAAc,KAAK,GAAG,OAAO,CAAC,iBAAiB;AACnD,YAAI,WAAW;AACb,uBAAa,SAAS;AACtB,sBAAY;AAAA,QACd;AACA,gBAAQ,YAAY;AACpB,oBAAA;AAAA,MACF,CAAC;AACD,UAAI,SAAS;AACX,oBAAY,WAAW,MAAM;AAC3B,sBAAY;AACZ,sBAAA;AACA,iBAAO,IAAI,MAAM,6BAA6B,OAAO,KAAK,CAAC,EAAE,CAAC;AAAA,QAChE,GAAG,OAAO;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,UACR,OACA,cACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa;AAC/C,UAAI;AACF,iBAAS,YAAY;AAAA,MACvB,SAAS,OAAO;AAEd,uBAAe,MAAM;AACnB,gBAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKU,iBAAuB;AAC/B,SAAK,UAAU,MAAA;AAAA,EACjB;AACF;"}
@@ -1,4 +1,4 @@
1
- import * as IR from "./query/ir.js";
1
+ import * as IR from './query/ir.js';
2
2
  export * from './collection/index.js';
3
3
  export * from './SortedMap.js';
4
4
  export * from './transactions.js';
package/dist/esm/index.js CHANGED
@@ -6,7 +6,7 @@ import { createArrayChangeProxy, createChangeProxy, withArrayChangeTracking, wit
6
6
  import { createOptimisticAction } from "./optimistic-action.js";
7
7
  import { localOnlyCollectionOptions } from "./local-only.js";
8
8
  import { localStorageCollectionOptions } from "./local-storage.js";
9
- import { AggregateFunctionNotInSelectError, AggregateNotSupportedError, CannotCombineEmptyExpressionListError, CollectionConfigurationError, CollectionInErrorStateError, CollectionInputNotFoundError, CollectionIsInErrorStateError, CollectionOperationError, CollectionRequiresConfigError, CollectionRequiresSyncConfigError, CollectionStateError, DeleteKeyNotFoundError, DistinctRequiresSelectError, DuplicateAliasInSubqueryError, DuplicateDbInstanceError, DuplicateKeyError, DuplicateKeySyncError, EmptyReferencePathError, GroupByError, HavingRequiresGroupByError, InvalidCollectionStatusTransitionError, InvalidJoinCondition, InvalidJoinConditionLeftSourceError, InvalidJoinConditionRightSourceError, InvalidJoinConditionSameSourceError, InvalidJoinConditionSourceMismatchError, InvalidSchemaError, InvalidSourceError, InvalidSourceTypeError, InvalidStorageDataFormatError, InvalidStorageObjectFormatError, JoinCollectionNotFoundError, JoinConditionMustBeEqualityError, JoinError, KeyUpdateNotAllowedError, LimitOffsetRequireOrderByError, LocalStorageCollectionError, MissingAliasInputsError, MissingDeleteHandlerError, MissingHandlerError, MissingInsertHandlerError, MissingMutationFunctionError, MissingUpdateArgumentError, MissingUpdateHandlerError, NegativeActiveSubscribersError, NoKeysPassedToDeleteError, NoKeysPassedToUpdateError, NoPendingSyncTransactionCommitError, NoPendingSyncTransactionWriteError, NonAggregateExpressionNotInGroupByError, NonRetriableError, OnMutateMustBeSynchronousError, OnlyOneSourceAllowedError, QueryBuilderError, QueryCompilationError, QueryMustHaveFromClauseError, QueryOptimizerError, SchemaMustBeSynchronousError, SchemaValidationError, SerializationError, SetWindowRequiresOrderByError, StorageError, StorageKeyRequiredError, SubQueryMustHaveFromClauseError, SubscriptionNotFoundError, SyncCleanupError, SyncTransactionAlreadyCommittedError, SyncTransactionAlreadyCommittedWriteError, TanStackDBError, TransactionAlreadyCompletedRollbackError, TransactionError, TransactionNotPendingCommitError, TransactionNotPendingMutateError, UndefinedKeyError, UnknownExpressionTypeError, UnknownFunctionError, UnknownHavingExpressionTypeError, UnsupportedAggregateFunctionError, UnsupportedFromTypeError, UnsupportedJoinSourceTypeError, UnsupportedJoinTypeError, UpdateKeyNotFoundError, WhereClauseConversionError } from "./errors.js";
9
+ import { AggregateFunctionNotInSelectError, AggregateNotSupportedError, CannotCombineEmptyExpressionListError, CollectionConfigurationError, CollectionInErrorStateError, CollectionInputNotFoundError, CollectionIsInErrorStateError, CollectionOperationError, CollectionRequiresConfigError, CollectionRequiresSyncConfigError, CollectionStateError, DeleteKeyNotFoundError, DistinctRequiresSelectError, DuplicateAliasInSubqueryError, DuplicateDbInstanceError, DuplicateKeyError, DuplicateKeySyncError, EmptyReferencePathError, GroupByError, HavingRequiresGroupByError, InvalidCollectionStatusTransitionError, InvalidJoinCondition, InvalidJoinConditionLeftSourceError, InvalidJoinConditionRightSourceError, InvalidJoinConditionSameSourceError, InvalidJoinConditionSourceMismatchError, InvalidKeyError, InvalidSchemaError, InvalidSourceError, InvalidSourceTypeError, InvalidStorageDataFormatError, InvalidStorageObjectFormatError, JoinCollectionNotFoundError, JoinConditionMustBeEqualityError, JoinError, KeyUpdateNotAllowedError, LimitOffsetRequireOrderByError, LocalStorageCollectionError, MissingAliasInputsError, MissingDeleteHandlerError, MissingHandlerError, MissingInsertHandlerError, MissingMutationFunctionError, MissingUpdateArgumentError, MissingUpdateHandlerError, NegativeActiveSubscribersError, NoKeysPassedToDeleteError, NoKeysPassedToUpdateError, NoPendingSyncTransactionCommitError, NoPendingSyncTransactionWriteError, NonAggregateExpressionNotInGroupByError, NonRetriableError, OnMutateMustBeSynchronousError, OnlyOneSourceAllowedError, QueryBuilderError, QueryCompilationError, QueryMustHaveFromClauseError, QueryOptimizerError, SchemaMustBeSynchronousError, SchemaValidationError, SerializationError, SetWindowRequiresOrderByError, StorageError, StorageKeyRequiredError, SubQueryMustHaveFromClauseError, SubscriptionNotFoundError, SyncCleanupError, SyncTransactionAlreadyCommittedError, SyncTransactionAlreadyCommittedWriteError, TanStackDBError, TransactionAlreadyCompletedRollbackError, TransactionError, TransactionNotPendingCommitError, TransactionNotPendingMutateError, UndefinedKeyError, UnknownExpressionTypeError, UnknownFunctionError, UnknownHavingExpressionTypeError, UnsupportedAggregateFunctionError, UnsupportedFromTypeError, UnsupportedJoinSourceTypeError, UnsupportedJoinTypeError, UpdateKeyNotFoundError, WhereClauseConversionError } from "./errors.js";
10
10
  import { deepEquals } from "./utils.js";
11
11
  import { createPacedMutations } from "./paced-mutations.js";
12
12
  import { BaseIndex, IndexOperation } from "./indexes/base-index.js";
@@ -17,7 +17,7 @@ import { add, and, avg, coalesce, concat, count, eq, gt, gte, ilike, inArray, is
17
17
  import { BaseQueryBuilder, Query } from "./query/builder/index.js";
18
18
  import { compileQuery } from "./query/compiler/index.js";
19
19
  import { createLiveQueryCollection, liveQueryCollectionOptions } from "./query/live-query-collection.js";
20
- import { isLimitSubset, isOrderBySubset, isPredicateSubset, isWhereSubset, minusWherePredicates, unionWherePredicates } from "./query/predicate-utils.js";
20
+ import { isLimitSubset, isOffsetLimitSubset, isOrderBySubset, isPredicateSubset, isWhereSubset, minusWherePredicates, unionWherePredicates } from "./query/predicate-utils.js";
21
21
  import { DeduplicatedLoadSubset } from "./query/subset-dedupe.js";
22
22
  import { debounceStrategy } from "./strategies/debounceStrategy.js";
23
23
  import { queueStrategy } from "./strategies/queueStrategy.js";
@@ -57,6 +57,7 @@ export {
57
57
  InvalidJoinConditionRightSourceError,
58
58
  InvalidJoinConditionSameSourceError,
59
59
  InvalidJoinConditionSourceMismatchError,
60
+ InvalidKeyError,
60
61
  InvalidSchemaError,
61
62
  InvalidSourceError,
62
63
  InvalidSourceTypeError,
@@ -144,6 +145,7 @@ export {
144
145
  inArray,
145
146
  isLimitSubset,
146
147
  isNull,
148
+ isOffsetLimitSubset,
147
149
  isOrderBySubset,
148
150
  isPredicateSubset,
149
151
  isUndefined,
@@ -1 +1 @@
1
- {"version":3,"file":"auto-index.js","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from \"../utils\"\nimport { BTreeIndex } from \"./btree-index\"\nimport type { CompareOptions } from \"../query/builder/types\"\nimport type { BasicExpression } from \"../query/ir\"\nimport type { CollectionImpl } from \"../collection/index.js\"\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions?: CompareOptions,\n compareFn?: (a: any, b: any) => number\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n // Use the proxy-based approach to create the proper accessor for nested paths\n collection.createIndex(\n (row) => {\n // Navigate through the field path\n let current: any = row\n for (const part of fieldPath) {\n current = current[part]\n }\n return current\n },\n {\n name: `auto:${fieldPath.join(`.`)}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions: compareOpts } : {},\n }\n )\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path \"${fieldPath.join(`.`)}\":`,\n error\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if the path is empty\n if (fieldPath.length === 0) {\n return\n }\n\n // For nested paths, use the full path joined with underscores as the field name\n // For simple paths, use the first (and only) element\n const fieldName = fieldPath.join(`_`)\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":[],"mappings":";;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,gBACA,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,GAAG;AAAA,IACH,GAAG,WAAW;AAAA,EAAA;AAIhB,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAAK,MAAM,sBAAsB,WAAW;AAAA,EAAA;AAG5E,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AAEF,eAAW;AAAA,MACT,CAAC,QAAQ;AAEP,YAAI,UAAe;AACnB,mBAAW,QAAQ,WAAW;AAC5B,oBAAU,QAAQ,IAAI;AAAA,QACxB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,QAAQ,UAAU,KAAK,GAAG,CAAC;AAAA,QACjC,WAAW;AAAA,QACX,SAAS,YAAY,EAAE,WAAW,gBAAgB,YAAA,IAAgB,CAAA;AAAA,MAAC;AAAA,IACrE;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,+CAA+C,UAAU,KAAK,GAAG,CAAC;AAAA,MAC/G;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAIA,UAAM,YAAY,UAAU,KAAK,GAAG;AACpC,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;"}
1
+ {"version":3,"file":"auto-index.js","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from '../utils'\nimport { BTreeIndex } from './btree-index'\nimport type { CompareOptions } from '../query/builder/types'\nimport type { BasicExpression } from '../query/ir'\nimport type { CollectionImpl } from '../collection/index.js'\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions?: CompareOptions,\n compareFn?: (a: any, b: any) => number,\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts),\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n // Use the proxy-based approach to create the proper accessor for nested paths\n collection.createIndex(\n (row) => {\n // Navigate through the field path\n let current: any = row\n for (const part of fieldPath) {\n current = current[part]\n }\n return current\n },\n {\n name: `auto:${fieldPath.join(`.`)}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions: compareOpts } : {},\n },\n )\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path \"${fieldPath.join(`.`)}\":`,\n error,\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>,\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression,\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if the path is empty\n if (fieldPath.length === 0) {\n return\n }\n\n // For nested paths, use the full path joined with underscores as the field name\n // For simple paths, use the first (and only) element\n const fieldName = fieldPath.join(`_`)\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":[],"mappings":";;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,gBACA,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,GAAG;AAAA,IACH,GAAG,WAAW;AAAA,EAAA;AAIhB,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAAK,MAAM,sBAAsB,WAAW;AAAA,EAAA;AAG5E,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AAEF,eAAW;AAAA,MACT,CAAC,QAAQ;AAEP,YAAI,UAAe;AACnB,mBAAW,QAAQ,WAAW;AAC5B,oBAAU,QAAQ,IAAI;AAAA,QACxB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,QAAQ,UAAU,KAAK,GAAG,CAAC;AAAA,QACjC,WAAW;AAAA,QACX,SAAS,YAAY,EAAE,WAAW,gBAAgB,YAAA,IAAgB,CAAA;AAAA,MAAC;AAAA,IACrE;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,+CAA+C,UAAU,KAAK,GAAG,CAAC;AAAA,MAC/G;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAIA,UAAM,YAAY,UAAU,KAAK,GAAG;AACpC,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;"}
@@ -1 +1 @@
1
- {"version":3,"file":"base-index.js","sources":["../../../src/indexes/base-index.ts"],"sourcesContent":["import { compileSingleRowExpression } from \"../query/compiler/evaluators.js\"\nimport { comparisonFunctions } from \"../query/builder/functions.js\"\nimport { DEFAULT_COMPARE_OPTIONS, deepEquals } from \"../utils.js\"\nimport type { RangeQueryOptions } from \"./btree-index.js\"\nimport type { CompareOptions } from \"../query/builder/types.js\"\nimport type { BasicExpression, OrderByDirection } from \"../query/ir.js\"\n\n/**\n * Operations that indexes can support, imported from available comparison functions\n */\nexport const IndexOperation = comparisonFunctions\n\n/**\n * Type for index operation values\n */\nexport type IndexOperation = (typeof comparisonFunctions)[number]\n\n/**\n * Statistics about index usage and performance\n */\nexport interface IndexStats {\n readonly entryCount: number\n readonly lookupCount: number\n readonly averageLookupTime: number\n readonly lastUpdated: Date\n}\n\nexport interface IndexInterface<\n TKey extends string | number = string | number,\n> {\n add: (key: TKey, item: any) => void\n remove: (key: TKey, item: any) => void\n update: (key: TKey, oldItem: any, newItem: any) => void\n\n build: (entries: Iterable<[TKey, any]>) => void\n clear: () => void\n\n lookup: (operation: IndexOperation, value: any) => Set<TKey>\n\n equalityLookup: (value: any) => Set<TKey>\n inArrayLookup: (values: Array<any>) => Set<TKey>\n\n rangeQuery: (options: RangeQueryOptions) => Set<TKey>\n rangeQueryReversed: (options: RangeQueryOptions) => Set<TKey>\n\n take: (\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean\n ) => Array<TKey>\n takeReversed: (\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean\n ) => Array<TKey>\n\n get keyCount(): number\n get orderedEntriesArray(): Array<[any, Set<TKey>]>\n get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>\n\n get indexedKeysSet(): Set<TKey>\n get valueMapData(): Map<any, Set<TKey>>\n\n supports: (operation: IndexOperation) => boolean\n\n matchesField: (fieldPath: Array<string>) => boolean\n matchesCompareOptions: (compareOptions: CompareOptions) => boolean\n matchesDirection: (direction: OrderByDirection) => boolean\n\n getStats: () => IndexStats\n}\n\n/**\n * Base abstract class that all index types extend\n */\nexport abstract class BaseIndex<\n TKey extends string | number = string | number,\n> implements IndexInterface<TKey> {\n public readonly id: number\n public readonly name?: string\n public readonly expression: BasicExpression\n public abstract readonly supportedOperations: Set<IndexOperation>\n\n protected lookupCount = 0\n protected totalLookupTime = 0\n protected lastUpdated = new Date()\n protected compareOptions: CompareOptions\n\n constructor(\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any\n ) {\n this.id = id\n this.expression = expression\n this.compareOptions = DEFAULT_COMPARE_OPTIONS\n this.name = name\n this.initialize(options)\n }\n\n // Abstract methods that each index type must implement\n abstract add(key: TKey, item: any): void\n abstract remove(key: TKey, item: any): void\n abstract update(key: TKey, oldItem: any, newItem: any): void\n abstract build(entries: Iterable<[TKey, any]>): void\n abstract clear(): void\n abstract lookup(operation: IndexOperation, value: any): Set<TKey>\n abstract take(\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean\n ): Array<TKey>\n abstract takeReversed(\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean\n ): Array<TKey>\n abstract get keyCount(): number\n abstract equalityLookup(value: any): Set<TKey>\n abstract inArrayLookup(values: Array<any>): Set<TKey>\n abstract rangeQuery(options: RangeQueryOptions): Set<TKey>\n abstract rangeQueryReversed(options: RangeQueryOptions): Set<TKey>\n abstract get orderedEntriesArray(): Array<[any, Set<TKey>]>\n abstract get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>\n abstract get indexedKeysSet(): Set<TKey>\n abstract get valueMapData(): Map<any, Set<TKey>>\n\n // Common methods\n supports(operation: IndexOperation): boolean {\n return this.supportedOperations.has(operation)\n }\n\n matchesField(fieldPath: Array<string>): boolean {\n return (\n this.expression.type === `ref` &&\n this.expression.path.length === fieldPath.length &&\n this.expression.path.every((part, i) => part === fieldPath[i])\n )\n }\n\n /**\n * Checks if the compare options match the index's compare options.\n * The direction is ignored because the index can be reversed if the direction is different.\n */\n matchesCompareOptions(compareOptions: CompareOptions): boolean {\n const thisCompareOptionsWithoutDirection = {\n ...this.compareOptions,\n direction: undefined,\n }\n const compareOptionsWithoutDirection = {\n ...compareOptions,\n direction: undefined,\n }\n\n return deepEquals(\n thisCompareOptionsWithoutDirection,\n compareOptionsWithoutDirection\n )\n }\n\n /**\n * Checks if the index matches the provided direction.\n */\n matchesDirection(direction: OrderByDirection): boolean {\n return this.compareOptions.direction === direction\n }\n\n getStats(): IndexStats {\n return {\n entryCount: this.keyCount,\n lookupCount: this.lookupCount,\n averageLookupTime:\n this.lookupCount > 0 ? this.totalLookupTime / this.lookupCount : 0,\n lastUpdated: this.lastUpdated,\n }\n }\n\n // Protected methods for subclasses\n protected abstract initialize(options?: any): void\n\n protected evaluateIndexExpression(item: any): any {\n const evaluator = compileSingleRowExpression(this.expression)\n return evaluator(item as Record<string, unknown>)\n }\n\n protected trackLookup(startTime: number): void {\n const duration = performance.now() - startTime\n this.lookupCount++\n this.totalLookupTime += duration\n }\n\n protected updateTimestamp(): void {\n this.lastUpdated = new Date()\n }\n}\n\n/**\n * Type for index constructor\n */\nexport type IndexConstructor<TKey extends string | number = string | number> =\n new (\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any\n ) => BaseIndex<TKey>\n\n/**\n * Index resolver can be either a class constructor or async loader\n */\nexport type IndexResolver<TKey extends string | number = string | number> =\n | IndexConstructor<TKey>\n | (() => Promise<IndexConstructor<TKey>>)\n"],"names":[],"mappings":";;;AAUO,MAAM,iBAAiB;AAiEvB,MAAe,UAEY;AAAA,EAWhC,YACE,IACA,YACA,MACA,SACA;AAVF,SAAU,cAAc;AACxB,SAAU,kBAAkB;AAC5B,SAAU,kCAAkB,KAAA;AAS1B,SAAK,KAAK;AACV,SAAK,aAAa;AAClB,SAAK,iBAAiB;AACtB,SAAK,OAAO;AACZ,SAAK,WAAW,OAAO;AAAA,EACzB;AAAA;AAAA,EA8BA,SAAS,WAAoC;AAC3C,WAAO,KAAK,oBAAoB,IAAI,SAAS;AAAA,EAC/C;AAAA,EAEA,aAAa,WAAmC;AAC9C,WACE,KAAK,WAAW,SAAS,SACzB,KAAK,WAAW,KAAK,WAAW,UAAU,UAC1C,KAAK,WAAW,KAAK,MAAM,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,CAAC;AAAA,EAEjE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,gBAAyC;AAC7D,UAAM,qCAAqC;AAAA,MACzC,GAAG,KAAK;AAAA,MACR,WAAW;AAAA,IAAA;AAEb,UAAM,iCAAiC;AAAA,MACrC,GAAG;AAAA,MACH,WAAW;AAAA,IAAA;AAGb,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,WAAsC;AACrD,WAAO,KAAK,eAAe,cAAc;AAAA,EAC3C;AAAA,EAEA,WAAuB;AACrB,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,mBACE,KAAK,cAAc,IAAI,KAAK,kBAAkB,KAAK,cAAc;AAAA,MACnE,aAAa,KAAK;AAAA,IAAA;AAAA,EAEtB;AAAA,EAKU,wBAAwB,MAAgB;AAChD,UAAM,YAAY,2BAA2B,KAAK,UAAU;AAC5D,WAAO,UAAU,IAA+B;AAAA,EAClD;AAAA,EAEU,YAAY,WAAyB;AAC7C,UAAM,WAAW,YAAY,IAAA,IAAQ;AACrC,SAAK;AACL,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEU,kBAAwB;AAChC,SAAK,kCAAkB,KAAA;AAAA,EACzB;AACF;"}
1
+ {"version":3,"file":"base-index.js","sources":["../../../src/indexes/base-index.ts"],"sourcesContent":["import { compileSingleRowExpression } from '../query/compiler/evaluators.js'\nimport { comparisonFunctions } from '../query/builder/functions.js'\nimport { DEFAULT_COMPARE_OPTIONS, deepEquals } from '../utils.js'\nimport type { RangeQueryOptions } from './btree-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { BasicExpression, OrderByDirection } from '../query/ir.js'\n\n/**\n * Operations that indexes can support, imported from available comparison functions\n */\nexport const IndexOperation = comparisonFunctions\n\n/**\n * Type for index operation values\n */\nexport type IndexOperation = (typeof comparisonFunctions)[number]\n\n/**\n * Statistics about index usage and performance\n */\nexport interface IndexStats {\n readonly entryCount: number\n readonly lookupCount: number\n readonly averageLookupTime: number\n readonly lastUpdated: Date\n}\n\nexport interface IndexInterface<\n TKey extends string | number = string | number,\n> {\n add: (key: TKey, item: any) => void\n remove: (key: TKey, item: any) => void\n update: (key: TKey, oldItem: any, newItem: any) => void\n\n build: (entries: Iterable<[TKey, any]>) => void\n clear: () => void\n\n lookup: (operation: IndexOperation, value: any) => Set<TKey>\n\n equalityLookup: (value: any) => Set<TKey>\n inArrayLookup: (values: Array<any>) => Set<TKey>\n\n rangeQuery: (options: RangeQueryOptions) => Set<TKey>\n rangeQueryReversed: (options: RangeQueryOptions) => Set<TKey>\n\n take: (\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean,\n ) => Array<TKey>\n takeReversed: (\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean,\n ) => Array<TKey>\n\n get keyCount(): number\n get orderedEntriesArray(): Array<[any, Set<TKey>]>\n get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>\n\n get indexedKeysSet(): Set<TKey>\n get valueMapData(): Map<any, Set<TKey>>\n\n supports: (operation: IndexOperation) => boolean\n\n matchesField: (fieldPath: Array<string>) => boolean\n matchesCompareOptions: (compareOptions: CompareOptions) => boolean\n matchesDirection: (direction: OrderByDirection) => boolean\n\n getStats: () => IndexStats\n}\n\n/**\n * Base abstract class that all index types extend\n */\nexport abstract class BaseIndex<\n TKey extends string | number = string | number,\n> implements IndexInterface<TKey> {\n public readonly id: number\n public readonly name?: string\n public readonly expression: BasicExpression\n public abstract readonly supportedOperations: Set<IndexOperation>\n\n protected lookupCount = 0\n protected totalLookupTime = 0\n protected lastUpdated = new Date()\n protected compareOptions: CompareOptions\n\n constructor(\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any,\n ) {\n this.id = id\n this.expression = expression\n this.compareOptions = DEFAULT_COMPARE_OPTIONS\n this.name = name\n this.initialize(options)\n }\n\n // Abstract methods that each index type must implement\n abstract add(key: TKey, item: any): void\n abstract remove(key: TKey, item: any): void\n abstract update(key: TKey, oldItem: any, newItem: any): void\n abstract build(entries: Iterable<[TKey, any]>): void\n abstract clear(): void\n abstract lookup(operation: IndexOperation, value: any): Set<TKey>\n abstract take(\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey>\n abstract takeReversed(\n n: number,\n from?: TKey,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey>\n abstract get keyCount(): number\n abstract equalityLookup(value: any): Set<TKey>\n abstract inArrayLookup(values: Array<any>): Set<TKey>\n abstract rangeQuery(options: RangeQueryOptions): Set<TKey>\n abstract rangeQueryReversed(options: RangeQueryOptions): Set<TKey>\n abstract get orderedEntriesArray(): Array<[any, Set<TKey>]>\n abstract get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>\n abstract get indexedKeysSet(): Set<TKey>\n abstract get valueMapData(): Map<any, Set<TKey>>\n\n // Common methods\n supports(operation: IndexOperation): boolean {\n return this.supportedOperations.has(operation)\n }\n\n matchesField(fieldPath: Array<string>): boolean {\n return (\n this.expression.type === `ref` &&\n this.expression.path.length === fieldPath.length &&\n this.expression.path.every((part, i) => part === fieldPath[i])\n )\n }\n\n /**\n * Checks if the compare options match the index's compare options.\n * The direction is ignored because the index can be reversed if the direction is different.\n */\n matchesCompareOptions(compareOptions: CompareOptions): boolean {\n const thisCompareOptionsWithoutDirection = {\n ...this.compareOptions,\n direction: undefined,\n }\n const compareOptionsWithoutDirection = {\n ...compareOptions,\n direction: undefined,\n }\n\n return deepEquals(\n thisCompareOptionsWithoutDirection,\n compareOptionsWithoutDirection,\n )\n }\n\n /**\n * Checks if the index matches the provided direction.\n */\n matchesDirection(direction: OrderByDirection): boolean {\n return this.compareOptions.direction === direction\n }\n\n getStats(): IndexStats {\n return {\n entryCount: this.keyCount,\n lookupCount: this.lookupCount,\n averageLookupTime:\n this.lookupCount > 0 ? this.totalLookupTime / this.lookupCount : 0,\n lastUpdated: this.lastUpdated,\n }\n }\n\n // Protected methods for subclasses\n protected abstract initialize(options?: any): void\n\n protected evaluateIndexExpression(item: any): any {\n const evaluator = compileSingleRowExpression(this.expression)\n return evaluator(item as Record<string, unknown>)\n }\n\n protected trackLookup(startTime: number): void {\n const duration = performance.now() - startTime\n this.lookupCount++\n this.totalLookupTime += duration\n }\n\n protected updateTimestamp(): void {\n this.lastUpdated = new Date()\n }\n}\n\n/**\n * Type for index constructor\n */\nexport type IndexConstructor<TKey extends string | number = string | number> =\n new (\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any,\n ) => BaseIndex<TKey>\n\n/**\n * Index resolver can be either a class constructor or async loader\n */\nexport type IndexResolver<TKey extends string | number = string | number> =\n | IndexConstructor<TKey>\n | (() => Promise<IndexConstructor<TKey>>)\n"],"names":[],"mappings":";;;AAUO,MAAM,iBAAiB;AAiEvB,MAAe,UAEY;AAAA,EAWhC,YACE,IACA,YACA,MACA,SACA;AAVF,SAAU,cAAc;AACxB,SAAU,kBAAkB;AAC5B,SAAU,kCAAkB,KAAA;AAS1B,SAAK,KAAK;AACV,SAAK,aAAa;AAClB,SAAK,iBAAiB;AACtB,SAAK,OAAO;AACZ,SAAK,WAAW,OAAO;AAAA,EACzB;AAAA;AAAA,EA8BA,SAAS,WAAoC;AAC3C,WAAO,KAAK,oBAAoB,IAAI,SAAS;AAAA,EAC/C;AAAA,EAEA,aAAa,WAAmC;AAC9C,WACE,KAAK,WAAW,SAAS,SACzB,KAAK,WAAW,KAAK,WAAW,UAAU,UAC1C,KAAK,WAAW,KAAK,MAAM,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,CAAC;AAAA,EAEjE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,gBAAyC;AAC7D,UAAM,qCAAqC;AAAA,MACzC,GAAG,KAAK;AAAA,MACR,WAAW;AAAA,IAAA;AAEb,UAAM,iCAAiC;AAAA,MACrC,GAAG;AAAA,MACH,WAAW;AAAA,IAAA;AAGb,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,WAAsC;AACrD,WAAO,KAAK,eAAe,cAAc;AAAA,EAC3C;AAAA,EAEA,WAAuB;AACrB,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,mBACE,KAAK,cAAc,IAAI,KAAK,kBAAkB,KAAK,cAAc;AAAA,MACnE,aAAa,KAAK;AAAA,IAAA;AAAA,EAEtB;AAAA,EAKU,wBAAwB,MAAgB;AAChD,UAAM,YAAY,2BAA2B,KAAK,UAAU;AAC5D,WAAO,UAAU,IAA+B;AAAA,EAClD;AAAA,EAEU,YAAY,WAAyB;AAC7C,UAAM,WAAW,YAAY,IAAA,IAAQ;AACrC,SAAK;AACL,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEU,kBAAwB;AAChC,SAAK,kCAAkB,KAAA;AAAA,EACzB;AACF;"}