@noy-db/hub 0.1.0-pre.9 → 0.2.0-pre.10
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.
- package/dist/aggregate/index.cjs +100 -36
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +16 -9
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs +305 -0
- package/dist/attestation/index.cjs.map +1 -0
- package/dist/attestation/index.d.cts +52 -0
- package/dist/attestation/index.d.ts +52 -0
- package/dist/attestation/index.js +36 -0
- package/dist/attestation/index.js.map +1 -0
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +7 -6
- package/dist/blobs/index.d.ts +7 -6
- package/dist/blobs/index.js +10 -8
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.cjs +19121 -60
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +175 -6
- package/dist/bundle/index.d.ts +175 -6
- package/dist/bundle/index.js +543 -4
- package/dist/bundle/index.js.map +1 -1
- package/dist/chunk-26NK23DZ.js +296 -0
- package/dist/chunk-26NK23DZ.js.map +1 -0
- package/dist/{chunk-TDR6T5CJ.js → chunk-2LPPNWF6.js} +91 -132
- package/dist/chunk-2LPPNWF6.js.map +1 -0
- package/dist/{chunk-PTVMYYON.js → chunk-2N62W5YP.js} +3 -3
- package/dist/{chunk-QGZRWRSL.js → chunk-3LPV6BXR.js} +4 -4
- package/dist/{chunk-QAVUREFT.js → chunk-4CLICFEY.js} +12 -6
- package/dist/chunk-4CLICFEY.js.map +1 -0
- package/dist/chunk-4USCAEDT.js +10529 -0
- package/dist/chunk-4USCAEDT.js.map +1 -0
- package/dist/chunk-5IXJGFF2.js +83 -0
- package/dist/chunk-5IXJGFF2.js.map +1 -0
- package/dist/chunk-5OEJ6GOT.js +124 -0
- package/dist/chunk-5OEJ6GOT.js.map +1 -0
- package/dist/{chunk-4PWAI7Q4.js → chunk-5OX6XVNS.js} +5 -5
- package/dist/{chunk-2CSJGFCB.js → chunk-6EOXTJS2.js} +6 -229
- package/dist/chunk-6EOXTJS2.js.map +1 -0
- package/dist/chunk-6T2UDBKG.js +53 -0
- package/dist/chunk-6T2UDBKG.js.map +1 -0
- package/dist/{chunk-GOUT6DND.js → chunk-6YLPHBKR.js} +382 -95
- package/dist/chunk-6YLPHBKR.js.map +1 -0
- package/dist/chunk-7CEGU63S.js +179 -0
- package/dist/chunk-7CEGU63S.js.map +1 -0
- package/dist/chunk-A3JMGXPG.js +125 -0
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/chunk-BB27JMWB.js +795 -0
- package/dist/chunk-BB27JMWB.js.map +1 -0
- package/dist/{chunk-SCZXXXU4.js → chunk-BDV7INMP.js} +7 -32
- package/dist/chunk-BDV7INMP.js.map +1 -0
- package/dist/chunk-C3WE6UJY.js +19 -0
- package/dist/chunk-C3WE6UJY.js.map +1 -0
- package/dist/chunk-CH22FZHT.js +96 -0
- package/dist/chunk-CH22FZHT.js.map +1 -0
- package/dist/chunk-CXFOITNS.js +34 -0
- package/dist/chunk-CXFOITNS.js.map +1 -0
- package/dist/chunk-CXJG63MA.js +109 -0
- package/dist/chunk-CXJG63MA.js.map +1 -0
- package/dist/chunk-DAP2XL7Q.js +51 -0
- package/dist/chunk-DAP2XL7Q.js.map +1 -0
- package/dist/{chunk-AVVPZ4BC.js → chunk-DJRWA3Q5.js} +4 -4
- package/dist/chunk-DRXIZOFV.js +233 -0
- package/dist/chunk-DRXIZOFV.js.map +1 -0
- package/dist/chunk-FO3UEG4S.js +313 -0
- package/dist/chunk-FO3UEG4S.js.map +1 -0
- package/dist/chunk-GAUEWM7D.js +147 -0
- package/dist/chunk-GAUEWM7D.js.map +1 -0
- package/dist/{chunk-MDDTIZUO.js → chunk-GNHAC43Q.js} +218 -119
- package/dist/chunk-GNHAC43Q.js.map +1 -0
- package/dist/chunk-HHOO7HGH.js +57 -0
- package/dist/chunk-HHOO7HGH.js.map +1 -0
- package/dist/{chunk-WDM5XGGS.js → chunk-HQSQC2XL.js} +182 -12
- package/dist/chunk-HQSQC2XL.js.map +1 -0
- package/dist/chunk-IMYKDWB4.js +139 -0
- package/dist/chunk-IMYKDWB4.js.map +1 -0
- package/dist/{chunk-M62XNWRA.js → chunk-LSTBFLL2.js} +2 -2
- package/dist/{chunk-ACLDOTNQ.js → chunk-O6EJ6WTI.js} +436 -3
- package/dist/chunk-O6EJ6WTI.js.map +1 -0
- package/dist/chunk-PC6ZEDRL.js +71 -0
- package/dist/chunk-PC6ZEDRL.js.map +1 -0
- package/dist/chunk-PM3QYWUU.js +251 -0
- package/dist/chunk-PM3QYWUU.js.map +1 -0
- package/dist/chunk-PVUUIWHY.js +73 -0
- package/dist/chunk-PVUUIWHY.js.map +1 -0
- package/dist/chunk-PXTQPZO4.js +830 -0
- package/dist/chunk-PXTQPZO4.js.map +1 -0
- package/dist/{chunk-ZFKD4QMV.js → chunk-QSOYKKMD.js} +4 -4
- package/dist/chunk-QSOYKKMD.js.map +1 -0
- package/dist/{chunk-MR4424N3.js → chunk-R233SLY3.js} +2 -2
- package/dist/chunk-RC6SU5NO.js +36 -0
- package/dist/chunk-RC6SU5NO.js.map +1 -0
- package/dist/{chunk-USKYUS74.js → chunk-RRNA5GKT.js} +2 -2
- package/dist/{chunk-R36SIKES.js → chunk-RYIL3PI2.js} +2 -2
- package/dist/chunk-STNPB3UM.js +9 -0
- package/dist/chunk-STNPB3UM.js.map +1 -0
- package/dist/{chunk-M5INGEFC.js → chunk-TV3YZ35S.js} +7 -1
- package/dist/chunk-TV3YZ35S.js.map +1 -0
- package/dist/chunk-TY32C732.js +59 -0
- package/dist/chunk-TY32C732.js.map +1 -0
- package/dist/chunk-UMLVJTYV.js +20 -0
- package/dist/chunk-UMLVJTYV.js.map +1 -0
- package/dist/{chunk-NPC4LFV5.js → chunk-WIBHRONM.js} +2 -2
- package/dist/chunk-WIBHRONM.js.map +1 -0
- package/dist/{chunk-RKJ6OL7K.js → chunk-WIRRPTFH.js} +1 -1
- package/dist/chunk-WIRRPTFH.js.map +1 -0
- package/dist/{chunk-VQBTTTUN.js → chunk-Y26YV5R3.js} +4 -4
- package/dist/{chunk-VQBTTTUN.js.map → chunk-Y26YV5R3.js.map} +1 -1
- package/dist/{chunk-NXFEYLVG.js → chunk-YM7LFCG7.js} +5 -4
- package/dist/{chunk-NXFEYLVG.js.map → chunk-YM7LFCG7.js.map} +1 -1
- package/dist/{chunk-CIMZBAZB.js → chunk-Z6FNBOTC.js} +1 -1
- package/dist/chunk-Z6FNBOTC.js.map +1 -0
- package/dist/chunk-ZROPXHJY.js +82 -0
- package/dist/chunk-ZROPXHJY.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +7 -6
- package/dist/consent/index.d.ts +7 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-IVKU7YTT.js → crypto-2CRLG4F4.js} +3 -3
- package/dist/{delegation-2DBS2EOH.js → delegation-ZTRT2PRV.js} +5 -4
- package/dist/derivations/index.cjs +368 -0
- package/dist/derivations/index.cjs.map +1 -0
- package/dist/derivations/index.d.cts +72 -0
- package/dist/derivations/index.d.ts +72 -0
- package/dist/derivations/index.js +27 -0
- package/dist/{dev-unlock-Da1B0TIK.d.cts → dev-unlock-AglVnkPY.d.cts} +1 -1
- package/dist/{dev-unlock-BdPp68qn.d.ts → dev-unlock-BOEYl1xl.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.cts +60 -0
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/executor-S76VN45G.js +8 -0
- package/dist/executor-UCXLIGLW.js +11 -0
- package/dist/executor-UCXLIGLW.js.map +1 -0
- package/dist/executor-ZCNZJMGR.js +8 -0
- package/dist/executor-ZCNZJMGR.js.map +1 -0
- package/dist/fanout-sidecar-OKPMMPLG.js +51 -0
- package/dist/fanout-sidecar-OKPMMPLG.js.map +1 -0
- package/dist/guards/index.cjs +322 -0
- package/dist/guards/index.cjs.map +1 -0
- package/dist/guards/index.d.cts +31 -0
- package/dist/guards/index.d.ts +31 -0
- package/dist/guards/index.js +29 -0
- package/dist/guards/index.js.map +1 -0
- package/dist/{hash-lsoL3eEW.d.ts → hash-B9m3_fhj.d.ts} +1 -1
- package/dist/{hash-BEfzPKwo.d.cts → hash-RVqz2zi8.d.cts} +1 -1
- package/dist/history/index.cjs +9 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +8 -7
- package/dist/history/index.d.ts +8 -7
- package/dist/history/index.js +6 -6
- package/dist/i18n/index.cjs +368 -27
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +7 -6
- package/dist/i18n/index.d.ts +7 -6
- package/dist/i18n/index.js +34 -6
- package/dist/i18n/index.js.map +1 -1
- package/dist/{index-DJTf9yxn.d.ts → index-B8bjExET.d.cts} +508 -14
- package/dist/{index-6xNpPsxR.d.cts → index-DfUbNad8.d.ts} +508 -14
- package/dist/index.cjs +8779 -1260
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +231 -19
- package/dist/index.d.ts +231 -19
- package/dist/index.js +311 -7370
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +7 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-3W6IVLKH.js +12 -0
- package/dist/issue-3W6IVLKH.js.map +1 -0
- package/dist/{lazy-builder-BwEoBQZ9.d.ts → lazy-builder-Ci5_YG73.d.cts} +2 -2
- package/dist/{lazy-builder-CZVLKh0Z.d.cts → lazy-builder-D5GU14TS.d.ts} +2 -2
- package/dist/{ledger-QZTTHQAQ.js → ledger-O7FXOG3D.js} +6 -6
- package/dist/ledger-O7FXOG3D.js.map +1 -0
- package/dist/materialized-views/index.cjs +856 -0
- package/dist/materialized-views/index.cjs.map +1 -0
- package/dist/materialized-views/index.d.cts +186 -0
- package/dist/materialized-views/index.d.ts +186 -0
- package/dist/materialized-views/index.js +45 -0
- package/dist/materialized-views/index.js.map +1 -0
- package/dist/noydb-YAZNH5TI.js +34 -0
- package/dist/noydb-YAZNH5TI.js.map +1 -0
- package/dist/overlay-views/index.cjs +369 -0
- package/dist/overlay-views/index.cjs.map +1 -0
- package/dist/overlay-views/index.d.cts +82 -0
- package/dist/overlay-views/index.d.ts +82 -0
- package/dist/overlay-views/index.js +25 -0
- package/dist/overlay-views/index.js.map +1 -0
- package/dist/periods/index.cjs +7 -1
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +7 -6
- package/dist/periods/index.d.ts +7 -6
- package/dist/periods/index.js +6 -6
- package/dist/{predicate-SBHmi6D0.d.cts → predicate-Bt5ft-9c.d.cts} +51 -2
- package/dist/{predicate-SBHmi6D0.d.ts → predicate-Bt5ft-9c.d.ts} +51 -2
- package/dist/{public-envelope-6JTACYJV.js → public-envelope-HMYHZIRH.js} +4 -4
- package/dist/public-envelope-HMYHZIRH.js.map +1 -0
- package/dist/query/index.cjs +555 -128
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +32 -11
- package/dist/read-only-facade-ITU6L7BL.js +7 -0
- package/dist/read-only-facade-ITU6L7BL.js.map +1 -0
- package/dist/registry-DKEXOJVO.js +7 -0
- package/dist/registry-DKEXOJVO.js.map +1 -0
- package/dist/registry-ST2VNFZC.js +10 -0
- package/dist/registry-ST2VNFZC.js.map +1 -0
- package/dist/registry-UFIK7CSR.js +8 -0
- package/dist/registry-UFIK7CSR.js.map +1 -0
- package/dist/registry-ZGYYSM5I.js +8 -0
- package/dist/registry-ZGYYSM5I.js.map +1 -0
- package/dist/revoke-S6JMSLUN.js +17 -0
- package/dist/revoke-S6JMSLUN.js.map +1 -0
- package/dist/session/index.cjs +7 -1
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +8 -7
- package/dist/session/index.d.ts +8 -7
- package/dist/session/index.js +10 -3
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +7 -6
- package/dist/shadow/index.d.ts +7 -6
- package/dist/shadow/index.js +2 -2
- package/dist/signer-7NPTB3SQ.js +18 -0
- package/dist/signer-7NPTB3SQ.js.map +1 -0
- package/dist/snapshots/index.cjs +937 -0
- package/dist/snapshots/index.cjs.map +1 -0
- package/dist/snapshots/index.d.cts +28 -0
- package/dist/snapshots/index.d.ts +28 -0
- package/dist/snapshots/index.js +152 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/stale-VKXSXJF4.js +13 -0
- package/dist/stale-VKXSXJF4.js.map +1 -0
- package/dist/store/index.cjs +14 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +7 -6
- package/dist/store/index.d.ts +7 -6
- package/dist/store/index.js +5 -2
- package/dist/{strategy-D-SrOLCl.d.ts → strategy-CT2LCKAX.d.cts} +84 -19
- package/dist/{strategy-D-SrOLCl.d.cts → strategy-CT2LCKAX.d.ts} +84 -19
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +6 -5
- package/dist/sync/index.d.ts +6 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +1554 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +7 -6
- package/dist/team/index.d.ts +7 -6
- package/dist/team/index.js +77 -8
- package/dist/tx/index.cjs +375 -43
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +8 -7
- package/dist/tx/index.d.ts +8 -7
- package/dist/tx/index.js +56 -3
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-Bo7NSXJr.d.ts → types-CaNQm4i8.d.ts} +3902 -614
- package/dist/{types-Bnb82f5R.d.cts → types-n2_IfwlQ.d.cts} +3902 -614
- package/dist/{index-CywCC1qZ.d.cts → ulid-B9SMWj5i.d.ts} +216 -27
- package/dist/{index-8QDuznDr.d.ts → ulid-CLMjmyhG.d.cts} +216 -27
- package/dist/util/index.cjs +7 -0
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.d.cts +2 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +5 -1
- package/dist/util/index.js.map +1 -1
- package/dist/with-derivation-CVIOPTUf.d.ts +13 -0
- package/dist/with-derivation-aKrtS7Jj.d.cts +13 -0
- package/dist/with-guard-DZQbPzoP.d.cts +18 -0
- package/dist/with-guard-DseETUrF.d.ts +18 -0
- package/dist/with-materialized-view-C1eA1_T_.d.cts +27 -0
- package/dist/with-materialized-view-DaYaE8-Q.d.ts +27 -0
- package/dist/with-overlayed-view-DQsh2p8H.d.ts +13 -0
- package/dist/with-overlayed-view-DleJfKcV.d.cts +13 -0
- package/package.json +77 -3
- package/dist/chunk-2CSJGFCB.js.map +0 -1
- package/dist/chunk-ACLDOTNQ.js.map +0 -1
- package/dist/chunk-BTDCBVJW.js +0 -160
- package/dist/chunk-BTDCBVJW.js.map +0 -1
- package/dist/chunk-CIMZBAZB.js.map +0 -1
- package/dist/chunk-EXHNQEV4.js +0 -392
- package/dist/chunk-EXHNQEV4.js.map +0 -1
- package/dist/chunk-GOUT6DND.js.map +0 -1
- package/dist/chunk-M5INGEFC.js.map +0 -1
- package/dist/chunk-MDDTIZUO.js.map +0 -1
- package/dist/chunk-NPC4LFV5.js.map +0 -1
- package/dist/chunk-QAVUREFT.js.map +0 -1
- package/dist/chunk-RKJ6OL7K.js.map +0 -1
- package/dist/chunk-SCZXXXU4.js.map +0 -1
- package/dist/chunk-TDR6T5CJ.js.map +0 -1
- package/dist/chunk-WDM5XGGS.js.map +0 -1
- package/dist/chunk-ZFKD4QMV.js.map +0 -1
- /package/dist/{chunk-PTVMYYON.js.map → chunk-2N62W5YP.js.map} +0 -0
- /package/dist/{chunk-QGZRWRSL.js.map → chunk-3LPV6BXR.js.map} +0 -0
- /package/dist/{chunk-4PWAI7Q4.js.map → chunk-5OX6XVNS.js.map} +0 -0
- /package/dist/{chunk-AVVPZ4BC.js.map → chunk-DJRWA3Q5.js.map} +0 -0
- /package/dist/{chunk-M62XNWRA.js.map → chunk-LSTBFLL2.js.map} +0 -0
- /package/dist/{chunk-MR4424N3.js.map → chunk-R233SLY3.js.map} +0 -0
- /package/dist/{chunk-USKYUS74.js.map → chunk-RRNA5GKT.js.map} +0 -0
- /package/dist/{chunk-R36SIKES.js.map → chunk-RYIL3PI2.js.map} +0 -0
- /package/dist/{crypto-IVKU7YTT.js.map → crypto-2CRLG4F4.js.map} +0 -0
- /package/dist/{delegation-2DBS2EOH.js.map → delegation-ZTRT2PRV.js.map} +0 -0
- /package/dist/{ledger-QZTTHQAQ.js.map → derivations/index.js.map} +0 -0
- /package/dist/{public-envelope-6JTACYJV.js.map → executor-S76VN45G.js.map} +0 -0
package/dist/query/index.cjs
CHANGED
|
@@ -22,11 +22,20 @@ var query_exports = {};
|
|
|
22
22
|
__export(query_exports, {
|
|
23
23
|
Aggregation: () => Aggregation,
|
|
24
24
|
CollectionIndexes: () => CollectionIndexes,
|
|
25
|
+
CrossJoinSourceUnknownError: () => CrossJoinSourceUnknownError,
|
|
26
|
+
CrossJoinTooLargeError: () => CrossJoinTooLargeError,
|
|
27
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS: () => DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
25
28
|
DEFAULT_JOIN_MAX_ROWS: () => DEFAULT_JOIN_MAX_ROWS,
|
|
29
|
+
DanglingReferenceError: () => DanglingReferenceError,
|
|
26
30
|
GROUPBY_MAX_CARDINALITY: () => GROUPBY_MAX_CARDINALITY,
|
|
27
31
|
GROUPBY_WARN_CARDINALITY: () => GROUPBY_WARN_CARDINALITY,
|
|
32
|
+
GroupCardinalityError: () => GroupCardinalityError,
|
|
28
33
|
GroupedAggregation: () => GroupedAggregation,
|
|
29
34
|
GroupedQuery: () => GroupedQuery,
|
|
35
|
+
GroupedQueryN: () => GroupedQueryN,
|
|
36
|
+
IndexRequiredError: () => IndexRequiredError,
|
|
37
|
+
IndexWriteFailureError: () => IndexWriteFailureError,
|
|
38
|
+
JoinTooLargeError: () => JoinTooLargeError,
|
|
30
39
|
Query: () => Query,
|
|
31
40
|
ScanBuilder: () => ScanBuilder,
|
|
32
41
|
applyJoins: () => applyJoins,
|
|
@@ -111,6 +120,12 @@ function evaluateClause(record, clause) {
|
|
|
111
120
|
return evaluateFieldClause(record, clause);
|
|
112
121
|
case "filter":
|
|
113
122
|
return clause.fn(record);
|
|
123
|
+
case "wherePredicate":
|
|
124
|
+
return clause.fn(record, clause.ctx);
|
|
125
|
+
case "crossJoin":
|
|
126
|
+
throw new Error(
|
|
127
|
+
`evaluateClause: 'crossJoin' clauses are expansion primitives and are not evaluated per-record. This is a query planner routing error \u2014 crossJoin clauses must be extracted from the clause list before calling evaluateClause or filterRecords.`
|
|
128
|
+
);
|
|
114
129
|
case "group":
|
|
115
130
|
if (clause.op === "and") {
|
|
116
131
|
for (const child of clause.clauses) {
|
|
@@ -154,6 +169,38 @@ var GroupCardinalityError = class extends NoydbError {
|
|
|
154
169
|
this.maxGroups = maxGroups;
|
|
155
170
|
}
|
|
156
171
|
};
|
|
172
|
+
var IndexRequiredError = class extends NoydbError {
|
|
173
|
+
collection;
|
|
174
|
+
touchedFields;
|
|
175
|
+
missingFields;
|
|
176
|
+
constructor(args) {
|
|
177
|
+
super(
|
|
178
|
+
"INDEX_REQUIRED",
|
|
179
|
+
`Collection "${args.collection}": query references unindexed fields in lazy mode (missing: ${args.missingFields.join(", ")}). Declare an index on each field, or use collection.scan() for non-indexed iteration.`
|
|
180
|
+
);
|
|
181
|
+
this.name = "IndexRequiredError";
|
|
182
|
+
this.collection = args.collection;
|
|
183
|
+
this.touchedFields = [...args.touchedFields];
|
|
184
|
+
this.missingFields = [...args.missingFields];
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var IndexWriteFailureError = class extends NoydbError {
|
|
188
|
+
recordId;
|
|
189
|
+
field;
|
|
190
|
+
op;
|
|
191
|
+
cause;
|
|
192
|
+
constructor(args) {
|
|
193
|
+
super(
|
|
194
|
+
"INDEX_WRITE_FAILURE",
|
|
195
|
+
`Index side-car ${args.op} failed for field "${args.field}" on record "${args.recordId}"`
|
|
196
|
+
);
|
|
197
|
+
this.name = "IndexWriteFailureError";
|
|
198
|
+
this.recordId = args.recordId;
|
|
199
|
+
this.field = args.field;
|
|
200
|
+
this.op = args.op;
|
|
201
|
+
this.cause = args.cause;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
157
204
|
var JoinTooLargeError = class extends NoydbError {
|
|
158
205
|
leftRows;
|
|
159
206
|
rightRows;
|
|
@@ -168,6 +215,34 @@ var JoinTooLargeError = class extends NoydbError {
|
|
|
168
215
|
this.side = opts.side;
|
|
169
216
|
}
|
|
170
217
|
};
|
|
218
|
+
var CrossJoinTooLargeError = class extends NoydbError {
|
|
219
|
+
target;
|
|
220
|
+
expected;
|
|
221
|
+
limit;
|
|
222
|
+
constructor(opts) {
|
|
223
|
+
super(
|
|
224
|
+
"CROSS_JOIN_TOO_LARGE",
|
|
225
|
+
`crossJoin("${opts.target}"): would produce ${opts.expected} rows, exceeding the limit of ${opts.limit}. Narrow the left side with .where() first, or raise the ceiling with crossJoin("${opts.target}", { ..., maxRows: ${opts.expected} }).`
|
|
226
|
+
);
|
|
227
|
+
this.name = "CrossJoinTooLargeError";
|
|
228
|
+
this.target = opts.target;
|
|
229
|
+
this.expected = opts.expected;
|
|
230
|
+
this.limit = opts.limit;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
var CrossJoinSourceUnknownError = class extends NoydbError {
|
|
234
|
+
target;
|
|
235
|
+
leftCollection;
|
|
236
|
+
constructor(target, leftCollection) {
|
|
237
|
+
super(
|
|
238
|
+
"CROSS_JOIN_SOURCE_UNKNOWN",
|
|
239
|
+
`crossJoin("${target}"): collection "${target}" is not known in the vault (cross-joining from "${leftCollection}"). Make sure "${target}" is open in the same vault before executing this query.`
|
|
240
|
+
);
|
|
241
|
+
this.name = "CrossJoinSourceUnknownError";
|
|
242
|
+
this.target = target;
|
|
243
|
+
this.leftCollection = leftCollection;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
171
246
|
var DanglingReferenceError = class extends NoydbError {
|
|
172
247
|
field;
|
|
173
248
|
target;
|
|
@@ -418,6 +493,9 @@ var NO_AGGREGATE = {
|
|
|
418
493
|
groupBy() {
|
|
419
494
|
throw NOT_ENABLED;
|
|
420
495
|
},
|
|
496
|
+
groupByN() {
|
|
497
|
+
throw NOT_ENABLED;
|
|
498
|
+
},
|
|
421
499
|
scanAggregate() {
|
|
422
500
|
throw NOT_ENABLED;
|
|
423
501
|
}
|
|
@@ -431,16 +509,89 @@ var EMPTY_PLAN = {
|
|
|
431
509
|
offset: 0,
|
|
432
510
|
joins: []
|
|
433
511
|
};
|
|
512
|
+
var DEFAULT_CROSS_JOIN_MAX_ROWS = 5e4;
|
|
434
513
|
var Query = class _Query {
|
|
435
514
|
source;
|
|
436
515
|
plan;
|
|
437
516
|
joinContext;
|
|
438
517
|
aggregateStrategy;
|
|
439
|
-
|
|
518
|
+
predicates;
|
|
519
|
+
constructor(source, plan = EMPTY_PLAN, joinContext, aggregateStrategy = NO_AGGREGATE, predicates) {
|
|
440
520
|
this.source = source;
|
|
441
521
|
this.plan = plan;
|
|
442
522
|
this.joinContext = joinContext;
|
|
443
523
|
this.aggregateStrategy = aggregateStrategy;
|
|
524
|
+
this.predicates = predicates;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* @internal — accessor for the materialized-view dependency
|
|
528
|
+
* analyzer. Not part of the public API; consumers should use the
|
|
529
|
+
* builder methods, not inspect the plan directly.
|
|
530
|
+
*/
|
|
531
|
+
_plan() {
|
|
532
|
+
return this.plan;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* @internal — accessor for the materialized-view dependency
|
|
536
|
+
* analyzer. Returns the join resolution context (or `undefined` for
|
|
537
|
+
* queries constructed without a Collection backing).
|
|
538
|
+
*/
|
|
539
|
+
_joinContext() {
|
|
540
|
+
return this.joinContext;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* @internal — clone this Query with a declared-predicate map
|
|
544
|
+
* attached. Used by the materialized-view registry to enable
|
|
545
|
+
* `.wherePredicate(name, ctx?)` for the MV's query callback.
|
|
546
|
+
* Consumers don't call this directly.
|
|
547
|
+
*/
|
|
548
|
+
_withPredicates(predicates) {
|
|
549
|
+
return new _Query(
|
|
550
|
+
this.source,
|
|
551
|
+
this.plan,
|
|
552
|
+
this.joinContext,
|
|
553
|
+
this.aggregateStrategy,
|
|
554
|
+
predicates
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Filter by a registered deterministic predicate. Requires
|
|
559
|
+
* the Query to have been augmented with a predicates map (typically
|
|
560
|
+
* via the materialized-view registry — bare Queries constructed
|
|
561
|
+
* outside an MV throw on `.wherePredicate()`).
|
|
562
|
+
*
|
|
563
|
+
* `ctx` is an optional opaque value passed verbatim to the predicate
|
|
564
|
+
* function. Both `predicateHash` (from the registration) and a
|
|
565
|
+
* canonical-JSON hash of `ctx` fold into the MV's `queryHash`, so
|
|
566
|
+
* either changing forces refresh on next visit.
|
|
567
|
+
*/
|
|
568
|
+
wherePredicate(name, ctx) {
|
|
569
|
+
if (!this.predicates) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`.wherePredicate("${name}"): no predicates registered on this Query. Function-based predicates require the Query to be obtained from inside a materialized-view query() callback whose strategy declares \`predicates: { ${name}: { hash, fn } }\`.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
const decl = this.predicates.get(name);
|
|
575
|
+
if (!decl) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`.wherePredicate("${name}"): predicate not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const clause = {
|
|
581
|
+
type: "wherePredicate",
|
|
582
|
+
name,
|
|
583
|
+
ctx,
|
|
584
|
+
predicateHash: decl.hash,
|
|
585
|
+
ctxHash: canonicalCtxHash(ctx),
|
|
586
|
+
fn: decl.fn
|
|
587
|
+
};
|
|
588
|
+
return new _Query(
|
|
589
|
+
this.source,
|
|
590
|
+
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
591
|
+
this.joinContext,
|
|
592
|
+
this.aggregateStrategy,
|
|
593
|
+
this.predicates
|
|
594
|
+
);
|
|
444
595
|
}
|
|
445
596
|
/** Add a field comparison. Multiple where() calls are AND-combined. */
|
|
446
597
|
where(field, op, value) {
|
|
@@ -449,7 +600,8 @@ var Query = class _Query {
|
|
|
449
600
|
this.source,
|
|
450
601
|
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
451
602
|
this.joinContext,
|
|
452
|
-
this.aggregateStrategy
|
|
603
|
+
this.aggregateStrategy,
|
|
604
|
+
this.predicates
|
|
453
605
|
);
|
|
454
606
|
}
|
|
455
607
|
/**
|
|
@@ -459,7 +611,7 @@ var Query = class _Query {
|
|
|
459
611
|
*/
|
|
460
612
|
or(builder) {
|
|
461
613
|
const sub = builder(
|
|
462
|
-
new _Query(this.source, EMPTY_PLAN, this.joinContext, this.aggregateStrategy)
|
|
614
|
+
new _Query(this.source, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates)
|
|
463
615
|
);
|
|
464
616
|
const group = {
|
|
465
617
|
type: "group",
|
|
@@ -470,7 +622,8 @@ var Query = class _Query {
|
|
|
470
622
|
this.source,
|
|
471
623
|
{ ...this.plan, clauses: [...this.plan.clauses, group] },
|
|
472
624
|
this.joinContext,
|
|
473
|
-
this.aggregateStrategy
|
|
625
|
+
this.aggregateStrategy,
|
|
626
|
+
this.predicates
|
|
474
627
|
);
|
|
475
628
|
}
|
|
476
629
|
/**
|
|
@@ -479,7 +632,7 @@ var Query = class _Query {
|
|
|
479
632
|
*/
|
|
480
633
|
and(builder) {
|
|
481
634
|
const sub = builder(
|
|
482
|
-
new _Query(this.source, EMPTY_PLAN, this.joinContext, this.aggregateStrategy)
|
|
635
|
+
new _Query(this.source, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates)
|
|
483
636
|
);
|
|
484
637
|
const group = {
|
|
485
638
|
type: "group",
|
|
@@ -490,7 +643,8 @@ var Query = class _Query {
|
|
|
490
643
|
this.source,
|
|
491
644
|
{ ...this.plan, clauses: [...this.plan.clauses, group] },
|
|
492
645
|
this.joinContext,
|
|
493
|
-
this.aggregateStrategy
|
|
646
|
+
this.aggregateStrategy,
|
|
647
|
+
this.predicates
|
|
494
648
|
);
|
|
495
649
|
}
|
|
496
650
|
/** Escape hatch: add an arbitrary predicate function. Not serializable. */
|
|
@@ -503,7 +657,8 @@ var Query = class _Query {
|
|
|
503
657
|
this.source,
|
|
504
658
|
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
505
659
|
this.joinContext,
|
|
506
|
-
this.aggregateStrategy
|
|
660
|
+
this.aggregateStrategy,
|
|
661
|
+
this.predicates
|
|
507
662
|
);
|
|
508
663
|
}
|
|
509
664
|
/** Sort by a field. Subsequent calls are tie-breakers. */
|
|
@@ -512,7 +667,8 @@ var Query = class _Query {
|
|
|
512
667
|
this.source,
|
|
513
668
|
{ ...this.plan, orderBy: [...this.plan.orderBy, { field, direction }] },
|
|
514
669
|
this.joinContext,
|
|
515
|
-
this.aggregateStrategy
|
|
670
|
+
this.aggregateStrategy,
|
|
671
|
+
this.predicates
|
|
516
672
|
);
|
|
517
673
|
}
|
|
518
674
|
/** Cap the result size. */
|
|
@@ -521,7 +677,8 @@ var Query = class _Query {
|
|
|
521
677
|
this.source,
|
|
522
678
|
{ ...this.plan, limit: n },
|
|
523
679
|
this.joinContext,
|
|
524
|
-
this.aggregateStrategy
|
|
680
|
+
this.aggregateStrategy,
|
|
681
|
+
this.predicates
|
|
525
682
|
);
|
|
526
683
|
}
|
|
527
684
|
/** Skip the first N matching records (after ordering). */
|
|
@@ -530,7 +687,8 @@ var Query = class _Query {
|
|
|
530
687
|
this.source,
|
|
531
688
|
{ ...this.plan, offset: n },
|
|
532
689
|
this.joinContext,
|
|
533
|
-
this.aggregateStrategy
|
|
690
|
+
this.aggregateStrategy,
|
|
691
|
+
this.predicates
|
|
534
692
|
);
|
|
535
693
|
}
|
|
536
694
|
/**
|
|
@@ -629,7 +787,80 @@ var Query = class _Query {
|
|
|
629
787
|
this.source,
|
|
630
788
|
{ ...this.plan, joins: [...this.plan.joins, leg] },
|
|
631
789
|
this.joinContext,
|
|
632
|
-
this.aggregateStrategy
|
|
790
|
+
this.aggregateStrategy,
|
|
791
|
+
this.predicates
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Cartesian-product cross-join against `target` collection. Each result row
|
|
796
|
+
* carries the original `T` fields plus `result[as]` populated from every
|
|
797
|
+
* right-side row (or the filtered subset when `on:` is supplied).
|
|
798
|
+
*
|
|
799
|
+
* **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);
|
|
800
|
+
* `.crossJoin().where('alias.field', ...)` filters AFTER (required when the
|
|
801
|
+
* where clause references the aliased fields).
|
|
802
|
+
*
|
|
803
|
+
* **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when
|
|
804
|
+
* `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.
|
|
805
|
+
* Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.
|
|
806
|
+
*
|
|
807
|
+
* **`on:` shapes:**
|
|
808
|
+
* - `on: (left) => TTarget[]` — subset form (most efficient)
|
|
809
|
+
* - `on: (left) => (right) => boolean` — predicate form
|
|
810
|
+
* - `on: { predicate: 'name' }` — MV-safe, hash-tracked form
|
|
811
|
+
* (requires the Query to have been augmented via `_withPredicates`)
|
|
812
|
+
*
|
|
813
|
+
* Requires a JoinContext (constructed via `collection.query()`).
|
|
814
|
+
*/
|
|
815
|
+
crossJoin(target, opts) {
|
|
816
|
+
if (!this.joinContext) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`Query.crossJoin("${target}"): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
let onFn;
|
|
822
|
+
let onPredicateName;
|
|
823
|
+
if (opts.on !== void 0) {
|
|
824
|
+
if (typeof opts.on === "function") {
|
|
825
|
+
onFn = opts.on;
|
|
826
|
+
if (this.predicates) {
|
|
827
|
+
console.warn(
|
|
828
|
+
`Query.crossJoin("${target}", { on: callback }): inline on: callback inside a withMaterializedView query() disables queryHash drift detection for this cross-join. Use on: { predicate: '<name>' } to enable it.`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
} else {
|
|
832
|
+
const predName = opts.on.predicate;
|
|
833
|
+
if (!this.predicates) {
|
|
834
|
+
throw new Error(
|
|
835
|
+
`Query.crossJoin("${target}", { on: { predicate: "${predName}" } }): the { predicate } form requires a predicates map. Use this form inside a withMaterializedView query() callback that declares predicates: { ${predName}: { hash, fn } }.`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const decl = this.predicates.get(predName);
|
|
839
|
+
if (!decl) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`Query.crossJoin("${target}"): predicate "${predName}" not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const as = opts.as;
|
|
845
|
+
const predicateFn = decl.fn;
|
|
846
|
+
onFn = (_left) => (right) => predicateFn({ ..._left, [as]: right });
|
|
847
|
+
onPredicateName = predName;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const clause = {
|
|
851
|
+
type: "crossJoin",
|
|
852
|
+
target,
|
|
853
|
+
as: opts.as,
|
|
854
|
+
...onFn !== void 0 && { on: onFn },
|
|
855
|
+
...onPredicateName !== void 0 && { onPredicateName },
|
|
856
|
+
...opts.maxRows !== void 0 && { maxRows: opts.maxRows }
|
|
857
|
+
};
|
|
858
|
+
return new _Query(
|
|
859
|
+
this.source,
|
|
860
|
+
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
861
|
+
this.joinContext,
|
|
862
|
+
this.aggregateStrategy,
|
|
863
|
+
this.predicates
|
|
633
864
|
);
|
|
634
865
|
}
|
|
635
866
|
/**
|
|
@@ -639,7 +870,7 @@ var Query = class _Query {
|
|
|
639
870
|
* for the ordering rationale.
|
|
640
871
|
*/
|
|
641
872
|
toArray() {
|
|
642
|
-
const base = executePlanWithSource(this.source, this.plan);
|
|
873
|
+
const base = executePlanWithSource(this.source, this.plan, this.joinContext);
|
|
643
874
|
if (this.plan.joins.length === 0) return base;
|
|
644
875
|
if (!this.joinContext) {
|
|
645
876
|
throw new Error(
|
|
@@ -663,6 +894,14 @@ var Query = class _Query {
|
|
|
663
894
|
* intent is purely to count.
|
|
664
895
|
*/
|
|
665
896
|
count() {
|
|
897
|
+
if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
898
|
+
if (!this.joinContext) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
`Query.count(): plan contains crossJoin clauses but no JoinContext is attached.`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length;
|
|
904
|
+
}
|
|
666
905
|
const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
|
|
667
906
|
if (remainingClauses.length === 0) return candidates.length;
|
|
668
907
|
return filterRecords(candidates, remainingClauses).length;
|
|
@@ -710,7 +949,13 @@ var Query = class _Query {
|
|
|
710
949
|
aggregate(spec) {
|
|
711
950
|
const source = this.source;
|
|
712
951
|
const clauses = this.plan.clauses;
|
|
952
|
+
const joinCtx = this.joinContext;
|
|
953
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
713
954
|
const executeRecords = () => {
|
|
955
|
+
if (hasCrossJoins) {
|
|
956
|
+
if (!joinCtx) throw new Error("Query.aggregate(): crossJoin requires a join context");
|
|
957
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
958
|
+
}
|
|
714
959
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
715
960
|
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
716
961
|
};
|
|
@@ -721,56 +966,19 @@ var Query = class _Query {
|
|
|
721
966
|
}
|
|
722
967
|
return this.aggregateStrategy.aggregate(executeRecords, spec, upstreams);
|
|
723
968
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
*
|
|
729
|
-
* ```ts
|
|
730
|
-
* const byClient = invoices.query()
|
|
731
|
-
* .where('status', '==', 'open')
|
|
732
|
-
* .groupBy('clientId')
|
|
733
|
-
* .aggregate({ total: sum('amount'), n: count() })
|
|
734
|
-
* .run()
|
|
735
|
-
* // → [ { clientId: 'c1', total: 5250, n: 3 }, … ]
|
|
736
|
-
* ```
|
|
737
|
-
*
|
|
738
|
-
* Result rows carry the group key value under the grouping field
|
|
739
|
-
* name plus every reducer output from the spec. Buckets are
|
|
740
|
-
* emitted in first-seen order — consumers who want a specific
|
|
741
|
-
* ordering should `.sort()` downstream.
|
|
742
|
-
*
|
|
743
|
-
* **Cardinality caps:** a one-shot warning fires at 10_000
|
|
744
|
-
* distinct groups; `GroupCardinalityError` throws at 100_000.
|
|
745
|
-
* Grouping on a high-uniqueness field like `id` or `createdAt` is
|
|
746
|
-
* almost always a query mistake — the error message names the
|
|
747
|
-
* field and observed cardinality and suggests narrowing with
|
|
748
|
-
* `.where()` first.
|
|
749
|
-
*
|
|
750
|
-
* **Null / undefined keys:** records with a missing or explicitly
|
|
751
|
-
* `null` group field get their own buckets. `Map`-based
|
|
752
|
-
* partitioning distinguishes `undefined` from `null`, so the two
|
|
753
|
-
* cases do NOT merge. Consumers who want them merged should
|
|
754
|
-
* coalesce upstream with `.filter()`.
|
|
755
|
-
*
|
|
756
|
-
* **Joins are not applied** — same rationale as `.count()` and
|
|
757
|
-
* `.aggregate()`. Joined fields in are projection-only, so
|
|
758
|
-
* running a join inside a grouping pipeline would be wasteful and
|
|
759
|
-
* could trigger `DanglingReferenceError` in strict mode for a
|
|
760
|
-
* call whose intent is purely to bucket-and-reduce. Grouping by
|
|
761
|
-
* a joined field is explicitly out of scope for — file an
|
|
762
|
-
* issue if a real consumer needs it.
|
|
763
|
-
*
|
|
764
|
-
* **Filter clauses (`.filter(fn)`):** grouped queries still
|
|
765
|
-
* support filter clauses in the underlying plan — they run in
|
|
766
|
-
* the same candidate/filter pipeline that `.aggregate()` uses.
|
|
767
|
-
* The performance caveat is the same: filter clauses cost O(N)
|
|
768
|
-
* per record and can't be index-accelerated.
|
|
769
|
-
*/
|
|
770
|
-
groupBy(field) {
|
|
969
|
+
groupBy(...fields) {
|
|
970
|
+
if (fields.length === 0) {
|
|
971
|
+
throw new Error(".groupBy() requires at least one field");
|
|
972
|
+
}
|
|
771
973
|
const source = this.source;
|
|
772
974
|
const clauses = this.plan.clauses;
|
|
975
|
+
const joinCtx = this.joinContext;
|
|
976
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
773
977
|
const executeRecords = () => {
|
|
978
|
+
if (hasCrossJoins) {
|
|
979
|
+
if (!joinCtx) throw new Error("Query.groupBy(): crossJoin requires a join context");
|
|
980
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
981
|
+
}
|
|
774
982
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
775
983
|
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
776
984
|
};
|
|
@@ -779,36 +987,21 @@ var Query = class _Query {
|
|
|
779
987
|
const subscribe = source.subscribe.bind(source);
|
|
780
988
|
upstreams.push({ subscribe: (cb) => subscribe(cb) });
|
|
781
989
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if (!labels) return void 0;
|
|
798
|
-
if (labels[locale] !== void 0) return labels[locale];
|
|
799
|
-
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
800
|
-
for (const fb of chain) {
|
|
801
|
-
if (fb === "any") {
|
|
802
|
-
const any = Object.values(labels)[0];
|
|
803
|
-
if (any !== void 0) return any;
|
|
804
|
-
} else if (labels[fb] !== void 0) {
|
|
805
|
-
return labels[fb];
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
return void 0;
|
|
809
|
-
};
|
|
810
|
-
})() : void 0;
|
|
811
|
-
return this.aggregateStrategy.groupBy(executeRecords, field, upstreams, dictLabelResolver);
|
|
990
|
+
if (fields.length === 1) {
|
|
991
|
+
const field = fields[0];
|
|
992
|
+
const dictLabelResolver = buildDictLabelResolver(this.joinContext, field);
|
|
993
|
+
return this.aggregateStrategy.groupBy(
|
|
994
|
+
executeRecords,
|
|
995
|
+
field,
|
|
996
|
+
upstreams,
|
|
997
|
+
dictLabelResolver
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
return this.aggregateStrategy.groupByN(
|
|
1001
|
+
executeRecords,
|
|
1002
|
+
fields,
|
|
1003
|
+
upstreams
|
|
1004
|
+
);
|
|
812
1005
|
}
|
|
813
1006
|
/**
|
|
814
1007
|
* Re-run the query whenever the source notifies of changes.
|
|
@@ -899,6 +1092,21 @@ var Query = class _Query {
|
|
|
899
1092
|
}
|
|
900
1093
|
}
|
|
901
1094
|
}
|
|
1095
|
+
if (this.joinContext) {
|
|
1096
|
+
const subscribedCross = /* @__PURE__ */ new Set();
|
|
1097
|
+
for (const clause of this.plan.clauses) {
|
|
1098
|
+
if (clause.type !== "crossJoin") continue;
|
|
1099
|
+
if (subscribedCross.has(clause.target)) continue;
|
|
1100
|
+
subscribedCross.add(clause.target);
|
|
1101
|
+
const rightSource = this.joinContext.resolveSource(clause.target);
|
|
1102
|
+
if (rightSource?.subscribe) {
|
|
1103
|
+
const rightSubscribe = rightSource.subscribe.bind(rightSource);
|
|
1104
|
+
upstreams.push({
|
|
1105
|
+
subscribe: (cb) => rightSubscribe(cb)
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
902
1110
|
return buildLiveQuery(() => this.toArray(), upstreams);
|
|
903
1111
|
}
|
|
904
1112
|
/**
|
|
@@ -910,9 +1118,20 @@ var Query = class _Query {
|
|
|
910
1118
|
return serializePlan(this.plan);
|
|
911
1119
|
}
|
|
912
1120
|
};
|
|
913
|
-
function executePlanWithSource(source, plan) {
|
|
914
|
-
const
|
|
915
|
-
let result
|
|
1121
|
+
function executePlanWithSource(source, plan, joinContext) {
|
|
1122
|
+
const hasCrossJoins = plan.clauses.some((c) => c.type === "crossJoin");
|
|
1123
|
+
let result;
|
|
1124
|
+
if (hasCrossJoins) {
|
|
1125
|
+
if (!joinContext) {
|
|
1126
|
+
throw new Error(
|
|
1127
|
+
`Query.toArray(): plan contains crossJoin clauses but no JoinContext is attached. Use collection.query() instead of new Query() for cross-join support.`
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
result = executeClausePipeline(source, plan.clauses, joinContext);
|
|
1131
|
+
} else {
|
|
1132
|
+
const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
|
|
1133
|
+
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
|
|
1134
|
+
}
|
|
916
1135
|
if (plan.orderBy.length > 0) {
|
|
917
1136
|
result = sortRecords(result, plan.orderBy);
|
|
918
1137
|
}
|
|
@@ -962,6 +1181,11 @@ function materializeIds(ids, lookupById) {
|
|
|
962
1181
|
return out;
|
|
963
1182
|
}
|
|
964
1183
|
function executePlan(records, plan) {
|
|
1184
|
+
if (plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
1185
|
+
throw new Error(
|
|
1186
|
+
`executePlan(): does not support crossJoin clauses. executePlan is a stateless pure function \u2014 it cannot resolve cross-join right-side collections. Use Query.toArray() (via collection.query()) instead.`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
965
1189
|
let result = filterRecords(records, plan.clauses);
|
|
966
1190
|
if (plan.orderBy.length > 0) {
|
|
967
1191
|
result = sortRecords(result, plan.orderBy);
|
|
@@ -989,6 +1213,74 @@ function filterRecords(records, clauses) {
|
|
|
989
1213
|
}
|
|
990
1214
|
return out;
|
|
991
1215
|
}
|
|
1216
|
+
function executeClausePipeline(source, clauses, joinContext) {
|
|
1217
|
+
let rel = [...source.snapshot()];
|
|
1218
|
+
let filterBatch = [];
|
|
1219
|
+
for (const clause of clauses) {
|
|
1220
|
+
if (clause.type === "crossJoin") {
|
|
1221
|
+
if (filterBatch.length > 0) {
|
|
1222
|
+
rel = filterRecords(rel, filterBatch);
|
|
1223
|
+
filterBatch = [];
|
|
1224
|
+
}
|
|
1225
|
+
const rightSource = joinContext.resolveSource(clause.target);
|
|
1226
|
+
if (!rightSource) {
|
|
1227
|
+
throw new CrossJoinSourceUnknownError(clause.target, joinContext.leftCollection);
|
|
1228
|
+
}
|
|
1229
|
+
rel = applyCrossJoin(rel, clause, rightSource);
|
|
1230
|
+
} else {
|
|
1231
|
+
filterBatch.push(clause);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (filterBatch.length > 0) {
|
|
1235
|
+
rel = filterRecords(rel, filterBatch);
|
|
1236
|
+
}
|
|
1237
|
+
return rel;
|
|
1238
|
+
}
|
|
1239
|
+
function applyCrossJoin(leftRel, clause, rightSource) {
|
|
1240
|
+
const rightRows = rightSource.snapshot();
|
|
1241
|
+
const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
|
|
1242
|
+
const { as } = clause;
|
|
1243
|
+
if (!clause.on) {
|
|
1244
|
+
const product = leftRel.length * rightRows.length;
|
|
1245
|
+
if (product > maxRows) {
|
|
1246
|
+
throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows });
|
|
1247
|
+
}
|
|
1248
|
+
const expanded2 = [];
|
|
1249
|
+
for (const left of leftRel) {
|
|
1250
|
+
const leftObj = left;
|
|
1251
|
+
for (const right of rightRows) {
|
|
1252
|
+
expanded2.push({ ...leftObj, [as]: right });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return expanded2;
|
|
1256
|
+
}
|
|
1257
|
+
const expanded = [];
|
|
1258
|
+
let cumulative = 0;
|
|
1259
|
+
for (const left of leftRel) {
|
|
1260
|
+
const callbackResult = clause.on(left);
|
|
1261
|
+
let filteredRight;
|
|
1262
|
+
if (Array.isArray(callbackResult)) {
|
|
1263
|
+
filteredRight = callbackResult;
|
|
1264
|
+
} else {
|
|
1265
|
+
filteredRight = rightRows.filter(
|
|
1266
|
+
callbackResult
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
cumulative += filteredRight.length;
|
|
1270
|
+
if (cumulative > maxRows) {
|
|
1271
|
+
throw new CrossJoinTooLargeError({
|
|
1272
|
+
target: clause.target,
|
|
1273
|
+
expected: cumulative,
|
|
1274
|
+
limit: maxRows
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
const leftObj = left;
|
|
1278
|
+
for (const right of filteredRight) {
|
|
1279
|
+
expanded.push({ ...leftObj, [as]: right });
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return expanded;
|
|
1283
|
+
}
|
|
992
1284
|
function sortRecords(records, orderBy) {
|
|
993
1285
|
return [...records].sort((a, b) => {
|
|
994
1286
|
for (const { field, direction } of orderBy) {
|
|
@@ -1034,6 +1326,16 @@ function serializeClause(clause) {
|
|
|
1034
1326
|
if (clause.type === "filter") {
|
|
1035
1327
|
return { type: "filter", fn: "[function]" };
|
|
1036
1328
|
}
|
|
1329
|
+
if (clause.type === "wherePredicate") {
|
|
1330
|
+
return {
|
|
1331
|
+
type: "wherePredicate",
|
|
1332
|
+
name: clause.name,
|
|
1333
|
+
ctx: clause.ctx,
|
|
1334
|
+
predicateHash: clause.predicateHash,
|
|
1335
|
+
ctxHash: clause.ctxHash,
|
|
1336
|
+
fn: "[function]"
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1037
1339
|
if (clause.type === "group") {
|
|
1038
1340
|
return {
|
|
1039
1341
|
type: "group",
|
|
@@ -1041,8 +1343,65 @@ function serializeClause(clause) {
|
|
|
1041
1343
|
clauses: clause.clauses.map(serializeClause)
|
|
1042
1344
|
};
|
|
1043
1345
|
}
|
|
1346
|
+
if (clause.type === "crossJoin") {
|
|
1347
|
+
return {
|
|
1348
|
+
type: "crossJoin",
|
|
1349
|
+
target: clause.target,
|
|
1350
|
+
as: clause.as,
|
|
1351
|
+
on: clause.on ? "[function]" : void 0,
|
|
1352
|
+
onPredicateName: clause.onPredicateName,
|
|
1353
|
+
maxRows: clause.maxRows
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1044
1356
|
return clause;
|
|
1045
1357
|
}
|
|
1358
|
+
function canonicalCtxHash(ctx) {
|
|
1359
|
+
if (ctx === void 0) return "";
|
|
1360
|
+
const canonical = JSON.stringify(ctx, (_key, value) => {
|
|
1361
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1362
|
+
const sorted = {};
|
|
1363
|
+
for (const k of Object.keys(value).sort()) {
|
|
1364
|
+
sorted[k] = value[k];
|
|
1365
|
+
}
|
|
1366
|
+
return sorted;
|
|
1367
|
+
}
|
|
1368
|
+
return value;
|
|
1369
|
+
});
|
|
1370
|
+
let h = 5381;
|
|
1371
|
+
for (let i = 0; i < canonical.length; i++) {
|
|
1372
|
+
h = (h << 5) + h ^ canonical.charCodeAt(i);
|
|
1373
|
+
}
|
|
1374
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
1375
|
+
}
|
|
1376
|
+
function buildDictLabelResolver(joinCtx, field) {
|
|
1377
|
+
if (!joinCtx?.resolveDictSource) return void 0;
|
|
1378
|
+
const dictSource = joinCtx.resolveDictSource(field);
|
|
1379
|
+
if (!dictSource) return void 0;
|
|
1380
|
+
const snapshot = dictSource.snapshot();
|
|
1381
|
+
const dictMap = /* @__PURE__ */ new Map();
|
|
1382
|
+
for (const entry of snapshot) {
|
|
1383
|
+
const k = entry["key"];
|
|
1384
|
+
const labels = entry["labels"];
|
|
1385
|
+
if (typeof k === "string" && labels && typeof labels === "object") {
|
|
1386
|
+
dictMap.set(k, labels);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return async (key, locale, fallback) => {
|
|
1390
|
+
const labels = dictMap.get(key);
|
|
1391
|
+
if (!labels) return void 0;
|
|
1392
|
+
if (labels[locale] !== void 0) return labels[locale];
|
|
1393
|
+
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
1394
|
+
for (const fb of chain) {
|
|
1395
|
+
if (fb === "any") {
|
|
1396
|
+
const any = Object.values(labels)[0];
|
|
1397
|
+
if (any !== void 0) return any;
|
|
1398
|
+
} else if (labels[fb] !== void 0) {
|
|
1399
|
+
return labels[fb];
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return void 0;
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1046
1405
|
|
|
1047
1406
|
// src/indexing/eager-indexes.ts
|
|
1048
1407
|
var CollectionIndexes = class {
|
|
@@ -1173,6 +1532,7 @@ function count(opts) {
|
|
|
1173
1532
|
const _seed = opts?.seed;
|
|
1174
1533
|
void _seed;
|
|
1175
1534
|
return {
|
|
1535
|
+
op: "count",
|
|
1176
1536
|
init: () => 0,
|
|
1177
1537
|
step: (state) => state + 1,
|
|
1178
1538
|
remove: (state) => state - 1,
|
|
@@ -1183,6 +1543,8 @@ function sum(field, opts) {
|
|
|
1183
1543
|
const _seed = opts?.seed;
|
|
1184
1544
|
void _seed;
|
|
1185
1545
|
return {
|
|
1546
|
+
op: "sum",
|
|
1547
|
+
field,
|
|
1186
1548
|
init: () => 0,
|
|
1187
1549
|
step: (state, record) => state + readNumber(record, field),
|
|
1188
1550
|
remove: (state, record) => state - readNumber(record, field),
|
|
@@ -1193,6 +1555,8 @@ function avg(field, opts) {
|
|
|
1193
1555
|
const _seed = opts?.seed;
|
|
1194
1556
|
void _seed;
|
|
1195
1557
|
return {
|
|
1558
|
+
op: "avg",
|
|
1559
|
+
field,
|
|
1196
1560
|
init: () => ({ sum: 0, count: 0 }),
|
|
1197
1561
|
step: (state, record) => ({
|
|
1198
1562
|
sum: state.sum + readNumber(record, field),
|
|
@@ -1219,6 +1583,8 @@ function min(field, opts) {
|
|
|
1219
1583
|
const _seed = opts?.seed;
|
|
1220
1584
|
void _seed;
|
|
1221
1585
|
return {
|
|
1586
|
+
op: "min",
|
|
1587
|
+
field,
|
|
1222
1588
|
init: () => ({ values: [] }),
|
|
1223
1589
|
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1224
1590
|
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
@@ -1237,6 +1603,8 @@ function max(field, opts) {
|
|
|
1237
1603
|
const _seed = opts?.seed;
|
|
1238
1604
|
void _seed;
|
|
1239
1605
|
return {
|
|
1606
|
+
op: "max",
|
|
1607
|
+
field,
|
|
1240
1608
|
init: () => ({ values: [] }),
|
|
1241
1609
|
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1242
1610
|
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
@@ -1379,31 +1747,53 @@ function buildLiveAggregation(recompute, upstreams) {
|
|
|
1379
1747
|
return new LiveAggregationImpl(recompute, upstreams);
|
|
1380
1748
|
}
|
|
1381
1749
|
|
|
1750
|
+
// src/aggregate/canonical-key.ts
|
|
1751
|
+
function canonicalGroupKey(fields, row) {
|
|
1752
|
+
const sorted = [...fields].sort();
|
|
1753
|
+
const parts = [];
|
|
1754
|
+
for (const name of sorted) {
|
|
1755
|
+
const v = row[name];
|
|
1756
|
+
const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
|
|
1757
|
+
parts.push(`${name}=${serialised}`);
|
|
1758
|
+
}
|
|
1759
|
+
return parts.join("|");
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1382
1762
|
// src/aggregate/groupby.ts
|
|
1383
1763
|
var GROUPBY_WARN_CARDINALITY = 1e4;
|
|
1384
1764
|
var GROUPBY_MAX_CARDINALITY = 1e5;
|
|
1385
1765
|
var warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
1386
|
-
function warnCardinalityApproaching(
|
|
1387
|
-
|
|
1388
|
-
warnedCardinalityFields.
|
|
1766
|
+
function warnCardinalityApproaching(fields, observed) {
|
|
1767
|
+
const key = JSON.stringify([...fields].sort());
|
|
1768
|
+
if (warnedCardinalityFields.has(key)) return;
|
|
1769
|
+
warnedCardinalityFields.add(key);
|
|
1770
|
+
const label = `[${fields.join(", ")}]`;
|
|
1389
1771
|
console.warn(
|
|
1390
|
-
`[noy-db] .groupBy(
|
|
1772
|
+
`[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.`
|
|
1391
1773
|
);
|
|
1392
1774
|
}
|
|
1393
1775
|
function resetGroupByWarnings() {
|
|
1394
1776
|
warnedCardinalityFields.clear();
|
|
1395
1777
|
}
|
|
1396
|
-
var
|
|
1397
|
-
constructor(executeRecords,
|
|
1778
|
+
var GroupedQueryBase = class {
|
|
1779
|
+
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
|
|
1398
1780
|
this.executeRecords = executeRecords;
|
|
1399
|
-
this.field = field;
|
|
1400
1781
|
this.upstreams = upstreams;
|
|
1401
1782
|
this.dictLabelResolver = dictLabelResolver;
|
|
1783
|
+
this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
|
|
1402
1784
|
}
|
|
1403
1785
|
executeRecords;
|
|
1404
|
-
field;
|
|
1405
1786
|
upstreams;
|
|
1406
1787
|
dictLabelResolver;
|
|
1788
|
+
/**
|
|
1789
|
+
* Field set this grouped query buckets on. Stored in declaration
|
|
1790
|
+
* order — the same order is preserved on every result row by
|
|
1791
|
+
* `groupAndReduce`. For the single-field constructor, this is
|
|
1792
|
+
* `[field]`.
|
|
1793
|
+
*/
|
|
1794
|
+
fields;
|
|
1795
|
+
};
|
|
1796
|
+
var GroupedQuery = class extends GroupedQueryBase {
|
|
1407
1797
|
/**
|
|
1408
1798
|
* Build a grouped aggregation. Returns a `GroupedAggregation`
|
|
1409
1799
|
* with `.run()`, `.runAsync()`, and `.live()` terminals — same shape
|
|
@@ -1413,70 +1803,93 @@ var GroupedQuery = class {
|
|
|
1413
1803
|
aggregate(spec) {
|
|
1414
1804
|
return new GroupedAggregation(
|
|
1415
1805
|
this.executeRecords,
|
|
1416
|
-
this.
|
|
1806
|
+
this.fields,
|
|
1807
|
+
spec,
|
|
1808
|
+
this.upstreams,
|
|
1809
|
+
this.dictLabelResolver
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
var GroupedQueryN = class extends GroupedQueryBase {
|
|
1814
|
+
aggregate(spec) {
|
|
1815
|
+
return new GroupedAggregation(
|
|
1816
|
+
this.executeRecords,
|
|
1817
|
+
this.fields,
|
|
1417
1818
|
spec,
|
|
1418
1819
|
this.upstreams,
|
|
1419
1820
|
this.dictLabelResolver
|
|
1420
1821
|
);
|
|
1421
1822
|
}
|
|
1422
1823
|
};
|
|
1423
|
-
function groupAndReduce(records,
|
|
1824
|
+
function groupAndReduce(records, fieldOrFields, spec) {
|
|
1825
|
+
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
1826
|
+
if (fields.length === 0) {
|
|
1827
|
+
throw new Error(".groupBy() requires at least one field");
|
|
1828
|
+
}
|
|
1424
1829
|
const buckets = /* @__PURE__ */ new Map();
|
|
1830
|
+
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
1425
1831
|
for (const record of records) {
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1832
|
+
const keyValues = {};
|
|
1833
|
+
for (const f of fields) {
|
|
1834
|
+
keyValues[f] = readPath(record, f);
|
|
1835
|
+
}
|
|
1836
|
+
const dedupKey = canonicalGroupKey(fields, keyValues);
|
|
1837
|
+
let bucket = buckets.get(dedupKey);
|
|
1428
1838
|
if (bucket === void 0) {
|
|
1429
1839
|
if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
|
|
1430
1840
|
throw new GroupCardinalityError(
|
|
1431
|
-
|
|
1841
|
+
fieldLabel,
|
|
1432
1842
|
buckets.size + 1,
|
|
1433
1843
|
GROUPBY_MAX_CARDINALITY
|
|
1434
1844
|
);
|
|
1435
1845
|
}
|
|
1436
|
-
bucket = [];
|
|
1437
|
-
buckets.set(
|
|
1846
|
+
bucket = { keyValues, records: [] };
|
|
1847
|
+
buckets.set(dedupKey, bucket);
|
|
1438
1848
|
}
|
|
1439
|
-
bucket.push(record);
|
|
1849
|
+
bucket.records.push(record);
|
|
1440
1850
|
}
|
|
1441
1851
|
if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
|
|
1442
|
-
warnCardinalityApproaching(
|
|
1852
|
+
warnCardinalityApproaching(fields, buckets.size);
|
|
1443
1853
|
}
|
|
1444
|
-
const
|
|
1854
|
+
const reducerKeys = Object.keys(spec);
|
|
1445
1855
|
const out = [];
|
|
1446
|
-
for (const
|
|
1856
|
+
for (const bucket of buckets.values()) {
|
|
1447
1857
|
const state = {};
|
|
1448
|
-
for (const
|
|
1449
|
-
state[
|
|
1858
|
+
for (const rk of reducerKeys) {
|
|
1859
|
+
state[rk] = spec[rk].init();
|
|
1450
1860
|
}
|
|
1451
|
-
for (const record of
|
|
1452
|
-
for (const
|
|
1453
|
-
state[
|
|
1861
|
+
for (const record of bucket.records) {
|
|
1862
|
+
for (const rk of reducerKeys) {
|
|
1863
|
+
state[rk] = spec[rk].step(state[rk], record);
|
|
1454
1864
|
}
|
|
1455
1865
|
}
|
|
1456
|
-
const row = {
|
|
1457
|
-
for (const
|
|
1458
|
-
row[
|
|
1866
|
+
const row = {};
|
|
1867
|
+
for (const f of fields) {
|
|
1868
|
+
row[f] = bucket.keyValues[f];
|
|
1869
|
+
}
|
|
1870
|
+
for (const rk of reducerKeys) {
|
|
1871
|
+
row[rk] = spec[rk].finalize(state[rk]);
|
|
1459
1872
|
}
|
|
1460
1873
|
out.push(row);
|
|
1461
1874
|
}
|
|
1462
1875
|
return out;
|
|
1463
1876
|
}
|
|
1464
1877
|
var GroupedAggregation = class {
|
|
1465
|
-
constructor(executeRecords,
|
|
1878
|
+
constructor(executeRecords, fields, spec, upstreams, dictLabelResolver) {
|
|
1466
1879
|
this.executeRecords = executeRecords;
|
|
1467
|
-
this.field = field;
|
|
1468
1880
|
this.spec = spec;
|
|
1469
1881
|
this.upstreams = upstreams;
|
|
1470
1882
|
this.dictLabelResolver = dictLabelResolver;
|
|
1883
|
+
this.fields = typeof fields === "string" ? [fields] : [...fields];
|
|
1471
1884
|
}
|
|
1472
1885
|
executeRecords;
|
|
1473
|
-
field;
|
|
1474
1886
|
spec;
|
|
1475
1887
|
upstreams;
|
|
1476
1888
|
dictLabelResolver;
|
|
1889
|
+
fields;
|
|
1477
1890
|
/** Execute the query, group, reduce, and return an array of rows. */
|
|
1478
1891
|
run() {
|
|
1479
|
-
return groupAndReduce(this.executeRecords(), this.
|
|
1892
|
+
return groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1480
1893
|
}
|
|
1481
1894
|
/**
|
|
1482
1895
|
* Execute the query, group, reduce, and resolve `<field>Label` for
|
|
@@ -1486,17 +1899,22 @@ var GroupedAggregation = class {
|
|
|
1486
1899
|
*
|
|
1487
1900
|
* The `<field>Label` field is appended to each row. Rows whose group
|
|
1488
1901
|
* key has no dictionary entry get `<field>Label: undefined`.
|
|
1902
|
+
*
|
|
1903
|
+
* Dict-label resolution is single-field only — multi-key groupings
|
|
1904
|
+
* do not produce a `<field>Label`. The resolver is only attached
|
|
1905
|
+
* by the builder when `fields.length === 1`.
|
|
1489
1906
|
*/
|
|
1490
1907
|
async runAsync(opts) {
|
|
1491
|
-
const rows = groupAndReduce(this.executeRecords(), this.
|
|
1492
|
-
if (!opts?.locale || !this.dictLabelResolver) return rows;
|
|
1908
|
+
const rows = groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1909
|
+
if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows;
|
|
1493
1910
|
const resolve = this.dictLabelResolver;
|
|
1494
1911
|
const locale = opts.locale;
|
|
1495
1912
|
const fallback = opts.fallback;
|
|
1496
|
-
const
|
|
1913
|
+
const field = this.fields[0];
|
|
1914
|
+
const labelKey = `${field}Label`;
|
|
1497
1915
|
return Promise.all(
|
|
1498
1916
|
rows.map(async (row) => {
|
|
1499
|
-
const key = row[
|
|
1917
|
+
const key = row[field];
|
|
1500
1918
|
if (typeof key !== "string") return row;
|
|
1501
1919
|
const label = await resolve(key, locale, fallback);
|
|
1502
1920
|
return { ...row, [labelKey]: label };
|
|
@@ -1520,7 +1938,7 @@ var GroupedAggregation = class {
|
|
|
1520
1938
|
* Always call `live.stop()` when finished.
|
|
1521
1939
|
*/
|
|
1522
1940
|
live() {
|
|
1523
|
-
const recompute = () => groupAndReduce(this.executeRecords(), this.
|
|
1941
|
+
const recompute = () => groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1524
1942
|
return buildLiveAggregation(recompute, this.upstreams);
|
|
1525
1943
|
}
|
|
1526
1944
|
};
|
|
@@ -1930,11 +2348,20 @@ function coerceRefKey2(value) {
|
|
|
1930
2348
|
0 && (module.exports = {
|
|
1931
2349
|
Aggregation,
|
|
1932
2350
|
CollectionIndexes,
|
|
2351
|
+
CrossJoinSourceUnknownError,
|
|
2352
|
+
CrossJoinTooLargeError,
|
|
2353
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
1933
2354
|
DEFAULT_JOIN_MAX_ROWS,
|
|
2355
|
+
DanglingReferenceError,
|
|
1934
2356
|
GROUPBY_MAX_CARDINALITY,
|
|
1935
2357
|
GROUPBY_WARN_CARDINALITY,
|
|
2358
|
+
GroupCardinalityError,
|
|
1936
2359
|
GroupedAggregation,
|
|
1937
2360
|
GroupedQuery,
|
|
2361
|
+
GroupedQueryN,
|
|
2362
|
+
IndexRequiredError,
|
|
2363
|
+
IndexWriteFailureError,
|
|
2364
|
+
JoinTooLargeError,
|
|
1938
2365
|
Query,
|
|
1939
2366
|
ScanBuilder,
|
|
1940
2367
|
applyJoins,
|