@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
@@ -25,6 +25,7 @@ __export(aggregate_exports, {
25
25
  GROUPBY_WARN_CARDINALITY: () => GROUPBY_WARN_CARDINALITY,
26
26
  GroupedAggregation: () => GroupedAggregation,
27
27
  GroupedQuery: () => GroupedQuery,
28
+ GroupedQueryN: () => GroupedQueryN,
28
29
  avg: () => avg,
29
30
  buildLiveAggregation: () => buildLiveAggregation,
30
31
  count: () => count,
@@ -176,6 +177,18 @@ function readPath(record, path) {
176
177
  return cursor;
177
178
  }
178
179
 
180
+ // src/aggregate/canonical-key.ts
181
+ function canonicalGroupKey(fields, row) {
182
+ const sorted = [...fields].sort();
183
+ const parts = [];
184
+ for (const name of sorted) {
185
+ const v = row[name];
186
+ const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
187
+ parts.push(`${name}=${serialised}`);
188
+ }
189
+ return parts.join("|");
190
+ }
191
+
179
192
  // src/errors.ts
180
193
  var NoydbError = class extends Error {
181
194
  /** Machine-readable error code. Stable across library versions. */
@@ -209,27 +222,37 @@ var GroupCardinalityError = class extends NoydbError {
209
222
  var GROUPBY_WARN_CARDINALITY = 1e4;
210
223
  var GROUPBY_MAX_CARDINALITY = 1e5;
211
224
  var warnedCardinalityFields = /* @__PURE__ */ new Set();
212
- function warnCardinalityApproaching(field, observed) {
213
- if (warnedCardinalityFields.has(field)) return;
214
- warnedCardinalityFields.add(field);
225
+ function warnCardinalityApproaching(fields, observed) {
226
+ const key = JSON.stringify([...fields].sort());
227
+ if (warnedCardinalityFields.has(key)) return;
228
+ warnedCardinalityFields.add(key);
229
+ const label = `[${fields.join(", ")}]`;
215
230
  console.warn(
216
- `[noy-db] .groupBy("${field}") 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.`
231
+ `[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.`
217
232
  );
218
233
  }
219
234
  function resetGroupByWarnings() {
220
235
  warnedCardinalityFields.clear();
221
236
  }
222
- var GroupedQuery = class {
223
- constructor(executeRecords, field, upstreams, dictLabelResolver) {
237
+ var GroupedQueryBase = class {
238
+ constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
224
239
  this.executeRecords = executeRecords;
225
- this.field = field;
226
240
  this.upstreams = upstreams;
227
241
  this.dictLabelResolver = dictLabelResolver;
242
+ this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
228
243
  }
229
244
  executeRecords;
230
- field;
231
245
  upstreams;
232
246
  dictLabelResolver;
247
+ /**
248
+ * Field set this grouped query buckets on. Stored in declaration
249
+ * order — the same order is preserved on every result row by
250
+ * `groupAndReduce`. For the single-field constructor, this is
251
+ * `[field]`.
252
+ */
253
+ fields;
254
+ };
255
+ var GroupedQuery = class extends GroupedQueryBase {
233
256
  /**
234
257
  * Build a grouped aggregation. Returns a `GroupedAggregation`
235
258
  * with `.run()`, `.runAsync()`, and `.live()` terminals — same shape
@@ -239,70 +262,93 @@ var GroupedQuery = class {
239
262
  aggregate(spec) {
240
263
  return new GroupedAggregation(
241
264
  this.executeRecords,
242
- this.field,
265
+ this.fields,
266
+ spec,
267
+ this.upstreams,
268
+ this.dictLabelResolver
269
+ );
270
+ }
271
+ };
272
+ var GroupedQueryN = class extends GroupedQueryBase {
273
+ aggregate(spec) {
274
+ return new GroupedAggregation(
275
+ this.executeRecords,
276
+ this.fields,
243
277
  spec,
244
278
  this.upstreams,
245
279
  this.dictLabelResolver
246
280
  );
247
281
  }
248
282
  };
249
- function groupAndReduce(records, field, spec) {
283
+ function groupAndReduce(records, fieldOrFields, spec) {
284
+ const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
285
+ if (fields.length === 0) {
286
+ throw new Error(".groupBy() requires at least one field");
287
+ }
250
288
  const buckets = /* @__PURE__ */ new Map();
289
+ const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
251
290
  for (const record of records) {
252
- const key = readPath(record, field);
253
- let bucket = buckets.get(key);
291
+ const keyValues = {};
292
+ for (const f of fields) {
293
+ keyValues[f] = readPath(record, f);
294
+ }
295
+ const dedupKey = canonicalGroupKey(fields, keyValues);
296
+ let bucket = buckets.get(dedupKey);
254
297
  if (bucket === void 0) {
255
298
  if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
256
299
  throw new GroupCardinalityError(
257
- field,
300
+ fieldLabel,
258
301
  buckets.size + 1,
259
302
  GROUPBY_MAX_CARDINALITY
260
303
  );
261
304
  }
262
- bucket = [];
263
- buckets.set(key, bucket);
305
+ bucket = { keyValues, records: [] };
306
+ buckets.set(dedupKey, bucket);
264
307
  }
265
- bucket.push(record);
308
+ bucket.records.push(record);
266
309
  }
267
310
  if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
268
- warnCardinalityApproaching(field, buckets.size);
311
+ warnCardinalityApproaching(fields, buckets.size);
269
312
  }
270
- const keys = Object.keys(spec);
313
+ const reducerKeys = Object.keys(spec);
271
314
  const out = [];
272
- for (const [groupKey, bucketRecords] of buckets) {
315
+ for (const bucket of buckets.values()) {
273
316
  const state = {};
274
- for (const key of keys) {
275
- state[key] = spec[key].init();
317
+ for (const rk of reducerKeys) {
318
+ state[rk] = spec[rk].init();
276
319
  }
277
- for (const record of bucketRecords) {
278
- for (const key of keys) {
279
- state[key] = spec[key].step(state[key], record);
320
+ for (const record of bucket.records) {
321
+ for (const rk of reducerKeys) {
322
+ state[rk] = spec[rk].step(state[rk], record);
280
323
  }
281
324
  }
282
- const row = { [field]: groupKey };
283
- for (const key of keys) {
284
- row[key] = spec[key].finalize(state[key]);
325
+ const row = {};
326
+ for (const f of fields) {
327
+ row[f] = bucket.keyValues[f];
328
+ }
329
+ for (const rk of reducerKeys) {
330
+ row[rk] = spec[rk].finalize(state[rk]);
285
331
  }
286
332
  out.push(row);
287
333
  }
288
334
  return out;
289
335
  }
290
336
  var GroupedAggregation = class {
291
- constructor(executeRecords, field, spec, upstreams, dictLabelResolver) {
337
+ constructor(executeRecords, fields, spec, upstreams, dictLabelResolver) {
292
338
  this.executeRecords = executeRecords;
293
- this.field = field;
294
339
  this.spec = spec;
295
340
  this.upstreams = upstreams;
296
341
  this.dictLabelResolver = dictLabelResolver;
342
+ this.fields = typeof fields === "string" ? [fields] : [...fields];
297
343
  }
298
344
  executeRecords;
299
- field;
300
345
  spec;
301
346
  upstreams;
302
347
  dictLabelResolver;
348
+ fields;
303
349
  /** Execute the query, group, reduce, and return an array of rows. */
304
350
  run() {
305
- return groupAndReduce(this.executeRecords(), this.field, this.spec);
351
+ return groupAndReduce(this.executeRecords(), this.fields, this.spec);
306
352
  }
307
353
  /**
308
354
  * Execute the query, group, reduce, and resolve `<field>Label` for
@@ -312,17 +358,22 @@ var GroupedAggregation = class {
312
358
  *
313
359
  * The `<field>Label` field is appended to each row. Rows whose group
314
360
  * key has no dictionary entry get `<field>Label: undefined`.
361
+ *
362
+ * Dict-label resolution is single-field only — multi-key groupings
363
+ * do not produce a `<field>Label`. The resolver is only attached
364
+ * by the builder when `fields.length === 1`.
315
365
  */
316
366
  async runAsync(opts) {
317
- const rows = groupAndReduce(this.executeRecords(), this.field, this.spec);
318
- if (!opts?.locale || !this.dictLabelResolver) return rows;
367
+ const rows = groupAndReduce(this.executeRecords(), this.fields, this.spec);
368
+ if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows;
319
369
  const resolve = this.dictLabelResolver;
320
370
  const locale = opts.locale;
321
371
  const fallback = opts.fallback;
322
- const labelKey = `${this.field}Label`;
372
+ const field = this.fields[0];
373
+ const labelKey = `${field}Label`;
323
374
  return Promise.all(
324
375
  rows.map(async (row) => {
325
- const key = row[this.field];
376
+ const key = row[field];
326
377
  if (typeof key !== "string") return row;
327
378
  const label = await resolve(key, locale, fallback);
328
379
  return { ...row, [labelKey]: label };
@@ -346,7 +397,7 @@ var GroupedAggregation = class {
346
397
  * Always call `live.stop()` when finished.
347
398
  */
348
399
  live() {
349
- const recompute = () => groupAndReduce(this.executeRecords(), this.field, this.spec);
400
+ const recompute = () => groupAndReduce(this.executeRecords(), this.fields, this.spec);
350
401
  return buildLiveAggregation(recompute, this.upstreams);
351
402
  }
352
403
  };
@@ -360,6 +411,9 @@ function withAggregate() {
360
411
  groupBy(executeRecords, field, upstreams, dictLabelResolver) {
361
412
  return new GroupedQuery(executeRecords, field, upstreams, dictLabelResolver);
362
413
  },
414
+ groupByN(executeRecords, fields, upstreams) {
415
+ return new GroupedQueryN(executeRecords, fields, upstreams);
416
+ },
363
417
  async scanAggregate(iter, spec) {
364
418
  const collected = [];
365
419
  for await (const record of iter) collected.push(record);
@@ -462,6 +516,7 @@ function readNumber(record, field) {
462
516
  GROUPBY_WARN_CARDINALITY,
463
517
  GroupedAggregation,
464
518
  GroupedQuery,
519
+ GroupedQueryN,
465
520
  avg,
466
521
  buildLiveAggregation,
467
522
  count,