@noy-db/hub 0.1.0-pre.14 → 0.1.0-pre.16

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 (232) hide show
  1. package/dist/aggregate/index.cjs +91 -36
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +15 -8
  6. package/dist/aggregate/index.js.map +1 -1
  7. package/dist/blobs/index.cjs.map +1 -1
  8. package/dist/blobs/index.d.cts +5 -4
  9. package/dist/blobs/index.d.ts +5 -4
  10. package/dist/blobs/index.js +4 -4
  11. package/dist/bundle/index.cjs +214 -7
  12. package/dist/bundle/index.cjs.map +1 -1
  13. package/dist/bundle/index.d.cts +5 -4
  14. package/dist/bundle/index.d.ts +5 -4
  15. package/dist/bundle/index.js +6 -4
  16. package/dist/{chunk-CX2S2IBB.js → chunk-23TTQXVO.js} +53 -79
  17. package/dist/chunk-23TTQXVO.js.map +1 -0
  18. package/dist/{chunk-WLZWITX3.js → chunk-2AXFIYHT.js} +1 -1
  19. package/dist/chunk-2AXFIYHT.js.map +1 -0
  20. package/dist/{chunk-SBOSKCXD.js → chunk-2R6G2WST.js} +209 -13
  21. package/dist/chunk-2R6G2WST.js.map +1 -0
  22. package/dist/{chunk-HENWE2QQ.js → chunk-34YSDCDP.js} +2 -2
  23. package/dist/{chunk-3J5PYYUL.js → chunk-5DWL3JBF.js} +2 -2
  24. package/dist/{chunk-562AT3V6.js → chunk-5NGHX6ME.js} +5 -5
  25. package/dist/chunk-5ZGZ6HIZ.js +100 -0
  26. package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
  27. package/dist/{chunk-ZJICDHNH.js → chunk-6HPZY4ON.js} +5 -4
  28. package/dist/chunk-6HPZY4ON.js.map +1 -0
  29. package/dist/{chunk-LTKFPLTW.js → chunk-A3K2ECAM.js} +192 -11
  30. package/dist/chunk-A3K2ECAM.js.map +1 -0
  31. package/dist/{chunk-W7M3E5M5.js → chunk-ADQ5MQ54.js} +48 -1
  32. package/dist/{chunk-W7M3E5M5.js.map → chunk-ADQ5MQ54.js.map} +1 -1
  33. package/dist/{chunk-Q3OWJPJI.js → chunk-DPMFBCV6.js} +32 -19
  34. package/dist/chunk-DPMFBCV6.js.map +1 -0
  35. package/dist/{chunk-AD4FMCXZ.js → chunk-DYBQG5PQ.js} +2 -2
  36. package/dist/{chunk-PNTWORBA.js → chunk-DYECX3IX.js} +2 -2
  37. package/dist/chunk-EGQYGYIU.js +51 -0
  38. package/dist/chunk-EGQYGYIU.js.map +1 -0
  39. package/dist/{chunk-RGHN6WV7.js → chunk-FCXOFQAJ.js} +2 -2
  40. package/dist/{chunk-PMMNH7VZ.js → chunk-H7XROQJM.js} +1 -1
  41. package/dist/chunk-H7XROQJM.js.map +1 -0
  42. package/dist/chunk-HB3Z2GCR.js +124 -0
  43. package/dist/chunk-HB3Z2GCR.js.map +1 -0
  44. package/dist/{chunk-CRNMA2FB.js → chunk-I6MX32UC.js} +4 -4
  45. package/dist/{chunk-PHSGPPV7.js → chunk-KESP7GOK.js} +3 -3
  46. package/dist/{chunk-FB34MDSJ.js → chunk-KJJKHKFP.js} +4 -4
  47. package/dist/{chunk-J3E2UMET.js → chunk-MIQHZESA.js} +3 -3
  48. package/dist/{chunk-TNYWH7Z2.js → chunk-MKSA2V7A.js} +2 -2
  49. package/dist/{chunk-E4ZSYZSN.js → chunk-NVBUGT76.js} +4 -4
  50. package/dist/{chunk-XFVOTOYQ.js → chunk-O4RHR7FV.js} +6 -6
  51. package/dist/{chunk-EEEFQTYN.js → chunk-OMLIZL2P.js} +2 -2
  52. package/dist/{chunk-GVPFL6XF.js → chunk-P7EQ2S5O.js} +2 -2
  53. package/dist/{chunk-E3VAKWJ7.js → chunk-QQUUAXDY.js} +7 -6
  54. package/dist/chunk-QQUUAXDY.js.map +1 -0
  55. package/dist/chunk-RD5LYKD6.js +82 -0
  56. package/dist/chunk-RD5LYKD6.js.map +1 -0
  57. package/dist/{chunk-7QGJ7RTL.js → chunk-RRMDLTIL.js} +4 -4
  58. package/dist/{chunk-432R4RDC.js → chunk-SIZWEV2Y.js} +35 -5
  59. package/dist/chunk-SIZWEV2Y.js.map +1 -0
  60. package/dist/{chunk-U7YDBNFC.js → chunk-UZXLQCHP.js} +2 -2
  61. package/dist/{chunk-PPJA7KL6.js → chunk-V6SGP2KX.js} +3 -3
  62. package/dist/{chunk-7AQKLLI5.js → chunk-WCA2NROQ.js} +2 -2
  63. package/dist/{chunk-ERV4AXVO.js → chunk-XGSOTWYX.js} +90 -131
  64. package/dist/chunk-XGSOTWYX.js.map +1 -0
  65. package/dist/{chunk-5Y4AKEZ6.js → chunk-Z72JH4KG.js} +2 -2
  66. package/dist/{chunk-IXFP66OV.js → chunk-ZNOEIM6Y.js} +2 -2
  67. package/dist/consent/index.cjs.map +1 -1
  68. package/dist/consent/index.d.cts +5 -4
  69. package/dist/consent/index.d.ts +5 -4
  70. package/dist/consent/index.js +3 -3
  71. package/dist/{crypto-VTEMMJK6.js → crypto-A7FRXYHC.js} +3 -3
  72. package/dist/{delegation-HYQULPZR.js → delegation-YBA4X4JN.js} +5 -5
  73. package/dist/derivations/index.cjs +97 -1
  74. package/dist/derivations/index.cjs.map +1 -1
  75. package/dist/derivations/index.d.cts +37 -10
  76. package/dist/derivations/index.d.ts +37 -10
  77. package/dist/derivations/index.js +6 -4
  78. package/dist/{dev-unlock-CpJ9rHW2.d.ts → dev-unlock-C8u6MRfS.d.ts} +1 -1
  79. package/dist/{dev-unlock-DEUUpWoR.d.cts → dev-unlock-gBjwzB6A.d.cts} +1 -1
  80. package/dist/executor-7E3VFGW7.js +11 -0
  81. package/dist/executor-CEWX2FQI.js +8 -0
  82. package/dist/executor-X4SQ3ZLC.js +8 -0
  83. package/dist/fanout-sidecar-ACJNXFU6.js +51 -0
  84. package/dist/fanout-sidecar-ACJNXFU6.js.map +1 -0
  85. package/dist/guards/index.cjs.map +1 -1
  86. package/dist/guards/index.d.cts +6 -5
  87. package/dist/guards/index.d.ts +6 -5
  88. package/dist/guards/index.js +6 -6
  89. package/dist/{hash-_9VyW44M.d.cts → hash-BXy5DuTD.d.cts} +1 -1
  90. package/dist/{hash-CJ9LfuY5.d.ts → hash-C6KOPK9Q.d.ts} +1 -1
  91. package/dist/history/index.cjs +2 -1
  92. package/dist/history/index.cjs.map +1 -1
  93. package/dist/history/index.d.cts +6 -5
  94. package/dist/history/index.d.ts +6 -5
  95. package/dist/history/index.js +6 -6
  96. package/dist/i18n/index.cjs.map +1 -1
  97. package/dist/i18n/index.d.cts +5 -4
  98. package/dist/i18n/index.d.ts +5 -4
  99. package/dist/i18n/index.js +6 -6
  100. package/dist/{index-s31g3m-D.d.cts → index-BA7A-MJs.d.cts} +96 -4
  101. package/dist/{index-BWAXR9tV.d.ts → index-CLK67MZE.d.ts} +96 -4
  102. package/dist/{index-7nd15fgy.d.ts → index-CNwA-B6-.d.ts} +70 -2
  103. package/dist/{index-CFJKJXWa.d.cts → index-CmVgTkqk.d.cts} +70 -2
  104. package/dist/index.cjs +2400 -571
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.d.cts +128 -18
  107. package/dist/index.d.ts +128 -18
  108. package/dist/index.js +1217 -107
  109. package/dist/index.js.map +1 -1
  110. package/dist/indexing/index.cjs.map +1 -1
  111. package/dist/indexing/index.js +2 -2
  112. package/dist/{ledger-LHLIVPJ3.js → ledger-GHIZQLKR.js} +6 -6
  113. package/dist/materialized-views/index.cjs +255 -21
  114. package/dist/materialized-views/index.cjs.map +1 -1
  115. package/dist/materialized-views/index.d.cts +7 -6
  116. package/dist/materialized-views/index.d.ts +7 -6
  117. package/dist/materialized-views/index.js +9 -5
  118. package/dist/overlay-views/index.cjs.map +1 -1
  119. package/dist/overlay-views/index.d.cts +6 -5
  120. package/dist/overlay-views/index.d.ts +6 -5
  121. package/dist/overlay-views/index.js +3 -3
  122. package/dist/periods/index.cjs +2 -1
  123. package/dist/periods/index.cjs.map +1 -1
  124. package/dist/periods/index.d.cts +5 -4
  125. package/dist/periods/index.d.ts +5 -4
  126. package/dist/periods/index.js +6 -6
  127. package/dist/{public-envelope-66ZBXVXK.js → public-envelope-CRJAVW3E.js} +4 -4
  128. package/dist/query/index.cjs +139 -113
  129. package/dist/query/index.cjs.map +1 -1
  130. package/dist/query/index.d.cts +2 -2
  131. package/dist/query/index.d.ts +2 -2
  132. package/dist/query/index.js +13 -9
  133. package/dist/{registry-JYALMHGM.js → registry-3L3N3PTG.js} +3 -3
  134. package/dist/registry-O47PUPSY.js +8 -0
  135. package/dist/registry-WLLMODKN.js +8 -0
  136. package/dist/session/index.cjs.map +1 -1
  137. package/dist/session/index.d.cts +6 -5
  138. package/dist/session/index.d.ts +6 -5
  139. package/dist/session/index.js +3 -3
  140. package/dist/shadow/index.cjs.map +1 -1
  141. package/dist/shadow/index.d.cts +5 -4
  142. package/dist/shadow/index.d.ts +5 -4
  143. package/dist/shadow/index.js +2 -2
  144. package/dist/{stale-EET5YFYL.js → stale-HSC5YO2O.js} +2 -2
  145. package/dist/store/index.cjs.map +1 -1
  146. package/dist/store/index.d.cts +5 -4
  147. package/dist/store/index.d.ts +5 -4
  148. package/dist/store/index.js +2 -2
  149. package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
  150. package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
  151. package/dist/sync/index.cjs.map +1 -1
  152. package/dist/sync/index.d.cts +4 -3
  153. package/dist/sync/index.d.ts +4 -3
  154. package/dist/sync/index.js +4 -4
  155. package/dist/team/index.cjs +136 -6
  156. package/dist/team/index.cjs.map +1 -1
  157. package/dist/team/index.d.cts +5 -4
  158. package/dist/team/index.d.ts +5 -4
  159. package/dist/team/index.js +7 -7
  160. package/dist/tx/index.cjs +2 -1
  161. package/dist/tx/index.cjs.map +1 -1
  162. package/dist/tx/index.d.cts +5 -4
  163. package/dist/tx/index.d.ts +5 -4
  164. package/dist/tx/index.js +2 -2
  165. package/dist/{types-2JGVRzO2.d.cts → types-DDy02bWN.d.cts} +1159 -257
  166. package/dist/{types-DIOLYPUq.d.ts → types-DImsOlJe.d.ts} +1159 -257
  167. package/dist/util/index.cjs.map +1 -1
  168. package/dist/util/index.js +1 -1
  169. package/dist/{with-derivation-ClhfQvRa.d.ts → with-derivation-3AdLeSw8.d.ts} +1 -1
  170. package/dist/{with-derivation-3JX8SMxY.d.cts → with-derivation-CkCHx_J8.d.cts} +1 -1
  171. package/dist/{with-guard-Br3YPxRW.d.ts → with-guard-Br9zLEdi.d.ts} +1 -1
  172. package/dist/{with-guard-fJVfWBLs.d.cts → with-guard-DtKsFExw.d.cts} +1 -1
  173. package/dist/with-materialized-view-CMhEX4FK.d.cts +27 -0
  174. package/dist/with-materialized-view-Zal2bE1T.d.ts +27 -0
  175. package/dist/{with-overlayed-view-BLUKxdiJ.d.cts → with-overlayed-view-CA6zHrBt.d.cts} +1 -1
  176. package/dist/{with-overlayed-view-CtvJxhU3.d.ts → with-overlayed-view-R4BNG9OB.d.ts} +1 -1
  177. package/package.json +14 -2
  178. package/dist/chunk-432R4RDC.js.map +0 -1
  179. package/dist/chunk-CX2S2IBB.js.map +0 -1
  180. package/dist/chunk-E3VAKWJ7.js.map +0 -1
  181. package/dist/chunk-ERV4AXVO.js.map +0 -1
  182. package/dist/chunk-GV47JHIF.js +0 -29
  183. package/dist/chunk-GV47JHIF.js.map +0 -1
  184. package/dist/chunk-LTKFPLTW.js.map +0 -1
  185. package/dist/chunk-PMMNH7VZ.js.map +0 -1
  186. package/dist/chunk-Q2L5ZYX7.js +0 -66
  187. package/dist/chunk-Q2L5ZYX7.js.map +0 -1
  188. package/dist/chunk-Q3OWJPJI.js.map +0 -1
  189. package/dist/chunk-SBK6CETA.js +0 -30
  190. package/dist/chunk-SBK6CETA.js.map +0 -1
  191. package/dist/chunk-SBOSKCXD.js.map +0 -1
  192. package/dist/chunk-WLZWITX3.js.map +0 -1
  193. package/dist/chunk-ZJICDHNH.js.map +0 -1
  194. package/dist/executor-3H3NSIVZ.js +0 -8
  195. package/dist/executor-7ZOXGY7P.js +0 -8
  196. package/dist/executor-A5LTUAG2.js +0 -9
  197. package/dist/registry-J2UI37XJ.js +0 -8
  198. package/dist/registry-UMAL72WL.js +0 -8
  199. package/dist/with-materialized-view-CHho83hN.d.cts +0 -15
  200. package/dist/with-materialized-view-CgPphnza.d.ts +0 -15
  201. /package/dist/{chunk-HENWE2QQ.js.map → chunk-34YSDCDP.js.map} +0 -0
  202. /package/dist/{chunk-3J5PYYUL.js.map → chunk-5DWL3JBF.js.map} +0 -0
  203. /package/dist/{chunk-562AT3V6.js.map → chunk-5NGHX6ME.js.map} +0 -0
  204. /package/dist/{chunk-AD4FMCXZ.js.map → chunk-DYBQG5PQ.js.map} +0 -0
  205. /package/dist/{chunk-PNTWORBA.js.map → chunk-DYECX3IX.js.map} +0 -0
  206. /package/dist/{chunk-RGHN6WV7.js.map → chunk-FCXOFQAJ.js.map} +0 -0
  207. /package/dist/{chunk-CRNMA2FB.js.map → chunk-I6MX32UC.js.map} +0 -0
  208. /package/dist/{chunk-PHSGPPV7.js.map → chunk-KESP7GOK.js.map} +0 -0
  209. /package/dist/{chunk-FB34MDSJ.js.map → chunk-KJJKHKFP.js.map} +0 -0
  210. /package/dist/{chunk-J3E2UMET.js.map → chunk-MIQHZESA.js.map} +0 -0
  211. /package/dist/{chunk-TNYWH7Z2.js.map → chunk-MKSA2V7A.js.map} +0 -0
  212. /package/dist/{chunk-E4ZSYZSN.js.map → chunk-NVBUGT76.js.map} +0 -0
  213. /package/dist/{chunk-XFVOTOYQ.js.map → chunk-O4RHR7FV.js.map} +0 -0
  214. /package/dist/{chunk-EEEFQTYN.js.map → chunk-OMLIZL2P.js.map} +0 -0
  215. /package/dist/{chunk-GVPFL6XF.js.map → chunk-P7EQ2S5O.js.map} +0 -0
  216. /package/dist/{chunk-7QGJ7RTL.js.map → chunk-RRMDLTIL.js.map} +0 -0
  217. /package/dist/{chunk-U7YDBNFC.js.map → chunk-UZXLQCHP.js.map} +0 -0
  218. /package/dist/{chunk-PPJA7KL6.js.map → chunk-V6SGP2KX.js.map} +0 -0
  219. /package/dist/{chunk-7AQKLLI5.js.map → chunk-WCA2NROQ.js.map} +0 -0
  220. /package/dist/{chunk-5Y4AKEZ6.js.map → chunk-Z72JH4KG.js.map} +0 -0
  221. /package/dist/{chunk-IXFP66OV.js.map → chunk-ZNOEIM6Y.js.map} +0 -0
  222. /package/dist/{crypto-VTEMMJK6.js.map → crypto-A7FRXYHC.js.map} +0 -0
  223. /package/dist/{delegation-HYQULPZR.js.map → delegation-YBA4X4JN.js.map} +0 -0
  224. /package/dist/{executor-3H3NSIVZ.js.map → executor-7E3VFGW7.js.map} +0 -0
  225. /package/dist/{executor-7ZOXGY7P.js.map → executor-CEWX2FQI.js.map} +0 -0
  226. /package/dist/{executor-A5LTUAG2.js.map → executor-X4SQ3ZLC.js.map} +0 -0
  227. /package/dist/{ledger-LHLIVPJ3.js.map → ledger-GHIZQLKR.js.map} +0 -0
  228. /package/dist/{public-envelope-66ZBXVXK.js.map → public-envelope-CRJAVW3E.js.map} +0 -0
  229. /package/dist/{registry-J2UI37XJ.js.map → registry-3L3N3PTG.js.map} +0 -0
  230. /package/dist/{registry-JYALMHGM.js.map → registry-O47PUPSY.js.map} +0 -0
  231. /package/dist/{registry-UMAL72WL.js.map → registry-WLLMODKN.js.map} +0 -0
  232. /package/dist/{stale-EET5YFYL.js.map → stale-HSC5YO2O.js.map} +0 -0
