@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":"group-by.js","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import {\n filter,\n groupBy,\n groupByOperators,\n map,\n serializeValue,\n} from '@tanstack/db-ivm'\nimport { Func, PropRef, getHavingExpression, isExpressionLike } from '../ir.js'\nimport {\n AggregateFunctionNotInSelectError,\n NonAggregateExpressionNotInGroupByError,\n UnknownHavingExpressionTypeError,\n UnsupportedAggregateFunctionError,\n} from '../../errors.js'\nimport { compileExpression, toBooleanPredicate } from './evaluators.js'\nimport type {\n Aggregate,\n BasicExpression,\n GroupBy,\n Having,\n Select,\n} from '../ir.js'\nimport type { NamespacedAndKeyedStream, NamespacedRow } from '../../types.js'\n\nconst { sum, count, avg, min, max } = groupByOperators\n\n/**\n * Interface for caching the mapping between GROUP BY expressions and SELECT expressions\n */\ninterface GroupBySelectMapping {\n selectToGroupByIndex: Map<string, number> // Maps SELECT alias to GROUP BY expression index\n groupByExpressions: Array<any> // The GROUP BY expressions for reference\n}\n\n/**\n * Validates that all non-aggregate expressions in SELECT are present in GROUP BY\n * and creates a cached mapping for efficient lookup during processing\n */\nfunction validateAndCreateMapping(\n groupByClause: GroupBy,\n selectClause?: Select,\n): GroupBySelectMapping {\n const selectToGroupByIndex = new Map<string, number>()\n const groupByExpressions = [...groupByClause]\n\n if (!selectClause) {\n return { selectToGroupByIndex, groupByExpressions }\n }\n\n // Validate each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg` || containsAggregate(expr)) {\n // Aggregate expressions (plain or wrapped) are allowed and don't need to be in GROUP BY\n continue\n }\n\n // Non-aggregate expression must be in GROUP BY\n const groupIndex = groupByExpressions.findIndex((groupExpr) =>\n expressionsEqual(expr, groupExpr),\n )\n\n if (groupIndex === -1) {\n throw new NonAggregateExpressionNotInGroupByError(alias)\n }\n\n // Cache the mapping\n selectToGroupByIndex.set(alias, groupIndex)\n }\n\n return { selectToGroupByIndex, groupByExpressions }\n}\n\n/**\n * Processes the GROUP BY clause with optional HAVING and SELECT\n * Works with the new $selected structure from early SELECT processing\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n groupByClause: GroupBy,\n havingClauses?: Array<Having>,\n selectClause?: Select,\n fnHavingClauses?: Array<(row: any) => any>,\n): NamespacedAndKeyedStream {\n // Handle empty GROUP BY (single-group aggregation)\n if (groupByClause.length === 0) {\n // For single-group aggregation, create a single group with all data\n const aggregates: Record<string, any> = {}\n\n // Expressions that wrap aggregates (e.g. coalesce(count(...), 0)).\n // Keys are the original SELECT aliases; values are pre-compiled evaluators\n // over the transformed (aggregate-free) expression.\n const wrappedAggExprs: Record<string, (data: any) => any> = {}\n const aggCounter = { value: 0 }\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n aggregates[alias] = getAggregateFunction(expr)\n } else if (containsAggregate(expr)) {\n const { transformed, extracted } = extractAndReplaceAggregates(\n expr as BasicExpression | Aggregate,\n aggCounter,\n )\n for (const [syntheticAlias, aggExpr] of Object.entries(extracted)) {\n aggregates[syntheticAlias] = getAggregateFunction(aggExpr)\n }\n wrappedAggExprs[alias] = compileExpression(transformed)\n }\n }\n }\n\n // Use a constant key for single group\n const keyExtractor = () => ({ __singleGroup: true })\n\n // Apply the groupBy operator with single group\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates),\n ) as NamespacedAndKeyedStream\n\n // Update $selected to include aggregate values\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing $selected from early SELECT processing\n const selectResults = (aggregatedRow as any).$selected || {}\n const finalResults: Record<string, any> = { ...selectResults }\n\n if (selectClause) {\n // First pass: populate plain aggregate results and synthetic aliases\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n }\n }\n evaluateWrappedAggregates(\n finalResults,\n aggregatedRow as Record<string, any>,\n wrappedAggExprs,\n )\n }\n\n // Use a single key for the result and update $selected\n return [\n `single_group`,\n {\n ...aggregatedRow,\n $selected: finalResults,\n },\n ] as [unknown, Record<string, any>]\n }),\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const havingExpression = getHavingExpression(havingClause)\n const transformedHavingClause = replaceAggregatesByRefs(\n havingExpression,\n selectClause || {},\n `$selected`,\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(compiledHaving(namespacedRow))\n }),\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(fnHaving(namespacedRow))\n }),\n )\n }\n }\n\n return pipeline\n }\n\n // Multi-group aggregation logic...\n // Validate and create mapping for non-aggregate expressions in SELECT\n const mapping = validateAndCreateMapping(groupByClause, selectClause)\n\n // Pre-compile groupBy expressions\n const compiledGroupByExpressions = groupByClause.map((e) =>\n compileExpression(e),\n )\n\n // Create a key extractor function using simple __key_X format\n const keyExtractor = ([, row]: [\n string,\n NamespacedRow & { $selected?: any },\n ]) => {\n // Use the original namespaced row for GROUP BY expressions, not $selected\n const namespacedRow = { ...row }\n delete (namespacedRow as any).$selected\n\n const key: Record<string, unknown> = {}\n\n // Use simple __key_X format for each groupBy expression\n for (let i = 0; i < groupByClause.length; i++) {\n const compiledExpr = compiledGroupByExpressions[i]!\n const value = compiledExpr(namespacedRow)\n key[`__key_${i}`] = value\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n const wrappedAggExprs: Record<string, (data: any) => any> = {}\n const aggCounter = { value: 0 }\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n aggregates[alias] = getAggregateFunction(expr)\n } else if (containsAggregate(expr)) {\n const { transformed, extracted } = extractAndReplaceAggregates(\n expr as BasicExpression | Aggregate,\n aggCounter,\n )\n for (const [syntheticAlias, aggExpr] of Object.entries(extracted)) {\n aggregates[syntheticAlias] = getAggregateFunction(aggExpr)\n }\n wrappedAggExprs[alias] = compileExpression(transformed)\n }\n }\n }\n\n // Apply the groupBy operator\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n\n // Update $selected to handle GROUP BY results\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing $selected from early SELECT processing\n const selectResults = (aggregatedRow as any).$selected || {}\n const finalResults: Record<string, any> = {}\n\n if (selectClause) {\n // First pass: populate group keys, plain aggregates, and synthetic aliases\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n } else if (!wrappedAggExprs[alias]) {\n // Use cached mapping to get the corresponding __key_X for non-aggregates\n const groupIndex = mapping.selectToGroupByIndex.get(alias)\n if (groupIndex !== undefined) {\n finalResults[alias] = aggregatedRow[`__key_${groupIndex}`]\n } else {\n // Fallback to original SELECT results\n finalResults[alias] = selectResults[alias]\n }\n }\n }\n evaluateWrappedAggregates(\n finalResults,\n aggregatedRow as Record<string, any>,\n wrappedAggExprs,\n )\n } else {\n // No SELECT clause - just use the group keys\n for (let i = 0; i < groupByClause.length; i++) {\n finalResults[`__key_${i}`] = aggregatedRow[`__key_${i}`]\n }\n }\n\n // Generate a simple key for the live collection using group values\n let finalKey: unknown\n if (groupByClause.length === 1) {\n finalKey = aggregatedRow[`__key_0`]\n } else {\n const keyParts: Array<unknown> = []\n for (let i = 0; i < groupByClause.length; i++) {\n keyParts.push(aggregatedRow[`__key_${i}`])\n }\n finalKey = serializeValue(keyParts)\n }\n\n return [\n finalKey,\n {\n ...aggregatedRow,\n $selected: finalResults,\n },\n ] as [unknown, Record<string, any>]\n }),\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const havingExpression = getHavingExpression(havingClause)\n const transformedHavingClause = replaceAggregatesByRefs(\n havingExpression,\n selectClause || {},\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return compiledHaving(namespacedRow)\n }),\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(fnHaving(namespacedRow))\n }),\n )\n }\n }\n\n return pipeline\n}\n\n/**\n * Helper function to check if two expressions are equal\n */\nfunction expressionsEqual(expr1: any, expr2: any): boolean {\n if (!expr1 || !expr2) return false\n if (expr1.type !== expr2.type) return false\n\n switch (expr1.type) {\n case `ref`:\n // Compare paths as arrays\n if (!expr1.path || !expr2.path) return false\n if (expr1.path.length !== expr2.path.length) return false\n return expr1.path.every(\n (segment: string, i: number) => segment === expr2.path[i],\n )\n case `val`:\n return expr1.value === expr2.value\n case `func`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i]),\n )\n )\n case `agg`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i]),\n )\n )\n default:\n return false\n }\n}\n\n/**\n * Helper function to get an aggregate function based on the Agg expression\n */\nfunction getAggregateFunction(aggExpr: Aggregate) {\n // Pre-compile the value extractor expression\n const compiledExpr = compileExpression(aggExpr.args[0]!)\n\n // Create a value extractor function for the expression to aggregate\n const valueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n const value = compiledExpr(namespacedRow)\n // Ensure we return a number for numeric aggregate functions\n if (typeof value === `number`) {\n return value\n }\n return value != null ? Number(value) : 0\n }\n\n // Create a value extractor function for min/max that preserves comparable types\n const valueExtractorForMinMax = ([, namespacedRow]: [\n string,\n NamespacedRow,\n ]) => {\n const value = compiledExpr(namespacedRow)\n // Preserve strings, numbers, Dates, and bigints for comparison\n if (\n typeof value === `number` ||\n typeof value === `string` ||\n typeof value === `bigint` ||\n value instanceof Date\n ) {\n return value\n }\n return value != null ? Number(value) : 0\n }\n\n // Create a raw value extractor function for the expression to aggregate\n const rawValueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n return compiledExpr(namespacedRow)\n }\n\n // Return the appropriate aggregate function\n switch (aggExpr.name.toLowerCase()) {\n case `sum`:\n return sum(valueExtractor)\n case `count`:\n return count(rawValueExtractor)\n case `avg`:\n return avg(valueExtractor)\n case `min`:\n return min(valueExtractorForMinMax)\n case `max`:\n return max(valueExtractorForMinMax)\n default:\n throw new UnsupportedAggregateFunctionError(aggExpr.name)\n }\n}\n\n/**\n * Transforms expressions to replace aggregate functions with references to computed values.\n *\n * For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them\n * with PropRef([resultAlias, alias]) to reference the computed aggregate value.\n *\n * Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged.\n * Function expressions are recursively transformed.\n *\n * @param havingExpr - The expression to transform (can be aggregate, ref, func, or val)\n * @param selectClause - The SELECT clause containing aliases and aggregate definitions\n * @param resultAlias - The namespace alias for SELECT results (default: '$selected')\n * @returns A transformed BasicExpression that references computed values instead of raw expressions\n */\nexport function replaceAggregatesByRefs(\n havingExpr: BasicExpression | Aggregate,\n selectClause: Select,\n resultAlias: string = `$selected`,\n): BasicExpression {\n switch (havingExpr.type) {\n case `agg`: {\n const aggExpr = havingExpr\n // Find matching aggregate in SELECT clause\n for (const [alias, selectExpr] of Object.entries(selectClause)) {\n if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {\n // Replace with a reference to the computed aggregate\n return new PropRef([resultAlias, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new AggregateFunctionNotInSelectError(aggExpr.name)\n }\n\n case `func`: {\n const funcExpr = havingExpr\n // Transform function arguments recursively\n const transformedArgs = funcExpr.args.map(\n (arg: BasicExpression | Aggregate) =>\n replaceAggregatesByRefs(arg, selectClause),\n )\n return new Func(funcExpr.name, transformedArgs)\n }\n\n case `ref`:\n // Ref expressions are passed through unchanged - they reference either:\n // - $selected fields (which are already in the correct namespace)\n // - Table column references (which remain valid)\n return havingExpr as BasicExpression\n\n case `val`:\n // Return as-is\n return havingExpr as BasicExpression\n\n default:\n throw new UnknownHavingExpressionTypeError((havingExpr as any).type)\n }\n}\n\n/**\n * Evaluates wrapped-aggregate expressions against the aggregated row.\n * Copies synthetic __agg_N values into finalResults so the compiled wrapper\n * expressions can reference them, evaluates each wrapper, then removes the\n * synthetic keys so they don't leak onto user-visible result rows.\n */\nfunction evaluateWrappedAggregates(\n finalResults: Record<string, any>,\n aggregatedRow: Record<string, any>,\n wrappedAggExprs: Record<string, (data: any) => any>,\n): void {\n for (const key of Object.keys(aggregatedRow)) {\n if (key.startsWith(`__agg_`)) {\n finalResults[key] = aggregatedRow[key]\n }\n }\n for (const [alias, evaluator] of Object.entries(wrappedAggExprs)) {\n finalResults[alias] = evaluator({ $selected: finalResults })\n }\n for (const key of Object.keys(finalResults)) {\n if (key.startsWith(`__agg_`)) delete finalResults[key]\n }\n}\n\n/**\n * Checks whether an expression contains an aggregate anywhere in its tree.\n * Returns true for a top-level Aggregate, or a Func whose args (recursively)\n * contain an Aggregate. Safely returns false for nested Select objects.\n */\nexport function containsAggregate(\n expr: BasicExpression | Aggregate | Select,\n): boolean {\n if (!isExpressionLike(expr)) {\n return false\n }\n if (expr.type === `agg`) {\n return true\n }\n if (expr.type === `func`) {\n return expr.args.some((arg: BasicExpression | Aggregate) =>\n containsAggregate(arg),\n )\n }\n return false\n}\n\n/**\n * Walks an expression tree containing nested aggregates.\n * Each Aggregate node is extracted, assigned a synthetic alias (__agg_N),\n * and replaced with PropRef([\"$selected\", \"__agg_N\"]) so the wrapper\n * expression can be compiled as a pure BasicExpression after groupBy\n * populates the synthetic values.\n */\nfunction extractAndReplaceAggregates(\n expr: BasicExpression | Aggregate,\n counter: { value: number },\n): {\n transformed: BasicExpression\n extracted: Record<string, Aggregate>\n} {\n if (expr.type === `agg`) {\n const alias = `__agg_${counter.value++}`\n return {\n transformed: new PropRef([`$selected`, alias]),\n extracted: { [alias]: expr },\n }\n }\n\n if (expr.type === `func`) {\n const allExtracted: Record<string, Aggregate> = {}\n const newArgs = expr.args.map((arg: BasicExpression | Aggregate) => {\n const result = extractAndReplaceAggregates(arg, counter)\n Object.assign(allExtracted, result.extracted)\n return result.transformed\n })\n return {\n transformed: new Func(expr.name, newArgs),\n extracted: allExtracted,\n }\n }\n\n // ref / val – pass through unchanged\n return { transformed: expr as BasicExpression, extracted: {} }\n}\n\n/**\n * Checks if two aggregate expressions are equal\n */\nfunction aggregatesEqual(agg1: Aggregate, agg2: Aggregate): boolean {\n return (\n agg1.name === agg2.name &&\n agg1.args.length === agg2.args.length &&\n agg1.args.every((arg, i) => expressionsEqual(arg, agg2.args[i]))\n )\n}\n"],"names":["aggregates","wrappedAggExprs","aggCounter","keyExtractor"],"mappings":"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"}
1
+ {"version":3,"file":"group-by.js","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import {\n filter,\n groupBy,\n groupByOperators,\n map,\n serializeValue,\n} from '@tanstack/db-ivm'\nimport { Func, PropRef, getHavingExpression, isExpressionLike } from '../ir.js'\nimport {\n AggregateFunctionNotInSelectError,\n NonAggregateExpressionNotInGroupByError,\n UnknownHavingExpressionTypeError,\n UnsupportedAggregateFunctionError,\n} from '../../errors.js'\nimport { compileExpression, toBooleanPredicate } from './evaluators.js'\nimport type {\n Aggregate,\n BasicExpression,\n GroupBy,\n Having,\n Select,\n} from '../ir.js'\nimport type { NamespacedAndKeyedStream, NamespacedRow } from '../../types.js'\nimport type { VirtualOrigin } from '../../virtual-props.js'\n\nconst VIRTUAL_SYNCED_KEY = `__virtual_synced__`\nconst VIRTUAL_HAS_LOCAL_KEY = `__virtual_has_local__`\n\ntype RowVirtualMetadata = {\n synced: boolean\n hasLocal: boolean\n}\n\nfunction getRowVirtualMetadata(row: NamespacedRow): RowVirtualMetadata {\n let found = false\n let allSynced = true\n let hasLocal = false\n\n for (const [alias, value] of Object.entries(row)) {\n if (alias === `$selected`) continue\n const asRecord = value\n const hasSyncedProp = `$synced` in asRecord\n const hasOriginProp = `$origin` in asRecord\n if (!hasSyncedProp && !hasOriginProp) {\n continue\n }\n found = true\n if (asRecord.$synced === false) {\n allSynced = false\n }\n if (asRecord.$origin === `local`) {\n hasLocal = true\n }\n }\n\n return {\n synced: found ? allSynced : true,\n hasLocal,\n }\n}\n\nconst { sum, count, avg, min, max } = groupByOperators\n\n/**\n * Interface for caching the mapping between GROUP BY expressions and SELECT expressions\n */\ninterface GroupBySelectMapping {\n selectToGroupByIndex: Map<string, number> // Maps SELECT alias to GROUP BY expression index\n groupByExpressions: Array<any> // The GROUP BY expressions for reference\n}\n\n/**\n * Validates that all non-aggregate expressions in SELECT are present in GROUP BY\n * and creates a cached mapping for efficient lookup during processing\n */\nfunction validateAndCreateMapping(\n groupByClause: GroupBy,\n selectClause?: Select,\n): GroupBySelectMapping {\n const selectToGroupByIndex = new Map<string, number>()\n const groupByExpressions = [...groupByClause]\n\n if (!selectClause) {\n return { selectToGroupByIndex, groupByExpressions }\n }\n\n // Validate each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg` || containsAggregate(expr)) {\n // Aggregate expressions (plain or wrapped) are allowed and don't need to be in GROUP BY\n continue\n }\n\n // Non-aggregate expression must be in GROUP BY\n const groupIndex = groupByExpressions.findIndex((groupExpr) =>\n expressionsEqual(expr, groupExpr),\n )\n\n if (groupIndex === -1) {\n throw new NonAggregateExpressionNotInGroupByError(alias)\n }\n\n // Cache the mapping\n selectToGroupByIndex.set(alias, groupIndex)\n }\n\n return { selectToGroupByIndex, groupByExpressions }\n}\n\n/**\n * Processes the GROUP BY clause with optional HAVING and SELECT\n * Works with the new $selected structure from early SELECT processing\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n groupByClause: GroupBy,\n havingClauses?: Array<Having>,\n selectClause?: Select,\n fnHavingClauses?: Array<(row: any) => any>,\n aggregateCollectionId?: string,\n mainSource?: string,\n): NamespacedAndKeyedStream {\n const virtualAggregates: Record<string, any> = {\n [VIRTUAL_SYNCED_KEY]: {\n preMap: ([, row]: [string, NamespacedRow]) =>\n getRowVirtualMetadata(row).synced,\n reduce: (values: Array<[boolean, number]>) => {\n for (const [isSynced, multiplicity] of values) {\n if (!isSynced && multiplicity > 0) {\n return false\n }\n }\n return true\n },\n },\n [VIRTUAL_HAS_LOCAL_KEY]: {\n preMap: ([, row]: [string, NamespacedRow]) =>\n getRowVirtualMetadata(row).hasLocal,\n reduce: (values: Array<[boolean, number]>) => {\n for (const [isLocal, multiplicity] of values) {\n if (isLocal && multiplicity > 0) {\n return true\n }\n }\n return false\n },\n },\n }\n\n // Handle empty GROUP BY (single-group aggregation)\n if (groupByClause.length === 0) {\n // For single-group aggregation, create a single group with all data\n const aggregates: Record<string, any> = virtualAggregates\n\n // Expressions that wrap aggregates (e.g. coalesce(count(...), 0)).\n // Keys are the original SELECT aliases; values are pre-compiled evaluators\n // over the transformed (aggregate-free) expression.\n const wrappedAggExprs: Record<string, (data: any) => any> = {}\n const aggCounter = { value: 0 }\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n aggregates[alias] = getAggregateFunction(expr)\n } else if (containsAggregate(expr)) {\n const { transformed, extracted } = extractAndReplaceAggregates(\n expr as BasicExpression | Aggregate,\n aggCounter,\n )\n for (const [syntheticAlias, aggExpr] of Object.entries(extracted)) {\n aggregates[syntheticAlias] = getAggregateFunction(aggExpr)\n }\n wrappedAggExprs[alias] = compileExpression(transformed)\n }\n }\n }\n\n // Use a constant key for single group.\n // When mainSource is set (includes mode), include __correlationKey so that\n // rows from different parents aggregate separately.\n const keyExtractor = mainSource\n ? ([, row]: [string, NamespacedRow]) => ({\n __singleGroup: true,\n __correlationKey: (row as any)?.[mainSource]?.__correlationKey,\n })\n : () => ({ __singleGroup: true })\n\n // Apply the groupBy operator with single group\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates),\n ) as NamespacedAndKeyedStream\n\n // Update $selected to include aggregate values\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing $selected from early SELECT processing\n const selectResults = (aggregatedRow as any).$selected || {}\n const finalResults: Record<string, any> = { ...selectResults }\n\n if (selectClause) {\n // First pass: populate plain aggregate results and synthetic aliases\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n }\n }\n evaluateWrappedAggregates(\n finalResults,\n aggregatedRow as Record<string, any>,\n wrappedAggExprs,\n )\n }\n\n // Use a single key for the result and update $selected.\n // When in includes mode, restore the namespaced source structure with\n // __correlationKey so output extraction can route results per-parent.\n const correlationKey = mainSource\n ? (aggregatedRow as any).__correlationKey\n : undefined\n const resultKey =\n correlationKey !== undefined\n ? `single_group_${serializeValue(correlationKey)}`\n : `single_group`\n const resultRow: Record<string, any> = {\n ...(aggregatedRow as Record<string, any>),\n $selected: finalResults,\n }\n const groupSynced = (aggregatedRow as Record<string, any>)[\n VIRTUAL_SYNCED_KEY\n ]\n const groupHasLocal = (aggregatedRow as Record<string, any>)[\n VIRTUAL_HAS_LOCAL_KEY\n ]\n resultRow.$synced = groupSynced ?? true\n resultRow.$origin = (\n groupHasLocal ? `local` : `remote`\n ) satisfies VirtualOrigin\n resultRow.$key = resultKey\n resultRow.$collectionId =\n aggregateCollectionId ?? resultRow.$collectionId\n if (mainSource && correlationKey !== undefined) {\n resultRow[mainSource] = { __correlationKey: correlationKey }\n }\n return [resultKey, resultRow] as [unknown, Record<string, any>]\n }),\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const havingExpression = getHavingExpression(havingClause)\n const transformedHavingClause = replaceAggregatesByRefs(\n havingExpression,\n selectClause || {},\n `$selected`,\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(compiledHaving(namespacedRow))\n }),\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(fnHaving(namespacedRow))\n }),\n )\n }\n }\n\n return pipeline\n }\n\n // Multi-group aggregation logic...\n // Validate and create mapping for non-aggregate expressions in SELECT\n const mapping = validateAndCreateMapping(groupByClause, selectClause)\n\n // Pre-compile groupBy expressions\n const compiledGroupByExpressions = groupByClause.map((e) =>\n compileExpression(e),\n )\n\n // Create a key extractor function using simple __key_X format.\n // When mainSource is set (includes mode), include __correlationKey so that\n // rows from different parents with the same group key aggregate separately.\n const keyExtractor = ([, row]: [\n string,\n NamespacedRow & { $selected?: any },\n ]) => {\n // Use the original namespaced row for GROUP BY expressions, not $selected\n const namespacedRow = { ...row }\n delete (namespacedRow as any).$selected\n\n const key: Record<string, unknown> = {}\n\n // Use simple __key_X format for each groupBy expression\n for (let i = 0; i < groupByClause.length; i++) {\n const compiledExpr = compiledGroupByExpressions[i]!\n const value = compiledExpr(namespacedRow)\n key[`__key_${i}`] = value\n }\n\n if (mainSource) {\n key.__correlationKey = (row as any)?.[mainSource]?.__correlationKey\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = virtualAggregates\n const wrappedAggExprs: Record<string, (data: any) => any> = {}\n const aggCounter = { value: 0 }\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n aggregates[alias] = getAggregateFunction(expr)\n } else if (containsAggregate(expr)) {\n const { transformed, extracted } = extractAndReplaceAggregates(\n expr as BasicExpression | Aggregate,\n aggCounter,\n )\n for (const [syntheticAlias, aggExpr] of Object.entries(extracted)) {\n aggregates[syntheticAlias] = getAggregateFunction(aggExpr)\n }\n wrappedAggExprs[alias] = compileExpression(transformed)\n }\n }\n }\n\n // Apply the groupBy operator\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n\n // Update $selected to handle GROUP BY results\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing $selected from early SELECT processing\n const selectResults = (aggregatedRow as any).$selected || {}\n const finalResults: Record<string, any> = {}\n\n if (selectClause) {\n // First pass: populate group keys, plain aggregates, and synthetic aliases\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n } else if (!wrappedAggExprs[alias]) {\n // Use cached mapping to get the corresponding __key_X for non-aggregates\n const groupIndex = mapping.selectToGroupByIndex.get(alias)\n if (groupIndex !== undefined) {\n finalResults[alias] = aggregatedRow[`__key_${groupIndex}`]\n } else {\n // Fallback to original SELECT results\n finalResults[alias] = selectResults[alias]\n }\n }\n }\n evaluateWrappedAggregates(\n finalResults,\n aggregatedRow as Record<string, any>,\n wrappedAggExprs,\n )\n } else {\n // No SELECT clause - just use the group keys\n for (let i = 0; i < groupByClause.length; i++) {\n finalResults[`__key_${i}`] = aggregatedRow[`__key_${i}`]\n }\n }\n\n // Generate a simple key for the live collection using group values.\n // When in includes mode, include the correlation key so that groups\n // from different parents don't collide.\n const correlationKey = mainSource\n ? (aggregatedRow as any).__correlationKey\n : undefined\n const keyParts: Array<unknown> = []\n for (let i = 0; i < groupByClause.length; i++) {\n keyParts.push(aggregatedRow[`__key_${i}`])\n }\n if (correlationKey !== undefined) {\n keyParts.push(correlationKey)\n }\n const finalKey =\n keyParts.length === 1 ? keyParts[0] : serializeValue(keyParts)\n\n // When in includes mode, restore the namespaced source structure with\n // __correlationKey so output extraction can route results per-parent.\n const resultRow: Record<string, any> = {\n ...(aggregatedRow as Record<string, any>),\n $selected: finalResults,\n }\n const groupSynced = (aggregatedRow as Record<string, any>)[\n VIRTUAL_SYNCED_KEY\n ]\n const groupHasLocal = (aggregatedRow as Record<string, any>)[\n VIRTUAL_HAS_LOCAL_KEY\n ]\n resultRow.$synced = groupSynced ?? true\n resultRow.$origin = (\n groupHasLocal ? `local` : `remote`\n ) satisfies VirtualOrigin\n resultRow.$key = finalKey\n resultRow.$collectionId = aggregateCollectionId ?? resultRow.$collectionId\n if (mainSource && correlationKey !== undefined) {\n resultRow[mainSource] = { __correlationKey: correlationKey }\n }\n return [finalKey, resultRow] as [unknown, Record<string, any>]\n }),\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const havingExpression = getHavingExpression(havingClause)\n const transformedHavingClause = replaceAggregatesByRefs(\n havingExpression,\n selectClause || {},\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return compiledHaving(namespacedRow)\n }),\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { $selected: (row as any).$selected }\n return toBooleanPredicate(fnHaving(namespacedRow))\n }),\n )\n }\n }\n\n return pipeline\n}\n\n/**\n * Helper function to check if two expressions are equal\n */\nfunction expressionsEqual(expr1: any, expr2: any): boolean {\n if (!expr1 || !expr2) return false\n if (expr1.type !== expr2.type) return false\n\n switch (expr1.type) {\n case `ref`:\n // Compare paths as arrays\n if (!expr1.path || !expr2.path) return false\n if (expr1.path.length !== expr2.path.length) return false\n return expr1.path.every(\n (segment: string, i: number) => segment === expr2.path[i],\n )\n case `val`:\n return expr1.value === expr2.value\n case `func`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i]),\n )\n )\n case `agg`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i]),\n )\n )\n default:\n return false\n }\n}\n\n/**\n * Helper function to get an aggregate function based on the Agg expression\n */\nfunction getAggregateFunction(aggExpr: Aggregate) {\n // Pre-compile the value extractor expression\n const compiledExpr = compileExpression(aggExpr.args[0]!)\n\n // Create a value extractor function for the expression to aggregate\n const valueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n const value = compiledExpr(namespacedRow)\n // Ensure we return a number for numeric aggregate functions\n if (typeof value === `number`) {\n return value\n }\n return value != null ? Number(value) : 0\n }\n\n // Create a value extractor function for min/max that preserves comparable types\n const valueExtractorForMinMax = ([, namespacedRow]: [\n string,\n NamespacedRow,\n ]) => {\n const value = compiledExpr(namespacedRow)\n // Preserve strings, numbers, Dates, and bigints for comparison\n if (\n typeof value === `number` ||\n typeof value === `string` ||\n typeof value === `bigint` ||\n value instanceof Date\n ) {\n return value\n }\n return value != null ? Number(value) : 0\n }\n\n // Create a raw value extractor function for the expression to aggregate\n const rawValueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n return compiledExpr(namespacedRow)\n }\n\n // Return the appropriate aggregate function\n switch (aggExpr.name.toLowerCase()) {\n case `sum`:\n return sum(valueExtractor)\n case `count`:\n return count(rawValueExtractor)\n case `avg`:\n return avg(valueExtractor)\n case `min`:\n return min(valueExtractorForMinMax)\n case `max`:\n return max(valueExtractorForMinMax)\n default:\n throw new UnsupportedAggregateFunctionError(aggExpr.name)\n }\n}\n\n/**\n * Transforms expressions to replace aggregate functions with references to computed values.\n *\n * For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them\n * with PropRef([resultAlias, alias]) to reference the computed aggregate value.\n *\n * Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged.\n * Function expressions are recursively transformed.\n *\n * @param havingExpr - The expression to transform (can be aggregate, ref, func, or val)\n * @param selectClause - The SELECT clause containing aliases and aggregate definitions\n * @param resultAlias - The namespace alias for SELECT results (default: '$selected')\n * @returns A transformed BasicExpression that references computed values instead of raw expressions\n */\nexport function replaceAggregatesByRefs(\n havingExpr: BasicExpression | Aggregate,\n selectClause: Select,\n resultAlias: string = `$selected`,\n): BasicExpression {\n switch (havingExpr.type) {\n case `agg`: {\n const aggExpr = havingExpr\n // Find matching aggregate in SELECT clause\n for (const [alias, selectExpr] of Object.entries(selectClause)) {\n if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {\n // Replace with a reference to the computed aggregate\n return new PropRef([resultAlias, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new AggregateFunctionNotInSelectError(aggExpr.name)\n }\n\n case `func`: {\n const funcExpr = havingExpr\n // Transform function arguments recursively\n const transformedArgs = funcExpr.args.map(\n (arg: BasicExpression | Aggregate) =>\n replaceAggregatesByRefs(arg, selectClause),\n )\n return new Func(funcExpr.name, transformedArgs)\n }\n\n case `ref`:\n // Ref expressions are passed through unchanged - they reference either:\n // - $selected fields (which are already in the correct namespace)\n // - Table column references (which remain valid)\n return havingExpr as BasicExpression\n\n case `val`:\n // Return as-is\n return havingExpr as BasicExpression\n\n default:\n throw new UnknownHavingExpressionTypeError((havingExpr as any).type)\n }\n}\n\n/**\n * Evaluates wrapped-aggregate expressions against the aggregated row.\n * Copies synthetic __agg_N values into finalResults so the compiled wrapper\n * expressions can reference them, evaluates each wrapper, then removes the\n * synthetic keys so they don't leak onto user-visible result rows.\n */\nfunction evaluateWrappedAggregates(\n finalResults: Record<string, any>,\n aggregatedRow: Record<string, any>,\n wrappedAggExprs: Record<string, (data: any) => any>,\n): void {\n for (const key of Object.keys(aggregatedRow)) {\n if (key.startsWith(`__agg_`)) {\n finalResults[key] = aggregatedRow[key]\n }\n }\n for (const [alias, evaluator] of Object.entries(wrappedAggExprs)) {\n finalResults[alias] = evaluator({ $selected: finalResults })\n }\n for (const key of Object.keys(finalResults)) {\n if (key.startsWith(`__agg_`)) delete finalResults[key]\n }\n}\n\n/**\n * Checks whether an expression contains an aggregate anywhere in its tree.\n * Returns true for a top-level Aggregate, or a Func whose args (recursively)\n * contain an Aggregate. Safely returns false for nested Select objects.\n */\nexport function containsAggregate(\n expr: BasicExpression | Aggregate | Select | { type: string },\n): boolean {\n if (!isExpressionLike(expr)) {\n return false\n }\n if (expr.type === `agg`) {\n return true\n }\n if (expr.type === `func` && `args` in expr) {\n return (expr.args as Array<BasicExpression | Aggregate>).some(\n (arg: BasicExpression | Aggregate) => containsAggregate(arg),\n )\n }\n return false\n}\n\n/**\n * Walks an expression tree containing nested aggregates.\n * Each Aggregate node is extracted, assigned a synthetic alias (__agg_N),\n * and replaced with PropRef([\"$selected\", \"__agg_N\"]) so the wrapper\n * expression can be compiled as a pure BasicExpression after groupBy\n * populates the synthetic values.\n */\nfunction extractAndReplaceAggregates(\n expr: BasicExpression | Aggregate,\n counter: { value: number },\n): {\n transformed: BasicExpression\n extracted: Record<string, Aggregate>\n} {\n if (expr.type === `agg`) {\n const alias = `__agg_${counter.value++}`\n return {\n transformed: new PropRef([`$selected`, alias]),\n extracted: { [alias]: expr },\n }\n }\n\n if (expr.type === `func`) {\n const allExtracted: Record<string, Aggregate> = {}\n const newArgs = expr.args.map((arg: BasicExpression | Aggregate) => {\n const result = extractAndReplaceAggregates(arg, counter)\n Object.assign(allExtracted, result.extracted)\n return result.transformed\n })\n return {\n transformed: new Func(expr.name, newArgs),\n extracted: allExtracted,\n }\n }\n\n // ref / val – pass through unchanged\n return { transformed: expr as BasicExpression, extracted: {} }\n}\n\n/**\n * Checks if two aggregate expressions are equal\n */\nfunction aggregatesEqual(agg1: Aggregate, agg2: Aggregate): boolean {\n return (\n agg1.name === agg2.name &&\n agg1.args.length === agg2.args.length &&\n agg1.args.every((arg, i) => expressionsEqual(arg, agg2.args[i]))\n )\n}\n"],"names":["aggregates","wrappedAggExprs","aggCounter","keyExtractor"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { PropRef, BasicExpression, QueryIR } from '../ir.js';
1
+ import { PropRef, BasicExpression, IncludesMaterialization, QueryIR } from '../ir.js';
2
2
  import { CollectionSubscription } from '../../collection/subscription.js';
3
3
  import { OrderByOptimizationInfo } from './order-by.js';
4
4
  import { LazyCollectionCallbacks } from './joins.js';
@@ -6,6 +6,32 @@ import { Collection } from '../../collection/index.js';
6
6
  import { KeyedStream, ResultStream } from '../../types.js';
7
7
  import { QueryCache, QueryMapping, WindowOptions } from './types.js';
8
8
  export type { WindowOptions } from './types.js';
9
+ /** Symbol used to tag parent $selected with routing metadata for includes */
10
+ export declare const INCLUDES_ROUTING: unique symbol;
11
+ /**
12
+ * Result of compiling an includes subquery, including the child pipeline
13
+ * and metadata needed to route child results to parent-scoped Collections.
14
+ */
15
+ export interface IncludesCompilationResult {
16
+ /** Filtered child pipeline (post inner-join with parent keys) */
17
+ pipeline: ResultStream;
18
+ /** Result field name on parent (e.g., "issues") */
19
+ fieldName: string;
20
+ /** Parent-side correlation ref (e.g., project.id) */
21
+ correlationField: PropRef;
22
+ /** Child-side correlation ref (e.g., issue.projectId) */
23
+ childCorrelationField: PropRef;
24
+ /** Whether the child query has an ORDER BY clause */
25
+ hasOrderBy: boolean;
26
+ /** Full compilation result for the child query (for nested includes + alias tracking) */
27
+ childCompilationResult: CompilationResult;
28
+ /** Parent-side projection refs for parent-referencing filters */
29
+ parentProjection?: Array<PropRef>;
30
+ /** How the output layer materializes the child result on the parent row */
31
+ materialization: IncludesMaterialization;
32
+ /** Internal field used to unwrap scalar child selects */
33
+ scalarField?: string;
34
+ }
9
35
  /**
10
36
  * Result of query compilation including both the pipeline and source-specific WHERE clauses
11
37
  */
@@ -36,6 +62,8 @@ export interface CompilationResult {
36
62
  * the inner aliases where collection subscriptions were created.
37
63
  */
38
64
  aliasRemapping: Record<string, string>;
65
+ /** Child pipelines for includes subqueries */
66
+ includes?: Array<IncludesCompilationResult>;
39
67
  }
40
68
  /**
41
69
  * Compiles a query IR into a D2 pipeline
@@ -50,7 +78,7 @@ export interface CompilationResult {
50
78
  * @param queryMapping Optional mapping from optimized queries to original queries
51
79
  * @returns A CompilationResult with the pipeline, source WHERE clauses, and alias metadata
52
80
  */
53
- export declare function compileQuery(rawQuery: QueryIR, inputs: Record<string, KeyedStream>, collections: Record<string, Collection<any, any, any, any, any>>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazySources: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, cache?: QueryCache, queryMapping?: QueryMapping): CompilationResult;
81
+ export declare function compileQuery(rawQuery: QueryIR, inputs: Record<string, KeyedStream>, collections: Record<string, Collection<any, any, any, any, any>>, subscriptions: Record<string, CollectionSubscription>, callbacks: Record<string, LazyCollectionCallbacks>, lazySources: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, cache?: QueryCache, queryMapping?: QueryMapping, parentKeyStream?: KeyedStream, childCorrelationField?: PropRef): CompilationResult;
54
82
  /**
55
83
  * Follows the given reference in a query
56
84
  * until its finds the root field the reference points to.
@@ -1,19 +1,22 @@
1
- import { map, filter, distinct } from "@tanstack/db-ivm";
1
+ import { map, join, filter, reduce, distinct } from "@tanstack/db-ivm";
2
2
  import { optimizeQuery } from "../optimizer.js";
3
3
  import { DistinctRequiresSelectError, FnSelectWithGroupByError, HavingRequiresGroupByError, LimitOffsetRequireOrderByError, DuplicateAliasInSubqueryError, UnsupportedFromTypeError, CollectionInputNotFoundError } from "../../errors.js";
4
- import { getWhereExpression, Value } from "../ir.js";
4
+ import { VIRTUAL_PROP_NAMES } from "../../virtual-props.js";
5
+ import { getWhereExpression, IncludesSubquery, Value } from "../ir.js";
5
6
  import { compileExpression, toBooleanPredicate } from "./evaluators.js";
6
7
  import { processJoins } from "./joins.js";
7
8
  import { processGroupBy, containsAggregate } from "./group-by.js";
8
9
  import { processOrderBy } from "./order-by.js";
9
10
  import { processSelect } from "./select.js";
10
- function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap()) {
11
+ const INCLUDES_ROUTING = /* @__PURE__ */ Symbol(`includesRouting`);
12
+ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap(), parentKeyStream, childCorrelationField) {
11
13
  const cachedResult = cache.get(rawQuery);
12
14
  if (cachedResult) {
13
15
  return cachedResult;
14
16
  }
15
17
  validateQueryStructure(rawQuery);
16
- const { optimizedQuery: query, sourceWhereClauses } = optimizeQuery(rawQuery);
18
+ const { optimizedQuery, sourceWhereClauses } = optimizeQuery(rawQuery);
19
+ let query = optimizedQuery;
17
20
  queryMapping.set(query, rawQuery);
18
21
  mapNestedQueries(query, rawQuery, queryMapping);
19
22
  const allInputs = { ...inputs };
@@ -40,9 +43,41 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
40
43
  sourceWhereClauses
41
44
  );
42
45
  sources[mainSource] = mainInput;
43
- let pipeline = mainInput.pipe(
46
+ let filteredMainInput = mainInput;
47
+ if (parentKeyStream && childCorrelationField) {
48
+ const childFieldPath = childCorrelationField.path.slice(1);
49
+ const childRekeyed = mainInput.pipe(
50
+ map(([key, row]) => {
51
+ const correlationValue = getNestedValue(row, childFieldPath);
52
+ return [correlationValue, [key, row]];
53
+ })
54
+ );
55
+ const joined = childRekeyed.pipe(join(parentKeyStream, `inner`));
56
+ filteredMainInput = joined.pipe(
57
+ filter(([_correlationValue, [childSide]]) => {
58
+ return childSide != null;
59
+ }),
60
+ map(([correlationValue, [childSide, parentSide]]) => {
61
+ const [childKey, childRow] = childSide;
62
+ const tagged = { ...childRow, __correlationKey: correlationValue };
63
+ if (parentSide != null) {
64
+ tagged.__parentContext = parentSide;
65
+ }
66
+ const effectiveKey = parentSide != null ? `${String(childKey)}::${JSON.stringify(parentSide)}` : childKey;
67
+ return [effectiveKey, tagged];
68
+ })
69
+ );
70
+ sources[mainSource] = filteredMainInput;
71
+ }
72
+ let pipeline = filteredMainInput.pipe(
44
73
  map(([key, row]) => {
45
- const ret = [key, { [mainSource]: row }];
74
+ const { __parentContext, ...cleanRow } = row;
75
+ const nsRow = { [mainSource]: cleanRow };
76
+ if (__parentContext) {
77
+ Object.assign(nsRow, __parentContext);
78
+ nsRow.__parentContext = __parentContext;
79
+ }
80
+ const ret = [key, nsRow];
46
81
  return ret;
47
82
  })
48
83
  );
@@ -89,6 +124,128 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
89
124
  );
90
125
  }
