@tanstack/db 0.5.32 → 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 (287) 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 +24 -4
  40. package/dist/cjs/index.cjs.map +1 -1
  41. package/dist/cjs/index.d.cts +12 -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/effect.cjs +602 -0
  81. package/dist/cjs/query/effect.cjs.map +1 -0
  82. package/dist/cjs/query/effect.d.cts +94 -0
  83. package/dist/cjs/query/index.d.cts +2 -1
  84. package/dist/cjs/query/ir.cjs +18 -1
  85. package/dist/cjs/query/ir.cjs.map +1 -1
  86. package/dist/cjs/query/ir.d.cts +21 -1
  87. package/dist/cjs/query/live/collection-config-builder.cjs +493 -66
  88. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  89. package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
  90. package/dist/cjs/query/live/collection-subscriber.cjs +33 -100
  91. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  92. package/dist/cjs/query/live/collection-subscriber.d.cts +0 -1
  93. package/dist/cjs/query/live/types.d.cts +3 -3
  94. package/dist/cjs/query/live/utils.cjs +219 -0
  95. package/dist/cjs/query/live/utils.cjs.map +1 -0
  96. package/dist/cjs/query/live/utils.d.cts +110 -0
  97. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  98. package/dist/cjs/query/live-query-collection.d.cts +9 -6
  99. package/dist/cjs/query/query-once.cjs.map +1 -1
  100. package/dist/cjs/query/query-once.d.cts +7 -5
  101. package/dist/cjs/query/subset-dedupe.cjs +9 -3
  102. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  103. package/dist/cjs/types.d.cts +42 -8
  104. package/dist/cjs/utils/array-utils.cjs +27 -0
  105. package/dist/cjs/utils/array-utils.cjs.map +1 -0
  106. package/dist/cjs/utils/array-utils.d.cts +16 -0
  107. package/dist/cjs/utils/comparison.cjs +11 -0
  108. package/dist/cjs/utils/comparison.cjs.map +1 -1
  109. package/dist/cjs/utils/index-optimization.cjs +4 -0
  110. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  111. package/dist/cjs/utils.cjs +7 -9
  112. package/dist/cjs/utils.cjs.map +1 -1
  113. package/dist/cjs/utils.d.cts +6 -1
  114. package/dist/cjs/virtual-props.cjs +33 -0
  115. package/dist/cjs/virtual-props.cjs.map +1 -0
  116. package/dist/cjs/virtual-props.d.cts +196 -0
  117. package/dist/esm/collection/change-events.d.ts +3 -2
  118. package/dist/esm/collection/change-events.js.map +1 -1
  119. package/dist/esm/collection/changes.d.ts +10 -1
  120. package/dist/esm/collection/changes.js +13 -4
  121. package/dist/esm/collection/changes.js.map +1 -1
  122. package/dist/esm/collection/cleanup-queue.d.ts +30 -0
  123. package/dist/esm/collection/cleanup-queue.js +89 -0
  124. package/dist/esm/collection/cleanup-queue.js.map +1 -0
  125. package/dist/esm/collection/events.d.ts +39 -1
  126. package/dist/esm/collection/events.js +14 -0
  127. package/dist/esm/collection/events.js.map +1 -1
  128. package/dist/esm/collection/index.d.ts +49 -36
  129. package/dist/esm/collection/index.js +67 -29
  130. package/dist/esm/collection/index.js.map +1 -1
  131. package/dist/esm/collection/indexes.d.ts +27 -17
  132. package/dist/esm/collection/indexes.js +211 -62
  133. package/dist/esm/collection/indexes.js.map +1 -1
  134. package/dist/esm/collection/lifecycle.d.ts +0 -1
  135. package/dist/esm/collection/lifecycle.js +5 -22
  136. package/dist/esm/collection/lifecycle.js.map +1 -1
  137. package/dist/esm/collection/mutations.d.ts +1 -0
  138. package/dist/esm/collection/mutations.js +18 -0
  139. package/dist/esm/collection/mutations.js.map +1 -1
  140. package/dist/esm/collection/state.d.ts +65 -1
  141. package/dist/esm/collection/state.js +381 -53
  142. package/dist/esm/collection/state.js.map +1 -1
  143. package/dist/esm/collection/subscription.d.ts +4 -0
  144. package/dist/esm/collection/subscription.js +6 -0
  145. package/dist/esm/collection/subscription.js.map +1 -1
  146. package/dist/esm/collection/sync.d.ts +2 -0
  147. package/dist/esm/collection/sync.js +108 -1
  148. package/dist/esm/collection/sync.js.map +1 -1
  149. package/dist/esm/collection/transaction-metadata.d.ts +1 -0
  150. package/dist/esm/collection/transaction-metadata.js +5 -0
  151. package/dist/esm/collection/transaction-metadata.js.map +1 -0
  152. package/dist/esm/errors.d.ts +3 -0
  153. package/dist/esm/errors.js +8 -0
  154. package/dist/esm/errors.js.map +1 -1
  155. package/dist/esm/index.d.ts +12 -3
  156. package/dist/esm/index.js +27 -7
  157. package/dist/esm/index.js.map +1 -1
  158. package/dist/esm/indexes/auto-index.js +13 -6
  159. package/dist/esm/indexes/auto-index.js.map +1 -1
  160. package/dist/esm/indexes/base-index.d.ts +2 -6
  161. package/dist/esm/indexes/base-index.js +1 -4
  162. package/dist/esm/indexes/base-index.js.map +1 -1
  163. package/dist/esm/indexes/basic-index.d.ts +102 -0
  164. package/dist/esm/indexes/basic-index.js +361 -0
  165. package/dist/esm/indexes/basic-index.js.map +1 -0
  166. package/dist/esm/indexes/btree-index.d.ts +1 -1
  167. package/dist/esm/indexes/btree-index.js.map +1 -1
  168. package/dist/esm/indexes/index-options.d.ts +8 -9
  169. package/dist/esm/indexes/index-registry.d.ts +61 -0
  170. package/dist/esm/indexes/index-registry.js +89 -0
  171. package/dist/esm/indexes/index-registry.js.map +1 -0
  172. package/dist/esm/local-only.js +5 -0
  173. package/dist/esm/local-only.js.map +1 -1
  174. package/dist/esm/query/builder/functions.d.ts +25 -3
  175. package/dist/esm/query/builder/functions.js +27 -11
  176. package/dist/esm/query/builder/functions.js.map +1 -1
  177. package/dist/esm/query/builder/index.d.ts +4 -3
  178. package/dist/esm/query/builder/index.js +201 -40
  179. package/dist/esm/query/builder/index.js.map +1 -1
  180. package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
  181. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  182. package/dist/esm/query/builder/types.d.ts +84 -19
  183. package/dist/esm/query/compiler/evaluators.js +51 -0
  184. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  185. package/dist/esm/query/compiler/group-by.d.ts +4 -2
  186. package/dist/esm/query/compiler/group-by.js +101 -29
  187. package/dist/esm/query/compiler/group-by.js.map +1 -1
  188. package/dist/esm/query/compiler/index.d.ts +30 -2
  189. package/dist/esm/query/compiler/index.js +285 -13
  190. package/dist/esm/query/compiler/index.js.map +1 -1
  191. package/dist/esm/query/compiler/order-by.d.ts +1 -1
  192. package/dist/esm/query/compiler/order-by.js +30 -11
  193. package/dist/esm/query/compiler/order-by.js.map +1 -1
  194. package/dist/esm/query/compiler/select.js +8 -0
  195. package/dist/esm/query/compiler/select.js.map +1 -1
  196. package/dist/esm/query/effect.d.ts +94 -0
  197. package/dist/esm/query/effect.js +602 -0
  198. package/dist/esm/query/effect.js.map +1 -0
  199. package/dist/esm/query/index.d.ts +2 -1
  200. package/dist/esm/query/ir.d.ts +21 -1
  201. package/dist/esm/query/ir.js +18 -1
  202. package/dist/esm/query/ir.js.map +1 -1
  203. package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
  204. package/dist/esm/query/live/collection-config-builder.js +492 -65
  205. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  206. package/dist/esm/query/live/collection-subscriber.d.ts +0 -1
  207. package/dist/esm/query/live/collection-subscriber.js +31 -98
  208. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  209. package/dist/esm/query/live/types.d.ts +3 -3
  210. package/dist/esm/query/live/utils.d.ts +110 -0
  211. package/dist/esm/query/live/utils.js +219 -0
  212. package/dist/esm/query/live/utils.js.map +1 -0
  213. package/dist/esm/query/live-query-collection.d.ts +9 -6
  214. package/dist/esm/query/live-query-collection.js.map +1 -1
  215. package/dist/esm/query/query-once.d.ts +7 -5
  216. package/dist/esm/query/query-once.js.map +1 -1
  217. package/dist/esm/query/subset-dedupe.js +9 -3
  218. package/dist/esm/query/subset-dedupe.js.map +1 -1
  219. package/dist/esm/types.d.ts +42 -8
  220. package/dist/esm/utils/array-utils.d.ts +16 -0
  221. package/dist/esm/utils/array-utils.js +27 -0
  222. package/dist/esm/utils/array-utils.js.map +1 -0
  223. package/dist/esm/utils/comparison.js +11 -0
  224. package/dist/esm/utils/comparison.js.map +1 -1
  225. package/dist/esm/utils/index-optimization.js +4 -0
  226. package/dist/esm/utils/index-optimization.js.map +1 -1
  227. package/dist/esm/utils.d.ts +6 -1
  228. package/dist/esm/utils.js +7 -9
  229. package/dist/esm/utils.js.map +1 -1
  230. package/dist/esm/virtual-props.d.ts +196 -0
  231. package/dist/esm/virtual-props.js +33 -0
  232. package/dist/esm/virtual-props.js.map +1 -0
  233. package/package.json +2 -2
  234. package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
  235. package/src/collection/change-events.ts +13 -9
  236. package/src/collection/changes.ts +30 -7
  237. package/src/collection/cleanup-queue.ts +105 -0
  238. package/src/collection/events.ts +65 -0
  239. package/src/collection/index.ts +110 -45
  240. package/src/collection/indexes.ts +283 -76
  241. package/src/collection/lifecycle.ts +5 -26
  242. package/src/collection/mutations.ts +21 -0
  243. package/src/collection/state.ts +545 -71
  244. package/src/collection/subscription.ts +7 -0
  245. package/src/collection/sync.ts +137 -0
  246. package/src/collection/transaction-metadata.ts +1 -0
  247. package/src/errors.ts +9 -0
  248. package/src/index.ts +57 -3
  249. package/src/indexes/auto-index.ts +18 -8
  250. package/src/indexes/base-index.ts +2 -10
  251. package/src/indexes/basic-index.ts +507 -0
  252. package/src/indexes/btree-index.ts +1 -1
  253. package/src/indexes/index-options.ts +17 -37
  254. package/src/indexes/index-registry.ts +174 -0
  255. package/src/local-only.ts +7 -0
  256. package/src/query/builder/functions.ts +84 -7
  257. package/src/query/builder/index.ts +329 -9
  258. package/src/query/builder/ref-proxy.ts +22 -4
  259. package/src/query/builder/types.ts +257 -62
  260. package/src/query/compiler/evaluators.ts +57 -0
  261. package/src/query/compiler/group-by.ts +156 -35
  262. package/src/query/compiler/index.ts +445 -15
  263. package/src/query/compiler/order-by.ts +51 -12
  264. package/src/query/compiler/select.ts +9 -0
  265. package/src/query/effect.ts +1119 -0
  266. package/src/query/index.ts +7 -0
  267. package/src/query/ir.ts +23 -2
  268. package/src/query/live/collection-config-builder.ts +778 -104
  269. package/src/query/live/collection-subscriber.ts +40 -156
  270. package/src/query/live/types.ts +10 -4
  271. package/src/query/live/utils.ts +417 -0
  272. package/src/query/live-query-collection.ts +43 -18
  273. package/src/query/query-once.ts +31 -12
  274. package/src/query/subset-dedupe.ts +11 -7
  275. package/src/types.ts +49 -9
  276. package/src/utils/array-utils.ts +49 -0
  277. package/src/utils/comparison.ts +14 -0
  278. package/src/utils/index-optimization.ts +4 -0
  279. package/src/utils.ts +12 -9
  280. package/src/virtual-props.ts +282 -0
  281. package/dist/cjs/indexes/lazy-index.cjs +0 -190
  282. package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
  283. package/dist/cjs/indexes/lazy-index.d.cts +0 -96
  284. package/dist/esm/indexes/lazy-index.d.ts +0 -96
  285. package/dist/esm/indexes/lazy-index.js +0 -190
  286. package/dist/esm/indexes/lazy-index.js.map +0 -1
  287. package/src/indexes/lazy-index.ts +0 -251