@@ -7,12 +7,12 @@ import {
7
7
  decodeIdxId,
8
8
  encodeIdxId,
9
9
  isIdxId
10
- } from "../chunk-PNTWORBA.js";
10
+ } from "../chunk-DYECX3IX.js";
11
11
  import {
12
12
  CollectionIndexes
13
13
  } from "../chunk-YMYK7US4.js";
14
14
  import "../chunk-MRIBLZL3.js";
15
- import "../chunk-W7M3E5M5.js";
15
+ import "../chunk-ADQ5MQ54.js";
16
16
 
17
17
  // src/indexing/active.ts
18
18
  function withIndexing() {
@@ -5,7 +5,7 @@ import {
5
5
  LedgerStore,
6
6
  applyPatch,
7
7
  computePatch
8
- } from "./chunk-E3VAKWJ7.js";
8
+ } from "./chunk-QQUUAXDY.js";
9
9
  import {
10
10
  canonicalJson,
11
11
  envelopePayloadHash,
@@ -13,10 +13,10 @@ import {
13
13
  paddedIndex,
14
14
  parseIndex,
15
15
  sha256Hex
16
- } from "./chunk-WLZWITX3.js";
17
- import "./chunk-PMMNH7VZ.js";
18
- import "./chunk-7AQKLLI5.js";
19
- import "./chunk-W7M3E5M5.js";
16
+ } from "./chunk-2AXFIYHT.js";
17
+ import "./chunk-H7XROQJM.js";
18
+ import "./chunk-WCA2NROQ.js";
19
+ import "./chunk-ADQ5MQ54.js";
20
20
  export {
21
21
  LEDGER_COLLECTION,
22
22
  LEDGER_DELTAS_COLLECTION,
@@ -30,4 +30,4 @@ export {
30
30
  parseIndex,
31
31
  sha256Hex
32
32
  };
33
- //# sourceMappingURL=ledger-LHLIVPJ3.js.map
33
+ //# sourceMappingURL=ledger-GHIZQLKR.js.map
@@ -21,7 +21,7 @@ var __copyProps = (to, from, except, desc) => {
21
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
22
 
23
23
  // src/errors.ts
24
- var NoydbError, ValidationError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError;
24
+ var NoydbError, ValidationError, GroupCardinalityError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError;
25
25
  var init_errors = __esm({
26
26
  "src/errors.ts"() {
27
27
  "use strict";
@@ -40,6 +40,24 @@ var init_errors = __esm({
40
40
  this.name = "ValidationError";
41
41
  }
42
42
  };
43
+ GroupCardinalityError = class extends NoydbError {
44
+ /** The field being grouped on. */
45
+ field;
46
+ /** Observed number of distinct groups at the moment the cap tripped. */
47
+ cardinality;
48
+ /** The cap that was exceeded. */
49
+ maxGroups;
50
+ constructor(field, cardinality, maxGroups) {
51
+ super(
52
+ "GROUP_CARDINALITY",
53
+ `.groupBy("${field}") produced ${cardinality} distinct groups, exceeding the ${maxGroups}-group ceiling. This is almost always a query mistake \u2014 grouping on a high-uniqueness field like "id" or "createdAt" produces one bucket per record. Narrow the query with .where() before grouping, or group on a lower-cardinality field (status, category, clientId). If you genuinely need high-cardinality grouping, file an issue with your use case.`
54
+ );
55
+ this.name = "GroupCardinalityError";
56
+ this.field = field;
57
+ this.cardinality = cardinality;
58
+ this.maxGroups = maxGroups;
59
+ }
60
+ };
43
61
  MaterializedViewCycleError = class extends NoydbError {
44
62
  path;
45
63
  constructor(path) {
@@ -79,6 +97,15 @@ var init_errors = __esm({
79
97
  this.limit = limit;
80
98
  }
81
99
  };
100
+ MaterializedViewConfigError = class extends NoydbError {
101
+ constructor(message) {
102
+ super(
103
+ "MATERIALIZED_VIEW_CONFIG",
104
+ `[noy-db] withMaterializedView: ${message}`
105
+ );
106
+ this.name = "MaterializedViewConfigError";
107
+ }
108
+ };
82
109
  }
83
110
  });
84
111
 
@@ -115,6 +142,12 @@ function summarizeQueryPlan(query) {
115
142
  joins: plan.joins.map((j) => ({ field: j.field, as: j.as, target: j.target, mode: j.mode }))
116
143
  });
117
144
  }
145
+ function summarizeUnionPlan(spec) {
146
+ const arms = (spec.unionSources ?? []).map((s) => s.collection).join(",");
147
+ const groupBy = Array.isArray(spec.groupBy) ? [...spec.groupBy].sort().join(",") : typeof spec.groupBy === "string" ? spec.groupBy : "";
148
+ const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(",") : "";
149
+ return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})`;
150
+ }
118
151
  var init_dependency_analyzer = __esm({
119
152
  "src/materialized-views/dependency-analyzer.ts"() {
120
153
  "use strict";
@@ -241,27 +274,34 @@ var init_registry = __esm({
241
274
  */
242
275
  async register(spec, db, options) {
243
276
  const dbForQuery = spec.predicates ? wrapDbWithPredicates(db, spec.predicates) : db;
244
- const q = spec.query(dbForQuery);
245
- const qAny = q;
246
- const isQuery = typeof qAny._plan === "function";
247
277
  let dependencies;
248
278
  let queryPlanSummary;
249
- if (isQuery) {
250
- dependencies = analyzeDependencies(q);
251
- queryPlanSummary = summarizeQueryPlan(q);
252
- const predicateRefs = extractPredicateRefs(qAny._plan());
253
- if (predicateRefs.length > 0) {
254
- queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs });
255
- }
256
- if (spec.sources) for (const s of spec.sources) dependencies.add(s);
279
+ let qAny = null;
280
+ let isQuery = false;
281
+ if (spec.unionSources) {
282
+ dependencies = new Set(spec.unionSources.map((s) => s.collection));
283
+ queryPlanSummary = summarizeUnionPlan(spec);
257
284
  } else {
258
- if (!spec.sources || spec.sources.length === 0) {
259
- throw new Error(
260
- `withMaterializedView "${spec.name}": query() returned an aggregate (Aggregation or GroupedAggregation) but no \`sources\` field is declared. The dependency analyzer cannot walk through groupBy().aggregate() back to the source \u2014 declare sources: [...] explicitly.`
261
- );
285
+ const q = spec.query(dbForQuery);
286
+ qAny = q;
287
+ isQuery = typeof qAny._plan === "function";
288
+ if (isQuery) {
289
+ dependencies = analyzeDependencies(q);
290
+ queryPlanSummary = summarizeQueryPlan(q);
291
+ const predicateRefs = extractPredicateRefs(qAny._plan());
292
+ if (predicateRefs.length > 0) {
293
+ queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs });
294
+ }
295
+ if (spec.sources) for (const s of spec.sources) dependencies.add(s);
296
+ } else {
297
+ if (!spec.sources || spec.sources.length === 0) {
298
+ throw new Error(
299
+ `withMaterializedView "${spec.name}": query() returned an aggregate (Aggregation or GroupedAggregation) but no \`sources\` field is declared. The dependency analyzer cannot walk through groupBy().aggregate() back to the source \u2014 declare sources: [...] explicitly.`
300
+ );
301
+ }
302
+ dependencies = new Set(spec.sources);
303
+ queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() });
262
304
  }