91
126
  }
127
+ const includesResults = [];
128
+ const includesRoutingFns = [];
129
+ if (query.select) {
130
+ const includesEntries = extractIncludesFromSelect(query.select);
131
+ if (includesEntries.length > 0) {
132
+ query = { ...query, select: { ...query.select } };
133
+ }
134
+ for (const { key, subquery } of includesEntries) {
135
+ const compiledCorrelation = compileExpression(subquery.correlationField);
136
+ let parentKeys;
137
+ if (subquery.parentProjection && subquery.parentProjection.length > 0) {
138
+ const compiledProjections = subquery.parentProjection.map((ref) => ({
139
+ alias: ref.path[0],
140
+ field: ref.path.slice(1),
141
+ compiled: compileExpression(ref)
142
+ }));
143
+ parentKeys = pipeline.pipe(
144
+ map(([_key, nsRow]) => {
145
+ const parentContext = {};
146
+ for (const proj of compiledProjections) {
147
+ if (!parentContext[proj.alias]) {
148
+ parentContext[proj.alias] = {};
149
+ }
150
+ const value = proj.compiled(nsRow);
151
+ let target = parentContext[proj.alias];
152
+ for (let i = 0; i < proj.field.length - 1; i++) {
153
+ if (!target[proj.field[i]]) {
154
+ target[proj.field[i]] = {};
155
+ }
156
+ target = target[proj.field[i]];
157
+ }
158
+ target[proj.field[proj.field.length - 1]] = value;
159
+ }
160
+ return [compiledCorrelation(nsRow), parentContext];
161
+ })
162
+ );
163
+ } else {
164
+ parentKeys = pipeline.pipe(
165
+ map(
166
+ ([_key, nsRow]) => [compiledCorrelation(nsRow), null]
167
+ )
168
+ );
169
+ }
170
+ parentKeys = parentKeys.pipe(
171
+ reduce(
172
+ (values) => values.map(([v, mult]) => [v, mult > 0 ? 1 : 0])
173
+ )
174
+ );
175
+ const childQuery = subquery.parentFilters && subquery.parentFilters.length > 0 ? {
176
+ ...subquery.query,
177
+ where: [
178
+ ...subquery.query.where || [],
179
+ ...subquery.parentFilters
180
+ ]
181
+ } : subquery.query;
182
+ const childResult = compileQuery(
183
+ childQuery,
184
+ allInputs,
185
+ collections,
186
+ subscriptions,
187
+ callbacks,
188
+ lazySources,
189
+ optimizableOrderByCollections,
190
+ setWindowFn,
191
+ cache,
192
+ queryMapping,
193
+ parentKeys,
194
+ subquery.childCorrelationField
195
+ );
196
+ Object.assign(aliasToCollectionId, childResult.aliasToCollectionId);
197
+ Object.assign(aliasRemapping, childResult.aliasRemapping);
198
+ includesResults.push({
199
+ pipeline: childResult.pipeline,
200
+ fieldName: subquery.fieldName,
201
+ correlationField: subquery.correlationField,
202
+ childCorrelationField: subquery.childCorrelationField,
203
+ hasOrderBy: !!(subquery.query.orderBy && subquery.query.orderBy.length > 0),
204
+ childCompilationResult: childResult,
205
+ parentProjection: subquery.parentProjection,
206
+ materialization: subquery.materialization,
207
+ scalarField: subquery.scalarField
208
+ });
209
+ if (subquery.parentProjection && subquery.parentProjection.length > 0) {
210
+ const compiledProjs = subquery.parentProjection.map((ref) => ({
211
+ alias: ref.path[0],
212
+ field: ref.path.slice(1),
213
+ compiled: compileExpression(ref)
214
+ }));
215
+ const compiledCorr = compiledCorrelation;
216
+ includesRoutingFns.push({
217
+ fieldName: subquery.fieldName,
218
+ getRouting: (nsRow) => {
219
+ const parentContext = {};
220
+ for (const proj of compiledProjs) {
221
+ if (!parentContext[proj.alias]) {
222
+ parentContext[proj.alias] = {};
223
+ }
224
+ const value = proj.compiled(nsRow);
225
+ let target = parentContext[proj.alias];
226
+ for (let i = 0; i < proj.field.length - 1; i++) {
227
+ if (!target[proj.field[i]]) {
228
+ target[proj.field[i]] = {};
229
+ }
230
+ target = target[proj.field[i]];
231
+ }
232
+ target[proj.field[proj.field.length - 1]] = value;
233
+ }
234
+ return { correlationKey: compiledCorr(nsRow), parentContext };
235
+ }
236
+ });
237
+ } else {
238
+ includesRoutingFns.push({
239
+ fieldName: subquery.fieldName,
240
+ getRouting: (nsRow) => ({
241
+ correlationKey: compiledCorrelation(nsRow),
242
+ parentContext: null
243
+ })
244
+ });
245
+ }
246
+ replaceIncludesInSelect(query.select, key);
247
+ }
248
+ }
92
249
  if (query.distinct && !query.fnSelect && !query.select) {
93
250
  throw new DistinctRequiresSelectError();
94
251
  }
