@tanstack/db 0.5.33 → 0.6.0

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 (273) hide show
  1. package/dist/cjs/collection/change-events.cjs.map +1 -1
  2. package/dist/cjs/collection/change-events.d.cts +3 -2
  3. package/dist/cjs/collection/changes.cjs +13 -4
  4. package/dist/cjs/collection/changes.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.d.cts +10 -1
  6. package/dist/cjs/collection/cleanup-queue.cjs +89 -0
  7. package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
  8. package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
  9. package/dist/cjs/collection/events.cjs +14 -0
  10. package/dist/cjs/collection/events.cjs.map +1 -1
  11. package/dist/cjs/collection/events.d.cts +39 -1
  12. package/dist/cjs/collection/index.cjs +66 -28
  13. package/dist/cjs/collection/index.cjs.map +1 -1
  14. package/dist/cjs/collection/index.d.cts +49 -36
  15. package/dist/cjs/collection/indexes.cjs +211 -62
  16. package/dist/cjs/collection/indexes.cjs.map +1 -1
  17. package/dist/cjs/collection/indexes.d.cts +27 -17
  18. package/dist/cjs/collection/lifecycle.cjs +5 -22
  19. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  20. package/dist/cjs/collection/lifecycle.d.cts +0 -1
  21. package/dist/cjs/collection/mutations.cjs +18 -0
  22. package/dist/cjs/collection/mutations.cjs.map +1 -1
  23. package/dist/cjs/collection/mutations.d.cts +1 -0
  24. package/dist/cjs/collection/state.cjs +381 -53
  25. package/dist/cjs/collection/state.cjs.map +1 -1
  26. package/dist/cjs/collection/state.d.cts +65 -1
  27. package/dist/cjs/collection/subscription.cjs +6 -0
  28. package/dist/cjs/collection/subscription.cjs.map +1 -1
  29. package/dist/cjs/collection/subscription.d.cts +4 -0
  30. package/dist/cjs/collection/sync.cjs +108 -1
  31. package/dist/cjs/collection/sync.cjs.map +1 -1
  32. package/dist/cjs/collection/sync.d.cts +2 -0
  33. package/dist/cjs/collection/transaction-metadata.cjs +5 -0
  34. package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
  35. package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
  36. package/dist/cjs/errors.cjs +8 -0
  37. package/dist/cjs/errors.cjs.map +1 -1
  38. package/dist/cjs/errors.d.cts +3 -0
  39. package/dist/cjs/index.cjs +22 -4
  40. package/dist/cjs/index.cjs.map +1 -1
  41. package/dist/cjs/index.d.cts +11 -3
  42. package/dist/cjs/indexes/auto-index.cjs +13 -6
  43. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  44. package/dist/cjs/indexes/base-index.cjs +0 -3
  45. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  46. package/dist/cjs/indexes/base-index.d.cts +2 -6
  47. package/dist/cjs/indexes/basic-index.cjs +361 -0
  48. package/dist/cjs/indexes/basic-index.cjs.map +1 -0
  49. package/dist/cjs/indexes/basic-index.d.cts +102 -0
  50. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  51. package/dist/cjs/indexes/btree-index.d.cts +1 -1
  52. package/dist/cjs/indexes/index-options.d.cts +8 -9
  53. package/dist/cjs/indexes/index-registry.cjs +89 -0
  54. package/dist/cjs/indexes/index-registry.cjs.map +1 -0
  55. package/dist/cjs/indexes/index-registry.d.cts +61 -0
  56. package/dist/cjs/local-only.cjs +5 -0
  57. package/dist/cjs/local-only.cjs.map +1 -1
  58. package/dist/cjs/query/builder/functions.cjs +27 -11
  59. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  60. package/dist/cjs/query/builder/functions.d.cts +25 -3
  61. package/dist/cjs/query/builder/index.cjs +200 -39
  62. package/dist/cjs/query/builder/index.cjs.map +1 -1
  63. package/dist/cjs/query/builder/index.d.cts +4 -3
  64. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  65. package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
  66. package/dist/cjs/query/builder/types.d.cts +84 -19
  67. package/dist/cjs/query/compiler/evaluators.cjs +51 -0
  68. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  69. package/dist/cjs/query/compiler/group-by.cjs +100 -28
  70. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  71. package/dist/cjs/query/compiler/group-by.d.cts +4 -2
  72. package/dist/cjs/query/compiler/index.cjs +283 -11
  73. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  74. package/dist/cjs/query/compiler/index.d.cts +30 -2
  75. package/dist/cjs/query/compiler/order-by.cjs +29 -10
  76. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  77. package/dist/cjs/query/compiler/order-by.d.cts +1 -1
  78. package/dist/cjs/query/compiler/select.cjs +8 -0
  79. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  80. package/dist/cjs/query/index.d.cts +2 -1
  81. package/dist/cjs/query/ir.cjs +18 -1
  82. package/dist/cjs/query/ir.cjs.map +1 -1
  83. package/dist/cjs/query/ir.d.cts +21 -1
  84. package/dist/cjs/query/live/collection-config-builder.cjs +501 -5
  85. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  86. package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
  87. package/dist/cjs/query/live/types.d.cts +3 -3
  88. package/dist/cjs/query/live/utils.cjs +43 -3
  89. package/dist/cjs/query/live/utils.cjs.map +1 -1
  90. package/dist/cjs/query/live/utils.d.cts +1 -0
  91. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  92. package/dist/cjs/query/live-query-collection.d.cts +9 -6
  93. package/dist/cjs/query/query-once.cjs.map +1 -1
  94. package/dist/cjs/query/query-once.d.cts +7 -5
  95. package/dist/cjs/query/subset-dedupe.cjs +9 -3
  96. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  97. package/dist/cjs/types.d.cts +42 -8
  98. package/dist/cjs/utils/array-utils.cjs +27 -0
  99. package/dist/cjs/utils/array-utils.cjs.map +1 -0
  100. package/dist/cjs/utils/array-utils.d.cts +16 -0
  101. package/dist/cjs/utils/comparison.cjs +11 -0
  102. package/dist/cjs/utils/comparison.cjs.map +1 -1
  103. package/dist/cjs/utils/index-optimization.cjs +4 -0
  104. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  105. package/dist/cjs/utils.cjs +7 -9
  106. package/dist/cjs/utils.cjs.map +1 -1
  107. package/dist/cjs/utils.d.cts +6 -1
  108. package/dist/cjs/virtual-props.cjs +33 -0
  109. package/dist/cjs/virtual-props.cjs.map +1 -0
  110. package/dist/cjs/virtual-props.d.cts +196 -0
  111. package/dist/esm/collection/change-events.d.ts +3 -2
  112. package/dist/esm/collection/change-events.js.map +1 -1
  113. package/dist/esm/collection/changes.d.ts +10 -1
  114. package/dist/esm/collection/changes.js +13 -4
  115. package/dist/esm/collection/changes.js.map +1 -1
  116. package/dist/esm/collection/cleanup-queue.d.ts +30 -0
  117. package/dist/esm/collection/cleanup-queue.js +89 -0
  118. package/dist/esm/collection/cleanup-queue.js.map +1 -0
  119. package/dist/esm/collection/events.d.ts +39 -1
  120. package/dist/esm/collection/events.js +14 -0
  121. package/dist/esm/collection/events.js.map +1 -1
  122. package/dist/esm/collection/index.d.ts +49 -36
  123. package/dist/esm/collection/index.js +67 -29
  124. package/dist/esm/collection/index.js.map +1 -1
  125. package/dist/esm/collection/indexes.d.ts +27 -17
  126. package/dist/esm/collection/indexes.js +211 -62
  127. package/dist/esm/collection/indexes.js.map +1 -1
  128. package/dist/esm/collection/lifecycle.d.ts +0 -1
  129. package/dist/esm/collection/lifecycle.js +5 -22
  130. package/dist/esm/collection/lifecycle.js.map +1 -1
  131. package/dist/esm/collection/mutations.d.ts +1 -0
  132. package/dist/esm/collection/mutations.js +18 -0
  133. package/dist/esm/collection/mutations.js.map +1 -1
  134. package/dist/esm/collection/state.d.ts +65 -1
  135. package/dist/esm/collection/state.js +381 -53
  136. package/dist/esm/collection/state.js.map +1 -1
  137. package/dist/esm/collection/subscription.d.ts +4 -0
  138. package/dist/esm/collection/subscription.js +6 -0
  139. package/dist/esm/collection/subscription.js.map +1 -1
  140. package/dist/esm/collection/sync.d.ts +2 -0
  141. package/dist/esm/collection/sync.js +108 -1
  142. package/dist/esm/collection/sync.js.map +1 -1
  143. package/dist/esm/collection/transaction-metadata.d.ts +1 -0
  144. package/dist/esm/collection/transaction-metadata.js +5 -0
  145. package/dist/esm/collection/transaction-metadata.js.map +1 -0
  146. package/dist/esm/errors.d.ts +3 -0
  147. package/dist/esm/errors.js +8 -0
  148. package/dist/esm/errors.js.map +1 -1
  149. package/dist/esm/index.d.ts +11 -3
  150. package/dist/esm/index.js +25 -7
  151. package/dist/esm/index.js.map +1 -1
  152. package/dist/esm/indexes/auto-index.js +13 -6
  153. package/dist/esm/indexes/auto-index.js.map +1 -1
  154. package/dist/esm/indexes/base-index.d.ts +2 -6
  155. package/dist/esm/indexes/base-index.js +1 -4
  156. package/dist/esm/indexes/base-index.js.map +1 -1
  157. package/dist/esm/indexes/basic-index.d.ts +102 -0
  158. package/dist/esm/indexes/basic-index.js +361 -0
  159. package/dist/esm/indexes/basic-index.js.map +1 -0
  160. package/dist/esm/indexes/btree-index.d.ts +1 -1
  161. package/dist/esm/indexes/btree-index.js.map +1 -1
  162. package/dist/esm/indexes/index-options.d.ts +8 -9
  163. package/dist/esm/indexes/index-registry.d.ts +61 -0
  164. package/dist/esm/indexes/index-registry.js +89 -0
  165. package/dist/esm/indexes/index-registry.js.map +1 -0
  166. package/dist/esm/local-only.js +5 -0
  167. package/dist/esm/local-only.js.map +1 -1
  168. package/dist/esm/query/builder/functions.d.ts +25 -3
  169. package/dist/esm/query/builder/functions.js +27 -11
  170. package/dist/esm/query/builder/functions.js.map +1 -1
  171. package/dist/esm/query/builder/index.d.ts +4 -3
  172. package/dist/esm/query/builder/index.js +201 -40
  173. package/dist/esm/query/builder/index.js.map +1 -1
  174. package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
  175. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  176. package/dist/esm/query/builder/types.d.ts +84 -19
  177. package/dist/esm/query/compiler/evaluators.js +51 -0
  178. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  179. package/dist/esm/query/compiler/group-by.d.ts +4 -2
  180. package/dist/esm/query/compiler/group-by.js +101 -29
  181. package/dist/esm/query/compiler/group-by.js.map +1 -1
  182. package/dist/esm/query/compiler/index.d.ts +30 -2
  183. package/dist/esm/query/compiler/index.js +285 -13
  184. package/dist/esm/query/compiler/index.js.map +1 -1
  185. package/dist/esm/query/compiler/order-by.d.ts +1 -1
  186. package/dist/esm/query/compiler/order-by.js +30 -11
  187. package/dist/esm/query/compiler/order-by.js.map +1 -1
  188. package/dist/esm/query/compiler/select.js +8 -0
  189. package/dist/esm/query/compiler/select.js.map +1 -1
  190. package/dist/esm/query/index.d.ts +2 -1
  191. package/dist/esm/query/ir.d.ts +21 -1
  192. package/dist/esm/query/ir.js +18 -1
  193. package/dist/esm/query/ir.js.map +1 -1
  194. package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
  195. package/dist/esm/query/live/collection-config-builder.js +503 -7
  196. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  197. package/dist/esm/query/live/types.d.ts +3 -3
  198. package/dist/esm/query/live/utils.d.ts +1 -0
  199. package/dist/esm/query/live/utils.js +43 -3
  200. package/dist/esm/query/live/utils.js.map +1 -1
  201. package/dist/esm/query/live-query-collection.d.ts +9 -6
  202. package/dist/esm/query/live-query-collection.js.map +1 -1
  203. package/dist/esm/query/query-once.d.ts +7 -5
  204. package/dist/esm/query/query-once.js.map +1 -1
  205. package/dist/esm/query/subset-dedupe.js +9 -3
  206. package/dist/esm/query/subset-dedupe.js.map +1 -1
  207. package/dist/esm/types.d.ts +42 -8
  208. package/dist/esm/utils/array-utils.d.ts +16 -0
  209. package/dist/esm/utils/array-utils.js +27 -0
  210. package/dist/esm/utils/array-utils.js.map +1 -0
  211. package/dist/esm/utils/comparison.js +11 -0
  212. package/dist/esm/utils/comparison.js.map +1 -1
  213. package/dist/esm/utils/index-optimization.js +4 -0
  214. package/dist/esm/utils/index-optimization.js.map +1 -1
  215. package/dist/esm/utils.d.ts +6 -1
  216. package/dist/esm/utils.js +7 -9
  217. package/dist/esm/utils.js.map +1 -1
  218. package/dist/esm/virtual-props.d.ts +196 -0
  219. package/dist/esm/virtual-props.js +33 -0
  220. package/dist/esm/virtual-props.js.map +1 -0
  221. package/package.json +2 -2
  222. package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
  223. package/src/collection/change-events.ts +13 -9
  224. package/src/collection/changes.ts +30 -7
  225. package/src/collection/cleanup-queue.ts +105 -0
  226. package/src/collection/events.ts +65 -0
  227. package/src/collection/index.ts +110 -45
  228. package/src/collection/indexes.ts +283 -76
  229. package/src/collection/lifecycle.ts +5 -26
  230. package/src/collection/mutations.ts +21 -0
  231. package/src/collection/state.ts +545 -71
  232. package/src/collection/subscription.ts +7 -0
  233. package/src/collection/sync.ts +137 -0
  234. package/src/collection/transaction-metadata.ts +1 -0
  235. package/src/errors.ts +9 -0
  236. package/src/index.ts +46 -3
  237. package/src/indexes/auto-index.ts +18 -8
  238. package/src/indexes/base-index.ts +2 -10
  239. package/src/indexes/basic-index.ts +507 -0
  240. package/src/indexes/btree-index.ts +1 -1
  241. package/src/indexes/index-options.ts +17 -37
  242. package/src/indexes/index-registry.ts +174 -0
  243. package/src/local-only.ts +7 -0
  244. package/src/query/builder/functions.ts +84 -7
  245. package/src/query/builder/index.ts +329 -9
  246. package/src/query/builder/ref-proxy.ts +22 -4
  247. package/src/query/builder/types.ts +257 -62
  248. package/src/query/compiler/evaluators.ts +57 -0
  249. package/src/query/compiler/group-by.ts +156 -35
  250. package/src/query/compiler/index.ts +445 -15
  251. package/src/query/compiler/order-by.ts +51 -12
  252. package/src/query/compiler/select.ts +9 -0
  253. package/src/query/index.ts +7 -0
  254. package/src/query/ir.ts +23 -2
  255. package/src/query/live/collection-config-builder.ts +809 -9
  256. package/src/query/live/types.ts +10 -4
  257. package/src/query/live/utils.ts +64 -3
  258. package/src/query/live-query-collection.ts +43 -18
  259. package/src/query/query-once.ts +31 -12
  260. package/src/query/subset-dedupe.ts +11 -7
  261. package/src/types.ts +49 -9
  262. package/src/utils/array-utils.ts +49 -0
  263. package/src/utils/comparison.ts +14 -0
  264. package/src/utils/index-optimization.ts +4 -0
  265. package/src/utils.ts +12 -9
  266. package/src/virtual-props.ts +282 -0
  267. package/dist/cjs/indexes/lazy-index.cjs +0 -190
  268. package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
  269. package/dist/cjs/indexes/lazy-index.d.cts +0 -96
  270. package/dist/esm/indexes/lazy-index.d.ts +0 -96
  271. package/dist/esm/indexes/lazy-index.js +0 -190
  272. package/dist/esm/indexes/lazy-index.js.map +0 -1
  273. package/src/indexes/lazy-index.ts +0 -251