263
- dependencies = new Set(spec.sources);
264
- queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() });
265
305
  }
266
306
  if (options?.knownCollections) {
267
307
  for (const dep of dependencies) {
@@ -370,6 +410,119 @@ var init_registry = __esm({
370
410
  }
371
411
  });
372
412
 
413
+ // src/query/predicate.ts
414
+ function readPath(record, path) {
415
+ if (record === null || record === void 0) return void 0;
416
+ if (!path.includes(".")) {
417
+ return record[path];
418
+ }
419
+ const segments = path.split(".");
420
+ let cursor = record;
421
+ for (const segment of segments) {
422
+ if (cursor === null || cursor === void 0) return void 0;
423
+ cursor = cursor[segment];
424
+ }
425
+ return cursor;
426
+ }
427
+ var init_predicate = __esm({
428
+ "src/query/predicate.ts"() {
429
+ "use strict";
430
+ }
431
+ });
432
+
433
+ // src/aggregate/canonical-key.ts
434
+ function canonicalGroupKey(fields, row) {
435
+ const sorted = [...fields].sort();
436
+ const parts = [];
437
+ for (const name of sorted) {
438
+ const v = row[name];
439
+ const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
440
+ parts.push(`${name}=${serialised}`);
441
+ }
442
+ return parts.join("|");
443
+ }
444
+ var init_canonical_key = __esm({
445
+ "src/aggregate/canonical-key.ts"() {
446
+ "use strict";
447
+ }
448
+ });
449
+
450
+ // src/aggregate/groupby.ts
451
+ function warnCardinalityApproaching(fields, observed) {
452
+ const key = JSON.stringify([...fields].sort());
453
+ if (warnedCardinalityFields.has(key)) return;
454
+ warnedCardinalityFields.add(key);
455
+ const label = `[${fields.join(", ")}]`;
456
+ console.warn(
457
+ `[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ${Math.round(observed / GROUPBY_MAX_CARDINALITY * 100)}% of the ${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with .where() before grouping, or switch to a lower-cardinality field.`
458
+ );
459
+ }
460
+ function groupAndReduce(records, fieldOrFields, spec) {
461
+ const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
462
+ if (fields.length === 0) {
463
+ throw new Error(".groupBy() requires at least one field");
464
+ }
465
+ const buckets = /* @__PURE__ */ new Map();
466
+ const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
467
+ for (const record of records) {
468
+ const keyValues = {};
469
+ for (const f of fields) {
470
+ keyValues[f] = readPath(record, f);
471
+ }
472
+ const dedupKey = canonicalGroupKey(fields, keyValues);
473
+ let bucket = buckets.get(dedupKey);
474
+ if (bucket === void 0) {
475
+ if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
476
+ throw new GroupCardinalityError(
477
+ fieldLabel,
478
+ buckets.size + 1,
479
+ GROUPBY_MAX_CARDINALITY
480
+ );
481
+ }
482
+ bucket = { keyValues, records: [] };
483
+ buckets.set(dedupKey, bucket);
484
+ }
485
+ bucket.records.push(record);
486
+ }
487
+ if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
488
+ warnCardinalityApproaching(fields, buckets.size);
489
+ }
490
+ const reducerKeys = Object.keys(spec);
491
+ const out = [];
492
+ for (const bucket of buckets.values()) {
493
+ const state = {};
494
+ for (const rk of reducerKeys) {
495
+ state[rk] = spec[rk].init();
496
+ }
497
+ for (const record of bucket.records) {
498
+ for (const rk of reducerKeys) {
499
+ state[rk] = spec[rk].step(state[rk], record);
500
+ }
501
+ }
502
+ const row = {};
503
+ for (const f of fields) {
504
+ row[f] = bucket.keyValues[f];
505
+ }
506
+ for (const rk of reducerKeys) {
507
+ row[rk] = spec[rk].finalize(state[rk]);
508
+ }
509
+ out.push(row);
510
+ }
511
+ return out;
512
+ }
513
+ var GROUPBY_WARN_CARDINALITY, GROUPBY_MAX_CARDINALITY, warnedCardinalityFields;
514
+ var init_groupby = __esm({
515
+ "src/aggregate/groupby.ts"() {
516
+ "use strict";
517
+ init_predicate();
518
+ init_canonical_key();
519
+ init_errors();
520
+ GROUPBY_WARN_CARDINALITY = 1e4;
521
+ GROUPBY_MAX_CARDINALITY = 1e5;
522
+ warnedCardinalityFields = /* @__PURE__ */ new Set();
523
+ }
524
+ });
525
+
373
526
  // src/materialized-views/executor.ts