@@ -124,13 +281,28 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
124
281
  })
125
282
  );
126
283
  }
284
+ if (includesRoutingFns.length > 0) {
285
+ pipeline = pipeline.pipe(
286
+ map(([key, namespacedRow]) => {
287
+ const routing = {};
288
+ for (const { fieldName, getRouting } of includesRoutingFns) {
289
+ routing[fieldName] = getRouting(namespacedRow);
290
+ }
291
+ namespacedRow.$selected[INCLUDES_ROUTING] = routing;
292
+ return [key, namespacedRow];
293
+ })
294
+ );
295
+ }
296
+ const groupByMainSource = parentKeyStream ? mainSource : void 0;
127
297
  if (query.groupBy && query.groupBy.length > 0) {
128
298
  pipeline = processGroupBy(
129
299
  pipeline,
130
300
  query.groupBy,
131
301
  query.having,
132
302
  query.select,
133
- query.fnHaving
303
+ query.fnHaving,
304
+ mainCollectionId,
305
+ groupByMainSource
134
306
  );
135
307
  } else if (query.select) {
136
308
  const hasAggregates = Object.values(query.select).some(
@@ -143,7 +315,9 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
143
315
  // Empty group by means single group
144
316
  query.having,
145
317
  query.select,
146
- query.fnHaving
318
+ query.fnHaving,
319
+ mainCollectionId,
320
+ groupByMainSource
147
321
  );
148
322
  }