@@ -3,6 +3,8 @@ import {
3
3
  Aggregate as AggregateExpr,
4
4
  CollectionRef,
5
5
  Func as FuncExpr,
6
+ INCLUDES_SCALAR_FIELD,
7
+ IncludesSubquery,
6
8
  PropRef,
7
9
  QueryRef,
8
10
  Value as ValueExpr,
@@ -23,14 +25,17 @@ import {
23
25
  isRefProxy,
24
26
  toExpression,
25
27
  } from './ref-proxy.js'
28
+ import { ConcatToArrayWrapper, ToArrayWrapper } from './functions.js'
26
29
  import type { NamespacedRow, SingleResult } from '../../types.js'
27
30
  import type {
28
31
  Aggregate,
29
32
  BasicExpression,
33
+ IncludesMaterialization,
30
34
  JoinClause,
31
35
  OrderBy,
32
36
  OrderByDirection,
33
37
  QueryIR,
38
+ Where,
34
39
  } from '../ir.js'
35
40
  import type {
36
41
  CompareOptions,
@@ -41,10 +46,13 @@ import type {
41
46
  JoinOnCallback,
42
47
  MergeContextForJoinCallback,
43
48
  MergeContextWithJoinType,
49
+ NonScalarSelectObject,
44
50
  OrderByCallback,
45
51
  OrderByOptions,
46
52
  RefsForContext,
47
53
  ResultTypeFromSelect,
54
+ ResultTypeFromSelectValue,
55
+ ScalarSelectValue,
48
56
  SchemaFromSource,
49
57
  SelectObject,
50
58
  Source,
@@ -486,12 +494,30 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
486
494
  * ```
487
495
  */
488
496
  select<TSelectObject extends SelectObject>(
489
- callback: (refs: RefsForContext<TContext>) => TSelectObject,
490
- ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {
497
+ callback: (
498
+ refs: RefsForContext<TContext>,
499
+ ) => NonScalarSelectObject<TSelectObject>,
500
+ ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>>
501
+ select<TSelectValue extends ScalarSelectValue>(
502
+ callback: (refs: RefsForContext<TContext>) => TSelectValue,
503
+ ): QueryBuilder<WithResult<TContext, ResultTypeFromSelectValue<TSelectValue>>>
504
+ select(
505
+ callback: (
506
+ refs: RefsForContext<TContext>,
507
+ ) => SelectObject | ScalarSelectValue,
508
+ ) {
491
509
  const aliases = this._getCurrentAliases()
492
510
  const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
493
- const selectObject = callback(refProxy)
494
- const select = buildNestedSelect(selectObject)
511
+ let selectObject = callback(refProxy)
512
+
513
+ // Returning a top-level alias directly is equivalent to spreading it.
514
+ // Leaf refs like `row.name` must remain scalar selections.
515
+ if (isRefProxy(selectObject) && selectObject.__path.length === 1) {
516
+ const sentinelKey = `__SPREAD_SENTINEL__${selectObject.__path[0]}__0`
517
+ selectObject = { [sentinelKey]: true }
518
+ }
519
+
520
+ const select = buildNestedSelect(selectObject, aliases)
495
521
 
496
522
  return new BaseQueryBuilder({
497
523
  ...this.query,
@@ -676,7 +702,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
676
702
  * // Get countries our users are from
677
703
  * query
678
704
  * .from({ users: usersCollection })
679
- * .select(({users}) => users.country)
705
+ * .select(({users}) => ({ country: users.country }))
680
706
  * .distinct()
681
707
  * ```
682
708
  */
@@ -706,7 +732,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
706
732
  // TODO: enforcing return only one result with also a default orderBy if none is specified
707
733
  // limit: 1,
708
734
  singleResult: true,
709
- })
735
+ }) as any
710
736
  }