374
527
  var executor_exports = {};
375
528
  __export(executor_exports, {
@@ -390,6 +543,27 @@ async function materializeQueryResult(q, mvName) {
390
543
  `MV "${mvName}": query() must return a Query<T>, Aggregation, or GroupedAggregation. Got something without a .toArray() or .run() terminal.`
391
544
  );
392
545
  }
546
+ async function materializeUnionResult(spec, db) {
547
+ const unified = [];
548
+ for (const arm of spec.unionSources) {
549
+ const coll = db.collection(arm.collection);
550
+ const sourceRows = coll.query().toArray();
551
+ for (const r of sourceRows) {
552
+ unified.push(arm.map(r));
553
+ }
554
+ }
555
+ if (!spec.groupBy) return unified;
556
+ const groupFields = typeof spec.groupBy === "string" ? [spec.groupBy] : spec.groupBy;
557
+ if (!spec.aggregate) {
558
+ const seen = /* @__PURE__ */ new Map();
559
+ for (const row of unified) {
560
+ const k = canonicalGroupKey(groupFields, row);
561
+ if (!seen.has(k)) seen.set(k, row);
562
+ }
563
+ return [...seen.values()];
564
+ }
565
+ return groupAndReduce(unified, groupFields, spec.aggregate);
566
+ }
393
567
  async function listOutputIds(outputColl) {
394
568
  const cAny = outputColl;
395
569
  const adapter = cAny.adapter;
@@ -409,6 +583,8 @@ var init_executor = __esm({
409
583
  "use strict";
410
584
  init_errors();
411
585
  init_registry();
586
+ init_groupby();
587
+ init_canonical_key();
412
588
  DEFAULT_MAX_ROWS = 1e5;
413
589
  MaterializedViewExecutor = {
414
590
  async refresh(reg, accessor) {
@@ -419,8 +595,13 @@ var init_executor = __esm({
419
595
  const strict = spec.strict ?? false;
420
596
  const baseCtx = accessor.getQueryContext();
421
597
  const ctxForQuery = spec.predicates ? wrapDbWithPredicates(baseCtx, spec.predicates) : baseCtx;
422
- const q = spec.query(ctxForQuery);
423
- const rows = await materializeQueryResult(q, spec.name);
598
+ let rows;
599
+ if (spec.unionSources) {
600
+ rows = await materializeUnionResult(spec, ctxForQuery);
601
+ } else {
602
+ const q = spec.query(ctxForQuery);
603
+ rows = await materializeQueryResult(q, spec.name);
604
+ }
424
605
  if (rows.length > maxRows) {
425
606
  throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows);
426
607
  }
@@ -489,6 +670,7 @@ var init_executor = __esm({
489
670
  // src/materialized-views/index.ts
490
671
  var materialized_views_exports = {};
491
672
  __export(materialized_views_exports, {
673
+ MaterializedViewConfigError: () => MaterializedViewConfigError,
492
674
  MaterializedViewCycleError: () => MaterializedViewCycleError,
493
675
  MaterializedViewExecutor: () => MaterializedViewExecutor,
494
676
  MaterializedViewRegistry: () => MaterializedViewRegistry,
@@ -512,9 +694,60 @@ function withMaterializedView(spec) {
512
694
  if (!spec.name || spec.name.length === 0) {
513
695
  throw new ValidationError("withMaterializedView: name is required");
514
696
  }
515
- if (typeof spec.query !== "function") {
697
+ if (spec.query && spec.unionSources) {
698
+ throw new MaterializedViewConfigError(
699
+ "query and unionSources are mutually exclusive \u2014 pick one"
700
+ );
701
+ }
702
+ if (!spec.query && !spec.unionSources) {
703
+ throw new MaterializedViewConfigError(
704
+ "strategy must declare either query or unionSources"
705
+ );
706
+ }
707
+ if (spec.query !== void 0 && typeof spec.query !== "function") {
516
708
  throw new ValidationError("withMaterializedView: query must be a function returning a Query<T>");
517
709
  }
710
+ if (spec.unionSources) {
711
+ if (spec.unionSources.length < 2) {
712
+ throw new MaterializedViewConfigError(
713
+ "unionSources requires at least 2 source collections"
714
+ );
715
+ }
716
+ const seen = /* @__PURE__ */ new Set();
717
+ for (const s of spec.unionSources) {
718
+ if (typeof s?.collection !== "string" || s.collection.length === 0) {
719
+ throw new MaterializedViewConfigError(
720
+ "each unionSources entry must declare a non-empty `collection` string"
721
+ );
722
+ }
723
+ if (typeof s.map !== "function") {
724
+ throw new MaterializedViewConfigError(
725
+ `unionSources entry for "${s.collection}" is missing a \`map\` function`
726
+ );
727
+ }
728
+ if (seen.has(s.collection)) {
729
+ throw new MaterializedViewConfigError(
730
+ `unionSources must reference distinct collections (duplicate: "${s.collection}")`
731
+ );
732
+ }
733
+ seen.add(s.collection);
734
+ }
735
+ if (Array.isArray(spec.groupBy) && spec.groupBy.length === 0) {
736
+ throw new MaterializedViewConfigError(
737
+ `withMaterializedView "${spec.name}": groupBy must not be an empty array \u2014 omit it or provide at least one field name`
738
+ );
739
+ }
740
+ if (spec.aggregate && !spec.groupBy) {
741
+ throw new MaterializedViewConfigError(
742
+ `withMaterializedView "${spec.name}": UNION strategy with aggregate requires groupBy \u2014 use groupBy to declare the bucketing keys, or remove aggregate for a pure dedup MV`
743
+ );
744
+ }
745
+ if (spec.predicates) {
746
+ throw new MaterializedViewConfigError(
747
+ `withMaterializedView "${spec.name}": predicates are not supported on UNION strategies \u2014 UNION mode does not use a Query<T> chain, so .wherePredicate() cannot fire. Use the query() form, or open an issue if per-arm predicates are needed`
748
+ );
749
+ }
750
+ }
518
751
  if (typeof spec.rowKey !== "function") {
519
752
  throw new ValidationError("withMaterializedView: rowKey is required (no default; see spec \xA7 Type surface)");
520
753
  }
@@ -585,6 +818,7 @@ function clearMVStale(registry, mvName) {
585
818
  init_errors();
586
819
  // Annotate the CommonJS export names for ESM import in node:
587
820
  0 && (module.exports = {
821
+ MaterializedViewConfigError,
588
822
  MaterializedViewCycleError,
589
823
  MaterializedViewExecutor,
590
824
  MaterializedViewRegistry,