149
323
  }
@@ -166,6 +340,14 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
166
340
  pipeline = pipeline.pipe(distinct(([_key, row]) => row.$selected));
167
341
  }
168
342
  if (query.orderBy && query.orderBy.length > 0) {
343
+ const includesGroupKeyFn = parentKeyStream && (query.limit !== void 0 || query.offset !== void 0) ? (_key, row) => {
344
+ const correlationKey = row?.[mainSource]?.__correlationKey;
345
+ const parentContext = row?.__parentContext;
346
+ if (parentContext != null) {
347
+ return JSON.stringify([correlationKey, parentContext]);
348
+ }
349
+ return correlationKey;
350
+ } : void 0;
169
351
  const orderedPipeline = processOrderBy(
170
352
  rawQuery,
171
353
  pipeline,
@@ -175,12 +357,26 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
175
357
  optimizableOrderByCollections,
176
358
  setWindowFn,
177
359
  query.limit,
178
- query.offset
360
+ query.offset,
361
+ includesGroupKeyFn
179
362
  );
180
363
  const resultPipeline2 = orderedPipeline.pipe(
181
364
  map(([key, [row, orderByIndex]]) => {
182
365
  const raw = row.$selected;
183
- const finalResults = unwrapValue(raw);
366
+ const finalResults = attachVirtualPropsToSelected(
367
+ unwrapValue(raw),
368
+ row
369
+ );
370
+ if (parentKeyStream) {
371
+ const correlationKey = row[mainSource]?.__correlationKey;
372
+ const parentContext = row.__parentContext ?? null;
373
+ delete finalResults.__correlationKey;
374
+ delete finalResults.__parentContext;
375
+ return [
376
+ key,
377
+ [finalResults, orderByIndex, correlationKey, parentContext]
378
+ ];
379
+ }
184
380
  return [key, [finalResults, orderByIndex]];
185
381
  })
186
382
  );
@@ -190,7 +386,8 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
190
386
  pipeline: result2,
191
387
  sourceWhereClauses,
192
388
  aliasToCollectionId,
193
- aliasRemapping
389
+ aliasRemapping,
390
+ includes: includesResults.length > 0 ? includesResults : void 0
194
391
  };