@@ -1 +1 @@
1
- {"version":3,"file":"collection-config-builder.cjs","sources":["../../../../src/query/live/collection-config-builder.ts"],"sourcesContent":["import { D2, output } from '@tanstack/db-ivm'\nimport { compileQuery } from '../compiler/index.js'\nimport {\n MissingAliasInputsError,\n SetWindowRequiresOrderByError,\n} from '../../errors.js'\nimport { transactionScopedScheduler } from '../../scheduler.js'\nimport { getActiveTransaction } from '../../transactions.js'\nimport { CollectionSubscriber } from './collection-subscriber.js'\nimport { getCollectionBuilder } from './collection-registry.js'\nimport { LIVE_QUERY_INTERNAL } from './internal.js'\nimport {\n buildQueryFromConfig,\n extractCollectionAliases,\n extractCollectionFromSource,\n extractCollectionsFromQuery,\n} from './utils.js'\nimport type { LiveQueryInternalUtils } from './internal.js'\nimport type { WindowOptions } from '../compiler/index.js'\nimport type { SchedulerContextId } from '../../scheduler.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type { RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n CollectionConfigSingleRowOption,\n KeyedStream,\n ResultStream,\n StringCollationConfig,\n SyncConfig,\n UtilsRecord,\n} from '../../types.js'\nimport type { Context, GetResult } from '../builder/types.js'\nimport type { BasicExpression, QueryIR } from '../ir.js'\nimport type { LazyCollectionCallbacks } from '../compiler/joins.js'\nimport type {\n Changes,\n FullSyncState,\n LiveQueryCollectionConfig,\n SyncState,\n} from './types.js'\nimport type { AllCollectionEvents } from '../../collection/events.js'\n\nexport type LiveQueryCollectionUtils = UtilsRecord & {\n getRunCount: () => number\n /**\n * Sets the offset and limit of an ordered query.\n * Is a no-op if the query is not ordered.\n *\n * @returns `true` if no subset loading was triggered, or `Promise<void>` that resolves when the subset has been loaded\n */\n setWindow: (options: WindowOptions) => true | Promise<void>\n /**\n * Gets the current window (offset and limit) for an ordered query.\n *\n * @returns The current window settings, or `undefined` if the query is not windowed\n */\n getWindow: () => { offset: number; limit: number } | undefined\n [LIVE_QUERY_INTERNAL]: LiveQueryInternalUtils\n}\n\ntype PendingGraphRun = {\n loadCallbacks: Set<() => boolean>\n}\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\ntype SyncMethods<TResult extends object> = Parameters<\n SyncConfig<TResult>[`sync`]\n>[0]\n\nexport class CollectionConfigBuilder<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n private readonly id: string\n readonly query: QueryIR\n private readonly collections: Record<string, Collection<any, any, any>>\n private readonly collectionByAlias: Record<string, Collection<any, any, any>>\n // Populated during compilation with all aliases (including subquery inner aliases)\n private compiledAliasToCollectionId: Record<string, string> = {}\n\n // WeakMap to store the keys of the results\n // so that we can retrieve them in the getKey function\n private readonly resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n private readonly orderByIndices = new WeakMap<object, string>()\n\n private readonly compare?: (val1: TResult, val2: TResult) => number\n private readonly compareOptions?: StringCollationConfig\n\n private isGraphRunning = false\n private runCount = 0\n\n // Current sync session state (set when sync starts, cleared when it stops)\n // Public for testing purposes (CollectionConfigBuilder is internal, not public API)\n public currentSyncConfig:\n | Parameters<SyncConfig<TResult>[`sync`]>[0]\n | undefined\n public currentSyncState: FullSyncState | undefined\n\n // Error state tracking\n private isInErrorState = false\n\n // Reference to the live query collection for error state transitions\n public liveQueryCollection?: Collection<TResult, any, any>\n\n private windowFn: ((options: WindowOptions) => void) | undefined\n private currentWindow: WindowOptions | undefined\n\n private maybeRunGraphFn: (() => void) | undefined\n\n private readonly aliasDependencies: Record<\n string,\n Array<CollectionConfigBuilder<any, any>>\n > = {}\n\n private readonly builderDependencies = new Set<\n CollectionConfigBuilder<any, any>\n >()\n\n // Pending graph runs per scheduler context (e.g., per transaction)\n // The builder manages its own state; the scheduler just orchestrates execution order\n // Only stores callbacks - if sync ends, pending jobs gracefully no-op\n private readonly pendingGraphRuns = new Map<\n SchedulerContextId,\n PendingGraphRun\n >()\n\n // Unsubscribe function for scheduler's onClear listener\n // Registered when sync starts, unregistered when sync stops\n // Prevents memory leaks by releasing the scheduler's reference to this builder\n private unsubscribeFromSchedulerClears?: () => void\n\n private graphCache: D2 | undefined\n private inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n private pipelineCache: ResultStream | undefined\n public sourceWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n\n // Map of source alias to subscription\n readonly subscriptions: Record<string, CollectionSubscription> = {}\n // Map of source aliases to functions that load keys for that lazy source\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of source aliases that are lazy (don't load initial state)\n readonly lazySources = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> = {}\n\n constructor(\n private readonly config: LiveQueryCollectionConfig<TContext, TResult>,\n ) {\n // Generate a unique ID if not provided\n this.id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n this.query = buildQueryFromConfig(config)\n this.collections = extractCollectionsFromQuery(this.query)\n const collectionAliasesById = extractCollectionAliases(this.query)\n\n // Build a reverse lookup map from alias to collection instance.\n // This enables self-join support where the same collection can be referenced\n // multiple times with different aliases (e.g., { employee: col, manager: col })\n this.collectionByAlias = {}\n for (const [collectionId, aliases] of collectionAliasesById.entries()) {\n const collection = this.collections[collectionId]\n if (!collection) continue\n for (const alias of aliases) {\n this.collectionByAlias[alias] = collection\n }\n }\n\n // Create compare function for ordering if the query has orderBy\n if (this.query.orderBy && this.query.orderBy.length > 0) {\n this.compare = createOrderByComparator<TResult>(this.orderByIndices)\n }\n\n // Use explicitly provided compareOptions if available, otherwise inherit from FROM collection\n this.compareOptions =\n this.config.defaultStringCollation ??\n extractCollectionFromSource(this.query).compareOptions\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n this.compileBasePipeline()\n }\n\n /**\n * Recursively checks if a query or any of its subqueries contains joins\n */\n private hasJoins(query: QueryIR): boolean {\n // Check if this query has joins\n if (query.join && query.join.length > 0) {\n return true\n }\n\n // Recursively check subqueries in the from clause\n if (query.from.type === `queryRef`) {\n if (this.hasJoins(query.from.query)) {\n return true\n }\n }\n\n return false\n }\n\n getConfig(): CollectionConfigSingleRowOption<TResult> & {\n utils: LiveQueryCollectionUtils\n } {\n return {\n id: this.id,\n getKey:\n this.config.getKey ||\n ((item) => this.resultKeys.get(item) as string | number),\n sync: this.getSyncConfig(),\n compare: this.compare,\n defaultStringCollation: this.compareOptions,\n gcTime: this.config.gcTime || 5000, // 5 seconds by default for live queries\n schema: this.config.schema,\n onInsert: this.config.onInsert,\n onUpdate: this.config.onUpdate,\n onDelete: this.config.onDelete,\n startSync: this.config.startSync,\n singleResult: this.query.singleResult,\n utils: {\n getRunCount: this.getRunCount.bind(this),\n setWindow: this.setWindow.bind(this),\n getWindow: this.getWindow.bind(this),\n [LIVE_QUERY_INTERNAL]: {\n getBuilder: () => this,\n hasCustomGetKey: !!this.config.getKey,\n hasJoins: this.hasJoins(this.query),\n hasDistinct: !!this.query.distinct,\n },\n },\n }\n }\n\n setWindow(options: WindowOptions): true | Promise<void> {\n if (!this.windowFn) {\n throw new SetWindowRequiresOrderByError()\n }\n\n this.currentWindow = options\n this.windowFn(options)\n this.maybeRunGraphFn?.()\n\n // Check if loading a subset was triggered\n if (this.liveQueryCollection?.isLoadingSubset) {\n // Loading was triggered, return a promise that resolves when it completes\n return new Promise<void>((resolve) => {\n const unsubscribe = this.liveQueryCollection!.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n unsubscribe()\n resolve()\n }\n },\n )\n })\n }\n\n // No loading was triggered\n return true\n }\n\n getWindow(): { offset: number; limit: number } | undefined {\n // Only return window if this is a windowed query (has orderBy and windowFn)\n if (!this.windowFn || !this.currentWindow) {\n return undefined\n }\n return {\n offset: this.currentWindow.offset ?? 0,\n limit: this.currentWindow.limit ?? 0,\n }\n }\n\n /**\n * Resolves a collection alias to its collection ID.\n *\n * Uses a two-tier lookup strategy:\n * 1. First checks compiled aliases (includes subquery inner aliases)\n * 2. Falls back to declared aliases from the query's from/join clauses\n *\n * @param alias - The alias to resolve (e.g., \"employee\", \"manager\")\n * @returns The collection ID that the alias references\n * @throws {Error} If the alias is not found in either lookup\n */\n getCollectionIdForAlias(alias: string): string {\n const compiled = this.compiledAliasToCollectionId[alias]\n if (compiled) {\n return compiled\n }\n const collection = this.collectionByAlias[alias]\n if (collection) {\n return collection.id\n }\n throw new Error(`Unknown source alias \"${alias}\"`)\n }\n\n isLazyAlias(alias: string): boolean {\n return this.lazySources.has(alias)\n }\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happen because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n maybeRunGraph(callback?: () => boolean) {\n if (this.isGraphRunning) {\n // no nested runs of the graph\n // which is possible if the `callback`\n // would call `maybeRunGraph` e.g. after it has loaded some more data\n return\n }\n\n // Should only be called when sync is active\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `maybeRunGraph called without active sync session. This should not happen.`,\n )\n }\n\n this.isGraphRunning = true\n\n try {\n const { begin, commit } = this.currentSyncConfig\n const syncState = this.currentSyncState\n\n // Don't run if the live query is in an error state\n if (this.isInErrorState) {\n return\n }\n\n // Always run the graph if subscribed (eager execution)\n if (syncState.subscribedToAllCollections) {\n let callbackCalled = false\n while (syncState.graph.pendingWork()) {\n syncState.graph.run()\n // Flush accumulated changes after each graph step to commit them as one transaction.\n // This ensures intermediate join states (like null on one side) don't cause\n // duplicate key errors when the full join result arrives in the same step.\n syncState.flushPendingChanges?.()\n callback?.()\n callbackCalled = true\n }\n\n // Ensure the callback runs at least once even when the graph has no pending work.\n // This handles lazy loading scenarios where setWindow() increases the limit or\n // an async loadSubset completes and we need to re-check if more data is needed.\n if (!callbackCalled) {\n callback?.()\n }\n\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (syncState.messagesCount === 0) {\n begin()\n commit()\n }\n\n // After graph processing completes, check if we should mark ready.\n // This is the canonical place to transition to ready state because:\n // 1. All data has been processed through the graph\n // 2. All source collections have had a chance to send their initial data\n // This prevents marking ready before data is processed (fixes isReady=true with empty data)\n this.updateLiveQueryStatus(this.currentSyncConfig)\n }\n } finally {\n this.isGraphRunning = false\n }\n }\n\n /**\n * Schedules a graph run with the transaction-scoped scheduler.\n * Ensures each builder runs at most once per transaction, with automatic dependency tracking\n * to run parent queries before child queries. Outside a transaction, runs immediately.\n *\n * Multiple calls during a transaction are coalesced into a single execution.\n * Dependencies are auto-discovered from subscribed live queries, or can be overridden.\n * Load callbacks are combined when entries merge.\n *\n * Uses the current sync session's config and syncState from instance properties.\n *\n * @param callback - Optional callback to load more data if needed (returns true when done)\n * @param options - Optional scheduling configuration\n * @param options.contextId - Transaction ID to group work; defaults to active transaction\n * @param options.jobId - Unique identifier for this job; defaults to this builder instance\n * @param options.alias - Source alias that triggered this schedule; adds alias-specific dependencies\n * @param options.dependencies - Explicit dependency list; overrides auto-discovered dependencies\n */\n scheduleGraphRun(\n callback?: () => boolean,\n options?: {\n contextId?: SchedulerContextId\n jobId?: unknown\n alias?: string\n dependencies?: Array<CollectionConfigBuilder<any, any>>\n },\n ) {\n const contextId = options?.contextId ?? getActiveTransaction()?.id\n // Use the builder instance as the job ID for deduplication. This is memory-safe\n // because the scheduler's context Map is deleted after flushing (no long-term retention).\n const jobId = options?.jobId ?? this\n const dependentBuilders = (() => {\n if (options?.dependencies) {\n return options.dependencies\n }\n\n const deps = new Set(this.builderDependencies)\n if (options?.alias) {\n const aliasDeps = this.aliasDependencies[options.alias]\n if (aliasDeps) {\n for (const dep of aliasDeps) {\n deps.add(dep)\n }\n }\n }\n\n deps.delete(this)\n\n return Array.from(deps)\n })()\n\n // Ensure dependent builders are actually scheduled in this context so that\n // dependency edges always point to a real job (or a deduped no-op if already scheduled).\n if (contextId) {\n for (const dep of dependentBuilders) {\n if (typeof dep.scheduleGraphRun === `function`) {\n dep.scheduleGraphRun(undefined, { contextId })\n }\n }\n }\n\n // We intentionally scope deduplication to the builder instance. Each instance\n // owns caches and compiled pipelines, so sharing work across instances that\n // merely reuse the same string id would execute the wrong builder's graph.\n\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `scheduleGraphRun called without active sync session. This should not happen.`,\n )\n }\n\n // Manage our own state - get or create pending callbacks for this context\n let pending = contextId ? this.pendingGraphRuns.get(contextId) : undefined\n if (!pending) {\n pending = {\n loadCallbacks: new Set(),\n }\n if (contextId) {\n this.pendingGraphRuns.set(contextId, pending)\n }\n }\n\n // Add callback if provided (this is what accumulates between schedules)\n if (callback) {\n pending.loadCallbacks.add(callback)\n }\n\n // Schedule execution (scheduler just orchestrates order, we manage state)\n // For immediate execution (no contextId), pass pending directly since it won't be in the map\n const pendingToPass = contextId ? undefined : pending\n transactionScopedScheduler.schedule({\n contextId,\n jobId,\n dependencies: dependentBuilders,\n run: () => this.executeGraphRun(contextId, pendingToPass),\n })\n }\n\n /**\n * Clears pending graph run state for a specific context.\n * Called when the scheduler clears a context (e.g., transaction rollback/abort).\n */\n clearPendingGraphRun(contextId: SchedulerContextId): void {\n this.pendingGraphRuns.delete(contextId)\n }\n\n /**\n * Returns true if this builder has a pending graph run for the given context.\n */\n hasPendingGraphRun(contextId: SchedulerContextId): boolean {\n return this.pendingGraphRuns.has(contextId)\n }\n\n /**\n * Executes a pending graph run. Called by the scheduler when dependencies are satisfied.\n * Clears the pending state BEFORE execution so that any re-schedules during the run\n * create fresh state and don't interfere with the current execution.\n * Uses instance sync state - if sync has ended, gracefully returns without executing.\n *\n * @param contextId - Optional context ID to look up pending state\n * @param pendingParam - For immediate execution (no context), pending state is passed directly\n */\n private executeGraphRun(\n contextId?: SchedulerContextId,\n pendingParam?: PendingGraphRun,\n ): void {\n // Get pending state: either from parameter (no context) or from map (with context)\n // Remove from map BEFORE checking sync state to prevent leaking entries when sync ends\n // before the transaction flushes (e.g., unsubscribe during in-flight transaction)\n const pending =\n pendingParam ??\n (contextId ? this.pendingGraphRuns.get(contextId) : undefined)\n if (contextId) {\n this.pendingGraphRuns.delete(contextId)\n }\n\n // If no pending state, nothing to execute (context was cleared)\n if (!pending) {\n return\n }\n\n // If sync session has ended, don't execute (graph is finalized, subscriptions cleared)\n if (!this.currentSyncConfig || !this.currentSyncState) {\n return\n }\n\n this.incrementRunCount()\n\n const combinedLoader = () => {\n let allDone = true\n let firstError: unknown\n pending.loadCallbacks.forEach((loader) => {\n try {\n allDone = loader() && allDone\n } catch (error) {\n allDone = false\n firstError ??= error\n }\n })\n if (firstError) {\n throw firstError\n }\n // Returning false signals that callers should schedule another pass.\n return allDone\n }\n\n this.maybeRunGraph(combinedLoader)\n }\n\n private getSyncConfig(): SyncConfig<TResult> {\n return {\n rowUpdateMode: `full`,\n sync: this.syncFn.bind(this),\n }\n }\n\n incrementRunCount() {\n this.runCount++\n }\n\n getRunCount() {\n return this.runCount\n }\n\n private syncFn(config: SyncMethods<TResult>) {\n // Store reference to the live query collection for error state transitions\n this.liveQueryCollection = config.collection\n // Store config and syncState as instance properties for the duration of this sync session\n this.currentSyncConfig = config\n\n const syncState: SyncState = {\n messagesCount: 0,\n subscribedToAllCollections: false,\n unsubscribeCallbacks: new Set<() => void>(),\n }\n\n // Extend the pipeline such that it applies the incoming changes to the collection\n const fullSyncState = this.extendPipelineWithChangeProcessing(\n config,\n syncState,\n )\n this.currentSyncState = fullSyncState\n\n // Listen for scheduler context clears to clean up our pending state\n // Re-register on each sync start so the listener is active for the sync session's lifetime\n this.unsubscribeFromSchedulerClears = transactionScopedScheduler.onClear(\n (contextId) => {\n this.clearPendingGraphRun(contextId)\n },\n )\n\n // Listen for loadingSubset changes on the live query collection BEFORE subscribing.\n // This ensures we don't miss the event if subset loading completes synchronously.\n // When isLoadingSubset becomes false, we may need to mark the collection as ready\n // (if all source collections are already ready but we were waiting for subset load to complete)\n const loadingSubsetUnsubscribe = config.collection.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n // Subset loading finished, check if we can now mark ready\n this.updateLiveQueryStatus(config)\n }\n },\n )\n syncState.unsubscribeCallbacks.add(loadingSubsetUnsubscribe)\n\n const loadSubsetDataCallbacks = this.subscribeToAllCollections(\n config,\n fullSyncState,\n )\n\n this.maybeRunGraphFn = () => this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Initial run with callback to load more data if needed\n this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Return the unsubscribe function\n return () => {\n syncState.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n\n // Clear current sync session state\n this.currentSyncConfig = undefined\n this.currentSyncState = undefined\n\n // Clear all pending graph runs to prevent memory leaks from in-flight transactions\n // that may flush after the sync session ends\n this.pendingGraphRuns.clear()\n\n // Reset caches so a fresh graph/pipeline is compiled on next start\n // This avoids reusing a finalized D2 graph across GC restarts\n this.graphCache = undefined\n this.inputsCache = undefined\n this.pipelineCache = undefined\n this.sourceWhereClausesCache = undefined\n\n // Reset lazy source alias state\n this.lazySources.clear()\n this.optimizableOrderByCollections = {}\n this.lazySourcesCallbacks = {}\n\n // Clear subscription references to prevent memory leaks\n // Note: Individual subscriptions are already unsubscribed via unsubscribeCallbacks\n Object.keys(this.subscriptions).forEach(\n (key) => delete this.subscriptions[key],\n )\n this.compiledAliasToCollectionId = {}\n\n // Unregister from scheduler's onClear listener to prevent memory leaks\n // The scheduler's listener Set would otherwise keep a strong reference to this builder\n this.unsubscribeFromSchedulerClears?.()\n this.unsubscribeFromSchedulerClears = undefined\n }\n }\n\n /**\n * Compiles the query pipeline with all declared aliases.\n */\n private compileBasePipeline() {\n this.graphCache = new D2()\n this.inputsCache = Object.fromEntries(\n Object.keys(this.collectionByAlias).map((alias) => [\n alias,\n this.graphCache!.newInput<any>(),\n ]),\n )\n\n const compilation = compileQuery(\n this.query,\n this.inputsCache as Record<string, KeyedStream>,\n this.collections,\n this.subscriptions,\n this.lazySourcesCallbacks,\n this.lazySources,\n this.optimizableOrderByCollections,\n (windowFn: (options: WindowOptions) => void) => {\n this.windowFn = windowFn\n },\n )\n\n this.pipelineCache = compilation.pipeline\n this.sourceWhereClausesCache = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n\n // Defensive check: verify all compiled aliases have corresponding inputs\n // This should never happen since all aliases come from user declarations,\n // but catch it early if the assumption is violated in the future.\n const missingAliases = Object.keys(this.compiledAliasToCollectionId).filter(\n (alias) => !Object.hasOwn(this.inputsCache!, alias),\n )\n if (missingAliases.length > 0) {\n throw new MissingAliasInputsError(missingAliases)\n }\n }\n\n private maybeCompileBasePipeline() {\n if (!this.graphCache || !this.inputsCache || !this.pipelineCache) {\n this.compileBasePipeline()\n }\n return {\n graph: this.graphCache!,\n inputs: this.inputsCache!,\n pipeline: this.pipelineCache!,\n }\n }\n\n private extendPipelineWithChangeProcessing(\n config: SyncMethods<TResult>,\n syncState: SyncState,\n ): FullSyncState {\n const { begin, commit } = config\n const { graph, inputs, pipeline } = this.maybeCompileBasePipeline()\n\n // Accumulator for changes across all output callbacks within a single graph run.\n // This allows us to batch all changes from intermediate join states into a single\n // transaction, avoiding duplicate key errors when joins produce multiple outputs\n // for the same key (e.g., first output with null, then output with joined data).\n let pendingChanges: Map<unknown, Changes<TResult>> = new Map()\n\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n // Accumulate changes from this output callback into the pending changes map.\n // Changes for the same key are merged (inserts/deletes are added together).\n messages.reduce(accumulateChanges<TResult>, pendingChanges)\n }),\n )\n\n // Flush pending changes and reset the accumulator.\n // Called at the end of each graph run to commit all accumulated changes.\n syncState.flushPendingChanges = () => {\n if (pendingChanges.size === 0) {\n return\n }\n\n let changesToApply = pendingChanges\n\n // When a custom getKey is provided, multiple D2 internal keys may map\n // to the same user-visible key. Re-accumulate by custom key so that a\n // retract + insert for the same logical row merges into an UPDATE\n // instead of a separate DELETE and INSERT that can race.\n if (this.config.getKey) {\n const merged = new Map<unknown, Changes<TResult>>()\n for (const [, changes] of pendingChanges) {\n const customKey = this.config.getKey(changes.value)\n const existing = merged.get(customKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n // Keep the value from the insert side (the new value)\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n merged.set(customKey, { ...changes })\n }\n }\n changesToApply = merged\n }\n\n begin()\n changesToApply.forEach(this.applyChanges.bind(this, config))\n commit()\n pendingChanges = new Map()\n }\n\n graph.finalize()\n\n // Extend the sync state with the graph, inputs, and pipeline\n syncState.graph = graph\n syncState.inputs = inputs\n syncState.pipeline = pipeline\n\n return syncState as FullSyncState\n }\n\n private applyChanges(\n config: SyncMethods<TResult>,\n changes: {\n deletes: number\n inserts: number\n value: TResult\n orderByIndex: string | undefined\n },\n key: unknown,\n ) {\n const { write, collection } = config\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n this.resultKeys.set(value, key)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n this.orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes && collection.has(collection.getKeyFromItem(value)))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `Could not apply changes: ${JSON.stringify(changes)}. This should never happen.`,\n )\n }\n }\n\n /**\n * Handle status changes from source collections\n */\n private handleSourceStatusChange(\n config: SyncMethods<TResult>,\n collectionId: string,\n event: AllCollectionEvents[`status:change`],\n ) {\n const { status } = event\n\n // Handle error state - any source collection in error puts live query in error\n if (status === `error`) {\n this.transitionToError(\n `Source collection '${collectionId}' entered error state`,\n )\n return\n }\n\n // Handle manual cleanup - this should not happen due to GC prevention,\n // but could happen if user manually calls cleanup()\n if (status === `cleaned-up`) {\n this.transitionToError(\n `Source collection '${collectionId}' was manually cleaned up while live query '${this.id}' depends on it. ` +\n `Live queries prevent automatic GC, so this was likely a manual cleanup() call.`,\n )\n return\n }\n\n // Update ready status based on all source collections\n this.updateLiveQueryStatus(config)\n }\n\n /**\n * Update the live query status based on source collection statuses\n */\n private updateLiveQueryStatus(config: SyncMethods<TResult>) {\n const { markReady } = config\n\n // Don't update status if already in error\n if (this.isInErrorState) {\n return\n }\n\n const subscribedToAll = this.currentSyncState?.subscribedToAllCollections\n const allReady = this.allCollectionsReady()\n const isLoading = this.liveQueryCollection?.isLoadingSubset\n // Mark ready when:\n // 1. All subscriptions are set up (subscribedToAllCollections)\n // 2. All source collections are ready\n // 3. The live query collection is not loading subset data\n // This prevents marking the live query ready before its data is processed\n // (fixes issue where useLiveQuery returns isReady=true with empty data)\n if (subscribedToAll && allReady && !isLoading) {\n markReady()\n }\n }\n\n /**\n * Transition the live query to error state\n */\n private transitionToError(message: string) {\n this.isInErrorState = true\n\n // Log error to console for debugging\n console.error(`[Live Query Error] ${message}`)\n\n // Transition live query collection to error state\n this.liveQueryCollection?._lifecycle.setStatus(`error`)\n }\n\n private allCollectionsReady() {\n return Object.values(this.collections).every((collection) =>\n collection.isReady(),\n )\n }\n\n /**\n * Creates per-alias subscriptions enabling self-join support.\n * Each alias gets its own subscription with independent filters, even for the same collection.\n * Example: `{ employee: col, manager: col }` creates two separate subscriptions.\n */\n private subscribeToAllCollections(\n config: SyncMethods<TResult>,\n syncState: FullSyncState,\n ) {\n // Use compiled aliases as the source of truth - these include all aliases from the query\n // including those from subqueries, which may not be in collectionByAlias\n const compiledAliases = Object.entries(this.compiledAliasToCollectionId)\n if (compiledAliases.length === 0) {\n throw new Error(\n `Compiler returned no alias metadata for query '${this.id}'. This should not happen; please report.`,\n )\n }\n\n // Create a separate subscription for each alias, enabling self-joins where the same\n // collection can be used multiple times with different filters and subscriptions\n const loaders = compiledAliases.map(([alias, collectionId]) => {\n // Try collectionByAlias first (for declared aliases), fall back to collections (for subquery aliases)\n const collection =\n this.collectionByAlias[alias] ?? this.collections[collectionId]!\n\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder && dependencyBuilder !== this) {\n this.aliasDependencies[alias] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.aliasDependencies[alias] = []\n }\n\n // CollectionSubscriber handles the actual subscription to the source collection\n // and feeds data into the D2 graph inputs for this specific alias\n const collectionSubscriber = new CollectionSubscriber(\n alias,\n collectionId,\n collection,\n this,\n )\n\n // Subscribe to status changes for status flow\n const statusUnsubscribe = collection.on(`status:change`, (event) => {\n this.handleSourceStatusChange(config, collectionId, event)\n })\n syncState.unsubscribeCallbacks.add(statusUnsubscribe)\n\n const subscription = collectionSubscriber.subscribe()\n // Store subscription by alias (not collection ID) to support lazy loading\n // which needs to look up subscriptions by their query alias\n this.subscriptions[alias] = subscription\n\n // Create a callback for loading more data if needed (used by OrderBy optimization)\n const loadMore = collectionSubscriber.loadMoreIfNeeded.bind(\n collectionSubscriber,\n subscription,\n )\n\n return loadMore\n })\n\n // Combine all loaders into a single callback that initiates loading more data\n // from any source that needs it. Returns true once all loaders have been called,\n // but the actual async loading may still be in progress.\n const loadSubsetDataCallbacks = () => {\n loaders.map((loader) => loader())\n return true\n }\n\n // Mark as subscribed so the graph can start running\n // (graph only runs when all collections are subscribed)\n syncState.subscribedToAllCollections = true\n\n // Note: We intentionally don't call updateLiveQueryStatus() here.\n // The graph hasn't run yet, so marking ready would be premature.\n // The canonical place to mark ready is after the graph processes data\n // in maybeRunGraph(), which ensures data has been processed first.\n\n return loadSubsetDataCallbacks\n }\n}\n\nfunction createOrderByComparator<T extends object>(\n orderByIndices: WeakMap<object, string>,\n) {\n return (val1: T, val2: T): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n}\n\nfunction accumulateChanges<T>(\n acc: Map<unknown, Changes<T>>,\n [[key, tupleData], multiplicity]: [\n [unknown, [any, string | undefined]],\n number,\n ],\n) {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [T, string | undefined]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n // Update value to the latest version for this key\n changes.value = value\n if (orderByIndex !== undefined) {\n changes.orderByIndex = orderByIndex\n }\n }\n acc.set(key, changes)\n return acc\n}\n"],"names":["buildQueryFromConfig","extractCollectionsFromQuery","extractCollectionAliases","extractCollectionFromSource","LIVE_QUERY_INTERNAL","SetWindowRequiresOrderByError","getActiveTransaction","transactionScopedScheduler","D2","compileQuery","MissingAliasInputsError","output","getCollectionBuilder","collectionSubscriber","CollectionSubscriber"],"mappings":"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"}
1
+ {"version":3,"file":"collection-config-builder.cjs","sources":["../../../../src/query/live/collection-config-builder.ts"],"sourcesContent":["import { D2, output, serializeValue } from '@tanstack/db-ivm'\nimport { INCLUDES_ROUTING, compileQuery } from '../compiler/index.js'\nimport { createCollection } from '../../collection/index.js'\nimport {\n MissingAliasInputsError,\n SetWindowRequiresOrderByError,\n} from '../../errors.js'\nimport { transactionScopedScheduler } from '../../scheduler.js'\nimport { getActiveTransaction } from '../../transactions.js'\nimport { CollectionSubscriber } from './collection-subscriber.js'\nimport { getCollectionBuilder } from './collection-registry.js'\nimport { LIVE_QUERY_INTERNAL } from './internal.js'\nimport {\n buildQueryFromConfig,\n extractCollectionAliases,\n extractCollectionFromSource,\n extractCollectionsFromQuery,\n} from './utils.js'\nimport type { LiveQueryInternalUtils } from './internal.js'\nimport type {\n IncludesCompilationResult,\n WindowOptions,\n} from '../compiler/index.js'\nimport type { SchedulerContextId } from '../../scheduler.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\nimport type { RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n ChangeMessage,\n CollectionConfigSingleRowOption,\n KeyedStream,\n ResultStream,\n StringCollationConfig,\n SyncConfig,\n UtilsRecord,\n} from '../../types.js'\nimport type { Context, GetResult } from '../builder/types.js'\nimport type {\n BasicExpression,\n IncludesMaterialization,\n PropRef,\n QueryIR,\n} from '../ir.js'\nimport type { LazyCollectionCallbacks } from '../compiler/joins.js'\nimport type {\n Changes,\n FullSyncState,\n LiveQueryCollectionConfig,\n SyncState,\n} from './types.js'\nimport type { AllCollectionEvents } from '../../collection/events.js'\n\nexport type LiveQueryCollectionUtils = UtilsRecord & {\n getRunCount: () => number\n /**\n * Sets the offset and limit of an ordered query.\n * Is a no-op if the query is not ordered.\n *\n * @returns `true` if no subset loading was triggered, or `Promise<void>` that resolves when the subset has been loaded\n */\n setWindow: (options: WindowOptions) => true | Promise<void>\n /**\n * Gets the current window (offset and limit) for an ordered query.\n *\n * @returns The current window settings, or `undefined` if the query is not windowed\n */\n getWindow: () => { offset: number; limit: number } | undefined\n [LIVE_QUERY_INTERNAL]: LiveQueryInternalUtils\n}\n\ntype PendingGraphRun = {\n loadCallbacks: Set<() => boolean>\n}\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\ntype SyncMethods<TResult extends object> = Parameters<\n SyncConfig<TResult>[`sync`]\n>[0]\n\nexport class CollectionConfigBuilder<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n private readonly id: string\n readonly query: QueryIR\n private readonly collections: Record<string, Collection<any, any, any>>\n private readonly collectionByAlias: Record<string, Collection<any, any, any>>\n // Populated during compilation with all aliases (including subquery inner aliases)\n private compiledAliasToCollectionId: Record<string, string> = {}\n\n // WeakMap to store the keys of the results\n // so that we can retrieve them in the getKey function\n private readonly resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n private readonly orderByIndices = new WeakMap<object, string>()\n\n private readonly compare?: (val1: TResult, val2: TResult) => number\n private readonly compareOptions?: StringCollationConfig\n\n private isGraphRunning = false\n private runCount = 0\n\n // Current sync session state (set when sync starts, cleared when it stops)\n // Public for testing purposes (CollectionConfigBuilder is internal, not public API)\n public currentSyncConfig:\n | Parameters<SyncConfig<TResult>[`sync`]>[0]\n | undefined\n public currentSyncState: FullSyncState | undefined\n\n // Error state tracking\n private isInErrorState = false\n\n // Reference to the live query collection for error state transitions\n public liveQueryCollection?: Collection<TResult, any, any>\n\n private windowFn: ((options: WindowOptions) => void) | undefined\n private currentWindow: WindowOptions | undefined\n\n private maybeRunGraphFn: (() => void) | undefined\n\n private readonly aliasDependencies: Record<\n string,\n Array<CollectionConfigBuilder<any, any>>\n > = {}\n\n private readonly builderDependencies = new Set<\n CollectionConfigBuilder<any, any>\n >()\n\n // Pending graph runs per scheduler context (e.g., per transaction)\n // The builder manages its own state; the scheduler just orchestrates execution order\n // Only stores callbacks - if sync ends, pending jobs gracefully no-op\n private readonly pendingGraphRuns = new Map<\n SchedulerContextId,\n PendingGraphRun\n >()\n\n // Unsubscribe function for scheduler's onClear listener\n // Registered when sync starts, unregistered when sync stops\n // Prevents memory leaks by releasing the scheduler's reference to this builder\n private unsubscribeFromSchedulerClears?: () => void\n\n private graphCache: D2 | undefined\n private inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n private pipelineCache: ResultStream | undefined\n public sourceWhereClausesCache:\n | Map<string, BasicExpression<boolean>>\n | undefined\n private includesCache: Array<IncludesCompilationResult> | undefined\n\n // Map of source alias to subscription\n readonly subscriptions: Record<string, CollectionSubscription> = {}\n // Map of source aliases to functions that load keys for that lazy source\n lazySourcesCallbacks: Record<string, LazyCollectionCallbacks> = {}\n // Set of source aliases that are lazy (don't load initial state)\n readonly lazySources = new Set<string>()\n // Set of collection IDs that include an optimizable ORDER BY clause\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo> = {}\n\n constructor(\n private readonly config: LiveQueryCollectionConfig<TContext, TResult>,\n ) {\n // Generate a unique ID if not provided\n this.id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n this.query = buildQueryFromConfig({\n query: config.query,\n requireObjectResult: true,\n })\n this.collections = extractCollectionsFromQuery(this.query)\n const collectionAliasesById = extractCollectionAliases(this.query)\n\n // Build a reverse lookup map from alias to collection instance.\n // This enables self-join support where the same collection can be referenced\n // multiple times with different aliases (e.g., { employee: col, manager: col })\n this.collectionByAlias = {}\n for (const [collectionId, aliases] of collectionAliasesById.entries()) {\n const collection = this.collections[collectionId]\n if (!collection) continue\n for (const alias of aliases) {\n this.collectionByAlias[alias] = collection\n }\n }\n\n // Create compare function for ordering if the query has orderBy\n if (this.query.orderBy && this.query.orderBy.length > 0) {\n this.compare = createOrderByComparator<TResult>(this.orderByIndices)\n }\n\n // Use explicitly provided compareOptions if available, otherwise inherit from FROM collection\n this.compareOptions =\n this.config.defaultStringCollation ??\n extractCollectionFromSource(this.query).compareOptions\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n this.compileBasePipeline()\n }\n\n /**\n * Recursively checks if a query or any of its subqueries contains joins\n */\n private hasJoins(query: QueryIR): boolean {\n // Check if this query has joins\n if (query.join && query.join.length > 0) {\n return true\n }\n\n // Recursively check subqueries in the from clause\n if (query.from.type === `queryRef`) {\n if (this.hasJoins(query.from.query)) {\n return true\n }\n }\n\n return false\n }\n\n getConfig(): CollectionConfigSingleRowOption<TResult> & {\n utils: LiveQueryCollectionUtils\n } {\n return {\n id: this.id,\n getKey:\n this.config.getKey ||\n ((item) => this.resultKeys.get(item) as string | number),\n sync: this.getSyncConfig(),\n compare: this.compare,\n defaultStringCollation: this.compareOptions,\n gcTime: this.config.gcTime || 5000, // 5 seconds by default for live queries\n schema: this.config.schema,\n onInsert: this.config.onInsert,\n onUpdate: this.config.onUpdate,\n onDelete: this.config.onDelete,\n startSync: this.config.startSync,\n singleResult: this.query.singleResult,\n utils: {\n getRunCount: this.getRunCount.bind(this),\n setWindow: this.setWindow.bind(this),\n getWindow: this.getWindow.bind(this),\n [LIVE_QUERY_INTERNAL]: {\n getBuilder: () => this,\n hasCustomGetKey: !!this.config.getKey,\n hasJoins: this.hasJoins(this.query),\n hasDistinct: !!this.query.distinct,\n },\n },\n }\n }\n\n setWindow(options: WindowOptions): true | Promise<void> {\n if (!this.windowFn) {\n throw new SetWindowRequiresOrderByError()\n }\n\n this.currentWindow = options\n this.windowFn(options)\n this.maybeRunGraphFn?.()\n\n // Check if loading a subset was triggered\n if (this.liveQueryCollection?.isLoadingSubset) {\n // Loading was triggered, return a promise that resolves when it completes\n return new Promise<void>((resolve) => {\n const unsubscribe = this.liveQueryCollection!.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n unsubscribe()\n resolve()\n }\n },\n )\n })\n }\n\n // No loading was triggered\n return true\n }\n\n getWindow(): { offset: number; limit: number } | undefined {\n // Only return window if this is a windowed query (has orderBy and windowFn)\n if (!this.windowFn || !this.currentWindow) {\n return undefined\n }\n return {\n offset: this.currentWindow.offset ?? 0,\n limit: this.currentWindow.limit ?? 0,\n }\n }\n\n /**\n * Resolves a collection alias to its collection ID.\n *\n * Uses a two-tier lookup strategy:\n * 1. First checks compiled aliases (includes subquery inner aliases)\n * 2. Falls back to declared aliases from the query's from/join clauses\n *\n * @param alias - The alias to resolve (e.g., \"employee\", \"manager\")\n * @returns The collection ID that the alias references\n * @throws {Error} If the alias is not found in either lookup\n */\n getCollectionIdForAlias(alias: string): string {\n const compiled = this.compiledAliasToCollectionId[alias]\n if (compiled) {\n return compiled\n }\n const collection = this.collectionByAlias[alias]\n if (collection) {\n return collection.id\n }\n throw new Error(`Unknown source alias \"${alias}\"`)\n }\n\n isLazyAlias(alias: string): boolean {\n return this.lazySources.has(alias)\n }\n\n // The callback function is called after the graph has run.\n // This gives the callback a chance to load more data if needed,\n // that's used to optimize orderBy operators that set a limit,\n // in order to load some more data if we still don't have enough rows after the pipeline has run.\n // That can happen because even though we load N rows, the pipeline might filter some of these rows out\n // causing the orderBy operator to receive less than N rows or even no rows at all.\n // So this callback would notice that it doesn't have enough rows and load some more.\n // The callback returns a boolean, when it's true it's done loading data and we can mark the collection as ready.\n maybeRunGraph(callback?: () => boolean) {\n if (this.isGraphRunning) {\n // no nested runs of the graph\n // which is possible if the `callback`\n // would call `maybeRunGraph` e.g. after it has loaded some more data\n return\n }\n\n // Should only be called when sync is active\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `maybeRunGraph called without active sync session. This should not happen.`,\n )\n }\n\n this.isGraphRunning = true\n\n try {\n const { begin, commit } = this.currentSyncConfig\n const syncState = this.currentSyncState\n\n // Don't run if the live query is in an error state\n if (this.isInErrorState) {\n return\n }\n\n // Always run the graph if subscribed (eager execution)\n if (syncState.subscribedToAllCollections) {\n let callbackCalled = false\n while (syncState.graph.pendingWork()) {\n syncState.graph.run()\n // Flush accumulated changes after each graph step to commit them as one transaction.\n // This ensures intermediate join states (like null on one side) don't cause\n // duplicate key errors when the full join result arrives in the same step.\n syncState.flushPendingChanges?.()\n callback?.()\n callbackCalled = true\n }\n\n // Ensure the callback runs at least once even when the graph has no pending work.\n // This handles lazy loading scenarios where setWindow() increases the limit or\n // an async loadSubset completes and we need to re-check if more data is needed.\n if (!callbackCalled) {\n callback?.()\n }\n\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (syncState.messagesCount === 0) {\n begin()\n commit()\n }\n\n // After graph processing completes, check if we should mark ready.\n // This is the canonical place to transition to ready state because:\n // 1. All data has been processed through the graph\n // 2. All source collections have had a chance to send their initial data\n // This prevents marking ready before data is processed (fixes isReady=true with empty data)\n this.updateLiveQueryStatus(this.currentSyncConfig)\n }\n } finally {\n this.isGraphRunning = false\n }\n }\n\n /**\n * Schedules a graph run with the transaction-scoped scheduler.\n * Ensures each builder runs at most once per transaction, with automatic dependency tracking\n * to run parent queries before child queries. Outside a transaction, runs immediately.\n *\n * Multiple calls during a transaction are coalesced into a single execution.\n * Dependencies are auto-discovered from subscribed live queries, or can be overridden.\n * Load callbacks are combined when entries merge.\n *\n * Uses the current sync session's config and syncState from instance properties.\n *\n * @param callback - Optional callback to load more data if needed (returns true when done)\n * @param options - Optional scheduling configuration\n * @param options.contextId - Transaction ID to group work; defaults to active transaction\n * @param options.jobId - Unique identifier for this job; defaults to this builder instance\n * @param options.alias - Source alias that triggered this schedule; adds alias-specific dependencies\n * @param options.dependencies - Explicit dependency list; overrides auto-discovered dependencies\n */\n scheduleGraphRun(\n callback?: () => boolean,\n options?: {\n contextId?: SchedulerContextId\n jobId?: unknown\n alias?: string\n dependencies?: Array<CollectionConfigBuilder<any, any>>\n },\n ) {\n const contextId = options?.contextId ?? getActiveTransaction()?.id\n // Use the builder instance as the job ID for deduplication. This is memory-safe\n // because the scheduler's context Map is deleted after flushing (no long-term retention).\n const jobId = options?.jobId ?? this\n const dependentBuilders = (() => {\n if (options?.dependencies) {\n return options.dependencies\n }\n\n const deps = new Set(this.builderDependencies)\n if (options?.alias) {\n const aliasDeps = this.aliasDependencies[options.alias]\n if (aliasDeps) {\n for (const dep of aliasDeps) {\n deps.add(dep)\n }\n }\n }\n\n deps.delete(this)\n\n return Array.from(deps)\n })()\n\n // Ensure dependent builders are actually scheduled in this context so that\n // dependency edges always point to a real job (or a deduped no-op if already scheduled).\n if (contextId) {\n for (const dep of dependentBuilders) {\n if (typeof dep.scheduleGraphRun === `function`) {\n dep.scheduleGraphRun(undefined, { contextId })\n }\n }\n }\n\n // We intentionally scope deduplication to the builder instance. Each instance\n // owns caches and compiled pipelines, so sharing work across instances that\n // merely reuse the same string id would execute the wrong builder's graph.\n\n if (!this.currentSyncConfig || !this.currentSyncState) {\n throw new Error(\n `scheduleGraphRun called without active sync session. This should not happen.`,\n )\n }\n\n // Manage our own state - get or create pending callbacks for this context\n let pending = contextId ? this.pendingGraphRuns.get(contextId) : undefined\n if (!pending) {\n pending = {\n loadCallbacks: new Set(),\n }\n if (contextId) {\n this.pendingGraphRuns.set(contextId, pending)\n }\n }\n\n // Add callback if provided (this is what accumulates between schedules)\n if (callback) {\n pending.loadCallbacks.add(callback)\n }\n\n // Schedule execution (scheduler just orchestrates order, we manage state)\n // For immediate execution (no contextId), pass pending directly since it won't be in the map\n const pendingToPass = contextId ? undefined : pending\n transactionScopedScheduler.schedule({\n contextId,\n jobId,\n dependencies: dependentBuilders,\n run: () => this.executeGraphRun(contextId, pendingToPass),\n })\n }\n\n /**\n * Clears pending graph run state for a specific context.\n * Called when the scheduler clears a context (e.g., transaction rollback/abort).\n */\n clearPendingGraphRun(contextId: SchedulerContextId): void {\n this.pendingGraphRuns.delete(contextId)\n }\n\n /**\n * Returns true if this builder has a pending graph run for the given context.\n */\n hasPendingGraphRun(contextId: SchedulerContextId): boolean {\n return this.pendingGraphRuns.has(contextId)\n }\n\n /**\n * Executes a pending graph run. Called by the scheduler when dependencies are satisfied.\n * Clears the pending state BEFORE execution so that any re-schedules during the run\n * create fresh state and don't interfere with the current execution.\n * Uses instance sync state - if sync has ended, gracefully returns without executing.\n *\n * @param contextId - Optional context ID to look up pending state\n * @param pendingParam - For immediate execution (no context), pending state is passed directly\n */\n private executeGraphRun(\n contextId?: SchedulerContextId,\n pendingParam?: PendingGraphRun,\n ): void {\n // Get pending state: either from parameter (no context) or from map (with context)\n // Remove from map BEFORE checking sync state to prevent leaking entries when sync ends\n // before the transaction flushes (e.g., unsubscribe during in-flight transaction)\n const pending =\n pendingParam ??\n (contextId ? this.pendingGraphRuns.get(contextId) : undefined)\n if (contextId) {\n this.pendingGraphRuns.delete(contextId)\n }\n\n // If no pending state, nothing to execute (context was cleared)\n if (!pending) {\n return\n }\n\n // If sync session has ended, don't execute (graph is finalized, subscriptions cleared)\n if (!this.currentSyncConfig || !this.currentSyncState) {\n return\n }\n\n this.incrementRunCount()\n\n const combinedLoader = () => {\n let allDone = true\n let firstError: unknown\n pending.loadCallbacks.forEach((loader) => {\n try {\n allDone = loader() && allDone\n } catch (error) {\n allDone = false\n firstError ??= error\n }\n })\n if (firstError) {\n throw firstError\n }\n // Returning false signals that callers should schedule another pass.\n return allDone\n }\n\n this.maybeRunGraph(combinedLoader)\n }\n\n private getSyncConfig(): SyncConfig<TResult> {\n return {\n rowUpdateMode: `full`,\n sync: this.syncFn.bind(this),\n }\n }\n\n incrementRunCount() {\n this.runCount++\n }\n\n getRunCount() {\n return this.runCount\n }\n\n private syncFn(config: SyncMethods<TResult>) {\n // Store reference to the live query collection for error state transitions\n this.liveQueryCollection = config.collection\n // Store config and syncState as instance properties for the duration of this sync session\n this.currentSyncConfig = config\n\n const syncState: SyncState = {\n messagesCount: 0,\n subscribedToAllCollections: false,\n unsubscribeCallbacks: new Set<() => void>(),\n }\n\n // Extend the pipeline such that it applies the incoming changes to the collection\n const fullSyncState = this.extendPipelineWithChangeProcessing(\n config,\n syncState,\n )\n this.currentSyncState = fullSyncState\n\n // Listen for scheduler context clears to clean up our pending state\n // Re-register on each sync start so the listener is active for the sync session's lifetime\n this.unsubscribeFromSchedulerClears = transactionScopedScheduler.onClear(\n (contextId) => {\n this.clearPendingGraphRun(contextId)\n },\n )\n\n // Listen for loadingSubset changes on the live query collection BEFORE subscribing.\n // This ensures we don't miss the event if subset loading completes synchronously.\n // When isLoadingSubset becomes false, we may need to mark the collection as ready\n // (if all source collections are already ready but we were waiting for subset load to complete)\n const loadingSubsetUnsubscribe = config.collection.on(\n `loadingSubset:change`,\n (event) => {\n if (!event.isLoadingSubset) {\n // Subset loading finished, check if we can now mark ready\n this.updateLiveQueryStatus(config)\n }\n },\n )\n syncState.unsubscribeCallbacks.add(loadingSubsetUnsubscribe)\n\n const loadSubsetDataCallbacks = this.subscribeToAllCollections(\n config,\n fullSyncState,\n )\n\n this.maybeRunGraphFn = () => this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Initial run with callback to load more data if needed\n this.scheduleGraphRun(loadSubsetDataCallbacks)\n\n // Return the unsubscribe function\n return () => {\n syncState.unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n\n // Clear current sync session state\n this.currentSyncConfig = undefined\n this.currentSyncState = undefined\n\n // Clear all pending graph runs to prevent memory leaks from in-flight transactions\n // that may flush after the sync session ends\n this.pendingGraphRuns.clear()\n\n // Reset caches so a fresh graph/pipeline is compiled on next start\n // This avoids reusing a finalized D2 graph across GC restarts\n this.graphCache = undefined\n this.inputsCache = undefined\n this.pipelineCache = undefined\n this.sourceWhereClausesCache = undefined\n this.includesCache = undefined\n\n // Reset lazy source alias state\n this.lazySources.clear()\n this.optimizableOrderByCollections = {}\n this.lazySourcesCallbacks = {}\n\n // Clear subscription references to prevent memory leaks\n // Note: Individual subscriptions are already unsubscribed via unsubscribeCallbacks\n Object.keys(this.subscriptions).forEach(\n (key) => delete this.subscriptions[key],\n )\n this.compiledAliasToCollectionId = {}\n\n // Unregister from scheduler's onClear listener to prevent memory leaks\n // The scheduler's listener Set would otherwise keep a strong reference to this builder\n this.unsubscribeFromSchedulerClears?.()\n this.unsubscribeFromSchedulerClears = undefined\n }\n }\n\n /**\n * Compiles the query pipeline with all declared aliases.\n */\n private compileBasePipeline() {\n this.graphCache = new D2()\n this.inputsCache = Object.fromEntries(\n Object.keys(this.collectionByAlias).map((alias) => [\n alias,\n this.graphCache!.newInput<any>(),\n ]),\n )\n\n const compilation = compileQuery(\n this.query,\n this.inputsCache as Record<string, KeyedStream>,\n this.collections,\n this.subscriptions,\n this.lazySourcesCallbacks,\n this.lazySources,\n this.optimizableOrderByCollections,\n (windowFn: (options: WindowOptions) => void) => {\n this.windowFn = windowFn\n },\n )\n\n this.pipelineCache = compilation.pipeline\n this.sourceWhereClausesCache = compilation.sourceWhereClauses\n this.compiledAliasToCollectionId = compilation.aliasToCollectionId\n this.includesCache = compilation.includes\n\n // Defensive check: verify all compiled aliases have corresponding inputs\n // This should never happen since all aliases come from user declarations,\n // but catch it early if the assumption is violated in the future.\n const missingAliases = Object.keys(this.compiledAliasToCollectionId).filter(\n (alias) => !Object.hasOwn(this.inputsCache!, alias),\n )\n if (missingAliases.length > 0) {\n throw new MissingAliasInputsError(missingAliases)\n }\n }\n\n private maybeCompileBasePipeline() {\n if (!this.graphCache || !this.inputsCache || !this.pipelineCache) {\n this.compileBasePipeline()\n }\n return {\n graph: this.graphCache!,\n inputs: this.inputsCache!,\n pipeline: this.pipelineCache!,\n }\n }\n\n private extendPipelineWithChangeProcessing(\n config: SyncMethods<TResult>,\n syncState: SyncState,\n ): FullSyncState {\n const { begin, commit } = config\n const { graph, inputs, pipeline } = this.maybeCompileBasePipeline()\n\n // Accumulator for changes across all output callbacks within a single graph run.\n // This allows us to batch all changes from intermediate join states into a single\n // transaction, avoiding duplicate key errors when joins produce multiple outputs\n // for the same key (e.g., first output with null, then output with joined data).\n let pendingChanges: Map<unknown, Changes<TResult>> = new Map()\n\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n // Accumulate changes from this output callback into the pending changes map.\n // Changes for the same key are merged (inserts/deletes are added together).\n messages.reduce(accumulateChanges<TResult>, pendingChanges)\n }),\n )\n\n // Set up includes output routing and child collection lifecycle\n const includesState = this.setupIncludesOutput(\n this.includesCache,\n syncState,\n )\n\n // Flush pending changes and reset the accumulator.\n // Called at the end of each graph run to commit all accumulated changes.\n syncState.flushPendingChanges = () => {\n const hasParentChanges = pendingChanges.size > 0\n const hasChildChanges = hasPendingIncludesChanges(includesState)\n\n if (!hasParentChanges && !hasChildChanges) {\n return\n }\n\n let changesToApply = pendingChanges\n\n // When a custom getKey is provided, multiple D2 internal keys may map\n // to the same user-visible key. Re-accumulate by custom key so that a\n // retract + insert for the same logical row merges into an UPDATE\n // instead of a separate DELETE and INSERT that can race.\n if (this.config.getKey) {\n const merged = new Map<unknown, Changes<TResult>>()\n for (const [, changes] of pendingChanges) {\n const customKey = this.config.getKey(changes.value)\n const existing = merged.get(customKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n // Keep the value from the insert side (the new value)\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n merged.set(customKey, { ...changes })\n }\n }\n changesToApply = merged\n }\n\n // 1. Flush parent changes\n if (hasParentChanges) {\n begin()\n changesToApply.forEach(this.applyChanges.bind(this, config))\n commit()\n }\n pendingChanges = new Map()\n\n // 2. Process includes: create/dispose child Collections, route child changes\n flushIncludesState(\n includesState,\n config.collection,\n this.id,\n hasParentChanges ? changesToApply : null,\n config,\n )\n }\n\n graph.finalize()\n\n // Extend the sync state with the graph, inputs, and pipeline\n syncState.graph = graph\n syncState.inputs = inputs\n syncState.pipeline = pipeline\n\n return syncState as FullSyncState\n }\n\n /**\n * Sets up output callbacks for includes child pipelines.\n * Each includes entry gets its own output callback that accumulates child changes,\n * and a child registry that maps correlation key → child Collection.\n */\n private setupIncludesOutput(\n includesEntries: Array<IncludesCompilationResult> | undefined,\n syncState: SyncState,\n ): Array<IncludesOutputState> {\n if (!includesEntries || includesEntries.length === 0) {\n return []\n }\n\n return includesEntries.map((entry) => {\n const state: IncludesOutputState = {\n fieldName: entry.fieldName,\n childCorrelationField: entry.childCorrelationField,\n hasOrderBy: entry.hasOrderBy,\n materialization: entry.materialization,\n scalarField: entry.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n // Attach output callback on the child pipeline\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n // Accumulate by [routingKey, childKey]\n let byChild = state.pendingChildChanges.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n state.pendingChildChanges.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n // Set up shared buffers for nested includes (e.g., comments inside issues)\n if (entry.childCompilationResult.includes) {\n state.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\n }\n\n private applyChanges(\n config: SyncMethods<TResult>,\n changes: {\n deletes: number\n inserts: number\n value: TResult\n orderByIndex: string | undefined\n },\n key: unknown,\n ) {\n const { write, collection } = config\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n this.resultKeys.set(value, key)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n this.orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes && collection.has(collection.getKeyFromItem(value)))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `Could not apply changes: ${JSON.stringify(changes)}. This should never happen.`,\n )\n }\n }\n\n /**\n * Handle status changes from source collections\n */\n private handleSourceStatusChange(\n config: SyncMethods<TResult>,\n collectionId: string,\n event: AllCollectionEvents[`status:change`],\n ) {\n const { status } = event\n\n // Handle error state - any source collection in error puts live query in error\n if (status === `error`) {\n this.transitionToError(\n `Source collection '${collectionId}' entered error state`,\n )\n return\n }\n\n // Handle manual cleanup - this should not happen due to GC prevention,\n // but could happen if user manually calls cleanup()\n if (status === `cleaned-up`) {\n this.transitionToError(\n `Source collection '${collectionId}' was manually cleaned up while live query '${this.id}' depends on it. ` +\n `Live queries prevent automatic GC, so this was likely a manual cleanup() call.`,\n )\n return\n }\n\n // Update ready status based on all source collections\n this.updateLiveQueryStatus(config)\n }\n\n /**\n * Update the live query status based on source collection statuses\n */\n private updateLiveQueryStatus(config: SyncMethods<TResult>) {\n const { markReady } = config\n\n // Don't update status if already in error\n if (this.isInErrorState) {\n return\n }\n\n const subscribedToAll = this.currentSyncState?.subscribedToAllCollections\n const allReady = this.allCollectionsReady()\n const isLoading = this.liveQueryCollection?.isLoadingSubset\n // Mark ready when:\n // 1. All subscriptions are set up (subscribedToAllCollections)\n // 2. All source collections are ready\n // 3. The live query collection is not loading subset data\n // This prevents marking the live query ready before its data is processed\n // (fixes issue where useLiveQuery returns isReady=true with empty data)\n if (subscribedToAll && allReady && !isLoading) {\n markReady()\n }\n }\n\n /**\n * Transition the live query to error state\n */\n private transitionToError(message: string) {\n this.isInErrorState = true\n\n // Log error to console for debugging\n console.error(`[Live Query Error] ${message}`)\n\n // Transition live query collection to error state\n this.liveQueryCollection?._lifecycle.setStatus(`error`)\n }\n\n private allCollectionsReady() {\n return Object.values(this.collections).every((collection) =>\n collection.isReady(),\n )\n }\n\n /**\n * Creates per-alias subscriptions enabling self-join support.\n * Each alias gets its own subscription with independent filters, even for the same collection.\n * Example: `{ employee: col, manager: col }` creates two separate subscriptions.\n */\n private subscribeToAllCollections(\n config: SyncMethods<TResult>,\n syncState: FullSyncState,\n ) {\n // Use compiled aliases as the source of truth - these include all aliases from the query\n // including those from subqueries, which may not be in collectionByAlias\n const compiledAliases = Object.entries(this.compiledAliasToCollectionId)\n if (compiledAliases.length === 0) {\n throw new Error(\n `Compiler returned no alias metadata for query '${this.id}'. This should not happen; please report.`,\n )\n }\n\n // Create a separate subscription for each alias, enabling self-joins where the same\n // collection can be used multiple times with different filters and subscriptions\n const loaders = compiledAliases.map(([alias, collectionId]) => {\n // Try collectionByAlias first (for declared aliases), fall back to collections (for subquery aliases)\n const collection =\n this.collectionByAlias[alias] ?? this.collections[collectionId]!\n\n const dependencyBuilder = getCollectionBuilder(collection)\n if (dependencyBuilder && dependencyBuilder !== this) {\n this.aliasDependencies[alias] = [dependencyBuilder]\n this.builderDependencies.add(dependencyBuilder)\n } else {\n this.aliasDependencies[alias] = []\n }\n\n // CollectionSubscriber handles the actual subscription to the source collection\n // and feeds data into the D2 graph inputs for this specific alias\n const collectionSubscriber = new CollectionSubscriber(\n alias,\n collectionId,\n collection,\n this,\n )\n\n // Subscribe to status changes for status flow\n const statusUnsubscribe = collection.on(`status:change`, (event) => {\n this.handleSourceStatusChange(config, collectionId, event)\n })\n syncState.unsubscribeCallbacks.add(statusUnsubscribe)\n\n const subscription = collectionSubscriber.subscribe()\n // Store subscription by alias (not collection ID) to support lazy loading\n // which needs to look up subscriptions by their query alias\n this.subscriptions[alias] = subscription\n\n // Create a callback for loading more data if needed (used by OrderBy optimization)\n const loadMore = collectionSubscriber.loadMoreIfNeeded.bind(\n collectionSubscriber,\n subscription,\n )\n\n return loadMore\n })\n\n // Combine all loaders into a single callback that initiates loading more data\n // from any source that needs it. Returns true once all loaders have been called,\n // but the actual async loading may still be in progress.\n const loadSubsetDataCallbacks = () => {\n loaders.map((loader) => loader())\n return true\n }\n\n // Mark as subscribed so the graph can start running\n // (graph only runs when all collections are subscribed)\n syncState.subscribedToAllCollections = true\n\n // Note: We intentionally don't call updateLiveQueryStatus() here.\n // The graph hasn't run yet, so marking ready would be premature.\n // The canonical place to mark ready is after the graph processes data\n // in maybeRunGraph(), which ensures data has been processed first.\n\n return loadSubsetDataCallbacks\n }\n}\n\nfunction createOrderByComparator<T extends object>(\n orderByIndices: WeakMap<object, string>,\n) {\n return (val1: T, val2: T): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n}\n\n/**\n * Shared buffer setup for a single nested includes level.\n * Pipeline output writes into the buffer; during flush the buffer is drained\n * into per-entry states via the routing index.\n */\ntype NestedIncludesSetup = {\n compilationResult: IncludesCompilationResult\n /** Shared buffer: nestedCorrelationKey → Map<childKey, Changes> */\n buffer: Map<unknown, Map<unknown, Changes<any>>>\n /** For 3+ levels of nesting */\n nestedSetups?: Array<NestedIncludesSetup>\n}\n\n/**\n * State tracked per includes entry for output routing and child lifecycle\n */\ntype IncludesOutputState = {\n fieldName: string\n childCorrelationField: PropRef\n /** Whether the child query has an ORDER BY clause */\n hasOrderBy: boolean\n /** How the child result is materialized on the parent row */\n materialization: IncludesMaterialization\n /** Internal field used to unwrap scalar child selects */\n scalarField?: string\n /** Maps correlation key value → child Collection entry */\n childRegistry: Map<unknown, ChildCollectionEntry>\n /** Pending child changes: correlationKey → Map<childKey, Changes> */\n pendingChildChanges: Map<unknown, Map<unknown, Changes<any>>>\n /** Reverse index: correlation key → Set of parent collection keys */\n correlationToParentKeys: Map<unknown, Set<unknown>>\n /** Shared nested pipeline setups (one per nested includes level) */\n nestedSetups?: Array<NestedIncludesSetup>\n /** nestedCorrelationKey → parentCorrelationKey */\n nestedRoutingIndex?: Map<unknown, unknown>\n /** parentCorrelationKey → Set<nestedCorrelationKeys> */\n nestedRoutingReverseIndex?: Map<unknown, Set<unknown>>\n}\n\ntype ChildCollectionEntry = {\n collection: Collection<any, any, any>\n syncMethods: SyncMethods<any> | null\n resultKeys: WeakMap<object, unknown>\n orderByIndices: WeakMap<object, string> | null\n /** Per-entry nested includes states (one per nested includes level) */\n includesStates?: Array<IncludesOutputState>\n}\n\nfunction materializesInline(state: IncludesOutputState): boolean {\n return state.materialization !== `collection`\n}\n\nfunction materializeIncludedValue(\n state: IncludesOutputState,\n entry: ChildCollectionEntry | undefined,\n): unknown {\n if (!entry) {\n if (state.materialization === `array`) {\n return []\n }\n if (state.materialization === `concat`) {\n return ``\n }\n return undefined\n }\n\n if (state.materialization === `collection`) {\n return entry.collection\n }\n\n const rows = [...entry.collection.toArray]\n const values = state.scalarField\n ? rows.map((row) => row?.[state.scalarField!])\n : rows\n\n if (state.materialization === `array`) {\n return values\n }\n\n return values.map((value) => String(value ?? ``)).join(``)\n}\n\n/**\n * Sets up shared buffers for nested includes pipelines.\n * Instead of writing directly into a single shared IncludesOutputState,\n * each nested pipeline writes into a buffer that is later drained per-entry.\n */\nfunction setupNestedPipelines(\n includes: Array<IncludesCompilationResult>,\n syncState: SyncState,\n): Array<NestedIncludesSetup> {\n return includes.map((entry) => {\n const buffer: Map<unknown, Map<unknown, Changes<any>>> = new Map()\n\n // Attach output callback that writes into the shared buffer\n entry.pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n syncState.messagesCount += messages.length\n\n for (const [[childKey, tupleData], multiplicity] of messages) {\n const [childResult, _orderByIndex, correlationKey, parentContext] =\n tupleData as unknown as [\n any,\n string | undefined,\n unknown,\n Record<string, any> | null,\n ]\n\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n let byChild = buffer.get(routingKey)\n if (!byChild) {\n byChild = new Map()\n buffer.set(routingKey, byChild)\n }\n\n const existing = byChild.get(childKey) || {\n deletes: 0,\n inserts: 0,\n value: childResult,\n orderByIndex: _orderByIndex,\n }\n\n if (multiplicity < 0) {\n existing.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n existing.inserts += multiplicity\n existing.value = childResult\n }\n\n byChild.set(childKey, existing)\n }\n }),\n )\n\n const setup: NestedIncludesSetup = {\n compilationResult: entry,\n buffer,\n }\n\n // Recursively set up deeper levels\n if (entry.childCompilationResult.includes) {\n setup.nestedSetups = setupNestedPipelines(\n entry.childCompilationResult.includes,\n syncState,\n )\n }\n\n return setup\n })\n}\n\n/**\n * Creates fresh per-entry IncludesOutputState array from NestedIncludesSetup array.\n * Each entry gets its own isolated state for nested includes.\n */\nfunction createPerEntryIncludesStates(\n setups: Array<NestedIncludesSetup>,\n): Array<IncludesOutputState> {\n return setups.map((setup) => {\n const state: IncludesOutputState = {\n fieldName: setup.compilationResult.fieldName,\n childCorrelationField: setup.compilationResult.childCorrelationField,\n hasOrderBy: setup.compilationResult.hasOrderBy,\n materialization: setup.compilationResult.materialization,\n scalarField: setup.compilationResult.scalarField,\n childRegistry: new Map(),\n pendingChildChanges: new Map(),\n correlationToParentKeys: new Map(),\n }\n\n if (setup.nestedSetups) {\n state.nestedSetups = setup.nestedSetups\n state.nestedRoutingIndex = new Map()\n state.nestedRoutingReverseIndex = new Map()\n }\n\n return state\n })\n}\n\n/**\n * Drains shared buffers into per-entry states using the routing index.\n * Returns the set of parent correlation keys that had changes routed to them.\n */\nfunction drainNestedBuffers(state: IncludesOutputState): Set<unknown> {\n const dirtyCorrelationKeys = new Set<unknown>()\n\n if (!state.nestedSetups) return dirtyCorrelationKeys\n\n for (let i = 0; i < state.nestedSetups.length; i++) {\n const setup = state.nestedSetups[i]!\n const toDelete: Array<unknown> = []\n\n for (const [nestedCorrelationKey, childChanges] of setup.buffer) {\n const parentCorrelationKey =\n state.nestedRoutingIndex!.get(nestedCorrelationKey)\n if (parentCorrelationKey === undefined) {\n // Unroutable — parent not yet seen; keep in buffer\n continue\n }\n\n const entry = state.childRegistry.get(parentCorrelationKey)\n if (!entry || !entry.includesStates) {\n continue\n }\n\n // Route changes into this entry's per-entry state at position i\n const entryState = entry.includesStates[i]!\n for (const [childKey, changes] of childChanges) {\n let byChild = entryState.pendingChildChanges.get(nestedCorrelationKey)\n if (!byChild) {\n byChild = new Map()\n entryState.pendingChildChanges.set(nestedCorrelationKey, byChild)\n }\n const existing = byChild.get(childKey)\n if (existing) {\n existing.inserts += changes.inserts\n existing.deletes += changes.deletes\n if (changes.inserts > 0) {\n existing.value = changes.value\n if (changes.orderByIndex !== undefined) {\n existing.orderByIndex = changes.orderByIndex\n }\n }\n } else {\n byChild.set(childKey, { ...changes })\n }\n }\n\n dirtyCorrelationKeys.add(parentCorrelationKey)\n toDelete.push(nestedCorrelationKey)\n }\n\n for (const key of toDelete) {\n setup.buffer.delete(key)\n }\n }\n\n return dirtyCorrelationKeys\n}\n\n/**\n * Updates the routing index after processing child changes.\n * Maps nested correlation keys to parent correlation keys so that\n * grandchild changes can be routed to the correct per-entry state.\n */\nfunction updateRoutingIndex(\n state: IncludesOutputState,\n correlationKey: unknown,\n childChanges: Map<unknown, Changes<any>>,\n): void {\n if (!state.nestedSetups) return\n\n for (const setup of state.nestedSetups) {\n for (const [, change] of childChanges) {\n if (change.inserts > 0) {\n // Read the nested routing key from the INCLUDES_ROUTING stamp.\n // Must use the composite routing key (not raw correlationKey) to match\n // how nested buffers are keyed by computeRoutingKey.\n const nestedRouting =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting?.correlationKey\n const nestedParentContext = nestedRouting?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.set(nestedRoutingKey, correlationKey)\n let reverseSet = state.nestedRoutingReverseIndex!.get(correlationKey)\n if (!reverseSet) {\n reverseSet = new Set()\n state.nestedRoutingReverseIndex!.set(correlationKey, reverseSet)\n }\n reverseSet.add(nestedRoutingKey)\n }\n } else if (change.deletes > 0 && change.inserts === 0) {\n // Remove from routing index\n const nestedRouting2 =\n change.value[INCLUDES_ROUTING]?.[setup.compilationResult.fieldName]\n const nestedCorrelationKey = nestedRouting2?.correlationKey\n const nestedParentContext2 = nestedRouting2?.parentContext ?? null\n const nestedRoutingKey = computeRoutingKey(\n nestedCorrelationKey,\n nestedParentContext2,\n )\n\n if (nestedCorrelationKey != null) {\n state.nestedRoutingIndex!.delete(nestedRoutingKey)\n const reverseSet =\n state.nestedRoutingReverseIndex!.get(correlationKey)\n if (reverseSet) {\n reverseSet.delete(nestedRoutingKey)\n if (reverseSet.size === 0) {\n state.nestedRoutingReverseIndex!.delete(correlationKey)\n }\n }\n }\n }\n }\n }\n}\n\n/**\n * Cleans routing index entries when a parent is deleted.\n * Uses the reverse index to find and remove all nested routing entries.\n */\nfunction cleanRoutingIndexOnDelete(\n state: IncludesOutputState,\n correlationKey: unknown,\n): void {\n if (!state.nestedRoutingReverseIndex) return\n\n const nestedKeys = state.nestedRoutingReverseIndex.get(correlationKey)\n if (nestedKeys) {\n for (const nestedKey of nestedKeys) {\n state.nestedRoutingIndex!.delete(nestedKey)\n }\n state.nestedRoutingReverseIndex.delete(correlationKey)\n }\n}\n\n/**\n * Recursively checks whether any nested buffer has pending changes.\n */\nfunction hasNestedBufferChanges(setups: Array<NestedIncludesSetup>): boolean {\n for (const setup of setups) {\n if (setup.buffer.size > 0) return true\n if (setup.nestedSetups && hasNestedBufferChanges(setup.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Computes a composite routing key from correlation key and parent context.\n * When parentContext is null (no parent filters), returns the raw correlationKey\n * for zero behavioral change on existing queries.\n */\nfunction computeRoutingKey(\n correlationKey: unknown,\n parentContext: Record<string, any> | null,\n): unknown {\n if (parentContext == null) return correlationKey\n return JSON.stringify([correlationKey, parentContext])\n}\n\n/**\n * Creates a child Collection entry for includes subqueries.\n * The child Collection is a full-fledged Collection instance that starts syncing immediately.\n */\nfunction createChildCollectionEntry(\n parentId: string,\n fieldName: string,\n correlationKey: unknown,\n hasOrderBy: boolean,\n nestedSetups?: Array<NestedIncludesSetup>,\n): ChildCollectionEntry {\n const resultKeys = new WeakMap<object, unknown>()\n const orderByIndices = hasOrderBy ? new WeakMap<object, string>() : null\n let syncMethods: SyncMethods<any> | null = null\n\n const compare = orderByIndices\n ? createOrderByComparator(orderByIndices)\n : undefined\n\n const collection = createCollection<any, string | number>({\n id: `__child-collection:${parentId}-${fieldName}-${serializeValue(correlationKey)}`,\n getKey: (item: any) => resultKeys.get(item) as string | number,\n compare,\n sync: {\n rowUpdateMode: `full`,\n sync: (methods) => {\n syncMethods = methods\n return () => {\n syncMethods = null\n }\n },\n },\n startSync: true,\n })\n\n const entry: ChildCollectionEntry = {\n collection,\n get syncMethods() {\n return syncMethods\n },\n resultKeys,\n orderByIndices,\n }\n\n if (nestedSetups) {\n entry.includesStates = createPerEntryIncludesStates(nestedSetups)\n }\n\n return entry\n}\n\n/**\n * Flushes includes state using a bottom-up per-entry approach.\n * Five phases ensure correct ordering:\n * 1. Parent INSERTs — create child entries with per-entry nested states\n * 2. Child changes — apply to child Collections, update routing index\n * 3. Drain nested buffers — route buffered grandchild changes to per-entry states\n * 4. Flush per-entry states — recursively flush nested includes on each entry\n * 5. Parent DELETEs — clean up child entries and routing index\n */\nfunction flushIncludesState(\n includesState: Array<IncludesOutputState>,\n parentCollection: Collection<any, any, any>,\n parentId: string,\n parentChanges: Map<unknown, Changes<any>> | null,\n parentSyncMethods: SyncMethods<any> | null,\n): void {\n for (const state of includesState) {\n // Phase 1: Parent INSERTs — ensure a child Collection exists for every parent\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.inserts > 0) {\n const parentResult = changes.value\n // Extract routing info from INCLUDES_ROUTING symbol (set by compiler)\n const routing = parentResult[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n\n if (correlationKey != null) {\n // Ensure child Collection exists for this routing key\n if (!state.childRegistry.has(routingKey)) {\n const entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n routingKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(routingKey, entry)\n }\n // Update reverse index: routing key → parent keys\n let parentKeys = state.correlationToParentKeys.get(routingKey)\n if (!parentKeys) {\n parentKeys = new Set()\n state.correlationToParentKeys.set(routingKey, parentKeys)\n }\n parentKeys.add(parentKey)\n\n const childValue = materializeIncludedValue(\n state,\n state.childRegistry.get(routingKey),\n )\n parentResult[state.fieldName] = childValue\n\n // Parent rows may already be materialized in the live collection by the\n // time includes state is flushed, so update the stored row as well.\n const storedParent = parentCollection.get(parentKey as any)\n if (storedParent && storedParent !== parentResult) {\n storedParent[state.fieldName] = childValue\n }\n }\n }\n }\n }\n\n // Track affected correlation keys for inline materializations before clearing child changes.\n const affectedCorrelationKeys = materializesInline(state)\n ? new Set<unknown>(state.pendingChildChanges.keys())\n : null\n\n // Phase 2: Child changes — apply to child Collections\n // Track which entries had child changes and capture their childChanges maps\n const entriesWithChildChanges = new Map<\n unknown,\n { entry: ChildCollectionEntry; childChanges: Map<unknown, Changes<any>> }\n >()\n if (state.pendingChildChanges.size > 0) {\n for (const [correlationKey, childChanges] of state.pendingChildChanges) {\n // Ensure child Collection exists for this correlation key\n let entry = state.childRegistry.get(correlationKey)\n if (!entry) {\n entry = createChildCollectionEntry(\n parentId,\n state.fieldName,\n correlationKey,\n state.hasOrderBy,\n state.nestedSetups,\n )\n state.childRegistry.set(correlationKey, entry)\n }\n\n if (state.materialization === `collection`) {\n attachChildCollectionToParent(\n parentCollection,\n state.fieldName,\n correlationKey,\n state.correlationToParentKeys,\n entry.collection,\n )\n }\n\n // Apply child changes to the child Collection\n if (entry.syncMethods) {\n entry.syncMethods.begin()\n for (const [childKey, change] of childChanges) {\n entry.resultKeys.set(change.value, childKey)\n if (entry.orderByIndices && change.orderByIndex !== undefined) {\n entry.orderByIndices.set(change.value, change.orderByIndex)\n }\n if (change.inserts > 0 && change.deletes === 0) {\n entry.syncMethods.write({ value: change.value, type: `insert` })\n } else if (\n change.inserts > change.deletes ||\n (change.inserts === change.deletes &&\n entry.syncMethods.collection.has(\n entry.syncMethods.collection.getKeyFromItem(change.value),\n ))\n ) {\n entry.syncMethods.write({ value: change.value, type: `update` })\n } else if (change.deletes > 0) {\n entry.syncMethods.write({ value: change.value, type: `delete` })\n }\n }\n entry.syncMethods.commit()\n }\n\n // Update routing index for nested includes\n updateRoutingIndex(state, correlationKey, childChanges)\n\n entriesWithChildChanges.set(correlationKey, { entry, childChanges })\n }\n state.pendingChildChanges.clear()\n }\n\n // Phase 3: Drain nested buffers — route buffered grandchild changes to per-entry states\n const dirtyFromBuffers = drainNestedBuffers(state)\n\n // Phase 4: Flush per-entry states\n // First: entries that had child changes in Phase 2\n for (const [, { entry, childChanges }] of entriesWithChildChanges) {\n if (entry.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n childChanges,\n entry.syncMethods,\n )\n }\n }\n // Then: entries that only had buffer-routed changes (no child changes at this level)\n for (const correlationKey of dirtyFromBuffers) {\n if (entriesWithChildChanges.has(correlationKey)) continue\n const entry = state.childRegistry.get(correlationKey)\n if (entry?.includesStates) {\n flushIncludesState(\n entry.includesStates,\n entry.collection,\n entry.collection.id,\n null,\n entry.syncMethods,\n )\n }\n }\n\n // For inline materializations: re-emit affected parents with updated snapshots.\n // We mutate items in-place (so collection.get() reflects changes immediately)\n // and emit UPDATE events directly. We bypass the sync methods because\n // commitPendingTransactions compares previous vs new visible state using\n // deepEquals, but in-place mutation means both sides reference the same\n // object, so the comparison always returns true and suppresses the event.\n const inlineReEmitKeys = materializesInline(state)\n ? new Set([...(affectedCorrelationKeys || []), ...dirtyFromBuffers])\n : null\n if (parentSyncMethods && inlineReEmitKeys && inlineReEmitKeys.size > 0) {\n const events: Array<ChangeMessage<any>> = []\n for (const correlationKey of inlineReEmitKeys) {\n const parentKeys = state.correlationToParentKeys.get(correlationKey)\n if (!parentKeys) continue\n const entry = state.childRegistry.get(correlationKey)\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n const key = parentSyncMethods.collection.getKeyFromItem(item)\n // Capture previous value before in-place mutation\n const previousValue = { ...item }\n item[state.fieldName] = materializeIncludedValue(state, entry)\n events.push({\n type: `update`,\n key,\n value: item,\n previousValue,\n })\n }\n }\n }\n if (events.length > 0) {\n // Emit directly — the in-place mutation already updated the data in\n // syncedData, so we only need to notify subscribers.\n const changesManager = (parentCollection as any)._changes as {\n emitEvents: (\n changes: Array<ChangeMessage<any>>,\n forceEmit?: boolean,\n ) => void\n }\n changesManager.emitEvents(events, true)\n }\n }\n\n // Phase 5: Parent DELETEs — dispose child Collections and clean up\n if (parentChanges) {\n for (const [parentKey, changes] of parentChanges) {\n if (changes.deletes > 0 && changes.inserts === 0) {\n const routing = changes.value[INCLUDES_ROUTING]?.[state.fieldName]\n const correlationKey = routing?.correlationKey\n const parentContext = routing?.parentContext ?? null\n const routingKey = computeRoutingKey(correlationKey, parentContext)\n if (correlationKey != null) {\n // Clean up reverse index first, only delete child collection\n // when the last parent referencing it is removed\n const parentKeys = state.correlationToParentKeys.get(routingKey)\n if (parentKeys) {\n parentKeys.delete(parentKey)\n if (parentKeys.size === 0) {\n cleanRoutingIndexOnDelete(state, routingKey)\n state.childRegistry.delete(routingKey)\n state.correlationToParentKeys.delete(routingKey)\n }\n }\n }\n }\n }\n }\n }\n\n // Clean up the internal routing stamp from parent/child results\n if (parentChanges) {\n for (const [, changes] of parentChanges) {\n delete changes.value[INCLUDES_ROUTING]\n }\n }\n}\n\n/**\n * Checks whether any includes state has pending changes that need to be flushed.\n * Checks direct pending child changes and shared nested buffers.\n */\nfunction hasPendingIncludesChanges(\n states: Array<IncludesOutputState>,\n): boolean {\n for (const state of states) {\n if (state.pendingChildChanges.size > 0) return true\n if (state.nestedSetups && hasNestedBufferChanges(state.nestedSetups))\n return true\n }\n return false\n}\n\n/**\n * Attaches a child Collection to parent rows that match a given correlation key.\n * Uses the reverse index to look up parent keys directly instead of scanning.\n */\nfunction attachChildCollectionToParent(\n parentCollection: Collection<any, any, any>,\n fieldName: string,\n correlationKey: unknown,\n correlationToParentKeys: Map<unknown, Set<unknown>>,\n childCollection: Collection<any, any, any>,\n): void {\n const parentKeys = correlationToParentKeys.get(correlationKey)\n if (!parentKeys) return\n\n for (const parentKey of parentKeys) {\n const item = parentCollection.get(parentKey as any)\n if (item) {\n item[fieldName] = childCollection\n }\n }\n}\n\nfunction accumulateChanges<T>(\n acc: Map<unknown, Changes<T>>,\n [[key, tupleData], multiplicity]: [\n [unknown, [any, string | undefined]],\n number,\n ],\n) {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [T, string | undefined]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n // Update value to the latest version for this key\n changes.value = value\n if (orderByIndex !== undefined) {\n changes.orderByIndex = orderByIndex\n }\n }\n acc.set(key, changes)\n return acc\n}\n"],"names":["buildQueryFromConfig","extractCollectionsFromQuery","extractCollectionAliases","extractCollectionFromSource","LIVE_QUERY_INTERNAL","SetWindowRequiresOrderByError","getActiveTransaction","transactionScopedScheduler","D2","compileQuery","MissingAliasInputsError","output","getCollectionBuilder","collectionSubscriber","CollectionSubscriber","INCLUDES_ROUTING","createCollection","serializeValue"],"mappings":"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"}
@@ -57,6 +57,7 @@ export declare class CollectionConfigBuilder<TContext extends Context, TResult e
57
57
  private inputsCache;
