@noy-db/hub 0.2.0-pre.30 → 0.2.0-pre.31
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/adapter/index.d.ts +9 -0
- package/dist/adapter/index.js +14 -0
- package/dist/attestation/index.d.ts +1 -1
- package/dist/attestation/index.js +3 -3
- package/dist/blobs/index.d.ts +3 -3
- package/dist/bundle/index.d.ts +3 -3
- package/dist/bundle/index.js +1 -1
- package/dist/{chunk-Y6YUWZTS.js → chunk-L5HHBOMS.js} +2 -2
- package/dist/{chunk-BVKLJBJJ.js → chunk-W5NVNJDX.js} +2 -1
- package/dist/{chunk-BVKLJBJJ.js.map → chunk-W5NVNJDX.js.map} +1 -1
- package/dist/consent/index.d.ts +2 -2
- package/dist/{decrypt-partition-Mhjh6nnG.d.ts → decrypt-partition-CXS5KopB.d.ts} +1 -1
- package/dist/derivations/index.d.ts +3 -3
- package/dist/{dev-unlock-DJ3IhgY9.d.ts → dev-unlock-CnHTTVea.d.ts} +1 -1
- package/dist/guards/index.d.ts +3 -3
- package/dist/{hash-DLwkYf1d.d.ts → hash-oc5paYl0.d.ts} +1 -1
- package/dist/history/index.d.ts +3 -3
- package/dist/i18n/index.d.ts +2 -2
- package/dist/{index-CMRIx_zx.d.ts → index-BmhGztUi.d.ts} +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +2 -2
- package/dist/kernel/index.d.ts +2 -2
- package/dist/materialized-views/index.d.ts +3 -3
- package/dist/{mime-magic-CacDBwYp.d.ts → mime-magic-JeLKHRng.d.ts} +1 -1
- package/dist/{noydb-5MIBD77B.js → noydb-NAU2DTFP.js} +2 -2
- package/dist/noydb-NAU2DTFP.js.map +1 -0
- package/dist/overlay-views/index.d.ts +3 -3
- package/dist/periods/index.d.ts +2 -2
- package/dist/session/index.d.ts +3 -3
- package/dist/shadow/index.d.ts +2 -2
- package/dist/snapshots/index.d.ts +2 -2
- package/dist/store/index.d.ts +2 -2
- package/dist/sync/index.d.ts +1 -1
- package/dist/team/index.d.ts +2 -2
- package/dist/{transition-guard-J04-jime.d.ts → transition-guard-Dy3NioQr.d.ts} +1 -1
- package/dist/tx/index.d.ts +2 -2
- package/dist/{with-materialized-view-DNfzC5lH.d.ts → with-materialized-view-DIVeez0W.d.ts} +1 -1
- package/dist/{with-overlayed-view-qk3lbhOd.d.ts → with-overlayed-view-DVZrc5Hb.d.ts} +1 -1
- package/dist/{with-rollup-Dd3_ZG4b.d.ts → with-rollup-CbU-O4wP.d.ts} +1 -1
- package/package.json +62 -221
- package/dist/aggregate/index.cjs +0 -1144
- package/dist/aggregate/index.cjs.map +0 -1
- package/dist/aggregate/index.d.cts +0 -39
- package/dist/attestation/index.cjs +0 -305
- package/dist/attestation/index.cjs.map +0 -1
- package/dist/attestation/index.d.cts +0 -54
- package/dist/blobs/index.cjs +0 -1967
- package/dist/blobs/index.cjs.map +0 -1
- package/dist/blobs/index.d.cts +0 -48
- package/dist/bundle/index.cjs +0 -41045
- package/dist/bundle/index.cjs.map +0 -1
- package/dist/bundle/index.d.cts +0 -187
- package/dist/consent/index.cjs +0 -204
- package/dist/consent/index.cjs.map +0 -1
- package/dist/consent/index.d.cts +0 -27
- package/dist/crdt/index.cjs +0 -152
- package/dist/crdt/index.cjs.map +0 -1
- package/dist/crdt/index.d.cts +0 -30
- package/dist/decrypt-partition-C0iDpbSd.d.cts +0 -558
- package/dist/derivations/index.cjs +0 -469
- package/dist/derivations/index.cjs.map +0 -1
- package/dist/derivations/index.d.cts +0 -74
- package/dist/dev-unlock-0Z6yxfS7.d.cts +0 -263
- package/dist/discriminant-BN9REW3o.d.cts +0 -60
- package/dist/errors-BAWO5Z5a.d.cts +0 -1500
- package/dist/forget/index.cjs +0 -43
- package/dist/forget/index.cjs.map +0 -1
- package/dist/forget/index.d.cts +0 -1
- package/dist/guards/index.cjs +0 -455
- package/dist/guards/index.cjs.map +0 -1
- package/dist/guards/index.d.cts +0 -39
- package/dist/hash-Db8ncXGr.d.cts +0 -63
- package/dist/history/index.cjs +0 -1245
- package/dist/history/index.cjs.map +0 -1
- package/dist/history/index.d.cts +0 -65
- package/dist/i18n/index.cjs +0 -1280
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/index.d.cts +0 -41
- package/dist/index-BMmajblo.d.cts +0 -362
- package/dist/index-C1SC1EPe.d.cts +0 -1325
- package/dist/index-xJEPkvMb.d.cts +0 -93
- package/dist/index.cjs +0 -48100
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1050
- package/dist/indexing/index.cjs +0 -803
- package/dist/indexing/index.cjs.map +0 -1
- package/dist/indexing/index.d.cts +0 -36
- package/dist/kernel/index.cjs +0 -751
- package/dist/kernel/index.cjs.map +0 -1
- package/dist/kernel/index.d.cts +0 -11
- package/dist/lazy-builder-eYZzLEL1.d.cts +0 -304
- package/dist/materialized-views/index.cjs +0 -1518
- package/dist/materialized-views/index.cjs.map +0 -1
- package/dist/materialized-views/index.d.cts +0 -187
- package/dist/mime-magic-sdMzsfVK.d.cts +0 -103
- package/dist/overlay-views/index.cjs +0 -399
- package/dist/overlay-views/index.cjs.map +0 -1
- package/dist/overlay-views/index.d.cts +0 -102
- package/dist/periods/index.cjs +0 -1041
- package/dist/periods/index.cjs.map +0 -1
- package/dist/periods/index.d.cts +0 -24
- package/dist/predicate-BmhBSPCH.d.cts +0 -267
- package/dist/query/index.cjs +0 -3480
- package/dist/query/index.cjs.map +0 -1
- package/dist/query/index.d.cts +0 -4
- package/dist/sealed-record/index.cjs +0 -139
- package/dist/sealed-record/index.cjs.map +0 -1
- package/dist/sealed-record/index.d.cts +0 -123
- package/dist/session/index.cjs +0 -495
- package/dist/session/index.cjs.map +0 -1
- package/dist/session/index.d.cts +0 -48
- package/dist/shadow/index.cjs +0 -133
- package/dist/shadow/index.cjs.map +0 -1
- package/dist/shadow/index.d.cts +0 -19
- package/dist/snapshots/index.cjs +0 -937
- package/dist/snapshots/index.cjs.map +0 -1
- package/dist/snapshots/index.d.cts +0 -30
- package/dist/store/index.cjs +0 -1091
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -501
- package/dist/strategy-BSxFXGzb.d.cts +0 -110
- package/dist/strategy-mJExw4LQ.d.cts +0 -1069
- package/dist/sync/index.cjs +0 -1062
- package/dist/sync/index.cjs.map +0 -1
- package/dist/sync/index.d.cts +0 -45
- package/dist/team/index.cjs +0 -2805
- package/dist/team/index.cjs.map +0 -1
- package/dist/team/index.d.cts +0 -120
- package/dist/transition-guard-tWZIsaXe.d.cts +0 -165
- package/dist/tx/index.cjs +0 -614
- package/dist/tx/index.cjs.map +0 -1
- package/dist/tx/index.d.cts +0 -39
- package/dist/types-CdVfiQgt.d.cts +0 -15275
- package/dist/ulid-DRH25k3y.d.cts +0 -66
- package/dist/util/index.cjs +0 -237
- package/dist/util/index.cjs.map +0 -1
- package/dist/util/index.d.cts +0 -79
- package/dist/with-materialized-view-DfgPcvi9.d.cts +0 -27
- package/dist/with-overlayed-view-Cvfkx1lg.d.cts +0 -13
- package/dist/with-rollup-nIxo7h9z.d.cts +0 -47
- /package/dist/{noydb-5MIBD77B.js.map → adapter/index.js.map} +0 -0
- /package/dist/{chunk-Y6YUWZTS.js.map → chunk-L5HHBOMS.js.map} +0 -0
- /package/dist/{types-DHn9lEP0.d.ts → index-DHn9lEP0.d.ts} +0 -0
package/dist/aggregate/index.cjs
DELETED
|
@@ -1,1144 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/aggregate/index.ts
|
|
21
|
-
var aggregate_exports = {};
|
|
22
|
-
__export(aggregate_exports, {
|
|
23
|
-
Aggregation: () => Aggregation,
|
|
24
|
-
GROUPBY_MAX_CARDINALITY: () => GROUPBY_MAX_CARDINALITY,
|
|
25
|
-
GROUPBY_WARN_CARDINALITY: () => GROUPBY_WARN_CARDINALITY,
|
|
26
|
-
GroupedAggregation: () => GroupedAggregation,
|
|
27
|
-
GroupedQuery: () => GroupedQuery,
|
|
28
|
-
GroupedQueryN: () => GroupedQueryN,
|
|
29
|
-
avg: () => avg,
|
|
30
|
-
buildLiveAggregation: () => buildLiveAggregation,
|
|
31
|
-
count: () => count,
|
|
32
|
-
groupAndReduce: () => groupAndReduce,
|
|
33
|
-
max: () => max,
|
|
34
|
-
min: () => min,
|
|
35
|
-
reduceRecords: () => reduceRecords,
|
|
36
|
-
resetGroupByWarnings: () => resetGroupByWarnings,
|
|
37
|
-
sum: () => sum,
|
|
38
|
-
withAggregate: () => withAggregate
|
|
39
|
-
});
|
|
40
|
-
module.exports = __toCommonJS(aggregate_exports);
|
|
41
|
-
|
|
42
|
-
// src/aggregate/aggregation.ts
|
|
43
|
-
function reduceRecords(records, spec) {
|
|
44
|
-
const state = {};
|
|
45
|
-
for (const key of Object.keys(spec)) {
|
|
46
|
-
state[key] = spec[key].init();
|
|
47
|
-
}
|
|
48
|
-
for (const record of records) {
|
|
49
|
-
for (const key of Object.keys(spec)) {
|
|
50
|
-
state[key] = spec[key].step(state[key], record);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const result = {};
|
|
54
|
-
for (const key of Object.keys(spec)) {
|
|
55
|
-
result[key] = spec[key].finalize(state[key]);
|
|
56
|
-
}
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
var LiveAggregationImpl = class {
|
|
60
|
-
constructor(recompute, upstreams) {
|
|
61
|
-
this.recompute = recompute;
|
|
62
|
-
try {
|
|
63
|
-
this.value = recompute();
|
|
64
|
-
this.error = void 0;
|
|
65
|
-
} catch (err) {
|
|
66
|
-
this.value = void 0;
|
|
67
|
-
this.error = err;
|
|
68
|
-
}
|
|
69
|
-
for (const upstream of upstreams) {
|
|
70
|
-
const unsub = upstream.subscribe(() => this.refresh());
|
|
71
|
-
this.unsubscribes.push(unsub);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
recompute;
|
|
75
|
-
value;
|
|
76
|
-
error;
|
|
77
|
-
listeners = /* @__PURE__ */ new Set();
|
|
78
|
-
unsubscribes = [];
|
|
79
|
-
stopped = false;
|
|
80
|
-
refresh() {
|
|
81
|
-
if (this.stopped) return;
|
|
82
|
-
try {
|
|
83
|
-
this.value = this.recompute();
|
|
84
|
-
this.error = void 0;
|
|
85
|
-
} catch (err) {
|
|
86
|
-
this.error = err;
|
|
87
|
-
}
|
|
88
|
-
for (const listener of this.listeners) {
|
|
89
|
-
try {
|
|
90
|
-
listener();
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.warn("[noy-db] LiveAggregation listener threw:", err);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
subscribe(cb) {
|
|
97
|
-
if (this.stopped) {
|
|
98
|
-
return () => {
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
this.listeners.add(cb);
|
|
102
|
-
return () => {
|
|
103
|
-
this.listeners.delete(cb);
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
stop() {
|
|
107
|
-
if (this.stopped) return;
|
|
108
|
-
this.stopped = true;
|
|
109
|
-
for (const unsub of this.unsubscribes) {
|
|
110
|
-
try {
|
|
111
|
-
unsub();
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.warn("[noy-db] LiveAggregation upstream unsubscribe threw:", err);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
this.unsubscribes.length = 0;
|
|
117
|
-
this.listeners.clear();
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
var Aggregation = class {
|
|
121
|
-
constructor(executeRecords, spec, upstreams) {
|
|
122
|
-
this.executeRecords = executeRecords;
|
|
123
|
-
this.spec = spec;
|
|
124
|
-
this.upstreams = upstreams;
|
|
125
|
-
}
|
|
126
|
-
executeRecords;
|
|
127
|
-
spec;
|
|
128
|
-
upstreams;
|
|
129
|
-
/**
|
|
130
|
-
* Execute the query and reduce the results synchronously.
|
|
131
|
-
* Returns the reduced shape matching the spec — e.g. a spec of
|
|
132
|
-
* `{ total: sum('amount'), n: count() }` returns
|
|
133
|
-
* `{ total: number, n: number }`.
|
|
134
|
-
*/
|
|
135
|
-
run() {
|
|
136
|
-
return reduceRecords(this.executeRecords(), this.spec);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Build a reactive `LiveAggregation<R>` that re-runs the reduction
|
|
140
|
-
* whenever any upstream source notifies of a change. The initial
|
|
141
|
-
* value is computed eagerly in the constructor, so consumers can
|
|
142
|
-
* read `live.value` immediately after calling `.live()`.
|
|
143
|
-
*
|
|
144
|
-
* Always call `live.stop()` when finished — it tears down the
|
|
145
|
-
* upstream subscriptions. Vue's `onUnmounted` is the canonical
|
|
146
|
-
* place.
|
|
147
|
-
*
|
|
148
|
-
* **Implementation note:** every upstream change triggers a full
|
|
149
|
-
* re-reduction. Incremental maintenance (O(1) per delta for
|
|
150
|
-
* sum/count/avg via the reducer protocol's `remove()` method) is a
|
|
151
|
-
* planned follow-up optimization — the protocol already supports
|
|
152
|
-
* it, but the executor doesn't drive it yet. Consumers get
|
|
153
|
-
* correct, reactive values today; future PRs can switch to
|
|
154
|
-
* delta-based maintenance without changing this API.
|
|
155
|
-
*/
|
|
156
|
-
live() {
|
|
157
|
-
const recompute = () => reduceRecords(this.executeRecords(), this.spec);
|
|
158
|
-
return new LiveAggregationImpl(recompute, this.upstreams);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
function buildLiveAggregation(recompute, upstreams) {
|
|
162
|
-
return new LiveAggregationImpl(recompute, upstreams);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// src/money/fixed-point.ts
|
|
166
|
-
function expandExponent(s) {
|
|
167
|
-
const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
|
|
168
|
-
if (!m) return s;
|
|
169
|
-
const sign = m[1] === "-" ? "-" : "";
|
|
170
|
-
const intp = m[2];
|
|
171
|
-
const frac = m[3] ?? "";
|
|
172
|
-
const exp = Number(m[4]);
|
|
173
|
-
const digits = intp + frac;
|
|
174
|
-
const pointPos = intp.length + exp;
|
|
175
|
-
let body;
|
|
176
|
-
if (pointPos <= 0) {
|
|
177
|
-
body = "0." + "0".repeat(-pointPos) + digits;
|
|
178
|
-
} else if (pointPos >= digits.length) {
|
|
179
|
-
body = digits + "0".repeat(pointPos - digits.length);
|
|
180
|
-
} else {
|
|
181
|
-
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
182
|
-
}
|
|
183
|
-
return sign + body;
|
|
184
|
-
}
|
|
185
|
-
function toCanonicalDecimalString(input) {
|
|
186
|
-
let s;
|
|
187
|
-
if (typeof input === "number") {
|
|
188
|
-
if (!Number.isFinite(input)) return null;
|
|
189
|
-
s = String(input);
|
|
190
|
-
} else {
|
|
191
|
-
s = input.trim();
|
|
192
|
-
}
|
|
193
|
-
s = expandExponent(s);
|
|
194
|
-
if (s.startsWith("+")) s = s.slice(1);
|
|
195
|
-
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
196
|
-
return s;
|
|
197
|
-
}
|
|
198
|
-
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
199
|
-
switch (mode) {
|
|
200
|
-
case "up":
|
|
201
|
-
return true;
|
|
202
|
-
case "down":
|
|
203
|
-
return false;
|
|
204
|
-
case "ceil":
|
|
205
|
-
return !negative;
|
|
206
|
-
case "floor":
|
|
207
|
-
return negative;
|
|
208
|
-
case "half-up":
|
|
209
|
-
return firstDiscarded >= 5;
|
|
210
|
-
case "half-down":
|
|
211
|
-
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
212
|
-
case "half-even":
|
|
213
|
-
if (firstDiscarded > 5) return true;
|
|
214
|
-
if (firstDiscarded < 5) return false;
|
|
215
|
-
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function parseToScaledInt(input, scale, rounding) {
|
|
219
|
-
const canonical = toCanonicalDecimalString(input);
|
|
220
|
-
if (canonical === null) return { ok: false, reason: "nonfinite" };
|
|
221
|
-
const negative = canonical.startsWith("-");
|
|
222
|
-
const unsigned = negative ? canonical.slice(1) : canonical;
|
|
223
|
-
const dot = unsigned.indexOf(".");
|
|
224
|
-
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
225
|
-
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
226
|
-
const intDigits = intPart === "" ? "0" : intPart;
|
|
227
|
-
if (fracPart.length <= scale) {
|
|
228
|
-
const keep2 = fracPart.padEnd(scale, "0");
|
|
229
|
-
const magnitude2 = BigInt(intDigits + keep2);
|
|
230
|
-
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
231
|
-
}
|
|
232
|
-
const keep = fracPart.slice(0, scale);
|
|
233
|
-
const tail = fracPart.slice(scale);
|
|
234
|
-
const magnitudeDigits = intDigits + keep;
|
|
235
|
-
let magnitude = BigInt(magnitudeDigits);
|
|
236
|
-
if (/^0+$/.test(tail)) {
|
|
237
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
238
|
-
}
|
|
239
|
-
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
240
|
-
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
241
|
-
const firstDiscarded = Number(tail[0]);
|
|
242
|
-
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
243
|
-
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
244
|
-
magnitude += 1n;
|
|
245
|
-
}
|
|
246
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
247
|
-
}
|
|
248
|
-
function formatScaledInt(value, scale) {
|
|
249
|
-
const negative = value < 0n;
|
|
250
|
-
const abs = (negative ? -value : value).toString();
|
|
251
|
-
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
252
|
-
const padded = abs.padStart(scale + 1, "0");
|
|
253
|
-
const cut = padded.length - scale;
|
|
254
|
-
const intPart = padded.slice(0, cut);
|
|
255
|
-
const fracPart = padded.slice(cut);
|
|
256
|
-
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// src/money/iso4217.ts
|
|
260
|
-
var MINOR_UNITS = {
|
|
261
|
-
// 2-decimal majors
|
|
262
|
-
EUR: 2,
|
|
263
|
-
USD: 2,
|
|
264
|
-
GBP: 2,
|
|
265
|
-
CHF: 2,
|
|
266
|
-
CAD: 2,
|
|
267
|
-
AUD: 2,
|
|
268
|
-
NZD: 2,
|
|
269
|
-
SGD: 2,
|
|
270
|
-
HKD: 2,
|
|
271
|
-
CNY: 2,
|
|
272
|
-
INR: 2,
|
|
273
|
-
BRL: 2,
|
|
274
|
-
MXN: 2,
|
|
275
|
-
ZAR: 2,
|
|
276
|
-
RUB: 2,
|
|
277
|
-
TRY: 2,
|
|
278
|
-
PLN: 2,
|
|
279
|
-
SEK: 2,
|
|
280
|
-
NOK: 2,
|
|
281
|
-
DKK: 2,
|
|
282
|
-
CZK: 2,
|
|
283
|
-
HUF: 2,
|
|
284
|
-
RON: 2,
|
|
285
|
-
ILS: 2,
|
|
286
|
-
THB: 2,
|
|
287
|
-
PHP: 2,
|
|
288
|
-
MYR: 2,
|
|
289
|
-
IDR: 2,
|
|
290
|
-
AED: 2,
|
|
291
|
-
SAR: 2,
|
|
292
|
-
QAR: 2,
|
|
293
|
-
EGP: 2,
|
|
294
|
-
// 0-decimal
|
|
295
|
-
JPY: 0,
|
|
296
|
-
KRW: 0,
|
|
297
|
-
ISK: 0,
|
|
298
|
-
CLP: 0,
|
|
299
|
-
VND: 0,
|
|
300
|
-
XOF: 0,
|
|
301
|
-
XAF: 0,
|
|
302
|
-
PYG: 0,
|
|
303
|
-
// 3-decimal
|
|
304
|
-
BHD: 3,
|
|
305
|
-
KWD: 3,
|
|
306
|
-
OMR: 3,
|
|
307
|
-
TND: 3,
|
|
308
|
-
JOD: 3,
|
|
309
|
-
IQD: 3,
|
|
310
|
-
LYD: 3
|
|
311
|
-
};
|
|
312
|
-
function scaleForCurrency(code) {
|
|
313
|
-
const v = MINOR_UNITS[code];
|
|
314
|
-
return v === void 0 ? null : v;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/errors.ts
|
|
318
|
-
var NoydbError = class extends Error {
|
|
319
|
-
/** Machine-readable error code. Stable across library versions. */
|
|
320
|
-
code;
|
|
321
|
-
constructor(code, message) {
|
|
322
|
-
super(message);
|
|
323
|
-
this.name = "NoydbError";
|
|
324
|
-
this.code = code;
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
var GroupCardinalityError = class extends NoydbError {
|
|
328
|
-
/** The field being grouped on. */
|
|
329
|
-
field;
|
|
330
|
-
/** Observed number of distinct groups at the moment the cap tripped. */
|
|
331
|
-
cardinality;
|
|
332
|
-
/** The cap that was exceeded. */
|
|
333
|
-
maxGroups;
|
|
334
|
-
constructor(field, cardinality, maxGroups) {
|
|
335
|
-
super(
|
|
336
|
-
"GROUP_CARDINALITY",
|
|
337
|
-
`.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.`
|
|
338
|
-
);
|
|
339
|
-
this.name = "GroupCardinalityError";
|
|
340
|
-
this.field = field;
|
|
341
|
-
this.cardinality = cardinality;
|
|
342
|
-
this.maxGroups = maxGroups;
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
var LocaleNotSpecifiedError = class extends NoydbError {
|
|
346
|
-
/** The field name that required a locale. */
|
|
347
|
-
field;
|
|
348
|
-
constructor(field, message) {
|
|
349
|
-
super(
|
|
350
|
-
"LOCALE_NOT_SPECIFIED",
|
|
351
|
-
message ?? `Cannot read i18nText field "${field}" without a locale. Pass { locale } to get()/list()/query() or set a default via openVault(name, { locale }).`
|
|
352
|
-
);
|
|
353
|
-
this.name = "LocaleNotSpecifiedError";
|
|
354
|
-
this.field = field;
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
// src/money/descriptor.ts
|
|
359
|
-
var MoneyUnsupportedError = class extends NoydbError {
|
|
360
|
-
constructor(field, message) {
|
|
361
|
-
super(
|
|
362
|
-
"MONEY_UNSUPPORTED",
|
|
363
|
-
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
364
|
-
);
|
|
365
|
-
this.field = field;
|
|
366
|
-
this.name = "MoneyUnsupportedError";
|
|
367
|
-
}
|
|
368
|
-
field;
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// src/query/predicate.ts
|
|
372
|
-
function readPath(record, path) {
|
|
373
|
-
if (record === null || record === void 0) return void 0;
|
|
374
|
-
if (!path.includes(".")) {
|
|
375
|
-
return record[path];
|
|
376
|
-
}
|
|
377
|
-
const segments = path.split(".");
|
|
378
|
-
let cursor = record;
|
|
379
|
-
for (const segment of segments) {
|
|
380
|
-
if (cursor === null || cursor === void 0) return void 0;
|
|
381
|
-
cursor = cursor[segment];
|
|
382
|
-
}
|
|
383
|
-
return cursor;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// src/aggregate/canonical-key.ts
|
|
387
|
-
function canonicalGroupKey(fields, row) {
|
|
388
|
-
const sorted = [...fields].sort();
|
|
389
|
-
const parts = [];
|
|
390
|
-
for (const name of sorted) {
|
|
391
|
-
const v = row[name];
|
|
392
|
-
const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
|
|
393
|
-
parts.push(`${name}=${serialised}`);
|
|
394
|
-
}
|
|
395
|
-
return parts.join("|");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// src/money/money-reducer.ts
|
|
399
|
-
function toScaledIntFromAny(v, scale) {
|
|
400
|
-
if (typeof v === "bigint") return v;
|
|
401
|
-
if (typeof v === "number") {
|
|
402
|
-
const r = parseToScaledInt(v, scale);
|
|
403
|
-
return r.ok ? r.value : null;
|
|
404
|
-
}
|
|
405
|
-
if (typeof v === "string") {
|
|
406
|
-
if (!v.includes(".")) {
|
|
407
|
-
try {
|
|
408
|
-
return BigInt(v);
|
|
409
|
-
} catch {
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const r = parseToScaledInt(v, scale);
|
|
414
|
-
return r.ok ? r.value : null;
|
|
415
|
-
}
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
function readMoney(record, field, desc) {
|
|
419
|
-
const raw = readPath(record, field);
|
|
420
|
-
if (raw === null || raw === void 0) return null;
|
|
421
|
-
if (desc.mode === "fixed") {
|
|
422
|
-
const cur = desc.fixedCurrency;
|
|
423
|
-
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
424
|
-
return value2 === null ? null : { currency: cur, value: value2 };
|
|
425
|
-
}
|
|
426
|
-
if (typeof raw !== "object") return null;
|
|
427
|
-
const o = raw;
|
|
428
|
-
if (typeof o.currency !== "string") return null;
|
|
429
|
-
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
430
|
-
const value = toScaledIntFromAny(o.amount, scale);
|
|
431
|
-
return value === null ? null : { currency: o.currency, value };
|
|
432
|
-
}
|
|
433
|
-
function targetScaleFor(desc, currency) {
|
|
434
|
-
if (desc.allows(currency)) return desc.scaleFor(currency);
|
|
435
|
-
const s = scaleForCurrency(currency);
|
|
436
|
-
if (s === null) {
|
|
437
|
-
throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
|
|
438
|
-
}
|
|
439
|
-
return s;
|
|
440
|
-
}
|
|
441
|
-
function parseRate(rate) {
|
|
442
|
-
const s = String(rate).trim();
|
|
443
|
-
const neg = s.startsWith("-");
|
|
444
|
-
const body = neg ? s.slice(1) : s;
|
|
445
|
-
const dot = body.indexOf(".");
|
|
446
|
-
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
447
|
-
const fracPart = dot === -1 ? "" : body.slice(dot + 1);
|
|
448
|
-
const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
|
|
449
|
-
return { int: neg ? -int : int, scale: fracPart.length };
|
|
450
|
-
}
|
|
451
|
-
function divRoundHalfEven(n, d) {
|
|
452
|
-
const q = n / d;
|
|
453
|
-
const r = n % d;
|
|
454
|
-
const twiceR = (r < 0n ? -r : r) * 2n;
|
|
455
|
-
if (twiceR < d) return q;
|
|
456
|
-
if (twiceR > d) return q + (n < 0n ? -1n : 1n);
|
|
457
|
-
return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
|
|
458
|
-
}
|
|
459
|
-
function convertScaled(value, srcScale, rate, targetScale) {
|
|
460
|
-
const { int: rateInt, scale: rateScale } = parseRate(rate);
|
|
461
|
-
const product = value * rateInt;
|
|
462
|
-
const curScale = srcScale + rateScale;
|
|
463
|
-
if (curScale === targetScale) return product;
|
|
464
|
-
if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
|
|
465
|
-
return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
|
|
466
|
-
}
|
|
467
|
-
function finalizeSum(state, desc, convertTo, fx) {
|
|
468
|
-
if (convertTo !== void 0) {
|
|
469
|
-
if (fx === void 0) {
|
|
470
|
-
throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
|
|
471
|
-
}
|
|
472
|
-
const targetScale = targetScaleFor(desc, convertTo);
|
|
473
|
-
let total = 0n;
|
|
474
|
-
for (const [cur, v] of state) {
|
|
475
|
-
if (cur === convertTo) {
|
|
476
|
-
total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const rate = fx[`${cur}->${convertTo}`];
|
|
480
|
-
if (rate === void 0) {
|
|
481
|
-
throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
|
|
482
|
-
}
|
|
483
|
-
total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
|
|
484
|
-
}
|
|
485
|
-
return formatScaledInt(total, targetScale);
|
|
486
|
-
}
|
|
487
|
-
if (desc.mode === "fixed") {
|
|
488
|
-
const cur = desc.fixedCurrency;
|
|
489
|
-
return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
|
|
490
|
-
}
|
|
491
|
-
const out = {};
|
|
492
|
-
for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
|
|
493
|
-
return out;
|
|
494
|
-
}
|
|
495
|
-
function moneySumReducer(field, desc, convertTo, fx) {
|
|
496
|
-
return {
|
|
497
|
-
op: "sum",
|
|
498
|
-
field,
|
|
499
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
500
|
-
step: (state, record) => {
|
|
501
|
-
const m = readMoney(record, field, desc);
|
|
502
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
|
|
503
|
-
return state;
|
|
504
|
-
},
|
|
505
|
-
remove: (state, record) => {
|
|
506
|
-
const m = readMoney(record, field, desc);
|
|
507
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
|
|
508
|
-
return state;
|
|
509
|
-
},
|
|
510
|
-
finalize: (state) => finalizeSum(state, desc, convertTo, fx)
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
function extremum(values, op) {
|
|
514
|
-
let out = values[0];
|
|
515
|
-
for (let i = 1; i < values.length; i++) {
|
|
516
|
-
const v = values[i];
|
|
517
|
-
if (op === "min" ? v < out : v > out) out = v;
|
|
518
|
-
}
|
|
519
|
-
return out;
|
|
520
|
-
}
|
|
521
|
-
function moneyMinMaxReducer(op, field, desc) {
|
|
522
|
-
return {
|
|
523
|
-
op,
|
|
524
|
-
field,
|
|
525
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
526
|
-
step: (state, record) => {
|
|
527
|
-
const m = readMoney(record, field, desc);
|
|
528
|
-
if (m) {
|
|
529
|
-
const arr = state.get(m.currency);
|
|
530
|
-
if (arr) arr.push(m.value);
|
|
531
|
-
else state.set(m.currency, [m.value]);
|
|
532
|
-
}
|
|
533
|
-
return state;
|
|
534
|
-
},
|
|
535
|
-
remove: (state, record) => {
|
|
536
|
-
const m = readMoney(record, field, desc);
|
|
537
|
-
if (m) {
|
|
538
|
-
const arr = state.get(m.currency);
|
|
539
|
-
if (arr) {
|
|
540
|
-
const idx = arr.indexOf(m.value);
|
|
541
|
-
if (idx >= 0) arr.splice(idx, 1);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return state;
|
|
545
|
-
},
|
|
546
|
-
finalize: (state) => {
|
|
547
|
-
if (desc.mode === "fixed") {
|
|
548
|
-
const cur = desc.fixedCurrency;
|
|
549
|
-
const arr = state.get(cur);
|
|
550
|
-
if (!arr || arr.length === 0) return null;
|
|
551
|
-
return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
552
|
-
}
|
|
553
|
-
const out = {};
|
|
554
|
-
for (const [cur, arr] of state) {
|
|
555
|
-
if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
556
|
-
}
|
|
557
|
-
return out;
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function wrapMoneyReducers(spec, moneyFields) {
|
|
562
|
-
let changed = false;
|
|
563
|
-
const out = {};
|
|
564
|
-
for (const [key, reducer] of Object.entries(spec)) {
|
|
565
|
-
const field = reducer.field;
|
|
566
|
-
const desc = field ? moneyFields[field] : void 0;
|
|
567
|
-
if (desc && reducer.op === "avg") {
|
|
568
|
-
throw new MoneyUnsupportedError(
|
|
569
|
-
field,
|
|
570
|
-
`avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
|
|
574
|
-
changed = true;
|
|
575
|
-
out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
|
|
576
|
-
} else {
|
|
577
|
-
out[key] = reducer;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return changed ? out : spec;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// src/i18n/policy.ts
|
|
584
|
-
function resolvePolicy(onMissing, layer) {
|
|
585
|
-
const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
|
|
586
|
-
const scalar = typeof onMissing === "string" ? onMissing : void 0;
|
|
587
|
-
const layerDefault = layer === "guard" ? "substitute" : void 0;
|
|
588
|
-
return explicit ?? layerDefault ?? scalar ?? "throw";
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// src/i18n/script.ts
|
|
592
|
-
var LATIN_BASE = /* @__PURE__ */ new Set([
|
|
593
|
-
"en",
|
|
594
|
-
"fr",
|
|
595
|
-
"de",
|
|
596
|
-
"es",
|
|
597
|
-
"it",
|
|
598
|
-
"pt",
|
|
599
|
-
"nl",
|
|
600
|
-
"sv",
|
|
601
|
-
"no",
|
|
602
|
-
"da",
|
|
603
|
-
"fi",
|
|
604
|
-
"is",
|
|
605
|
-
"pl",
|
|
606
|
-
"cs",
|
|
607
|
-
"sk",
|
|
608
|
-
"hu",
|
|
609
|
-
"ro",
|
|
610
|
-
"hr",
|
|
611
|
-
"sl",
|
|
612
|
-
"et",
|
|
613
|
-
"lv",
|
|
614
|
-
"lt",
|
|
615
|
-
"tr",
|
|
616
|
-
"vi",
|
|
617
|
-
"id",
|
|
618
|
-
"ms",
|
|
619
|
-
"tl",
|
|
620
|
-
"sw",
|
|
621
|
-
"af",
|
|
622
|
-
"ca",
|
|
623
|
-
"gl",
|
|
624
|
-
"eu",
|
|
625
|
-
"cy",
|
|
626
|
-
"ga"
|
|
627
|
-
]);
|
|
628
|
-
var SCRIPT_TABLE = {
|
|
629
|
-
th: ["Thai"],
|
|
630
|
-
ko: ["Hangul", "Han"],
|
|
631
|
-
ja: ["Han", "Hiragana", "Katakana"],
|
|
632
|
-
zh: ["Han"],
|
|
633
|
-
ar: ["Arabic"],
|
|
634
|
-
fa: ["Arabic"],
|
|
635
|
-
ur: ["Arabic"],
|
|
636
|
-
ru: ["Cyrillic"],
|
|
637
|
-
uk: ["Cyrillic"],
|
|
638
|
-
bg: ["Cyrillic"],
|
|
639
|
-
sr: ["Cyrillic"],
|
|
640
|
-
he: ["Hebrew"],
|
|
641
|
-
el: ["Greek"],
|
|
642
|
-
hi: ["Devanagari"],
|
|
643
|
-
ta: ["Tamil"],
|
|
644
|
-
km: ["Khmer"],
|
|
645
|
-
lo: ["Lao"],
|
|
646
|
-
my: ["Myanmar"]
|
|
647
|
-
};
|
|
648
|
-
var SUBTAG_SCRIPTS = {
|
|
649
|
-
Latn: ["Latin"],
|
|
650
|
-
Cyrl: ["Cyrillic", "Latin"],
|
|
651
|
-
Hans: ["Han", "Latin"],
|
|
652
|
-
Hant: ["Han", "Latin"],
|
|
653
|
-
Thai: ["Thai", "Latin"],
|
|
654
|
-
Arab: ["Arabic", "Latin"]
|
|
655
|
-
};
|
|
656
|
-
function inferScripts(locale) {
|
|
657
|
-
const parts = locale.split("-");
|
|
658
|
-
const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
|
|
659
|
-
if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
|
|
660
|
-
const base = (parts[0] ?? "").toLowerCase();
|
|
661
|
-
if (LATIN_BASE.has(base)) return ["Latin"];
|
|
662
|
-
const primary = SCRIPT_TABLE[base];
|
|
663
|
-
if (primary) return [...primary, "Latin"];
|
|
664
|
-
return ["Latin"];
|
|
665
|
-
}
|
|
666
|
-
var BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
|
|
667
|
-
|
|
668
|
-
// src/i18n/core.ts
|
|
669
|
-
function toChain(fallback) {
|
|
670
|
-
return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
671
|
-
}
|
|
672
|
-
function pickFromChain(value, chain) {
|
|
673
|
-
for (const fb of chain) {
|
|
674
|
-
if (fb === "any") {
|
|
675
|
-
const any = Object.values(value).find((v) => v !== "");
|
|
676
|
-
if (any !== void 0) return any;
|
|
677
|
-
} else if (value[fb] !== void 0 && value[fb] !== "") {
|
|
678
|
-
return value[fb];
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return void 0;
|
|
682
|
-
}
|
|
683
|
-
function resolveI18nText(value, locale, fallback, field, opts) {
|
|
684
|
-
if (locale === "raw") {
|
|
685
|
-
return value;
|
|
686
|
-
}
|
|
687
|
-
if (!locale) {
|
|
688
|
-
throw new LocaleNotSpecifiedError(field ?? "<unknown>");
|
|
689
|
-
}
|
|
690
|
-
if (value[locale] !== void 0 && value[locale] !== "") {
|
|
691
|
-
return value[locale];
|
|
692
|
-
}
|
|
693
|
-
const policy = opts?.policy ?? "throw";
|
|
694
|
-
const callerChain = toChain(fallback);
|
|
695
|
-
const callerHit = pickFromChain(value, callerChain);
|
|
696
|
-
if (callerHit !== void 0) return callerHit;
|
|
697
|
-
if (policy === "substitute") {
|
|
698
|
-
const subHit = pickFromChain(value, toChain(opts?.substitute));
|
|
699
|
-
if (subHit !== void 0) return subHit;
|
|
700
|
-
if (opts?.smartSubstitute) {
|
|
701
|
-
const smartHit = pickNearestScript(value, locale);
|
|
702
|
-
if (smartHit !== void 0) return smartHit;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
if (policy === "throw") {
|
|
706
|
-
throw new LocaleNotSpecifiedError(
|
|
707
|
-
field ?? "<unknown>",
|
|
708
|
-
`No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
return null;
|
|
712
|
-
}
|
|
713
|
-
function pickNearestScript(value, target) {
|
|
714
|
-
const targetScript = inferScripts(target)[0] ?? "Latin";
|
|
715
|
-
let best;
|
|
716
|
-
for (const [loc, v] of Object.entries(value)) {
|
|
717
|
-
if (typeof v !== "string" || v === "") continue;
|
|
718
|
-
const s = inferScripts(loc)[0] ?? "Latin";
|
|
719
|
-
const score = s === targetScript ? 0 : s === "Latin" ? 1 : 2;
|
|
720
|
-
if (best === void 0 || score < best.score) best = { score, v };
|
|
721
|
-
if (score === 0) break;
|
|
722
|
-
}
|
|
723
|
-
return best?.v;
|
|
724
|
-
}
|
|
725
|
-
function applyAtPath(obj, path, locale, fallback, opts) {
|
|
726
|
-
const arrayIdx = path.indexOf("[].");
|
|
727
|
-
if (arrayIdx !== -1) {
|
|
728
|
-
const arrayKey = path.slice(0, arrayIdx);
|
|
729
|
-
const restPath = path.slice(arrayIdx + 3);
|
|
730
|
-
const arr = obj[arrayKey];
|
|
731
|
-
if (!Array.isArray(arr)) return obj;
|
|
732
|
-
return {
|
|
733
|
-
...obj,
|
|
734
|
-
[arrayKey]: arr.map((item) => {
|
|
735
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
736
|
-
return applyAtPath(item, restPath, locale, fallback, opts);
|
|
737
|
-
})
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
const dotIdx = path.indexOf(".");
|
|
741
|
-
if (dotIdx !== -1) {
|
|
742
|
-
const head = path.slice(0, dotIdx);
|
|
743
|
-
const rest = path.slice(dotIdx + 1);
|
|
744
|
-
const nested = obj[head];
|
|
745
|
-
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
|
|
746
|
-
return {
|
|
747
|
-
...obj,
|
|
748
|
-
[head]: applyAtPath(nested, rest, locale, fallback, opts)
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
const raw = obj[path];
|
|
752
|
-
if (raw === void 0 || raw === null) return obj;
|
|
753
|
-
if (typeof raw !== "object" || Array.isArray(raw)) return obj;
|
|
754
|
-
return {
|
|
755
|
-
...obj,
|
|
756
|
-
[path]: resolveI18nText(raw, locale, fallback, path, opts)
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
760
|
-
const fieldNames = Object.keys(i18nFields);
|
|
761
|
-
if (fieldNames.length === 0) return record;
|
|
762
|
-
let result = record;
|
|
763
|
-
for (const [field, descriptor] of Object.entries(i18nFields)) {
|
|
764
|
-
const { onMissing, substitute, smartSubstitute } = descriptor.options;
|
|
765
|
-
const opts = {
|
|
766
|
-
policy: resolvePolicy(onMissing, layer),
|
|
767
|
-
...substitute !== void 0 ? { substitute } : {},
|
|
768
|
-
...smartSubstitute ? { smartSubstitute } : {}
|
|
769
|
-
};
|
|
770
|
-
result = applyAtPath(result, field, locale, fallback, opts);
|
|
771
|
-
}
|
|
772
|
-
result = stripI18nFilled(result);
|
|
773
|
-
return result;
|
|
774
|
-
}
|
|
775
|
-
function stripI18nFilled(record) {
|
|
776
|
-
if (!Object.prototype.hasOwnProperty.call(record, "_i18nFilled")) return record;
|
|
777
|
-
const rest = { ...record };
|
|
778
|
-
delete rest._i18nFilled;
|
|
779
|
-
return rest;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// src/aggregate/groupby.ts
|
|
783
|
-
var GROUPBY_WARN_CARDINALITY = 1e4;
|
|
784
|
-
var GROUPBY_MAX_CARDINALITY = 1e5;
|
|
785
|
-
var warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
786
|
-
function warnCardinalityApproaching(fields, observed) {
|
|
787
|
-
const key = JSON.stringify([...fields].sort());
|
|
788
|
-
if (warnedCardinalityFields.has(key)) return;
|
|
789
|
-
warnedCardinalityFields.add(key);
|
|
790
|
-
const label = `[${fields.join(", ")}]`;
|
|
791
|
-
console.warn(
|
|
792
|
-
`[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.`
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
function resetGroupByWarnings() {
|
|
796
|
-
warnedCardinalityFields.clear();
|
|
797
|
-
}
|
|
798
|
-
var GroupedQueryBase = class {
|
|
799
|
-
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver, moneyFields) {
|
|
800
|
-
this.executeRecords = executeRecords;
|
|
801
|
-
this.upstreams = upstreams;
|
|
802
|
-
this.dictLabelResolver = dictLabelResolver;
|
|
803
|
-
this.moneyFields = moneyFields;
|
|
804
|
-
this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
|
|
805
|
-
}
|
|
806
|
-
executeRecords;
|
|
807
|
-
upstreams;
|
|
808
|
-
dictLabelResolver;
|
|
809
|
-
moneyFields;
|
|
810
|
-
/**
|
|
811
|
-
* Field set this grouped query buckets on. Stored in declaration
|
|
812
|
-
* order — the same order is preserved on every result row by
|
|
813
|
-
* `groupAndReduce`. For the single-field constructor, this is
|
|
814
|
-
* `[field]`.
|
|
815
|
-
*/
|
|
816
|
-
fields;
|
|
817
|
-
/** Apply money-aware reducer rewriting when money fields are declared. */
|
|
818
|
-
wrapSpec(spec) {
|
|
819
|
-
return this.moneyFields ? wrapMoneyReducers(spec, this.moneyFields) : spec;
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
var GroupedQuery = class extends GroupedQueryBase {
|
|
823
|
-
/**
|
|
824
|
-
* Build a grouped aggregation. Returns a `GroupedAggregation`
|
|
825
|
-
* with `.run()`, `.runAsync()`, and `.live()` terminals — same shape
|
|
826
|
-
* as the non-grouped `.aggregate()` wrapper, just with an array
|
|
827
|
-
* result (one row per bucket) instead of a single reduced object.
|
|
828
|
-
*/
|
|
829
|
-
aggregate(spec) {
|
|
830
|
-
return new GroupedAggregation(
|
|
831
|
-
this.executeRecords,
|
|
832
|
-
this.fields,
|
|
833
|
-
this.wrapSpec(spec),
|
|
834
|
-
this.upstreams,
|
|
835
|
-
this.dictLabelResolver
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
var GroupedQueryN = class extends GroupedQueryBase {
|
|
840
|
-
aggregate(spec) {
|
|
841
|
-
return new GroupedAggregation(
|
|
842
|
-
this.executeRecords,
|
|
843
|
-
this.fields,
|
|
844
|
-
this.wrapSpec(spec),
|
|
845
|
-
this.upstreams,
|
|
846
|
-
this.dictLabelResolver
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
};
|
|
850
|
-
function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
851
|
-
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
852
|
-
if (fields.length === 0) {
|
|
853
|
-
throw new Error(".groupBy() requires at least one field");
|
|
854
|
-
}
|
|
855
|
-
if (moneyFields) {
|
|
856
|
-
spec = wrapMoneyReducers(spec, moneyFields);
|
|
857
|
-
}
|
|
858
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
859
|
-
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
860
|
-
for (const record of records) {
|
|
861
|
-
const keyValues = {};
|
|
862
|
-
for (const f of fields) {
|
|
863
|
-
keyValues[f] = readPath(record, f);
|
|
864
|
-
}
|
|
865
|
-
const dedupKey = canonicalGroupKey(fields, keyValues);
|
|
866
|
-
let bucket = buckets.get(dedupKey);
|
|
867
|
-
if (bucket === void 0) {
|
|
868
|
-
if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
|
|
869
|
-
throw new GroupCardinalityError(
|
|
870
|
-
fieldLabel,
|
|
871
|
-
buckets.size + 1,
|
|
872
|
-
GROUPBY_MAX_CARDINALITY
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
bucket = { keyValues, records: [] };
|
|
876
|
-
buckets.set(dedupKey, bucket);
|
|
877
|
-
}
|
|
878
|
-
bucket.records.push(record);
|
|
879
|
-
}
|
|
880
|
-
if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
|
|
881
|
-
warnCardinalityApproaching(fields, buckets.size);
|
|
882
|
-
}
|
|
883
|
-
const reducerKeys = Object.keys(spec);
|
|
884
|
-
const out = [];
|
|
885
|
-
for (const bucket of buckets.values()) {
|
|
886
|
-
const state = {};
|
|
887
|
-
for (const rk of reducerKeys) {
|
|
888
|
-
state[rk] = spec[rk].init();
|
|
889
|
-
}
|
|
890
|
-
for (const record of bucket.records) {
|
|
891
|
-
for (const rk of reducerKeys) {
|
|
892
|
-
state[rk] = spec[rk].step(state[rk], record);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
const row = {};
|
|
896
|
-
for (const f of fields) {
|
|
897
|
-
row[f] = bucket.keyValues[f];
|
|
898
|
-
}
|
|
899
|
-
for (const rk of reducerKeys) {
|
|
900
|
-
row[rk] = spec[rk].finalize(state[rk]);
|
|
901
|
-
}
|
|
902
|
-
out.push(row);
|
|
903
|
-
}
|
|
904
|
-
return out;
|
|
905
|
-
}
|
|
906
|
-
var GroupedAggregation = class {
|
|
907
|
-
constructor(executeRecords, fields, spec, upstreams, dictLabelResolver) {
|
|
908
|
-
this.executeRecords = executeRecords;
|
|
909
|
-
this.spec = spec;
|
|
910
|
-
this.upstreams = upstreams;
|
|
911
|
-
this.dictLabelResolver = dictLabelResolver;
|
|
912
|
-
this.fields = typeof fields === "string" ? [fields] : [...fields];
|
|
913
|
-
}
|
|
914
|
-
executeRecords;
|
|
915
|
-
spec;
|
|
916
|
-
upstreams;
|
|
917
|
-
dictLabelResolver;
|
|
918
|
-
fields;
|
|
919
|
-
/**
|
|
920
|
-
* Execute the query, group, reduce, and return an array of rows.
|
|
921
|
-
*
|
|
922
|
-
* `opts` (#285 query-form MV grouping): when a `locale` + `i18nFields` are
|
|
923
|
-
* given, the declared group-key `i18nText` fields are resolved to that locale
|
|
924
|
-
* at the `mv` layer BEFORE bucketing — so an i18n group key is a stable string
|
|
925
|
-
* instead of a raw `{locale}` map. The MV executor passes the MV's
|
|
926
|
-
* `i18nLocale`/`i18nFields`; ordinary `.run()` callers pass nothing and are
|
|
927
|
-
* unaffected.
|
|
928
|
-
*/
|
|
929
|
-
run(opts) {
|
|
930
|
-
let records = this.executeRecords();
|
|
931
|
-
if (opts?.locale !== void 0 && opts.i18nFields !== void 0) {
|
|
932
|
-
const groupI18n = {};
|
|
933
|
-
for (const f of this.fields) {
|
|
934
|
-
const d = opts.i18nFields[f];
|
|
935
|
-
if (d !== void 0) groupI18n[f] = d;
|
|
936
|
-
}
|
|
937
|
-
if (Object.keys(groupI18n).length > 0) {
|
|
938
|
-
records = records.map((r) => applyI18nLocale(r, groupI18n, opts.locale, void 0, "mv"));
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return groupAndReduce(records, this.fields, this.spec);
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Execute the query, group, reduce, and resolve `<field>Label` for
|
|
945
|
-
* each result row when the grouping field is a `dictKey` and a
|
|
946
|
-
* `locale` is provided. Returns `R[]` synchronously when
|
|
947
|
-
* no locale is specified (identical to `.run()`).
|
|
948
|
-
*
|
|
949
|
-
* The `<field>Label` field is appended to each row. Rows whose group
|
|
950
|
-
* key has no dictionary entry get `<field>Label: undefined`.
|
|
951
|
-
*
|
|
952
|
-
* Dict-label resolution is single-field only — multi-key groupings
|
|
953
|
-
* do not produce a `<field>Label`. The resolver is only attached
|
|
954
|
-
* by the builder when `fields.length === 1`.
|
|
955
|
-
*/
|
|
956
|
-
async runAsync(opts) {
|
|
957
|
-
const rows = groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
958
|
-
if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows;
|
|
959
|
-
const resolve = this.dictLabelResolver;
|
|
960
|
-
const locale = opts.locale;
|
|
961
|
-
const fallback = opts.fallback;
|
|
962
|
-
const field = this.fields[0];
|
|
963
|
-
const labelKey = `${field}Label`;
|
|
964
|
-
return Promise.all(
|
|
965
|
-
rows.map(async (row) => {
|
|
966
|
-
const key = row[field];
|
|
967
|
-
if (typeof key !== "string") return row;
|
|
968
|
-
const label = await resolve(key, locale, fallback);
|
|
969
|
-
return { ...row, [labelKey]: label };
|
|
970
|
-
})
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Build a reactive `LiveAggregation<R[]>` that re-runs the full
|
|
975
|
-
* group-and-reduce pipeline whenever any upstream source notifies
|
|
976
|
-
* of a change. Same error-isolation and idempotent-stop contract
|
|
977
|
-
* as `Aggregation.live()` — the implementation delegates to the
|
|
978
|
-
* same `LiveAggregationImpl` class by threading a fresh
|
|
979
|
-
* recompute closure through the existing constructor.
|
|
980
|
-
*
|
|
981
|
-
* uses naive full re-run on every change. Incremental
|
|
982
|
-
* per-bucket maintenance (apply `step` on inserted records,
|
|
983
|
-
* `remove` on deleted records, route by bucket key) is a future
|
|
984
|
-
* optimization — the reducer protocol admits it, but wiring
|
|
985
|
-
* delta-aware source subscriptions is a separate PR.
|
|
986
|
-
*
|
|
987
|
-
* Always call `live.stop()` when finished.
|
|
988
|
-
*/
|
|
989
|
-
live() {
|
|
990
|
-
const recompute = () => groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
991
|
-
return buildLiveAggregation(recompute, this.upstreams);
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
// src/aggregate/active.ts
|
|
996
|
-
function withAggregate() {
|
|
997
|
-
return {
|
|
998
|
-
aggregate(executeRecords, spec, upstreams) {
|
|
999
|
-
return new Aggregation(executeRecords, spec, upstreams);
|
|
1000
|
-
},
|
|
1001
|
-
groupBy(executeRecords, field, upstreams, dictLabelResolver, moneyFields) {
|
|
1002
|
-
return new GroupedQuery(executeRecords, field, upstreams, dictLabelResolver, moneyFields);
|
|
1003
|
-
},
|
|
1004
|
-
groupByN(executeRecords, fields, upstreams, moneyFields) {
|
|
1005
|
-
return new GroupedQueryN(executeRecords, fields, upstreams, void 0, moneyFields);
|
|
1006
|
-
},
|
|
1007
|
-
async scanAggregate(iter, spec) {
|
|
1008
|
-
const collected = [];
|
|
1009
|
-
for await (const record of iter) collected.push(record);
|
|
1010
|
-
return reduceRecords(collected, spec);
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// src/aggregate/reducers.ts
|
|
1016
|
-
function count(opts) {
|
|
1017
|
-
const _seed = opts?.seed;
|
|
1018
|
-
void _seed;
|
|
1019
|
-
return {
|
|
1020
|
-
op: "count",
|
|
1021
|
-
init: () => 0,
|
|
1022
|
-
step: (state) => state + 1,
|
|
1023
|
-
remove: (state) => state - 1,
|
|
1024
|
-
finalize: (state) => state,
|
|
1025
|
-
merge: (a, b) => a + b
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
function sum(field, opts) {
|
|
1029
|
-
const _seed = opts?.seed;
|
|
1030
|
-
void _seed;
|
|
1031
|
-
return {
|
|
1032
|
-
op: "sum",
|
|
1033
|
-
field,
|
|
1034
|
-
// Money-only metadata, read by `wrapMoneyReducers`. No effect on a
|
|
1035
|
-
// generic numeric sum.
|
|
1036
|
-
...opts?.convertTo !== void 0 ? { convertTo: opts.convertTo } : {},
|
|
1037
|
-
...opts?.fx !== void 0 ? { fx: opts.fx } : {},
|
|
1038
|
-
init: () => 0,
|
|
1039
|
-
step: (state, record) => state + readNumber(record, field),
|
|
1040
|
-
remove: (state, record) => state - readNumber(record, field),
|
|
1041
|
-
finalize: (state) => state,
|
|
1042
|
-
merge: (a, b) => a + b
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
function avg(field, opts) {
|
|
1046
|
-
const _seed = opts?.seed;
|
|
1047
|
-
void _seed;
|
|
1048
|
-
return {
|
|
1049
|
-
op: "avg",
|
|
1050
|
-
field,
|
|
1051
|
-
init: () => ({ sum: 0, count: 0 }),
|
|
1052
|
-
step: (state, record) => ({
|
|
1053
|
-
sum: state.sum + readNumber(record, field),
|
|
1054
|
-
count: state.count + 1
|
|
1055
|
-
}),
|
|
1056
|
-
remove: (state, record) => ({
|
|
1057
|
-
sum: state.sum - readNumber(record, field),
|
|
1058
|
-
count: state.count - 1
|
|
1059
|
-
}),
|
|
1060
|
-
finalize: (state) => state.count === 0 ? null : state.sum / state.count,
|
|
1061
|
-
merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count })
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
function pushValue(state, value) {
|
|
1065
|
-
return { values: [...state.values, value] };
|
|
1066
|
-
}
|
|
1067
|
-
function removeValue(state, value) {
|
|
1068
|
-
const idx = state.values.indexOf(value);
|
|
1069
|
-
if (idx < 0) return state;
|
|
1070
|
-
const next = state.values.slice();
|
|
1071
|
-
next.splice(idx, 1);
|
|
1072
|
-
return { values: next };
|
|
1073
|
-
}
|
|
1074
|
-
function min(field, opts) {
|
|
1075
|
-
const _seed = opts?.seed;
|
|
1076
|
-
void _seed;
|
|
1077
|
-
return {
|
|
1078
|
-
op: "min",
|
|
1079
|
-
field,
|
|
1080
|
-
init: () => ({ values: [] }),
|
|
1081
|
-
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1082
|
-
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
1083
|
-
finalize: (state) => {
|
|
1084
|
-
if (state.values.length === 0) return null;
|
|
1085
|
-
let out = state.values[0];
|
|
1086
|
-
for (let i = 1; i < state.values.length; i++) {
|
|
1087
|
-
const v = state.values[i];
|
|
1088
|
-
if (v < out) out = v;
|
|
1089
|
-
}
|
|
1090
|
-
return out;
|
|
1091
|
-
},
|
|
1092
|
-
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
function max(field, opts) {
|
|
1096
|
-
const _seed = opts?.seed;
|
|
1097
|
-
void _seed;
|
|
1098
|
-
return {
|
|
1099
|
-
op: "max",
|
|
1100
|
-
field,
|
|
1101
|
-
init: () => ({ values: [] }),
|
|
1102
|
-
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1103
|
-
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
1104
|
-
finalize: (state) => {
|
|
1105
|
-
if (state.values.length === 0) return null;
|
|
1106
|
-
let out = state.values[0];
|
|
1107
|
-
for (let i = 1; i < state.values.length; i++) {
|
|
1108
|
-
const v = state.values[i];
|
|
1109
|
-
if (v > out) out = v;
|
|
1110
|
-
}
|
|
1111
|
-
return out;
|
|
1112
|
-
},
|
|
1113
|
-
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
function readNumber(record, field) {
|
|
1117
|
-
const value = readPath(record, field);
|
|
1118
|
-
if (typeof value === "object" && value !== null && "amount" in value && "currency" in value) {
|
|
1119
|
-
throw new Error(
|
|
1120
|
-
`aggregate: field "${field}" holds a money value but was not money-aware \u2014 declare it in the collection's moneyFields so sum/min/max stay exact`
|
|
1121
|
-
);
|
|
1122
|
-
}
|
|
1123
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1124
|
-
}
|
|
1125
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1126
|
-
0 && (module.exports = {
|
|
1127
|
-
Aggregation,
|
|
1128
|
-
GROUPBY_MAX_CARDINALITY,
|
|
1129
|
-
GROUPBY_WARN_CARDINALITY,
|
|
1130
|
-
GroupedAggregation,
|
|
1131
|
-
GroupedQuery,
|
|
1132
|
-
GroupedQueryN,
|
|
1133
|
-
avg,
|
|
1134
|
-
buildLiveAggregation,
|
|
1135
|
-
count,
|
|
1136
|
-
groupAndReduce,
|
|
1137
|
-
max,
|
|
1138
|
-
min,
|
|
1139
|
-
reduceRecords,
|
|
1140
|
-
resetGroupByWarnings,
|
|
1141
|
-
sum,
|
|
1142
|
-
withAggregate
|
|
1143
|
-
});
|
|
1144
|
-
//# sourceMappingURL=index.cjs.map
|