195
392
  cache.set(rawQuery, compilationResult2);
196
393
  return compilationResult2;
@@ -200,7 +397,20 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
200
397
  const resultPipeline = pipeline.pipe(
201
398
  map(([key, row]) => {
202
399
  const raw = row.$selected;
203
- const finalResults = unwrapValue(raw);
400
+ const finalResults = attachVirtualPropsToSelected(
401
+ unwrapValue(raw),
402
+ row
403
+ );
404
+ if (parentKeyStream) {
405
+ const correlationKey = row[mainSource]?.__correlationKey;
406
+ const parentContext = row.__parentContext ?? null;
407
+ delete finalResults.__correlationKey;
408
+ delete finalResults.__parentContext;
409
+ return [
410
+ key,
411
+ [finalResults, void 0, correlationKey, parentContext]
412
+ ];
413
+ }
204
414
  return [key, [finalResults, void 0]];
205
415
  })
206
416
  );
@@ -210,7 +420,8 @@ function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, l
210
420
  pipeline: result,
211
421
  sourceWhereClauses,
212
422
  aliasToCollectionId,
213
- aliasRemapping
423
+ aliasRemapping,
424
+ includes: includesResults.length > 0 ? includesResults : void 0
214
425
  };
215
426
  cache.set(rawQuery, compilationResult);