58
58
  private pipelineCache;
59
59
  sourceWhereClausesCache: Map<string, BasicExpression<boolean>> | undefined;
60
+ private includesCache;
60
61
  readonly subscriptions: Record<string, CollectionSubscription>;
61
62
  lazySourcesCallbacks: Record<string, LazyCollectionCallbacks>;
62
63
  readonly lazySources: Set<string>;
@@ -141,6 +142,12 @@ export declare class CollectionConfigBuilder<TContext extends Context, TResult e
141
142
  private compileBasePipeline;
142
143
  private maybeCompileBasePipeline;
143
144
  private extendPipelineWithChangeProcessing;
145
+ /**
146
+ * Sets up output callbacks for includes child pipelines.
147
+ * Each includes entry gets its own output callback that accumulates child changes,
148
+ * and a child registry that maps correlation key → child Collection.
149
+ */
150
+ private setupIncludesOutput;
144
151
  private applyChanges;
145
152
  /**
146
153
  * Handle status changes from source collections
@@ -1,7 +1,7 @@
1
1
  import { D2, RootStreamBuilder } from '@tanstack/db-ivm';
2
2
  import { CollectionConfig, ResultStream, StringCollationConfig } from '../../types.js';
3
3
  import { InitialQueryBuilder, QueryBuilder } from '../builder/index.js';
4
- import { Context, GetResult } from '../builder/types.js';
4
+ import { Context, RootObjectResultConstraint, RootQueryResult } from '../builder/types.js';
5
5
  export type Changes<T> = {
6
6
  deletes: number;
7
7
  inserts: number;
@@ -42,7 +42,7 @@ export type FullSyncState = Required<Omit<SyncState, `flushPendingChanges`>> & P
42
42
  * }
43
43
  * ```
44
44
  */