711
737
 
712
738
  // Helper methods
@@ -769,7 +795,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
769
795
  ...builder.query,
770
796
  select: undefined, // remove the select clause if it exists
771
797
  fnSelect: callback,
772
- })
798
+ }) as any
773
799
  },
774
800
  /**
775
801
  * Filter rows using a function that operates on each row
@@ -867,7 +893,7 @@ function isPlainObject(value: any): value is Record<string, any> {
867
893
  )
868
894
  }
869
895
 
870
- function buildNestedSelect(obj: any): any {
896
+ function buildNestedSelect(obj: any, parentAliases: Array<string> = []): any {
871
897
  if (!isPlainObject(obj)) return toExpr(obj)
872
898
  const out: Record<string, any> = {}
873
899
  for (const [k, v] of Object.entries(obj)) {
@@ -876,11 +902,305 @@ function buildNestedSelect(obj: any): any {
876
902
  out[k] = v
877
903
  continue
878
904
  }
879
- out[k] = buildNestedSelect(v)
905
+ if (v instanceof BaseQueryBuilder) {
906
+ out[k] = buildIncludesSubquery(v, k, parentAliases, `collection`)
907
+ continue
908
+ }
909
+ if (v instanceof ToArrayWrapper) {
910
+ if (!(v.query instanceof BaseQueryBuilder)) {
911
+ throw new Error(`toArray() must wrap a subquery builder`)
912
+ }
913
+ out[k] = buildIncludesSubquery(v.query, k, parentAliases, `array`)
914
+ continue
915
+ }
916
+ if (v instanceof ConcatToArrayWrapper) {
917
+ if (!(v.query instanceof BaseQueryBuilder)) {
918
+ throw new Error(`concat(toArray(...)) must wrap a subquery builder`)
919
+ }
920
+ out[k] = buildIncludesSubquery(v.query, k, parentAliases, `concat`)
921
+ continue
922
+ }
923
+ out[k] = buildNestedSelect(v, parentAliases)
880
924
  }
881
925
  return out
882
926
  }
883
927
 
928
+ /**
929
+ * Recursively collects all PropRef nodes from an expression tree.
930
+ */
931
+ function collectRefsFromExpression(expr: BasicExpression): Array<PropRef> {
932
+ const refs: Array<PropRef> = []
933
+ switch (expr.type) {
934
+ case `ref`:
935
+ refs.push(expr)
936
+ break
937
+ case `func`:
938
+ for (const arg of (expr as any).args ?? []) {
939
+ refs.push(...collectRefsFromExpression(arg))
940
+ }
941
+ break
942
+ default:
943
+ break
944
+ }
945
+ return refs
946
+ }
947
+
948
+ /**
949
+ * Checks whether a WHERE clause references any parent alias.
950
+ */
951
+ function referencesParent(where: Where, parentAliases: Array<string>): boolean {
952
+ const expr =
953
+ typeof where === `object` && `expression` in where
954
+ ? where.expression
955
+ : where
956
+ return collectRefsFromExpression(expr).some(
957
+ (ref) => ref.path[0] != null && parentAliases.includes(ref.path[0]),
958
+ )
959
+ }
960
+
961
+ /**
962
+ * Builds an IncludesSubquery IR node from a child query builder.
963
+ * Extracts the correlation condition from the child's WHERE clauses by finding
964
+ * an eq() predicate that references both a parent alias and a child alias.
965
+ */
966
+ function buildIncludesSubquery(
967
+ childBuilder: BaseQueryBuilder,
968
+ fieldName: string,
969
+ parentAliases: Array<string>,
970
+ materialization: IncludesMaterialization,
971
+ ): IncludesSubquery {
972
+ const childQuery = childBuilder._getQuery()
973
+
974
+ // Collect child's own aliases
975
+ const childAliases: Array<string> = [childQuery.from.alias]
976
+ if (childQuery.join) {
977
+ for (const j of childQuery.join) {
978
+ childAliases.push(j.from.alias)
979
+ }
980
+ }
981
+
982
+ // Walk child's WHERE clauses to find the correlation condition.
983
+ // The correlation eq() may be a standalone WHERE or nested inside a top-level and().
984
+ let parentRef: PropRef | undefined
985
+ let childRef: PropRef | undefined
986
+ let correlationWhereIndex = -1
987
+ let correlationAndArgIndex = -1 // >= 0 when found inside an and()
988
+
989
+ if (childQuery.where) {
990
+ for (let i = 0; i < childQuery.where.length; i++) {
991
+ const where = childQuery.where[i]!
992
+ const expr =
993
+ typeof where === `object` && `expression` in where
994
+ ? where.expression
995
+ : where
996
+
997
+ // Try standalone eq()
998
+ if (
999
+ expr.type === `func` &&
1000
+ expr.name === `eq` &&
1001
+ expr.args.length === 2
1002
+ ) {
1003
+ const result = extractCorrelation(
1004
+ expr.args[0]!,
1005
+ expr.args[1]!,
1006
+ parentAliases,
1007
+ childAliases,
1008
+ )
1009
+ if (result) {
1010
+ parentRef = result.parentRef
1011
+ childRef = result.childRef
1012
+ correlationWhereIndex = i
1013
+ break
1014
+ }
1015
+ }
1016
+
1017
+ // Try inside top-level and()
1018
+ if (
1019
+ expr.type === `func` &&
1020
+ expr.name === `and` &&
1021
+ expr.args.length >= 2
1022
+ ) {
1023
+ for (let j = 0; j < expr.args.length; j++) {
1024
+ const arg = expr.args[j]!
1025
+ if (
1026
+ arg.type === `func` &&
1027
+ arg.name === `eq` &&
1028
+ arg.args.length === 2
1029
+ ) {
1030
+ const result = extractCorrelation(
1031
+ arg.args[0]!,
1032
+ arg.args[1]!,
1033
+ parentAliases,
1034
+ childAliases,
1035
+ )
1036
+ if (result) {
1037
+ parentRef = result.parentRef
1038
+ childRef = result.childRef
1039
+ correlationWhereIndex = i
1040
+ correlationAndArgIndex = j
1041
+ break
1042
+ }
1043
+ }
1044
+ }
1045
+ if (parentRef) break
1046
+ }
1047
+ }
1048
+ }
1049
+
1050
+ if (!parentRef || !childRef || correlationWhereIndex === -1) {
1051
+ throw new Error(
1052
+ `Includes subquery for "${fieldName}" must have a WHERE clause with an eq() condition ` +
1053
+ `that correlates a parent field with a child field. ` +
1054
+ `Example: .where(({child}) => eq(child.parentId, parent.id))`,
1055
+ )
1056
+ }
1057
+
1058
+ // Remove the correlation eq() from the child query's WHERE clauses.
1059
+ // If it was inside an and(), remove just that arg (collapsing the and() if needed).
1060
+ const modifiedWhere = [...childQuery.where!]
1061
+ if (correlationAndArgIndex >= 0) {
1062
+ const where = modifiedWhere[correlationWhereIndex]!
1063
+ const expr =
1064
+ typeof where === `object` && `expression` in where
1065
+ ? where.expression
1066
+ : where
1067
+ const remainingArgs = (expr as any).args.filter(
1068
+ (_: any, idx: number) => idx !== correlationAndArgIndex,
1069
+ )
1070
+ if (remainingArgs.length === 1) {
1071
+ // Collapse and() with single remaining arg to just that expression
1072
+ const isResidual =
1073
+ typeof where === `object` && `expression` in where && where.residual
1074
+ modifiedWhere[correlationWhereIndex] = isResidual
1075
+ ? { expression: remainingArgs[0], residual: true }
1076
+ : remainingArgs[0]
1077
+ } else {
1078
+ // Rebuild and() without the extracted arg
1079
+ const newAnd = new FuncExpr(`and`, remainingArgs)
1080
+ const isResidual =
1081
+ typeof where === `object` && `expression` in where && where.residual
1082
+ modifiedWhere[correlationWhereIndex] = isResidual
1083
+ ? { expression: newAnd, residual: true }
1084
+ : newAnd
1085
+ }
1086
+ } else {
1087
+ modifiedWhere.splice(correlationWhereIndex, 1)
1088
+ }
1089
+
1090
+ // Separate remaining WHEREs into pure-child vs parent-referencing
1091
+ const pureChildWhere: Array<Where> = []
1092
+ const parentFilters: Array<Where> = []
1093
+ for (const w of modifiedWhere) {
1094
+ if (referencesParent(w, parentAliases)) {
1095
+ parentFilters.push(w)
1096
+ } else {
1097
+ pureChildWhere.push(w)
1098
+ }
1099
+ }
1100
+
1101
+ // Collect distinct parent PropRefs from parent-referencing filters
1102
+ let parentProjection: Array<PropRef> | undefined
1103
+ if (parentFilters.length > 0) {
1104
+ const seen = new Set<string>()
1105
+ parentProjection = []
1106
+ for (const w of parentFilters) {
1107
+ const expr = typeof w === `object` && `expression` in w ? w.expression : w
1108
+ for (const ref of collectRefsFromExpression(expr)) {
1109
+ if (
1110
+ ref.path[0] != null &&
1111
+ parentAliases.includes(ref.path[0]) &&
1112
+ !seen.has(ref.path.join(`.`))
1113
+ ) {
1114
+ seen.add(ref.path.join(`.`))
1115
+ parentProjection.push(ref)
1116
+ }
1117
+ }
1118
+ }
1119
+ }
1120
+
1121
+ const modifiedQuery: QueryIR = {
1122
+ ...childQuery,
1123
+ where: pureChildWhere.length > 0 ? pureChildWhere : undefined,
1124
+ }
1125
+
1126
+ const rawChildSelect = modifiedQuery.select as any
1127
+ const hasObjectSelect =
1128
+ rawChildSelect === undefined || isPlainObject(rawChildSelect)
1129
+ let includesQuery = modifiedQuery
1130
+ let scalarField: string | undefined
1131
+
1132
+ if (materialization === `concat`) {
1133
+ if (rawChildSelect === undefined || hasObjectSelect) {
1134
+ throw new Error(
1135
+ `concat(toArray(...)) for "${fieldName}" requires the subquery to select a scalar value`,
1136
+ )
1137
+ }
1138
+ }
1139
+
1140
+ if (!hasObjectSelect) {
1141
+ if (materialization === `collection`) {
1142
+ throw new Error(
1143
+ `Includes subquery for "${fieldName}" must select an object when materializing as a Collection`,
1144
+ )
1145
+ }
1146
+
1147
+ scalarField = INCLUDES_SCALAR_FIELD
1148
+ includesQuery = {
1149
+ ...modifiedQuery,
1150
+ select: {
1151
+ [scalarField]: rawChildSelect,
1152
+ },
1153
+ }
1154
+ }
1155
+
1156
+ return new IncludesSubquery(
1157
+ includesQuery,
1158
+ parentRef,
1159
+ childRef,
1160
+ fieldName,
1161
+ parentFilters.length > 0 ? parentFilters : undefined,
1162
+ parentProjection,
1163
+ materialization,
1164
+ scalarField,
1165
+ )
1166
+ }
1167
+
1168
+ /**
1169
+ * Checks if two eq() arguments form a parent-child correlation.
1170
+ * Returns the parent and child PropRefs if found, undefined otherwise.
1171
+ */
1172
+ function extractCorrelation(
1173
+ argA: BasicExpression,
1174
+ argB: BasicExpression,
1175
+ parentAliases: Array<string>,
1176
+ childAliases: Array<string>,
1177
+ ): { parentRef: PropRef; childRef: PropRef } | undefined {
1178
+ if (argA.type === `ref` && argB.type === `ref`) {
1179
+ const aAlias = argA.path[0]
1180
+ const bAlias = argB.path[0]
1181
+
1182
+ if (
1183
+ aAlias &&
1184
+ bAlias &&
1185
+ parentAliases.includes(aAlias) &&
1186
+ childAliases.includes(bAlias)
1187
+ ) {
1188
+ return { parentRef: argA, childRef: argB }
1189
+ }
1190
+
1191
+ if (
1192
+ aAlias &&
1193
+ bAlias &&
1194
+ parentAliases.includes(bAlias) &&
1195
+ childAliases.includes(aAlias)
1196
+ ) {
1197
+ return { parentRef: argB, childRef: argA }
1198
+ }
1199
+ }
1200
+
1201
+ return undefined
1202
+ }
1203
+
884
1204
  // Internal function to build a query from a callback