216
427
  return compilationResult;
@@ -322,6 +533,27 @@ function isValue(raw) {
322
533
  function unwrapValue(value) {
323
534
  return isValue(value) ? value.value : value;
324
535
  }
536
+ function attachVirtualPropsToSelected(selected, row) {
537
+ if (!selected || typeof selected !== `object`) {
538
+ return selected;
539
+ }
540
+ let needsMerge = false;
541
+ for (const prop of VIRTUAL_PROP_NAMES) {
542
+ if (selected[prop] == null && prop in row) {
543
+ needsMerge = true;
544
+ break;
545
+ }
546
+ }
547
+ if (!needsMerge) {
548
+ return selected;
549
+ }
550
+ for (const prop of VIRTUAL_PROP_NAMES) {
551
+ if (selected[prop] == null && prop in row) {
552
+ selected[prop] = row[prop];
553
+ }
554
+ }
555
+ return selected;
556
+ }
325
557
  function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
326
558
  if (optimizedQuery.from.type === `queryRef` && originalQuery.from.type === `queryRef`) {
327
559
  queryMapping.set(optimizedQuery.from.query, originalQuery.from.query);
@@ -346,7 +578,47 @@ function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
346
578
  }
347
579
  }
348
580
  }
581
+ function extractIncludesFromSelect(select) {
582
+ const results = [];
583
+ for (const [key, value] of Object.entries(select)) {
584
+ if (key.startsWith(`__SPREAD_SENTINEL__`)) continue;
585
+ if (value instanceof IncludesSubquery) {
586
+ results.push({ key, subquery: value });
587
+ } else if (isNestedSelectObject(value)) {
588
+ assertNoNestedIncludes(value, key);
589
+ }
590
+ }
591
+ return results;
592
+ }
593
+ function isNestedSelectObject(value) {
594
+ return value != null && typeof value === `object` && !Array.isArray(value) && typeof value.type !== `string`;
595
+ }
596
+ function assertNoNestedIncludes(obj, parentPath) {
597
+ for (const [key, value] of Object.entries(obj)) {
598
+ if (key.startsWith(`__SPREAD_SENTINEL__`)) continue;
599
+ if (value instanceof IncludesSubquery) {
600
+ throw new Error(
601
+ `Includes subqueries must be at the top level of select(). Found nested includes at "${parentPath}.${key}".`
602
+ );
603
+ }
604
+ if (isNestedSelectObject(value)) {
605
+ assertNoNestedIncludes(value, `${parentPath}.${key}`);
606
+ }
607
+ }
608
+ }
609
+ function replaceIncludesInSelect(select, key) {
610
+ select[key] = new Value(null);
611
+ }
612
+ function getNestedValue(obj, path) {
613
+ let value = obj;
614
+ for (const segment of path) {
615
+ if (value == null) return value;
616
+ value = value[segment];
617
+ }
618
+ return value;
619
+ }
349
620
  export {
621
+ INCLUDES_ROUTING,
350
622
  compileQuery
351
623
  };
352
624
  //# sourceMappingURL=index.js.map