45
- export interface LiveQueryCollectionConfig<TContext extends Context, TResult extends object = GetResult<TContext> & object> {
45
+ export interface LiveQueryCollectionConfig<TContext extends Context, TResult extends object = RootQueryResult<TContext>> {
46
46
  /**
47
47
  * Unique identifier for the collection
48
48
  * If not provided, defaults to `live-query-${number}` with auto-incrementing number
@@ -51,7 +51,7 @@ export interface LiveQueryCollectionConfig<TContext extends Context, TResult ext
51
51
  /**
52
52
  * Query builder function that defines the live query
53
53
  */
54
- query: ((q: InitialQueryBuilder) => QueryBuilder<TContext>) | QueryBuilder<TContext>;
54
+ query: ((q: InitialQueryBuilder) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>) | (QueryBuilder<TContext> & RootObjectResultConstraint<TContext>);
55
55
  /**
56
56
  * Function to extract the key from result items
57
57
  * If not provided, defaults to using the key from the D2 stream
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const dbIvm = require("@tanstack/db-ivm");
4
+ const errors = require("../../errors.cjs");
4
5
  const expressions = require("../compiler/expressions.cjs");
5
6
  const index = require("../builder/index.cjs");
7
+ const ir = require("../ir.cjs");
6
8
  function extractCollectionsFromQuery(query) {
7
9
  const collections = {};
8
10
  function extractFromSource(source) {
@@ -23,6 +25,21 @@ function extractCollectionsFromQuery(query) {
23
25
  }
24
26
  }
25
27
  }
28
+ if (q.select) {
29
+ extractFromSelect(q.select);
30
+ }
31
+ }
32
+ function extractFromSelect(select) {
33
+ for (const [key, value] of Object.entries(select)) {
34
+ if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {
35
+ continue;
36
+ }
37
+ if (value instanceof ir.IncludesSubquery) {
38
+ extractFromQuery(value.query);
39
+ } else if (isNestedSelectObject(value)) {
40
+ extractFromSelect(value);
41
+ }
42
+ }
26
43
  }
27
44
  extractFromQuery(query);
28
45
  return collections;
@@ -54,6 +71,18 @@ function extractCollectionAliases(query) {
54
71
  traverse(source.query);
55
72
  }
56
73
  }
74
+ function traverseSelect(select) {
75
+ for (const [key, value] of Object.entries(select)) {
76
+ if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {
77
+ continue;
78
+ }
79
+ if (value instanceof ir.IncludesSubquery) {
80
+ traverse(value.query);
81
+ } else if (isNestedSelectObject(value)) {
82
+ traverseSelect(value);
83
+ }
84
+ }
85
+ }
57
86
  function traverse(q) {
58
87
  if (!q) return;
59
88
  recordAlias(q.from);
@@ -62,15 +91,26 @@ function extractCollectionAliases(query) {
62
91
  recordAlias(joinClause.from);
63
92
  }
64
93
  }
94
+ if (q.select) {
95
+ traverseSelect(q.select);
96
+ }
65
97
  }
66
98
  traverse(query);
67
99
  return aliasesById;
68
100
  }
101
+ function isNestedSelectObject(obj) {
102
+ if (obj === null || typeof obj !== `object`) return false;
103
+ if (obj instanceof ir.IncludesSubquery) return false;
104
+ if (`type` in obj && typeof obj.type === `string`) return false;
105
+ if (obj.__refProxy) return false;
106
+ return true;
107
+ }
69
108
  function buildQueryFromConfig(config) {
70
- if (typeof config.query === `function`) {
71
- return index.buildQuery(config.query);
109
+ const query = typeof config.query === `function` ? index.buildQuery(config.query) : index.getQueryIR(config.query);
110
+ if (config.requireObjectResult && query.select && !isNestedSelectObject(query.select)) {
111
+ throw new errors.UnsupportedRootScalarSelectError();
72
112
  }
73
- return index.getQueryIR(config.query);
113
+ return query;
74
114
  }
75
115
  function sendChangesToInput(input, changes, getKey) {
76
116
  const multiSetArray = [];
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","sources":["../../../../src/query/live/utils.ts"],"sourcesContent":["import { MultiSet, serializeValue } from '@tanstack/db-ivm'\nimport { normalizeOrderByPaths } from '../compiler/expressions.js'\nimport { buildQuery, getQueryIR } from '../builder/index.js'\nimport type { MultiSetArray, RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { Collection } from '../../collection/index.js'\nimport type { ChangeMessage } from '../../types.js'\nimport type { InitialQueryBuilder, QueryBuilder } from '../builder/index.js'\nimport type { Context } from '../builder/types.js'\nimport type { OrderBy, QueryIR } from '../ir.js'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\n\n/**\n * Helper function to extract collections from a compiled query.\n * Traverses the query IR to find all collection references.\n * Maps collections by their ID (not alias) as expected by the compiler.\n */\nexport function extractCollectionsFromQuery(\n query: any,\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Helper function to extract the collection that is referenced in the query's FROM clause.\n * The FROM clause may refer directly to a collection or indirectly to a subquery.\n */\nexport function extractCollectionFromSource(\n query: any,\n): Collection<any, any, any> {\n const from = query.from\n\n if (from.type === `collectionRef`) {\n return from.collection\n } else if (from.type === `queryRef`) {\n // Recursively extract from subquery\n return extractCollectionFromSource(from.query)\n }\n\n throw new Error(\n `Failed to extract collection. Invalid FROM clause: ${JSON.stringify(query)}`,\n )\n}\n\n/**\n * Extracts all aliases used for each collection across the entire query tree.\n *\n * Traverses the QueryIR recursively to build a map from collection ID to all aliases\n * that reference that collection. This is essential for self-join support, where the\n * same collection may be referenced multiple times with different aliases.\n *\n * For example, given a query like:\n * ```ts\n * q.from({ employee: employeesCollection })\n * .join({ manager: employeesCollection }, ({ employee, manager }) =>\n * eq(employee.managerId, manager.id)\n * )\n * ```\n *\n * This function would return:\n * ```\n * Map { \"employees\" => Set { \"employee\", \"manager\" } }\n * ```\n *\n * @param query - The query IR to extract aliases from\n * @returns A map from collection ID to the set of all aliases referencing that collection\n */\nexport function extractCollectionAliases(\n query: QueryIR,\n): Map<string, Set<string>> {\n const aliasesById = new Map<string, Set<string>>()\n\n function recordAlias(source: any) {\n if (!source) return\n\n if (source.type === `collectionRef`) {\n const { id } = source.collection\n const existing = aliasesById.get(id)\n if (existing) {\n existing.add(source.alias)\n } else {\n aliasesById.set(id, new Set([source.alias]))\n }\n } else if (source.type === `queryRef`) {\n traverse(source.query)\n }\n }\n\n function traverse(q?: QueryIR) {\n if (!q) return\n\n recordAlias(q.from)\n\n if (q.join) {\n for (const joinClause of q.join) {\n recordAlias(joinClause.from)\n }\n }\n }\n\n traverse(query)\n\n return aliasesById\n}\n\n/**\n * Builds a query IR from a config object that contains either a query builder\n * function or a QueryBuilder instance.\n */\nexport function buildQueryFromConfig<TContext extends Context>(config: {\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n}): QueryIR {\n // Build the query using the provided query builder function or instance\n if (typeof config.query === `function`) {\n return buildQuery<TContext>(config.query)\n }\n return getQueryIR(config.query)\n}\n\n/**\n * Helper function to send changes to a D2 input stream.\n * Converts ChangeMessages to D2 MultiSet data and sends to the input.\n *\n * @returns The number of multiset entries sent\n */\nexport function sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any,\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n\n if (multiSetArray.length !== 0) {\n input.sendData(new MultiSet(multiSetArray))\n }\n\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nexport function* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\n/**\n * Filter changes to prevent duplicate inserts to a D2 pipeline.\n * Maintains D2 multiplicity at 1 for visible items so that deletes\n * properly reduce multiplicity to 0.\n *\n * Mutates `sentKeys` in place: adds keys on insert, removes on delete.\n */\nexport function filterDuplicateInserts(\n changes: Array<ChangeMessage<any, string | number>>,\n sentKeys: Set<string | number>,\n): Array<ChangeMessage<any, string | number>> {\n const filtered: Array<ChangeMessage<any, string | number>> = []\n for (const change of changes) {\n if (change.type === `insert`) {\n if (sentKeys.has(change.key)) {\n continue // Skip duplicate\n }\n sentKeys.add(change.key)\n } else if (change.type === `delete`) {\n sentKeys.delete(change.key)\n }\n filtered.push(change)\n }\n return filtered\n}\n\n/**\n * Track the biggest value seen in a stream of changes, used for cursor-based\n * pagination in ordered subscriptions. Returns whether the load request key\n * should be reset (allowing another load).\n *\n * @param changes - changes to process (deletes are skipped)\n * @param current - the current biggest value (or undefined if none)\n * @param sentKeys - set of keys already sent to D2 (for new-key detection)\n * @param comparator - orderBy comparator\n * @returns `{ biggest, shouldResetLoadKey }` — the new biggest value and\n * whether the caller should clear its last-load-request-key\n */\nexport function trackBiggestSentValue(\n changes: Array<ChangeMessage<any, string | number>>,\n current: unknown | undefined,\n sentKeys: Set<string | number>,\n comparator: (a: any, b: any) => number,\n): { biggest: unknown; shouldResetLoadKey: boolean } {\n let biggest = current\n let shouldResetLoadKey = false\n\n for (const change of changes) {\n if (change.type === `delete`) continue\n\n const isNewKey = !sentKeys.has(change.key)\n\n if (biggest === undefined) {\n biggest = change.value\n shouldResetLoadKey = true\n } else if (comparator(biggest, change.value) < 0) {\n biggest = change.value\n shouldResetLoadKey = true\n } else if (isNewKey) {\n // New key at same sort position — allow another load if needed\n shouldResetLoadKey = true\n }\n }\n\n return { biggest, shouldResetLoadKey }\n}\n\n/**\n * Compute orderBy/limit subscription hints for an alias.\n * Returns normalised orderBy and effective limit suitable for passing to\n * `subscribeChanges`, or `undefined` values when the query's orderBy cannot\n * be scoped to the given alias (e.g. cross-collection refs or aggregates).\n */\nexport function computeSubscriptionOrderByHints(\n query: { orderBy?: OrderBy; limit?: number; offset?: number },\n alias: string,\n): { orderBy: OrderBy | undefined; limit: number | undefined } {\n const { orderBy, limit, offset } = query\n const effectiveLimit =\n limit !== undefined && offset !== undefined ? limit + offset : limit\n\n const normalizedOrderBy = orderBy\n ? normalizeOrderByPaths(orderBy, alias)\n : undefined\n\n // Only pass orderBy when it is scoped to this alias and uses simple refs,\n // to avoid leaking cross-collection paths into backend-specific compilers.\n const canPassOrderBy =\n normalizedOrderBy?.every((clause) => {\n const exp = clause.expression\n if (exp.type !== `ref`) return false\n const path = exp.path\n return Array.isArray(path) && path.length === 1\n }) ?? false\n\n return {\n orderBy: canPassOrderBy ? normalizedOrderBy : undefined,\n limit: canPassOrderBy ? effectiveLimit : undefined,\n }\n}\n\n/**\n * Compute the cursor for loading the next batch of ordered data.\n * Extracts values from the biggest sent row and builds the `minValues`\n * array and a deduplication key.\n *\n * @returns `undefined` if the load should be skipped (duplicate request),\n * otherwise `{ minValues, normalizedOrderBy, loadRequestKey }`.\n */\nexport function computeOrderedLoadCursor(\n orderByInfo: Pick<\n OrderByOptimizationInfo,\n 'orderBy' | 'valueExtractorForRawRow' | 'offset'\n >,\n biggestSentRow: unknown | undefined,\n lastLoadRequestKey: string | undefined,\n alias: string,\n limit: number,\n):\n | {\n minValues: Array<unknown> | undefined\n normalizedOrderBy: OrderBy\n loadRequestKey: string\n }\n | undefined {\n const { orderBy, valueExtractorForRawRow, offset } = orderByInfo\n\n // Extract all orderBy column values from the biggest sent row\n // For single-column: returns single value, for multi-column: returns array\n const extractedValues = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow as Record<string, unknown>)\n : undefined\n\n // Normalize to array format for minValues\n let minValues: Array<unknown> | undefined\n if (extractedValues !== undefined) {\n minValues = Array.isArray(extractedValues)\n ? extractedValues\n : [extractedValues]\n }\n\n // Deduplicate: skip if we already issued an identical load request\n const loadRequestKey = serializeValue({\n minValues: minValues ?? null,\n offset,\n limit,\n })\n if (lastLoadRequestKey === loadRequestKey) {\n return undefined\n }\n\n const normalizedOrderBy = normalizeOrderByPaths(orderBy, alias)\n\n return { minValues, normalizedOrderBy, loadRequestKey }\n}\n"],"names":["buildQuery","getQueryIR","MultiSet","normalizeOrderByPaths","serializeValue"],"mappings":"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"}
1
+ {"version":3,"file":"utils.cjs","sources":["../../../../src/query/live/utils.ts"],"sourcesContent":["import { MultiSet, serializeValue } from '@tanstack/db-ivm'\nimport { UnsupportedRootScalarSelectError } from '../../errors.js'\nimport { normalizeOrderByPaths } from '../compiler/expressions.js'\nimport { buildQuery, getQueryIR } from '../builder/index.js'\nimport { IncludesSubquery } from '../ir.js'\nimport type { MultiSetArray, RootStreamBuilder } from '@tanstack/db-ivm'\nimport type { Collection } from '../../collection/index.js'\nimport type { ChangeMessage } from '../../types.js'\nimport type { InitialQueryBuilder, QueryBuilder } from '../builder/index.js'\nimport type { Context } from '../builder/types.js'\nimport type { OrderBy, QueryIR } from '../ir.js'\nimport type { OrderByOptimizationInfo } from '../compiler/order-by.js'\n\n/**\n * Helper function to extract collections from a compiled query.\n * Traverses the query IR to find all collection references.\n * Maps collections by their ID (not alias) as expected by the compiler.\n */\nexport function extractCollectionsFromQuery(\n query: any,\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n\n // Extract from SELECT (for IncludesSubquery)\n if (q.select) {\n extractFromSelect(q.select)\n }\n }\n\n function extractFromSelect(select: any) {\n for (const [key, value] of Object.entries(select)) {\n if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {\n continue\n }\n if (value instanceof IncludesSubquery) {\n extractFromQuery(value.query)\n } else if (isNestedSelectObject(value)) {\n extractFromSelect(value)\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n\n/**\n * Helper function to extract the collection that is referenced in the query's FROM clause.\n * The FROM clause may refer directly to a collection or indirectly to a subquery.\n */\nexport function extractCollectionFromSource(\n query: any,\n): Collection<any, any, any> {\n const from = query.from\n\n if (from.type === `collectionRef`) {\n return from.collection\n } else if (from.type === `queryRef`) {\n // Recursively extract from subquery\n return extractCollectionFromSource(from.query)\n }\n\n throw new Error(\n `Failed to extract collection. Invalid FROM clause: ${JSON.stringify(query)}`,\n )\n}\n\n/**\n * Extracts all aliases used for each collection across the entire query tree.\n *\n * Traverses the QueryIR recursively to build a map from collection ID to all aliases\n * that reference that collection. This is essential for self-join support, where the\n * same collection may be referenced multiple times with different aliases.\n *\n * For example, given a query like:\n * ```ts\n * q.from({ employee: employeesCollection })\n * .join({ manager: employeesCollection }, ({ employee, manager }) =>\n * eq(employee.managerId, manager.id)\n * )\n * ```\n *\n * This function would return:\n * ```\n * Map { \"employees\" => Set { \"employee\", \"manager\" } }\n * ```\n *\n * @param query - The query IR to extract aliases from\n * @returns A map from collection ID to the set of all aliases referencing that collection\n */\nexport function extractCollectionAliases(\n query: QueryIR,\n): Map<string, Set<string>> {\n const aliasesById = new Map<string, Set<string>>()\n\n function recordAlias(source: any) {\n if (!source) return\n\n if (source.type === `collectionRef`) {\n const { id } = source.collection\n const existing = aliasesById.get(id)\n if (existing) {\n existing.add(source.alias)\n } else {\n aliasesById.set(id, new Set([source.alias]))\n }\n } else if (source.type === `queryRef`) {\n traverse(source.query)\n }\n }\n\n function traverseSelect(select: any) {\n for (const [key, value] of Object.entries(select)) {\n if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {\n continue\n }\n if (value instanceof IncludesSubquery) {\n traverse(value.query)\n } else if (isNestedSelectObject(value)) {\n traverseSelect(value)\n }\n }\n }\n\n function traverse(q?: QueryIR) {\n if (!q) return\n\n recordAlias(q.from)\n\n if (q.join) {\n for (const joinClause of q.join) {\n recordAlias(joinClause.from)\n }\n }\n\n if (q.select) {\n traverseSelect(q.select)\n }\n }\n\n traverse(query)\n\n return aliasesById\n}\n\n/**\n * Check if a value is a nested select object (plain object, not an expression)\n */\nfunction isNestedSelectObject(obj: any): boolean {\n if (obj === null || typeof obj !== `object`) return false\n if (obj instanceof IncludesSubquery) return false\n // Expression-like objects have a .type property\n if (`type` in obj && typeof obj.type === `string`) return false\n // Ref proxies from spread operations\n if (obj.__refProxy) return false\n return true\n}\n\n/**\n * Builds a query IR from a config object that contains either a query builder\n * function or a QueryBuilder instance.\n */\nexport function buildQueryFromConfig<TContext extends Context>(config: {\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n requireObjectResult?: boolean\n}): QueryIR {\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n if (\n config.requireObjectResult &&\n query.select &&\n !isNestedSelectObject(query.select)\n ) {\n throw new UnsupportedRootScalarSelectError()\n }\n\n return query\n}\n\n/**\n * Helper function to send changes to a D2 input stream.\n * Converts ChangeMessages to D2 MultiSet data and sends to the input.\n *\n * @returns The number of multiset entries sent\n */\nexport function sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any,\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n\n if (multiSetArray.length !== 0) {\n input.sendData(new MultiSet(multiSetArray))\n }\n\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nexport function* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\n/**\n * Filter changes to prevent duplicate inserts to a D2 pipeline.\n * Maintains D2 multiplicity at 1 for visible items so that deletes\n * properly reduce multiplicity to 0.\n *\n * Mutates `sentKeys` in place: adds keys on insert, removes on delete.\n */\nexport function filterDuplicateInserts(\n changes: Array<ChangeMessage<any, string | number>>,\n sentKeys: Set<string | number>,\n): Array<ChangeMessage<any, string | number>> {\n const filtered: Array<ChangeMessage<any, string | number>> = []\n for (const change of changes) {\n if (change.type === `insert`) {\n if (sentKeys.has(change.key)) {\n continue // Skip duplicate\n }\n sentKeys.add(change.key)\n } else if (change.type === `delete`) {\n sentKeys.delete(change.key)\n }\n filtered.push(change)\n }\n return filtered\n}\n\n/**\n * Track the biggest value seen in a stream of changes, used for cursor-based\n * pagination in ordered subscriptions. Returns whether the load request key\n * should be reset (allowing another load).\n *\n * @param changes - changes to process (deletes are skipped)\n * @param current - the current biggest value (or undefined if none)\n * @param sentKeys - set of keys already sent to D2 (for new-key detection)\n * @param comparator - orderBy comparator\n * @returns `{ biggest, shouldResetLoadKey }` — the new biggest value and\n * whether the caller should clear its last-load-request-key\n */\nexport function trackBiggestSentValue(\n changes: Array<ChangeMessage<any, string | number>>,\n current: unknown | undefined,\n sentKeys: Set<string | number>,\n comparator: (a: any, b: any) => number,\n): { biggest: unknown; shouldResetLoadKey: boolean } {\n let biggest = current\n let shouldResetLoadKey = false\n\n for (const change of changes) {\n if (change.type === `delete`) continue\n\n const isNewKey = !sentKeys.has(change.key)\n\n if (biggest === undefined) {\n biggest = change.value\n shouldResetLoadKey = true\n } else if (comparator(biggest, change.value) < 0) {\n biggest = change.value\n shouldResetLoadKey = true\n } else if (isNewKey) {\n // New key at same sort position — allow another load if needed\n shouldResetLoadKey = true\n }\n }\n\n return { biggest, shouldResetLoadKey }\n}\n\n/**\n * Compute orderBy/limit subscription hints for an alias.\n * Returns normalised orderBy and effective limit suitable for passing to\n * `subscribeChanges`, or `undefined` values when the query's orderBy cannot\n * be scoped to the given alias (e.g. cross-collection refs or aggregates).\n */\nexport function computeSubscriptionOrderByHints(\n query: { orderBy?: OrderBy; limit?: number; offset?: number },\n alias: string,\n): { orderBy: OrderBy | undefined; limit: number | undefined } {\n const { orderBy, limit, offset } = query\n const effectiveLimit =\n limit !== undefined && offset !== undefined ? limit + offset : limit\n\n const normalizedOrderBy = orderBy\n ? normalizeOrderByPaths(orderBy, alias)\n : undefined\n\n // Only pass orderBy when it is scoped to this alias and uses simple refs,\n // to avoid leaking cross-collection paths into backend-specific compilers.\n const canPassOrderBy =\n normalizedOrderBy?.every((clause) => {\n const exp = clause.expression\n if (exp.type !== `ref`) return false\n const path = exp.path\n return Array.isArray(path) && path.length === 1\n }) ?? false\n\n return {\n orderBy: canPassOrderBy ? normalizedOrderBy : undefined,\n limit: canPassOrderBy ? effectiveLimit : undefined,\n }\n}\n\n/**\n * Compute the cursor for loading the next batch of ordered data.\n * Extracts values from the biggest sent row and builds the `minValues`\n * array and a deduplication key.\n *\n * @returns `undefined` if the load should be skipped (duplicate request),\n * otherwise `{ minValues, normalizedOrderBy, loadRequestKey }`.\n */\nexport function computeOrderedLoadCursor(\n orderByInfo: Pick<\n OrderByOptimizationInfo,\n 'orderBy' | 'valueExtractorForRawRow' | 'offset'\n >,\n biggestSentRow: unknown | undefined,\n lastLoadRequestKey: string | undefined,\n alias: string,\n limit: number,\n):\n | {\n minValues: Array<unknown> | undefined\n normalizedOrderBy: OrderBy\n loadRequestKey: string\n }\n | undefined {\n const { orderBy, valueExtractorForRawRow, offset } = orderByInfo\n\n // Extract all orderBy column values from the biggest sent row\n // For single-column: returns single value, for multi-column: returns array\n const extractedValues = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow as Record<string, unknown>)\n : undefined\n\n // Normalize to array format for minValues\n let minValues: Array<unknown> | undefined\n if (extractedValues !== undefined) {\n minValues = Array.isArray(extractedValues)\n ? extractedValues\n : [extractedValues]\n }\n\n // Deduplicate: skip if we already issued an identical load request\n const loadRequestKey = serializeValue({\n minValues: minValues ?? null,\n offset,\n limit,\n })\n if (lastLoadRequestKey === loadRequestKey) {\n return undefined\n }\n\n const normalizedOrderBy = normalizeOrderByPaths(orderBy, alias)\n\n return { minValues, normalizedOrderBy, loadRequestKey }\n}\n"],"names":["IncludesSubquery","buildQuery","getQueryIR","UnsupportedRootScalarSelectError","MultiSet","normalizeOrderByPaths","serializeValue"],"mappings":"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"}
@@ -46,6 +46,7 @@ export declare function extractCollectionAliases(query: QueryIR): Map<string, Se
46
46
  */
47
47
  export declare function buildQueryFromConfig<TContext extends Context>(config: {
48
48
  query: ((q: InitialQueryBuilder) => QueryBuilder<TContext>) | QueryBuilder<TContext>;
49
+ requireObjectResult?: boolean;
49
50
  }): QueryIR;
50
51
  /**
51
52
  * Helper function to send changes to a D2 input stream.
@@ -1 +1 @@
1
- {"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from '../collection/index.js'\nimport { CollectionConfigBuilder } from './live/collection-config-builder.js'\nimport {\n getBuilderFromConfig,\n registerCollectionBuilder,\n} from './live/collection-registry.js'\nimport type { LiveQueryCollectionUtils } from './live/collection-config-builder.js'\nimport type { LiveQueryCollectionConfig } from './live/types.js'\nimport type { InitialQueryBuilder, QueryBuilder } from './builder/index.js'\nimport type { Collection } from '../collection/index.js'\nimport type {\n CollectionConfig,\n CollectionConfigSingleRowOption,\n NonSingleResult,\n SingleResult,\n UtilsRecord,\n} from '../types.js'\nimport type { Context, GetResult } from './builder/types.js'\n\ntype CollectionConfigForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n SingleResult\n : CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n NonSingleResult\n\ntype CollectionForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? Collection<TResult, string | number, TUtils> & SingleResult\n : Collection<TResult, string | number, TUtils> & NonSingleResult\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>,\n): CollectionConfigForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n} {\n const collectionConfigBuilder = new CollectionConfigBuilder<\n TContext,\n TResult\n >(config)\n return collectionConfigBuilder.getConfig() as CollectionConfigForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n}\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils },\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n}\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>),\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n} {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Merge custom utils if provided, preserving the getBuilder() method for dependency tracking\n if (config.utils) {\n options.utils = { ...options.utils, ...config.utils }\n }\n\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's output type without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils: TUtils },\n): Collection<TResult, string | number, TUtils> {\n const collection = createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n LiveQueryCollectionUtils\n >\n\n const builder = getBuilderFromConfig(options)\n if (builder) {\n registerCollectionBuilder(collection, builder)\n }\n\n return collection as unknown as Collection<TResult, string | number, TUtils>\n}\n"],"names":["collectionConfigBuilder","CollectionConfigBuilder","createCollection","getBuilderFromConfig","registerCollectionBuilder"],"mappings":";;;;;AA6DO,SAAS,2BAId,QAGA;AACA,QAAMA,4BAA0B,IAAIC,wBAAAA,wBAGlC,MAAM;AACR,SAAOD,0BAAwB,UAAA;AAIjC;AA0DO,SAAS,0BAKd,eAKA;AAEA,MAAI,OAAO,kBAAkB,YAAY;AAEvC,UAAM,SAAuD;AAAA,MAC3D,OAAO;AAAA,IAAA;AAIT,UAAM,UAAU,2BAA8C,MAAM;AACpE,WAAO,yBAAyB,OAAO;AAAA,EAIzC,OAAO;AAEL,UAAM,SAAS;AAIf,UAAM,UAAU,2BAA8C,MAAM;AAGpE,QAAI,OAAO,OAAO;AAChB,cAAQ,QAAQ,EAAE,GAAG,QAAQ,OAAO,GAAG,OAAO,MAAA;AAAA,IAChD;AAEA,WAAO,yBAAyB,OAAO;AAAA,EAIzC;AACF;AAMA,SAAS,yBAIP,SAC8C;AAC9C,QAAM,aAAaE,MAAAA,iBAAiB,OAAc;AAMlD,QAAM,UAAUC,mBAAAA,qBAAqB,OAAO;AAC5C,MAAI,SAAS;AACXC,uBAAAA,0BAA0B,YAAY,OAAO;AAAA,EAC/C;AAEA,SAAO;AACT;;;"}
1
+ {"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from '../collection/index.js'\nimport { CollectionConfigBuilder } from './live/collection-config-builder.js'\nimport {\n getBuilderFromConfig,\n registerCollectionBuilder,\n} from './live/collection-registry.js'\nimport type { LiveQueryCollectionUtils } from './live/collection-config-builder.js'\nimport type { LiveQueryCollectionConfig } from './live/types.js'\nimport type {\n ExtractContext,\n InitialQueryBuilder,\n QueryBuilder,\n} from './builder/index.js'\nimport type { Collection } from '../collection/index.js'\nimport type {\n CollectionConfig,\n CollectionConfigSingleRowOption,\n NonSingleResult,\n SingleResult,\n UtilsRecord,\n} from '../types.js'\nimport type {\n Context,\n RootObjectResultConstraint,\n RootQueryBuilder,\n RootQueryFn,\n RootQueryResult,\n} from './builder/types.js'\n\ntype CollectionConfigForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n SingleResult\n : CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n NonSingleResult\n\ntype CollectionForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? Collection<TResult, string | number, TUtils> & SingleResult\n : Collection<TResult, string | number, TUtils> & NonSingleResult\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TQuery extends QueryBuilder<any>,\n TContext extends Context = ExtractContext<TQuery>,\n TResult extends object = RootQueryResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & {\n query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>\n },\n): CollectionConfigForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n} {\n const collectionConfigBuilder = new CollectionConfigBuilder<\n TContext,\n TResult\n >(config)\n return collectionConfigBuilder.getConfig() as CollectionConfigForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TQueryFn extends (q: InitialQueryBuilder) => QueryBuilder<any>,\n TQuery extends QueryBuilder<any> = ReturnType<TQueryFn>,\n>(\n query: TQueryFn & RootQueryFn<TQuery>,\n): CollectionForContext<\n ExtractContext<TQuery>,\n RootQueryResult<ExtractContext<TQuery>>\n> & {\n utils: LiveQueryCollectionUtils\n}\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TQuery extends QueryBuilder<any>,\n TContext extends Context = ExtractContext<TQuery>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, RootQueryResult<TContext>> & {\n query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>\n utils?: TUtils\n },\n): CollectionForContext<TContext, RootQueryResult<TContext>> & {\n utils: LiveQueryCollectionUtils & TUtils\n}\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = RootQueryResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>),\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n} {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>,\n }\n // The implementation accepts both overload shapes, but TypeScript cannot\n // preserve the overload-specific query-builder inference through this branch.\n const options = liveQueryCollectionOptions(config as any)\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n // Same overload implementation limitation as above: the config has already\n // been validated by the public signatures, but the branch loses that precision.\n const options = liveQueryCollectionOptions(config as any)\n\n // Merge custom utils if provided, preserving the getBuilder() method for dependency tracking\n if (config.utils) {\n options.utils = { ...options.utils, ...config.utils }\n }\n\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's output type without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils: TUtils },\n): Collection<TResult, string | number, TUtils> {\n const collection = createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n LiveQueryCollectionUtils\n >\n\n const builder = getBuilderFromConfig(options)\n if (builder) {\n registerCollectionBuilder(collection, builder)\n }\n\n return collection as unknown as Collection<TResult, string | number, TUtils>\n}\n"],"names":["collectionConfigBuilder","CollectionConfigBuilder","createCollection","getBuilderFromConfig","registerCollectionBuilder"],"mappings":";;;;;AAuEO,SAAS,2BAKd,QAKA;AACA,QAAMA,4BAA0B,IAAIC,wBAAAA,wBAGlC,MAAM;AACR,SAAOD,0BAAwB,UAAA;AAIjC;AAgEO,SAAS,0BAKd,eAOA;AAEA,MAAI,OAAO,kBAAkB,YAAY;AAEvC,UAAM,SAAuD;AAAA,MAC3D,OAAO;AAAA,IAAA;AAMT,UAAM,UAAU,2BAA2B,MAAa;AACxD,WAAO,yBAAyB,OAAO;AAAA,EAIzC,OAAO;AAEL,UAAM,SAAS;AAMf,UAAM,UAAU,2BAA2B,MAAa;AAGxD,QAAI,OAAO,OAAO;AAChB,cAAQ,QAAQ,EAAE,GAAG,QAAQ,OAAO,GAAG,OAAO,MAAA;AAAA,IAChD;AAEA,WAAO,yBAAyB,OAAO;AAAA,EAIzC;AACF;AAMA,SAAS,yBAIP,SAC8C;AAC9C,QAAM,aAAaE,MAAAA,iBAAiB,OAAc;AAMlD,QAAM,UAAUC,mBAAAA,qBAAqB,OAAO;AAC5C,MAAI,SAAS;AACXC,uBAAAA,0BAA0B,YAAY,OAAO;AAAA,EAC/C;AAEA,SAAO;AACT;;;"}