885
1205
  // used by liveQueryCollectionOptions.query
886
1206
  export function buildQuery<TContext extends Context>(
@@ -1,6 +1,7 @@
1
1
  import { PropRef, Value } from '../ir.js'
2
2
  import type { BasicExpression } from '../ir.js'
3
3
  import type { RefLeaf } from './types.js'
4
+ import type { VirtualRowProps } from '../../virtual-props.js'
4
5
 
5
6
  export interface RefProxy<T = any> {
6
7
  /** @internal */
@@ -11,18 +12,35 @@ export interface RefProxy<T = any> {
11
12
  readonly __type: T
12
13
  }
13
14
 
15
+ /**
16
+ * Virtual properties available on all row ref proxies.
17
+ * These allow querying on sync status, origin, key, and collection ID.
18
+ */
19
+ export type VirtualPropsRefProxy<
20
+ TKey extends string | number = string | number,
21
+ > = {
22
+ readonly [K in keyof VirtualRowProps<TKey>]: RefLeaf<VirtualRowProps<TKey>[K]>
23
+ }
24
+
14
25
  /**
15
26
  * Type for creating a RefProxy for a single row/type without namespacing
16
27
  * Used in collection indexes and where clauses
28
+ *
29
+ * Includes virtual properties ($synced, $origin, $key, $collectionId) for
30
+ * querying on sync status and row metadata.
17
31
  */
18
- export type SingleRowRefProxy<T> =
32
+ export type SingleRowRefProxy<
33
+ T,
34
+ TKey extends string | number = string | number,
35
+ > =
19
36
  T extends Record<string, any>
20
37
  ? {
21
38
  [K in keyof T]: T[K] extends Record<string, any>
22
- ? SingleRowRefProxy<T[K]> & RefProxy<T[K]>
39
+ ? SingleRowRefProxy<T[K], TKey> & RefProxy<T[K]>
23
40
  : RefLeaf<T[K]>
24
- } & RefProxy<T>
25
- : RefProxy<T>
41
+ } & RefProxy<T> &
42
+ VirtualPropsRefProxy<TKey>
43
+ : RefProxy<T> & VirtualPropsRefProxy<TKey>
26
44
 
27
45
  /**
28
46
  * Creates a proxy object that records property access paths for a single row