@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
|
@@ -1,1518 +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 __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
-
|
|
23
|
-
// src/errors.ts
|
|
24
|
-
var NoydbError, ValidationError, GroupCardinalityError, LocaleNotSpecifiedError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError;
|
|
25
|
-
var init_errors = __esm({
|
|
26
|
-
"src/errors.ts"() {
|
|
27
|
-
"use strict";
|
|
28
|
-
NoydbError = class extends Error {
|
|
29
|
-
/** Machine-readable error code. Stable across library versions. */
|
|
30
|
-
code;
|
|
31
|
-
constructor(code, message) {
|
|
32
|
-
super(message);
|
|
33
|
-
this.name = "NoydbError";
|
|
34
|
-
this.code = code;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
ValidationError = class extends NoydbError {
|
|
38
|
-
constructor(message = "Validation error") {
|
|
39
|
-
super("VALIDATION_ERROR", message);
|
|
40
|
-
this.name = "ValidationError";
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
GroupCardinalityError = class extends NoydbError {
|
|
44
|
-
/** The field being grouped on. */
|
|
45
|
-
field;
|
|
46
|
-
/** Observed number of distinct groups at the moment the cap tripped. */
|
|
47
|
-
cardinality;
|
|
48
|
-
/** The cap that was exceeded. */
|
|
49
|
-
maxGroups;
|
|
50
|
-
constructor(field, cardinality, maxGroups) {
|
|
51
|
-
super(
|
|
52
|
-
"GROUP_CARDINALITY",
|
|
53
|
-
`.groupBy("${field}") produced ${cardinality} distinct groups, exceeding the ${maxGroups}-group ceiling. This is almost always a query mistake \u2014 grouping on a high-uniqueness field like "id" or "createdAt" produces one bucket per record. Narrow the query with .where() before grouping, or group on a lower-cardinality field (status, category, clientId). If you genuinely need high-cardinality grouping, file an issue with your use case.`
|
|
54
|
-
);
|
|
55
|
-
this.name = "GroupCardinalityError";
|
|
56
|
-
this.field = field;
|
|
57
|
-
this.cardinality = cardinality;
|
|
58
|
-
this.maxGroups = maxGroups;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
LocaleNotSpecifiedError = class extends NoydbError {
|
|
62
|
-
/** The field name that required a locale. */
|
|
63
|
-
field;
|
|
64
|
-
constructor(field, message) {
|
|
65
|
-
super(
|
|
66
|
-
"LOCALE_NOT_SPECIFIED",
|
|
67
|
-
message ?? `Cannot read i18nText field "${field}" without a locale. Pass { locale } to get()/list()/query() or set a default via openVault(name, { locale }).`
|
|
68
|
-
);
|
|
69
|
-
this.name = "LocaleNotSpecifiedError";
|
|
70
|
-
this.field = field;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
MaterializedViewCycleError = class extends NoydbError {
|
|
74
|
-
path;
|
|
75
|
-
constructor(path) {
|
|
76
|
-
super(
|
|
77
|
-
"MATERIALIZED_VIEW_CYCLE",
|
|
78
|
-
`Materialized-view graph contains a cycle: ${path.join(" \u2192 ")}. Refusing to open vault \u2014 break the cycle before retrying.`
|
|
79
|
-
);
|
|
80
|
-
this.name = "MaterializedViewCycleError";
|
|
81
|
-
this.path = path;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
MaterializedViewSourceUnknownError = class extends NoydbError {
|
|
85
|
-
mvName;
|
|
86
|
-
collection;
|
|
87
|
-
constructor(mvName, collection) {
|
|
88
|
-
super(
|
|
89
|
-
"MATERIALIZED_VIEW_SOURCE_UNKNOWN",
|
|
90
|
-
`Materialized view "${mvName}" references unknown source collection "${collection}". Declare the collection (e.g. via schema or by writing to it once) before registering the MV.`
|
|
91
|
-
);
|
|
92
|
-
this.name = "MaterializedViewSourceUnknownError";
|
|
93
|
-
this.mvName = mvName;
|
|
94
|
-
this.collection = collection;
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
MaterializedViewTooLargeError = class extends NoydbError {
|
|
98
|
-
mvName;
|
|
99
|
-
expected;
|
|
100
|
-
limit;
|
|
101
|
-
constructor(mvName, expected, limit) {
|
|
102
|
-
super(
|
|
103
|
-
"MATERIALIZED_VIEW_TOO_LARGE",
|
|
104
|
-
`Materialized view "${mvName}" would emit ${expected} rows, exceeding the configured limit of ${limit}. Override via { maxRows: N } on the MV strategy if intentional, or tighten the query's filter/groupBy.`
|
|
105
|
-
);
|
|
106
|
-
this.name = "MaterializedViewTooLargeError";
|
|
107
|
-
this.mvName = mvName;
|
|
108
|
-
this.expected = expected;
|
|
109
|
-
this.limit = limit;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
MaterializedViewConfigError = class extends NoydbError {
|
|
113
|
-
constructor(message) {
|
|
114
|
-
super(
|
|
115
|
-
"MATERIALIZED_VIEW_CONFIG",
|
|
116
|
-
`[noy-db] withMaterializedView: ${message}`
|
|
117
|
-
);
|
|
118
|
-
this.name = "MaterializedViewConfigError";
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// src/materialized-views/dependency-analyzer.ts
|
|
125
|
-
function analyzeDependencies(query) {
|
|
126
|
-
const deps = /* @__PURE__ */ new Set();
|
|
127
|
-
const plan = query._plan();
|
|
128
|
-
const ctx = query._joinContext();
|
|
129
|
-
if (ctx?.leftCollection) {
|
|
130
|
-
deps.add(ctx.leftCollection);
|
|
131
|
-
}
|
|
132
|
-
for (const leg of plan.joins) {
|
|
133
|
-
deps.add(leg.target);
|
|
134
|
-
}
|
|
135
|
-
for (const clause of plan.clauses) {
|
|
136
|
-
if (clause.type === "crossJoin") {
|
|
137
|
-
deps.add(clause.target);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
walkClausesForJoins(plan, deps, ctx);
|
|
141
|
-
return deps;
|
|
142
|
-
}
|
|
143
|
-
function walkClausesForJoins(plan, deps, ctx) {
|
|
144
|
-
void ctx;
|
|
145
|
-
for (const clause of plan.clauses) {
|
|
146
|
-
if (clause.type === "group") {
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function summarizeQueryPlan(query) {
|
|
151
|
-
const plan = query._plan();
|
|
152
|
-
const ctx = query._joinContext();
|
|
153
|
-
return JSON.stringify({
|
|
154
|
-
root: ctx?.leftCollection ?? null,
|
|
155
|
-
clauses: plan.clauses.map((c) => {
|
|
156
|
-
if (c.type === "crossJoin") {
|
|
157
|
-
return {
|
|
158
|
-
type: "crossJoin",
|
|
159
|
-
target: c.target,
|
|
160
|
-
as: c.as,
|
|
161
|
-
// Inline on: callback: use sentinel — drift detection disabled for this MV
|
|
162
|
-
onPredicateName: c.onPredicateName ?? (c.on ? "[inline]" : null),
|
|
163
|
-
maxRows: c.maxRows ?? null
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return c;
|
|
167
|
-
}),
|
|
168
|
-
orderBy: plan.orderBy,
|
|
169
|
-
limit: plan.limit ?? null,
|
|
170
|
-
offset: plan.offset,
|
|
171
|
-
joins: plan.joins.map((j) => ({ field: j.field, as: j.as, target: j.target, mode: j.mode }))
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
function summarizeUnionPlan(spec) {
|
|
175
|
-
const arms = (spec.unionSources ?? []).map((s) => {
|
|
176
|
-
const joins = s.join?.length ? `[${s.join.map((j) => `${j.field}\u2192${j.as}`).join(";")}]` : "";
|
|
177
|
-
return `${s.collection}${joins}`;
|
|
178
|
-
}).join(",");
|
|
179
|
-
const groupBy = Array.isArray(spec.groupBy) ? [...spec.groupBy].sort().join(",") : typeof spec.groupBy === "string" ? spec.groupBy : "";
|
|
180
|
-
const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(",") : "";
|
|
181
|
-
const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(",") : "";
|
|
182
|
-
return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`;
|
|
183
|
-
}
|
|
184
|
-
var init_dependency_analyzer = __esm({
|
|
185
|
-
"src/materialized-views/dependency-analyzer.ts"() {
|
|
186
|
-
"use strict";
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// src/materialized-views/query-hash.ts
|
|
191
|
-
async function computeQueryHash(mvName, dependencies, queryPlanSummary) {
|
|
192
|
-
const canonical = JSON.stringify({
|
|
193
|
-
mvName,
|
|
194
|
-
dependencies: [...dependencies].sort(),
|
|
195
|
-
queryPlanSummary
|
|
196
|
-
});
|
|
197
|
-
const bytes = new TextEncoder().encode(canonical);
|
|
198
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
199
|
-
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
200
|
-
}
|
|
201
|
-
function canonicalizeQueryPlan(plan) {
|
|
202
|
-
return JSON.stringify(plan, (_key, value) => {
|
|
203
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
204
|
-
const sorted = {};
|
|
205
|
-
for (const k of Object.keys(value).sort()) {
|
|
206
|
-
sorted[k] = value[k];
|
|
207
|
-
}
|
|
208
|
-
return sorted;
|
|
209
|
-
}
|
|
210
|
-
return value;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
var init_query_hash = __esm({
|
|
214
|
-
"src/materialized-views/query-hash.ts"() {
|
|
215
|
-
"use strict";
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// src/materialized-views/registry.ts
|
|
220
|
-
function isFieldClauseOnField(clause, field) {
|
|
221
|
-
return clause.type === "field" && clause.field === field;
|
|
222
|
-
}
|
|
223
|
-
function wrapDbWithPredicates(db, predicates) {
|
|
224
|
-
const map = /* @__PURE__ */ new Map();
|
|
225
|
-
for (const [name, decl] of Object.entries(predicates)) {
|
|
226
|
-
map.set(name, {
|
|
227
|
-
hash: decl.hash,
|
|
228
|
-
fn: decl.fn
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
-
collection(name) {
|
|
234
|
-
const c = db.collection(name);
|
|
235
|
-
return new Proxy(c, {
|
|
236
|
-
get(target, prop, receiver) {
|
|
237
|
-
if (prop === "query") {
|
|
238
|
-
return (...args) => {
|
|
239
|
-
const q = target.query(...args);
|
|
240
|
-
if (q && typeof q._withPredicates === "function") {
|
|
241
|
-
return q._withPredicates(map);
|
|
242
|
-
}
|
|
243
|
-
return q;
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
return Reflect.get(target, prop, receiver);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
function extractPredicateRefs(plan) {
|
|
253
|
-
const refs = [];
|
|
254
|
-
const walk = (clauses) => {
|
|
255
|
-
for (const c of clauses) {
|
|
256
|
-
if (c.type === "wherePredicate") {
|
|
257
|
-
refs.push({ name: c.name, predicateHash: c.predicateHash, ctxHash: c.ctxHash });
|
|
258
|
-
} else if (c.type === "group") {
|
|
259
|
-
walk(c.clauses);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
walk(plan.clauses);
|
|
264
|
-
refs.sort((a, b) => {
|
|
265
|
-
if (a.name !== b.name) return a.name < b.name ? -1 : 1;
|
|
266
|
-
if (a.predicateHash !== b.predicateHash) return a.predicateHash < b.predicateHash ? -1 : 1;
|
|
267
|
-
return a.ctxHash < b.ctxHash ? -1 : a.ctxHash > b.ctxHash ? 1 : 0;
|
|
268
|
-
});
|
|
269
|
-
return refs;
|
|
270
|
-
}
|
|
271
|
-
function partitionDisjoint(reg) {
|
|
272
|
-
const partition = reg.spec.output?.partition;
|
|
273
|
-
if (partition === void 0) return false;
|
|
274
|
-
const value = partition.value;
|
|
275
|
-
for (const c of reg.partitionClauses) {
|
|
276
|
-
if (c.op === "==" && c.value !== value) return true;
|
|
277
|
-
if (c.op === "!=" && c.value === value) return true;
|
|
278
|
-
if (c.op === "in" && Array.isArray(c.value)) {
|
|
279
|
-
const list = c.value;
|
|
280
|
-
if (!list.includes(value)) return true;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
var MaterializedViewRegistry;
|
|
286
|
-
var init_registry = __esm({
|
|
287
|
-
"src/materialized-views/registry.ts"() {
|
|
288
|
-
"use strict";
|
|
289
|
-
init_errors();
|
|
290
|
-
init_dependency_analyzer();
|
|
291
|
-
init_query_hash();
|
|
292
|
-
MaterializedViewRegistry = class {
|
|
293
|
-
/** Keyed by `spec.name`. */
|
|
294
|
-
_byName = /* @__PURE__ */ new Map();
|
|
295
|
-
/** Keyed by dependency source-collection → MVs that depend on it. */
|
|
296
|
-
_bySource = /* @__PURE__ */ new Map();
|
|
297
|
-
/**
|
|
298
|
-
* Register an MV. Invokes `spec.query()` once at registration time to
|
|
299
|
-
* read the plan + join context; the resulting `Query<T>` is discarded
|
|
300
|
-
* after dependency extraction. `vault.collection(...)` must therefore
|
|
301
|
-
* be functional by the time this runs — typically wired from
|
|
302
|
-
* `Vault._initMaterializedViews` after collection bootstrap.
|
|
303
|
-
*
|
|
304
|
-
* Throws `MaterializedViewSourceUnknownError` if the analyzer
|
|
305
|
-
* surfaces a dependency the vault doesn't know about (when a
|
|
306
|
-
* `knownCollections` checker is supplied).
|
|
307
|
-
*/
|
|
308
|
-
async register(spec, db, options) {
|
|
309
|
-
const dbForQuery = spec.predicates ? wrapDbWithPredicates(db, spec.predicates) : db;
|
|
310
|
-
let dependencies;
|
|
311
|
-
let queryPlanSummary;
|
|
312
|
-
let qAny = null;
|
|
313
|
-
let isQuery = false;
|
|
314
|
-
if (spec.unionSources) {
|
|
315
|
-
dependencies = new Set(spec.unionSources.map((s) => s.collection));
|
|
316
|
-
if (spec.sources) for (const s of spec.sources) dependencies.add(s);
|
|
317
|
-
queryPlanSummary = summarizeUnionPlan(spec);
|
|
318
|
-
} else {
|
|
319
|
-
const q = spec.query(dbForQuery);
|
|
320
|
-
qAny = q;
|
|
321
|
-
isQuery = typeof qAny._plan === "function";
|
|
322
|
-
if (isQuery) {
|
|
323
|
-
dependencies = analyzeDependencies(q);
|
|
324
|
-
queryPlanSummary = summarizeQueryPlan(q);
|
|
325
|
-
const predicateRefs = extractPredicateRefs(qAny._plan());
|
|
326
|
-
if (predicateRefs.length > 0) {
|
|
327
|
-
queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs });
|
|
328
|
-
}
|
|
329
|
-
if (spec.sources) for (const s of spec.sources) dependencies.add(s);
|
|
330
|
-
} else {
|
|
331
|
-
if (!spec.sources || spec.sources.length === 0) {
|
|
332
|
-
throw new Error(
|
|
333
|
-
`withMaterializedView "${spec.name}": query() returned an aggregate (Aggregation or GroupedAggregation) but no \`sources\` field is declared. The dependency analyzer cannot walk through groupBy().aggregate() back to the source \u2014 declare sources: [...] explicitly.`
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
dependencies = new Set(spec.sources);
|
|
337
|
-
queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() });
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (options?.knownCollections) {
|
|
341
|
-
for (const dep of dependencies) {
|
|
342
|
-
if (!options.knownCollections(dep)) {
|
|
343
|
-
throw new MaterializedViewSourceUnknownError(spec.name, dep);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const outputCollection = spec.output?.collection ?? spec.name;
|
|
348
|
-
const queryHash = await computeQueryHash(spec.name, dependencies, queryPlanSummary);
|
|
349
|
-
const partitionClauses = [];
|
|
350
|
-
const partitionField = spec.output?.partition?.field;
|
|
351
|
-
if (partitionField !== void 0 && isQuery) {
|
|
352
|
-
const plan = qAny._plan();
|
|
353
|
-
for (const clause of plan.clauses) {
|
|
354
|
-
if (isFieldClauseOnField(clause, partitionField)) partitionClauses.push(clause);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
const reg = { spec, outputCollection, dependencies, queryHash, partitionClauses };
|
|
358
|
-
this._byName.set(spec.name, reg);
|
|
359
|
-
for (const dep of dependencies) {
|
|
360
|
-
const arr = this._bySource.get(dep);
|
|
361
|
-
if (arr) arr.push(reg);
|
|
362
|
-
else this._bySource.set(dep, [reg]);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/** All MVs that depend on `source`, in registration order. */
|
|
366
|
-
mvsForSource(source) {
|
|
367
|
-
return this._bySource.get(source) ?? [];
|
|
368
|
-
}
|
|
369
|
-
/** Single MV by name, or `undefined`. */
|
|
370
|
-
byName(name) {
|
|
371
|
-
return this._byName.get(name);
|
|
372
|
-
}
|
|
373
|
-
/** Iterate over every registered MV. */
|
|
374
|
-
all() {
|
|
375
|
-
return [...this._byName.values()];
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Cycle detection over the combined derivation + MV graph. Edges:
|
|
379
|
-
* - Derivation: derivation.source → output.collection (each output)
|
|
380
|
-
* - MV: every dep in MV.dependencies → MV.outputCollection
|
|
381
|
-
*
|
|
382
|
-
* Throws `MaterializedViewCycleError` if the cycle's terminal node
|
|
383
|
-
* is an MV output collection; otherwise (a pure-derivation cycle)
|
|
384
|
-
* the caller's `DerivationRegistry.validate()` will surface
|
|
385
|
-
* `DerivationCycleError` separately at vault open.
|
|
386
|
-
*
|
|
387
|
-
* Call AFTER all `register()` calls complete.
|
|
388
|
-
*/
|
|
389
|
-
validate(derivationRegistry) {
|
|
390
|
-
const visited = /* @__PURE__ */ new Set();
|
|
391
|
-
const stack = [];
|
|
392
|
-
const mvOutputs = /* @__PURE__ */ new Set();
|
|
393
|
-
for (const reg of this._byName.values()) mvOutputs.add(reg.outputCollection);
|
|
394
|
-
const edges = /* @__PURE__ */ new Map();
|
|
395
|
-
for (const reg of this._byName.values()) {
|
|
396
|
-
for (const dep of reg.dependencies) {
|
|
397
|
-
if (dep === reg.outputCollection && partitionDisjoint(reg)) continue;
|
|
398
|
-
const arr = edges.get(dep);
|
|
399
|
-
if (arr) arr.push(reg.outputCollection);
|
|
400
|
-
else edges.set(dep, [reg.outputCollection]);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (derivationRegistry) {
|
|
404
|
-
for (const reg of this._byName.values()) {
|
|
405
|
-
void reg;
|
|
406
|
-
}
|
|
407
|
-
const sourcesToScan = /* @__PURE__ */ new Set();
|
|
408
|
-
for (const reg of this._byName.values()) {
|
|
409
|
-
for (const dep of reg.dependencies) sourcesToScan.add(dep);
|
|
410
|
-
sourcesToScan.add(reg.outputCollection);
|
|
411
|
-
}
|
|
412
|
-
for (const src of sourcesToScan) {
|
|
413
|
-
const strategies = derivationRegistry.strategiesForSource(src);
|
|
414
|
-
if (strategies.length === 0) continue;
|
|
415
|
-
for (const s of strategies) {
|
|
416
|
-
for (const key of Object.keys(s.spec.outputs)) {
|
|
417
|
-
const o = s.spec.outputs[key];
|
|
418
|
-
if (!o) continue;
|
|
419
|
-
const arr = edges.get(src);
|
|
420
|
-
if (arr) arr.push(o.collection);
|
|
421
|
-
else edges.set(src, [o.collection]);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const visit = (node) => {
|
|
427
|
-
if (stack.includes(node)) {
|
|
428
|
-
const cycle = stack.slice(stack.indexOf(node)).concat(node);
|
|
429
|
-
if (cycle.some((n) => mvOutputs.has(n))) {
|
|
430
|
-
throw new MaterializedViewCycleError(cycle);
|
|
431
|
-
}
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (visited.has(node)) return;
|
|
435
|
-
stack.push(node);
|
|
436
|
-
const outs = edges.get(node);
|
|
437
|
-
if (outs) for (const o of outs) visit(o);
|
|
438
|
-
stack.pop();
|
|
439
|
-
visited.add(node);
|
|
440
|
-
};
|
|
441
|
-
for (const node of edges.keys()) visit(node);
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// src/money/fixed-point.ts
|
|
448
|
-
function expandExponent(s) {
|
|
449
|
-
const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
|
|
450
|
-
if (!m) return s;
|
|
451
|
-
const sign = m[1] === "-" ? "-" : "";
|
|
452
|
-
const intp = m[2];
|
|
453
|
-
const frac = m[3] ?? "";
|
|
454
|
-
const exp = Number(m[4]);
|
|
455
|
-
const digits = intp + frac;
|
|
456
|
-
const pointPos = intp.length + exp;
|
|
457
|
-
let body;
|
|
458
|
-
if (pointPos <= 0) {
|
|
459
|
-
body = "0." + "0".repeat(-pointPos) + digits;
|
|
460
|
-
} else if (pointPos >= digits.length) {
|
|
461
|
-
body = digits + "0".repeat(pointPos - digits.length);
|
|
462
|
-
} else {
|
|
463
|
-
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
464
|
-
}
|
|
465
|
-
return sign + body;
|
|
466
|
-
}
|
|
467
|
-
function toCanonicalDecimalString(input) {
|
|
468
|
-
let s;
|
|
469
|
-
if (typeof input === "number") {
|
|
470
|
-
if (!Number.isFinite(input)) return null;
|
|
471
|
-
s = String(input);
|
|
472
|
-
} else {
|
|
473
|
-
s = input.trim();
|
|
474
|
-
}
|
|
475
|
-
s = expandExponent(s);
|
|
476
|
-
if (s.startsWith("+")) s = s.slice(1);
|
|
477
|
-
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
478
|
-
return s;
|
|
479
|
-
}
|
|
480
|
-
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
481
|
-
switch (mode) {
|
|
482
|
-
case "up":
|
|
483
|
-
return true;
|
|
484
|
-
case "down":
|
|
485
|
-
return false;
|
|
486
|
-
case "ceil":
|
|
487
|
-
return !negative;
|
|
488
|
-
case "floor":
|
|
489
|
-
return negative;
|
|
490
|
-
case "half-up":
|
|
491
|
-
return firstDiscarded >= 5;
|
|
492
|
-
case "half-down":
|
|
493
|
-
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
494
|
-
case "half-even":
|
|
495
|
-
if (firstDiscarded > 5) return true;
|
|
496
|
-
if (firstDiscarded < 5) return false;
|
|
497
|
-
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
function parseToScaledInt(input, scale, rounding) {
|
|
501
|
-
const canonical = toCanonicalDecimalString(input);
|
|
502
|
-
if (canonical === null) return { ok: false, reason: "nonfinite" };
|
|
503
|
-
const negative = canonical.startsWith("-");
|
|
504
|
-
const unsigned = negative ? canonical.slice(1) : canonical;
|
|
505
|
-
const dot = unsigned.indexOf(".");
|
|
506
|
-
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
507
|
-
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
508
|
-
const intDigits = intPart === "" ? "0" : intPart;
|
|
509
|
-
if (fracPart.length <= scale) {
|
|
510
|
-
const keep2 = fracPart.padEnd(scale, "0");
|
|
511
|
-
const magnitude2 = BigInt(intDigits + keep2);
|
|
512
|
-
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
513
|
-
}
|
|
514
|
-
const keep = fracPart.slice(0, scale);
|
|
515
|
-
const tail = fracPart.slice(scale);
|
|
516
|
-
const magnitudeDigits = intDigits + keep;
|
|
517
|
-
let magnitude = BigInt(magnitudeDigits);
|
|
518
|
-
if (/^0+$/.test(tail)) {
|
|
519
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
520
|
-
}
|
|
521
|
-
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
522
|
-
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
523
|
-
const firstDiscarded = Number(tail[0]);
|
|
524
|
-
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
525
|
-
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
526
|
-
magnitude += 1n;
|
|
527
|
-
}
|
|
528
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
529
|
-
}
|
|
530
|
-
function formatScaledInt(value, scale) {
|
|
531
|
-
const negative = value < 0n;
|
|
532
|
-
const abs = (negative ? -value : value).toString();
|
|
533
|
-
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
534
|
-
const padded = abs.padStart(scale + 1, "0");
|
|
535
|
-
const cut = padded.length - scale;
|
|
536
|
-
const intPart = padded.slice(0, cut);
|
|
537
|
-
const fracPart = padded.slice(cut);
|
|
538
|
-
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
539
|
-
}
|
|
540
|
-
var init_fixed_point = __esm({
|
|
541
|
-
"src/money/fixed-point.ts"() {
|
|
542
|
-
"use strict";
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
// src/money/iso4217.ts
|
|
547
|
-
function scaleForCurrency(code) {
|
|
548
|
-
const v = MINOR_UNITS[code];
|
|
549
|
-
return v === void 0 ? null : v;
|
|
550
|
-
}
|
|
551
|
-
var MINOR_UNITS;
|
|
552
|
-
var init_iso4217 = __esm({
|
|
553
|
-
"src/money/iso4217.ts"() {
|
|
554
|
-
"use strict";
|
|
555
|
-
MINOR_UNITS = {
|
|
556
|
-
// 2-decimal majors
|
|
557
|
-
EUR: 2,
|
|
558
|
-
USD: 2,
|
|
559
|
-
GBP: 2,
|
|
560
|
-
CHF: 2,
|
|
561
|
-
CAD: 2,
|
|
562
|
-
AUD: 2,
|
|
563
|
-
NZD: 2,
|
|
564
|
-
SGD: 2,
|
|
565
|
-
HKD: 2,
|
|
566
|
-
CNY: 2,
|
|
567
|
-
INR: 2,
|
|
568
|
-
BRL: 2,
|
|
569
|
-
MXN: 2,
|
|
570
|
-
ZAR: 2,
|
|
571
|
-
RUB: 2,
|
|
572
|
-
TRY: 2,
|
|
573
|
-
PLN: 2,
|
|
574
|
-
SEK: 2,
|
|
575
|
-
NOK: 2,
|
|
576
|
-
DKK: 2,
|
|
577
|
-
CZK: 2,
|
|
578
|
-
HUF: 2,
|
|
579
|
-
RON: 2,
|
|
580
|
-
ILS: 2,
|
|
581
|
-
THB: 2,
|
|
582
|
-
PHP: 2,
|
|
583
|
-
MYR: 2,
|
|
584
|
-
IDR: 2,
|
|
585
|
-
AED: 2,
|
|
586
|
-
SAR: 2,
|
|
587
|
-
QAR: 2,
|
|
588
|
-
EGP: 2,
|
|
589
|
-
// 0-decimal
|
|
590
|
-
JPY: 0,
|
|
591
|
-
KRW: 0,
|
|
592
|
-
ISK: 0,
|
|
593
|
-
CLP: 0,
|
|
594
|
-
VND: 0,
|
|
595
|
-
XOF: 0,
|
|
596
|
-
XAF: 0,
|
|
597
|
-
PYG: 0,
|
|
598
|
-
// 3-decimal
|
|
599
|
-
BHD: 3,
|
|
600
|
-
KWD: 3,
|
|
601
|
-
OMR: 3,
|
|
602
|
-
TND: 3,
|
|
603
|
-
JOD: 3,
|
|
604
|
-
IQD: 3,
|
|
605
|
-
LYD: 3
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
// src/money/descriptor.ts
|
|
611
|
-
var MoneyUnsupportedError;
|
|
612
|
-
var init_descriptor = __esm({
|
|
613
|
-
"src/money/descriptor.ts"() {
|
|
614
|
-
"use strict";
|
|
615
|
-
init_errors();
|
|
616
|
-
MoneyUnsupportedError = class extends NoydbError {
|
|
617
|
-
constructor(field, message) {
|
|
618
|
-
super(
|
|
619
|
-
"MONEY_UNSUPPORTED",
|
|
620
|
-
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
621
|
-
);
|
|
622
|
-
this.field = field;
|
|
623
|
-
this.name = "MoneyUnsupportedError";
|
|
624
|
-
}
|
|
625
|
-
field;
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
// src/query/predicate.ts
|
|
631
|
-
function readPath(record, path) {
|
|
632
|
-
if (record === null || record === void 0) return void 0;
|
|
633
|
-
if (!path.includes(".")) {
|
|
634
|
-
return record[path];
|
|
635
|
-
}
|
|
636
|
-
const segments = path.split(".");
|
|
637
|
-
let cursor = record;
|
|
638
|
-
for (const segment of segments) {
|
|
639
|
-
if (cursor === null || cursor === void 0) return void 0;
|
|
640
|
-
cursor = cursor[segment];
|
|
641
|
-
}
|
|
642
|
-
return cursor;
|
|
643
|
-
}
|
|
644
|
-
var init_predicate = __esm({
|
|
645
|
-
"src/query/predicate.ts"() {
|
|
646
|
-
"use strict";
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// src/aggregate/canonical-key.ts
|
|
651
|
-
function canonicalGroupKey(fields, row) {
|
|
652
|
-
const sorted = [...fields].sort();
|
|
653
|
-
const parts = [];
|
|
654
|
-
for (const name of sorted) {
|
|
655
|
-
const v = row[name];
|
|
656
|
-
const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
|
|
657
|
-
parts.push(`${name}=${serialised}`);
|
|
658
|
-
}
|
|
659
|
-
return parts.join("|");
|
|
660
|
-
}
|
|
661
|
-
var init_canonical_key = __esm({
|
|
662
|
-
"src/aggregate/canonical-key.ts"() {
|
|
663
|
-
"use strict";
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
// src/money/money-reducer.ts
|
|
668
|
-
function toScaledIntFromAny(v, scale) {
|
|
669
|
-
if (typeof v === "bigint") return v;
|
|
670
|
-
if (typeof v === "number") {
|
|
671
|
-
const r = parseToScaledInt(v, scale);
|
|
672
|
-
return r.ok ? r.value : null;
|
|
673
|
-
}
|
|
674
|
-
if (typeof v === "string") {
|
|
675
|
-
if (!v.includes(".")) {
|
|
676
|
-
try {
|
|
677
|
-
return BigInt(v);
|
|
678
|
-
} catch {
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
const r = parseToScaledInt(v, scale);
|
|
683
|
-
return r.ok ? r.value : null;
|
|
684
|
-
}
|
|
685
|
-
return null;
|
|
686
|
-
}
|
|
687
|
-
function readMoney(record, field, desc) {
|
|
688
|
-
const raw = readPath(record, field);
|
|
689
|
-
if (raw === null || raw === void 0) return null;
|
|
690
|
-
if (desc.mode === "fixed") {
|
|
691
|
-
const cur = desc.fixedCurrency;
|
|
692
|
-
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
693
|
-
return value2 === null ? null : { currency: cur, value: value2 };
|
|
694
|
-
}
|
|
695
|
-
if (typeof raw !== "object") return null;
|
|
696
|
-
const o = raw;
|
|
697
|
-
if (typeof o.currency !== "string") return null;
|
|
698
|
-
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
699
|
-
const value = toScaledIntFromAny(o.amount, scale);
|
|
700
|
-
return value === null ? null : { currency: o.currency, value };
|
|
701
|
-
}
|
|
702
|
-
function targetScaleFor(desc, currency) {
|
|
703
|
-
if (desc.allows(currency)) return desc.scaleFor(currency);
|
|
704
|
-
const s = scaleForCurrency(currency);
|
|
705
|
-
if (s === null) {
|
|
706
|
-
throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
|
|
707
|
-
}
|
|
708
|
-
return s;
|
|
709
|
-
}
|
|
710
|
-
function parseRate(rate) {
|
|
711
|
-
const s = String(rate).trim();
|
|
712
|
-
const neg = s.startsWith("-");
|
|
713
|
-
const body = neg ? s.slice(1) : s;
|
|
714
|
-
const dot = body.indexOf(".");
|
|
715
|
-
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
716
|
-
const fracPart = dot === -1 ? "" : body.slice(dot + 1);
|
|
717
|
-
const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
|
|
718
|
-
return { int: neg ? -int : int, scale: fracPart.length };
|
|
719
|
-
}
|
|
720
|
-
function divRoundHalfEven(n, d) {
|
|
721
|
-
const q = n / d;
|
|
722
|
-
const r = n % d;
|
|
723
|
-
const twiceR = (r < 0n ? -r : r) * 2n;
|
|
724
|
-
if (twiceR < d) return q;
|
|
725
|
-
if (twiceR > d) return q + (n < 0n ? -1n : 1n);
|
|
726
|
-
return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
|
|
727
|
-
}
|
|
728
|
-
function convertScaled(value, srcScale, rate, targetScale) {
|
|
729
|
-
const { int: rateInt, scale: rateScale } = parseRate(rate);
|
|
730
|
-
const product = value * rateInt;
|
|
731
|
-
const curScale = srcScale + rateScale;
|
|
732
|
-
if (curScale === targetScale) return product;
|
|
733
|
-
if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
|
|
734
|
-
return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
|
|
735
|
-
}
|
|
736
|
-
function finalizeSum(state, desc, convertTo, fx) {
|
|
737
|
-
if (convertTo !== void 0) {
|
|
738
|
-
if (fx === void 0) {
|
|
739
|
-
throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
|
|
740
|
-
}
|
|
741
|
-
const targetScale = targetScaleFor(desc, convertTo);
|
|
742
|
-
let total = 0n;
|
|
743
|
-
for (const [cur, v] of state) {
|
|
744
|
-
if (cur === convertTo) {
|
|
745
|
-
total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
const rate = fx[`${cur}->${convertTo}`];
|
|
749
|
-
if (rate === void 0) {
|
|
750
|
-
throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
|
|
751
|
-
}
|
|
752
|
-
total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
|
|
753
|
-
}
|
|
754
|
-
return formatScaledInt(total, targetScale);
|
|
755
|
-
}
|
|
756
|
-
if (desc.mode === "fixed") {
|
|
757
|
-
const cur = desc.fixedCurrency;
|
|
758
|
-
return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
|
|
759
|
-
}
|
|
760
|
-
const out = {};
|
|
761
|
-
for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
|
|
762
|
-
return out;
|
|
763
|
-
}
|
|
764
|
-
function moneySumReducer(field, desc, convertTo, fx) {
|
|
765
|
-
return {
|
|
766
|
-
op: "sum",
|
|
767
|
-
field,
|
|
768
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
769
|
-
step: (state, record) => {
|
|
770
|
-
const m = readMoney(record, field, desc);
|
|
771
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
|
|
772
|
-
return state;
|
|
773
|
-
},
|
|
774
|
-
remove: (state, record) => {
|
|
775
|
-
const m = readMoney(record, field, desc);
|
|
776
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
|
|
777
|
-
return state;
|
|
778
|
-
},
|
|
779
|
-
finalize: (state) => finalizeSum(state, desc, convertTo, fx)
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
function extremum(values, op) {
|
|
783
|
-
let out = values[0];
|
|
784
|
-
for (let i = 1; i < values.length; i++) {
|
|
785
|
-
const v = values[i];
|
|
786
|
-
if (op === "min" ? v < out : v > out) out = v;
|
|
787
|
-
}
|
|
788
|
-
return out;
|
|
789
|
-
}
|
|
790
|
-
function moneyMinMaxReducer(op, field, desc) {
|
|
791
|
-
return {
|
|
792
|
-
op,
|
|
793
|
-
field,
|
|
794
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
795
|
-
step: (state, record) => {
|
|
796
|
-
const m = readMoney(record, field, desc);
|
|
797
|
-
if (m) {
|
|
798
|
-
const arr = state.get(m.currency);
|
|
799
|
-
if (arr) arr.push(m.value);
|
|
800
|
-
else state.set(m.currency, [m.value]);
|
|
801
|
-
}
|
|
802
|
-
return state;
|
|
803
|
-
},
|
|
804
|
-
remove: (state, record) => {
|
|
805
|
-
const m = readMoney(record, field, desc);
|
|
806
|
-
if (m) {
|
|
807
|
-
const arr = state.get(m.currency);
|
|
808
|
-
if (arr) {
|
|
809
|
-
const idx = arr.indexOf(m.value);
|
|
810
|
-
if (idx >= 0) arr.splice(idx, 1);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
return state;
|
|
814
|
-
},
|
|
815
|
-
finalize: (state) => {
|
|
816
|
-
if (desc.mode === "fixed") {
|
|
817
|
-
const cur = desc.fixedCurrency;
|
|
818
|
-
const arr = state.get(cur);
|
|
819
|
-
if (!arr || arr.length === 0) return null;
|
|
820
|
-
return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
821
|
-
}
|
|
822
|
-
const out = {};
|
|
823
|
-
for (const [cur, arr] of state) {
|
|
824
|
-
if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
825
|
-
}
|
|
826
|
-
return out;
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
function wrapMoneyReducers(spec, moneyFields) {
|
|
831
|
-
let changed = false;
|
|
832
|
-
const out = {};
|
|
833
|
-
for (const [key, reducer] of Object.entries(spec)) {
|
|
834
|
-
const field = reducer.field;
|
|
835
|
-
const desc = field ? moneyFields[field] : void 0;
|
|
836
|
-
if (desc && reducer.op === "avg") {
|
|
837
|
-
throw new MoneyUnsupportedError(
|
|
838
|
-
field,
|
|
839
|
-
`avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
|
|
843
|
-
changed = true;
|
|
844
|
-
out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
|
|
845
|
-
} else {
|
|
846
|
-
out[key] = reducer;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return changed ? out : spec;
|
|
850
|
-
}
|
|
851
|
-
var init_money_reducer = __esm({
|
|
852
|
-
"src/money/money-reducer.ts"() {
|
|
853
|
-
"use strict";
|
|
854
|
-
init_predicate();
|
|
855
|
-
init_fixed_point();
|
|
856
|
-
init_iso4217();
|
|
857
|
-
init_descriptor();
|
|
858
|
-
}
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// src/i18n/policy.ts
|
|
862
|
-
function resolvePolicy(onMissing, layer) {
|
|
863
|
-
const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
|
|
864
|
-
const scalar = typeof onMissing === "string" ? onMissing : void 0;
|
|
865
|
-
const layerDefault = layer === "guard" ? "substitute" : void 0;
|
|
866
|
-
return explicit ?? layerDefault ?? scalar ?? "throw";
|
|
867
|
-
}
|
|
868
|
-
var init_policy = __esm({
|
|
869
|
-
"src/i18n/policy.ts"() {
|
|
870
|
-
"use strict";
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
// src/i18n/script.ts
|
|
875
|
-
function inferScripts(locale) {
|
|
876
|
-
const parts = locale.split("-");
|
|
877
|
-
const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
|
|
878
|
-
if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
|
|
879
|
-
const base = (parts[0] ?? "").toLowerCase();
|
|
880
|
-
if (LATIN_BASE.has(base)) return ["Latin"];
|
|
881
|
-
const primary = SCRIPT_TABLE[base];
|
|
882
|
-
if (primary) return [...primary, "Latin"];
|
|
883
|
-
return ["Latin"];
|
|
884
|
-
}
|
|
885
|
-
var LATIN_BASE, SCRIPT_TABLE, SUBTAG_SCRIPTS, BASELINE;
|
|
886
|
-
var init_script = __esm({
|
|
887
|
-
"src/i18n/script.ts"() {
|
|
888
|
-
"use strict";
|
|
889
|
-
LATIN_BASE = /* @__PURE__ */ new Set([
|
|
890
|
-
"en",
|
|
891
|
-
"fr",
|
|
892
|
-
"de",
|
|
893
|
-
"es",
|
|
894
|
-
"it",
|
|
895
|
-
"pt",
|
|
896
|
-
"nl",
|
|
897
|
-
"sv",
|
|
898
|
-
"no",
|
|
899
|
-
"da",
|
|
900
|
-
"fi",
|
|
901
|
-
"is",
|
|
902
|
-
"pl",
|
|
903
|
-
"cs",
|
|
904
|
-
"sk",
|
|
905
|
-
"hu",
|
|
906
|
-
"ro",
|
|
907
|
-
"hr",
|
|
908
|
-
"sl",
|
|
909
|
-
"et",
|
|
910
|
-
"lv",
|
|
911
|
-
"lt",
|
|
912
|
-
"tr",
|
|
913
|
-
"vi",
|
|
914
|
-
"id",
|
|
915
|
-
"ms",
|
|
916
|
-
"tl",
|
|
917
|
-
"sw",
|
|
918
|
-
"af",
|
|
919
|
-
"ca",
|
|
920
|
-
"gl",
|
|
921
|
-
"eu",
|
|
922
|
-
"cy",
|
|
923
|
-
"ga"
|
|
924
|
-
]);
|
|
925
|
-
SCRIPT_TABLE = {
|
|
926
|
-
th: ["Thai"],
|
|
927
|
-
ko: ["Hangul", "Han"],
|
|
928
|
-
ja: ["Han", "Hiragana", "Katakana"],
|
|
929
|
-
zh: ["Han"],
|
|
930
|
-
ar: ["Arabic"],
|
|
931
|
-
fa: ["Arabic"],
|
|
932
|
-
ur: ["Arabic"],
|
|
933
|
-
ru: ["Cyrillic"],
|
|
934
|
-
uk: ["Cyrillic"],
|
|
935
|
-
bg: ["Cyrillic"],
|
|
936
|
-
sr: ["Cyrillic"],
|
|
937
|
-
he: ["Hebrew"],
|
|
938
|
-
el: ["Greek"],
|
|
939
|
-
hi: ["Devanagari"],
|
|
940
|
-
ta: ["Tamil"],
|
|
941
|
-
km: ["Khmer"],
|
|
942
|
-
lo: ["Lao"],
|
|
943
|
-
my: ["Myanmar"]
|
|
944
|
-
};
|
|
945
|
-
SUBTAG_SCRIPTS = {
|
|
946
|
-
Latn: ["Latin"],
|
|
947
|
-
Cyrl: ["Cyrillic", "Latin"],
|
|
948
|
-
Hans: ["Han", "Latin"],
|
|
949
|
-
Hant: ["Han", "Latin"],
|
|
950
|
-
Thai: ["Thai", "Latin"],
|
|
951
|
-
Arab: ["Arabic", "Latin"]
|
|
952
|
-
};
|
|
953
|
-
BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
// src/i18n/core.ts
|
|
958
|
-
function toChain(fallback) {
|
|
959
|
-
return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
960
|
-
}
|
|
961
|
-
function pickFromChain(value, chain) {
|
|
962
|
-
for (const fb of chain) {
|
|
963
|
-
if (fb === "any") {
|
|
964
|
-
const any = Object.values(value).find((v) => v !== "");
|
|
965
|
-
if (any !== void 0) return any;
|
|
966
|
-
} else if (value[fb] !== void 0 && value[fb] !== "") {
|
|
967
|
-
return value[fb];
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
return void 0;
|
|
971
|
-
}
|
|
972
|
-
function resolveI18nText(value, locale, fallback, field, opts) {
|
|
973
|
-
if (locale === "raw") {
|
|
974
|
-
return value;
|
|
975
|
-
}
|
|
976
|
-
if (!locale) {
|
|
977
|
-
throw new LocaleNotSpecifiedError(field ?? "<unknown>");
|
|
978
|
-
}
|
|
979
|
-
if (value[locale] !== void 0 && value[locale] !== "") {
|
|
980
|
-
return value[locale];
|
|
981
|
-
}
|
|
982
|
-
const policy = opts?.policy ?? "throw";
|
|
983
|
-
const callerChain = toChain(fallback);
|
|
984
|
-
const callerHit = pickFromChain(value, callerChain);
|
|
985
|
-
if (callerHit !== void 0) return callerHit;
|
|
986
|
-
if (policy === "substitute") {
|
|
987
|
-
const subHit = pickFromChain(value, toChain(opts?.substitute));
|
|
988
|
-
if (subHit !== void 0) return subHit;
|
|
989
|
-
if (opts?.smartSubstitute) {
|
|
990
|
-
const smartHit = pickNearestScript(value, locale);
|
|
991
|
-
if (smartHit !== void 0) return smartHit;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
if (policy === "throw") {
|
|
995
|
-
throw new LocaleNotSpecifiedError(
|
|
996
|
-
field ?? "<unknown>",
|
|
997
|
-
`No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1002
|
-
function pickNearestScript(value, target) {
|
|
1003
|
-
const targetScript = inferScripts(target)[0] ?? "Latin";
|
|
1004
|
-
let best;
|
|
1005
|
-
for (const [loc, v] of Object.entries(value)) {
|
|
1006
|
-
if (typeof v !== "string" || v === "") continue;
|
|
1007
|
-
const s = inferScripts(loc)[0] ?? "Latin";
|
|
1008
|
-
const score = s === targetScript ? 0 : s === "Latin" ? 1 : 2;
|
|
1009
|
-
if (best === void 0 || score < best.score) best = { score, v };
|
|
1010
|
-
if (score === 0) break;
|
|
1011
|
-
}
|
|
1012
|
-
return best?.v;
|
|
1013
|
-
}
|
|
1014
|
-
function applyAtPath(obj, path, locale, fallback, opts) {
|
|
1015
|
-
const arrayIdx = path.indexOf("[].");
|
|
1016
|
-
if (arrayIdx !== -1) {
|
|
1017
|
-
const arrayKey = path.slice(0, arrayIdx);
|
|
1018
|
-
const restPath = path.slice(arrayIdx + 3);
|
|
1019
|
-
const arr = obj[arrayKey];
|
|
1020
|
-
if (!Array.isArray(arr)) return obj;
|
|
1021
|
-
return {
|
|
1022
|
-
...obj,
|
|
1023
|
-
[arrayKey]: arr.map((item) => {
|
|
1024
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
1025
|
-
return applyAtPath(item, restPath, locale, fallback, opts);
|
|
1026
|
-
})
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
const dotIdx = path.indexOf(".");
|
|
1030
|
-
if (dotIdx !== -1) {
|
|
1031
|
-
const head = path.slice(0, dotIdx);
|
|
1032
|
-
const rest = path.slice(dotIdx + 1);
|
|
1033
|
-
const nested = obj[head];
|
|
1034
|
-
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
|
|
1035
|
-
return {
|
|
1036
|
-
...obj,
|
|
1037
|
-
[head]: applyAtPath(nested, rest, locale, fallback, opts)
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
const raw = obj[path];
|
|
1041
|
-
if (raw === void 0 || raw === null) return obj;
|
|
1042
|
-
if (typeof raw !== "object" || Array.isArray(raw)) return obj;
|
|
1043
|
-
return {
|
|
1044
|
-
...obj,
|
|
1045
|
-
[path]: resolveI18nText(raw, locale, fallback, path, opts)
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
1049
|
-
const fieldNames = Object.keys(i18nFields);
|
|
1050
|
-
if (fieldNames.length === 0) return record;
|
|
1051
|
-
let result = record;
|
|
1052
|
-
for (const [field, descriptor] of Object.entries(i18nFields)) {
|
|
1053
|
-
const { onMissing, substitute, smartSubstitute } = descriptor.options;
|
|
1054
|
-
const opts = {
|
|
1055
|
-
policy: resolvePolicy(onMissing, layer),
|
|
1056
|
-
...substitute !== void 0 ? { substitute } : {},
|
|
1057
|
-
...smartSubstitute ? { smartSubstitute } : {}
|
|
1058
|
-
};
|
|
1059
|
-
result = applyAtPath(result, field, locale, fallback, opts);
|
|
1060
|
-
}
|
|
1061
|
-
result = stripI18nFilled(result);
|
|
1062
|
-
return result;
|
|
1063
|
-
}
|
|
1064
|
-
function stripI18nFilled(record) {
|
|
1065
|
-
if (!Object.prototype.hasOwnProperty.call(record, "_i18nFilled")) return record;
|
|
1066
|
-
const rest = { ...record };
|
|
1067
|
-
delete rest._i18nFilled;
|
|
1068
|
-
return rest;
|
|
1069
|
-
}
|
|
1070
|
-
var init_core = __esm({
|
|
1071
|
-
"src/i18n/core.ts"() {
|
|
1072
|
-
"use strict";
|
|
1073
|
-
init_errors();
|
|
1074
|
-
init_policy();
|
|
1075
|
-
init_script();
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
|
|
1079
|
-
// src/aggregate/groupby.ts
|
|
1080
|
-
function warnCardinalityApproaching(fields, observed) {
|
|
1081
|
-
const key = JSON.stringify([...fields].sort());
|
|
1082
|
-
if (warnedCardinalityFields.has(key)) return;
|
|
1083
|
-
warnedCardinalityFields.add(key);
|
|
1084
|
-
const label = `[${fields.join(", ")}]`;
|
|
1085
|
-
console.warn(
|
|
1086
|
-
`[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.`
|
|
1087
|
-
);
|
|
1088
|
-
}
|
|
1089
|
-
function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
1090
|
-
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
1091
|
-
if (fields.length === 0) {
|
|
1092
|
-
throw new Error(".groupBy() requires at least one field");
|
|
1093
|
-
}
|
|
1094
|
-
if (moneyFields) {
|
|
1095
|
-
spec = wrapMoneyReducers(spec, moneyFields);
|
|
1096
|
-
}
|
|
1097
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1098
|
-
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
1099
|
-
for (const record of records) {
|
|
1100
|
-
const keyValues = {};
|
|
1101
|
-
for (const f of fields) {
|
|
1102
|
-
keyValues[f] = readPath(record, f);
|
|
1103
|
-
}
|
|
1104
|
-
const dedupKey = canonicalGroupKey(fields, keyValues);
|
|
1105
|
-
let bucket = buckets.get(dedupKey);
|
|
1106
|
-
if (bucket === void 0) {
|
|
1107
|
-
if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
|
|
1108
|
-
throw new GroupCardinalityError(
|
|
1109
|
-
fieldLabel,
|
|
1110
|
-
buckets.size + 1,
|
|
1111
|
-
GROUPBY_MAX_CARDINALITY
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
1114
|
-
bucket = { keyValues, records: [] };
|
|
1115
|
-
buckets.set(dedupKey, bucket);
|
|
1116
|
-
}
|
|
1117
|
-
bucket.records.push(record);
|
|
1118
|
-
}
|
|
1119
|
-
if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
|
|
1120
|
-
warnCardinalityApproaching(fields, buckets.size);
|
|
1121
|
-
}
|
|
1122
|
-
const reducerKeys = Object.keys(spec);
|
|
1123
|
-
const out = [];
|
|
1124
|
-
for (const bucket of buckets.values()) {
|
|
1125
|
-
const state = {};
|
|
1126
|
-
for (const rk of reducerKeys) {
|
|
1127
|
-
state[rk] = spec[rk].init();
|
|
1128
|
-
}
|
|
1129
|
-
for (const record of bucket.records) {
|
|
1130
|
-
for (const rk of reducerKeys) {
|
|
1131
|
-
state[rk] = spec[rk].step(state[rk], record);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
const row = {};
|
|
1135
|
-
for (const f of fields) {
|
|
1136
|
-
row[f] = bucket.keyValues[f];
|
|
1137
|
-
}
|
|
1138
|
-
for (const rk of reducerKeys) {
|
|
1139
|
-
row[rk] = spec[rk].finalize(state[rk]);
|
|
1140
|
-
}
|
|
1141
|
-
out.push(row);
|
|
1142
|
-
}
|
|
1143
|
-
return out;
|
|
1144
|
-
}
|
|
1145
|
-
var GROUPBY_WARN_CARDINALITY, GROUPBY_MAX_CARDINALITY, warnedCardinalityFields;
|
|
1146
|
-
var init_groupby = __esm({
|
|
1147
|
-
"src/aggregate/groupby.ts"() {
|
|
1148
|
-
"use strict";
|
|
1149
|
-
init_predicate();
|
|
1150
|
-
init_canonical_key();
|
|
1151
|
-
init_errors();
|
|
1152
|
-
init_money_reducer();
|
|
1153
|
-
GROUPBY_WARN_CARDINALITY = 1e4;
|
|
1154
|
-
GROUPBY_MAX_CARDINALITY = 1e5;
|
|
1155
|
-
warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
// src/materialized-views/executor.ts
|
|
1160
|
-
var executor_exports = {};
|
|
1161
|
-
__export(executor_exports, {
|
|
1162
|
-
MaterializedViewExecutor: () => MaterializedViewExecutor
|
|
1163
|
-
});
|
|
1164
|
-
async function materializeQueryResult(q, mvName, i18nLocale, i18nFields) {
|
|
1165
|
-
if (typeof q?.toArray === "function") {
|
|
1166
|
-
return await q.toArray();
|
|
1167
|
-
}
|
|
1168
|
-
if (typeof q?.run === "function") {
|
|
1169
|
-
const runOpts = i18nLocale !== void 0 ? { locale: i18nLocale, i18nFields } : void 0;
|
|
1170
|
-
const result = await Promise.resolve(q.run(runOpts));
|
|
1171
|
-
if (Array.isArray(result)) {
|
|
1172
|
-
return result;
|
|
1173
|
-
}
|
|
1174
|
-
return [result];
|
|
1175
|
-
}
|
|
1176
|
-
throw new Error(
|
|
1177
|
-
`MV "${mvName}": query() must return a Query<T>, Aggregation, or GroupedAggregation. Got something without a .toArray() or .run() terminal.`
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
async function materializeUnionResult(spec, db) {
|
|
1181
|
-
const unified = [];
|
|
1182
|
-
for (const arm of spec.unionSources) {
|
|
1183
|
-
const coll = db.collection(arm.collection);
|
|
1184
|
-
let q = coll.query();
|
|
1185
|
-
if (arm.join?.length) {
|
|
1186
|
-
for (const leg of arm.join) {
|
|
1187
|
-
q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy });
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
const sourceRows = q.toArray();
|
|
1191
|
-
for (const r of sourceRows) {
|
|
1192
|
-
const mapped = arm.map(r);
|
|
1193
|
-
if (mapped == null) continue;
|
|
1194
|
-
unified.push(mapped);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
if (!spec.groupBy) return unified;
|
|
1198
|
-
const groupFields = typeof spec.groupBy === "string" ? [spec.groupBy] : spec.groupBy;
|
|
1199
|
-
if (spec.i18nLocale !== void 0 && spec.i18nFields !== void 0) {
|
|
1200
|
-
const groupI18n = {};
|
|
1201
|
-
for (const f of groupFields) {
|
|
1202
|
-
const d = spec.i18nFields[f];
|
|
1203
|
-
if (d !== void 0) groupI18n[f] = d;
|
|
1204
|
-
}
|
|
1205
|
-
if (Object.keys(groupI18n).length > 0) {
|
|
1206
|
-
for (let i = 0; i < unified.length; i++) {
|
|
1207
|
-
unified[i] = applyI18nLocale(unified[i], groupI18n, spec.i18nLocale, void 0, "mv");
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
for (const f of groupFields) {
|
|
1212
|
-
for (const row of unified) {
|
|
1213
|
-
const v = row[f];
|
|
1214
|
-
if (v !== null && typeof v === "object") {
|
|
1215
|
-
throw new LocaleNotSpecifiedError(
|
|
1216
|
-
f,
|
|
1217
|
-
`Materialized view "${spec.name}" groups by "${f}", whose value is a raw i18n locale map \u2014 an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
if (!spec.aggregate) {
|
|
1223
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1224
|
-
for (const row of unified) {
|
|
1225
|
-
const k = canonicalGroupKey(groupFields, row);
|
|
1226
|
-
if (!seen.has(k)) seen.set(k, row);
|
|
1227
|
-
}
|
|
1228
|
-
return [...seen.values()];
|
|
1229
|
-
}
|
|
1230
|
-
return groupAndReduce(unified, groupFields, spec.aggregate, spec.moneyFields);
|
|
1231
|
-
}
|
|
1232
|
-
async function listOutputIds(outputColl) {
|
|
1233
|
-
const cAny = outputColl;
|
|
1234
|
-
const adapter = cAny.adapter;
|
|
1235
|
-
const vault = cAny.vault;
|
|
1236
|
-
const name = cAny.name;
|
|
1237
|
-
if (typeof adapter?.list !== "function") return [];
|
|
1238
|
-
try {
|
|
1239
|
-
const ids = await adapter.list(vault, name);
|
|
1240
|
-
return [...ids];
|
|
1241
|
-
} catch {
|
|
1242
|
-
return [];
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
var DEFAULT_MAX_ROWS, MaterializedViewExecutor;
|
|
1246
|
-
var init_executor = __esm({
|
|
1247
|
-
"src/materialized-views/executor.ts"() {
|
|
1248
|
-
"use strict";
|
|
1249
|
-
init_errors();
|
|
1250
|
-
init_registry();
|
|
1251
|
-
init_groupby();
|
|
1252
|
-
init_canonical_key();
|
|
1253
|
-
init_core();
|
|
1254
|
-
DEFAULT_MAX_ROWS = 1e5;
|
|
1255
|
-
MaterializedViewExecutor = {
|
|
1256
|
-
async refresh(reg, accessor) {
|
|
1257
|
-
const spec = reg.spec;
|
|
1258
|
-
const outputColl = accessor.getCollection(reg.outputCollection);
|
|
1259
|
-
const maxRows = spec.maxRows ?? DEFAULT_MAX_ROWS;
|
|
1260
|
-
const onEmpty = spec.onEmpty ?? "delete";
|
|
1261
|
-
const strict = spec.strict ?? false;
|
|
1262
|
-
const baseCtx = accessor.getQueryContext();
|
|
1263
|
-
const ctxForQuery = spec.predicates ? wrapDbWithPredicates(baseCtx, spec.predicates) : baseCtx;
|
|
1264
|
-
let rows;
|
|
1265
|
-
if (spec.unionSources) {
|
|
1266
|
-
rows = await materializeUnionResult(spec, ctxForQuery);
|
|
1267
|
-
} else {
|
|
1268
|
-
const q = spec.query(ctxForQuery);
|
|
1269
|
-
rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields);
|
|
1270
|
-
}
|
|
1271
|
-
if (rows.length > maxRows) {
|
|
1272
|
-
throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows);
|
|
1273
|
-
}
|
|
1274
|
-
const txCtx = accessor.getActiveTxContext();
|
|
1275
|
-
const adapter = outputColl.adapter;
|
|
1276
|
-
const vaultName = outputColl.vault;
|
|
1277
|
-
const newIds = /* @__PURE__ */ new Set();
|
|
1278
|
-
const enrichedRows = [];
|
|
1279
|
-
for (const row of rows) {
|
|
1280
|
-
const id = spec.rowKey(row);
|
|
1281
|
-
newIds.add(id);
|
|
1282
|
-
const meta = {
|
|
1283
|
-
mvName: spec.name,
|
|
1284
|
-
queryHash: reg.queryHash,
|
|
1285
|
-
sourceVersions: {},
|
|
1286
|
-
materializedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1287
|
-
};
|
|
1288
|
-
enrichedRows.push({ id, record: { ...row, _materializedFrom: meta } });
|
|
1289
|
-
}
|
|
1290
|
-
let written = 0;
|
|
1291
|
-
let failed = 0;
|
|
1292
|
-
for (const { id, record } of enrichedRows) {
|
|
1293
|
-
try {
|
|
1294
|
-
if (txCtx !== null) {
|
|
1295
|
-
const prior = await adapter.get(vaultName, reg.outputCollection, id);
|
|
1296
|
-
txCtx._executed.push({
|
|
1297
|
-
op: { type: "put", vaultName, collectionName: reg.outputCollection, id },
|
|
1298
|
-
priorEnvelope: prior
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
await outputColl.put(id, record);
|
|
1302
|
-
written++;
|
|
1303
|
-
} catch (err) {
|
|
1304
|
-
failed++;
|
|
1305
|
-
if (strict) throw err;
|
|
1306
|
-
console.warn(`[mv] "${spec.name}" row write failed:`, err);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
let deleted = 0;
|
|
1310
|
-
if (onEmpty === "delete") {
|
|
1311
|
-
const priorIds = await listOutputIds(outputColl);
|
|
1312
|
-
for (const priorId of priorIds) {
|
|
1313
|
-
if (newIds.has(priorId)) continue;
|
|
1314
|
-
try {
|
|
1315
|
-
const outAny = outputColl;
|
|
1316
|
-
if (typeof outAny._internalDelete === "function") {
|
|
1317
|
-
await outAny._internalDelete(priorId, txCtx);
|
|
1318
|
-
deleted++;
|
|
1319
|
-
} else {
|
|
1320
|
-
await outputColl.delete(priorId);
|
|
1321
|
-
deleted++;
|
|
1322
|
-
}
|
|
1323
|
-
} catch (err) {
|
|
1324
|
-
failed++;
|
|
1325
|
-
if (strict) throw err;
|
|
1326
|
-
console.warn(`[mv] "${spec.name}" tombstone failed for id="${priorId}":`, err);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
return { written, deleted, failed };
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
// src/materialized-views/index.ts
|
|
1337
|
-
var materialized_views_exports = {};
|
|
1338
|
-
__export(materialized_views_exports, {
|
|
1339
|
-
MaterializedViewConfigError: () => MaterializedViewConfigError,
|
|
1340
|
-
MaterializedViewCycleError: () => MaterializedViewCycleError,
|
|
1341
|
-
MaterializedViewExecutor: () => MaterializedViewExecutor,
|
|
1342
|
-
MaterializedViewRegistry: () => MaterializedViewRegistry,
|
|
1343
|
-
MaterializedViewSourceUnknownError: () => MaterializedViewSourceUnknownError,
|
|
1344
|
-
MaterializedViewTooLargeError: () => MaterializedViewTooLargeError,
|
|
1345
|
-
analyzeDependencies: () => analyzeDependencies,
|
|
1346
|
-
canonicalizeQueryPlan: () => canonicalizeQueryPlan,
|
|
1347
|
-
clearMVStale: () => clearMVStale,
|
|
1348
|
-
computeQueryHash: () => computeQueryHash,
|
|
1349
|
-
isMVStale: () => isMVStale,
|
|
1350
|
-
markMVStale: () => markMVStale,
|
|
1351
|
-
resolveStaleMVOnRead: () => resolveStaleMVOnRead,
|
|
1352
|
-
summarizeQueryPlan: () => summarizeQueryPlan,
|
|
1353
|
-
withMaterializedView: () => withMaterializedView
|
|
1354
|
-
});
|
|
1355
|
-
module.exports = __toCommonJS(materialized_views_exports);
|
|
1356
|
-
|
|
1357
|
-
// src/materialized-views/with-materialized-view.ts
|
|
1358
|
-
init_errors();
|
|
1359
|
-
function withMaterializedView(spec) {
|
|
1360
|
-
if (!spec.name || spec.name.length === 0) {
|
|
1361
|
-
throw new ValidationError("withMaterializedView: name is required");
|
|
1362
|
-
}
|
|
1363
|
-
if (spec.query && spec.unionSources) {
|
|
1364
|
-
throw new MaterializedViewConfigError(
|
|
1365
|
-
"query and unionSources are mutually exclusive \u2014 pick one"
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
if (!spec.query && !spec.unionSources) {
|
|
1369
|
-
throw new MaterializedViewConfigError(
|
|
1370
|
-
"strategy must declare either query or unionSources"
|
|
1371
|
-
);
|
|
1372
|
-
}
|
|
1373
|
-
if (spec.query !== void 0 && typeof spec.query !== "function") {
|
|
1374
|
-
throw new ValidationError("withMaterializedView: query must be a function returning a Query<T>");
|
|
1375
|
-
}
|
|
1376
|
-
if (spec.unionSources) {
|
|
1377
|
-
if (spec.unionSources.length < 1) {
|
|
1378
|
-
throw new MaterializedViewConfigError(
|
|
1379
|
-
"unionSources requires at least 1 source collection"
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1383
|
-
for (const s of spec.unionSources) {
|
|
1384
|
-
if (typeof s?.collection !== "string" || s.collection.length === 0) {
|
|
1385
|
-
throw new MaterializedViewConfigError(
|
|
1386
|
-
"each unionSources entry must declare a non-empty `collection` string"
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
if (typeof s.map !== "function") {
|
|
1390
|
-
throw new MaterializedViewConfigError(
|
|
1391
|
-
`unionSources entry for "${s.collection}" is missing a \`map\` function`
|
|
1392
|
-
);
|
|
1393
|
-
}
|
|
1394
|
-
if (seen.has(s.collection)) {
|
|
1395
|
-
throw new MaterializedViewConfigError(
|
|
1396
|
-
`unionSources must reference distinct collections (duplicate: "${s.collection}")`
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
seen.add(s.collection);
|
|
1400
|
-
}
|
|
1401
|
-
if (Array.isArray(spec.groupBy) && spec.groupBy.length === 0) {
|
|
1402
|
-
throw new MaterializedViewConfigError(
|
|
1403
|
-
`withMaterializedView "${spec.name}": groupBy must not be an empty array \u2014 omit it or provide at least one field name`
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
if (spec.aggregate && !spec.groupBy) {
|
|
1407
|
-
throw new MaterializedViewConfigError(
|
|
1408
|
-
`withMaterializedView "${spec.name}": UNION strategy with aggregate requires groupBy \u2014 use groupBy to declare the bucketing keys, or remove aggregate for a pure dedup MV`
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
if (spec.moneyFields && !spec.aggregate) {
|
|
1412
|
-
throw new MaterializedViewConfigError(
|
|
1413
|
-
`withMaterializedView "${spec.name}": moneyFields requires aggregate \u2014 moneyFields rewrites sum/min/max reducers over money output fields, so it is meaningless without an aggregate spec`
|
|
1414
|
-
);
|
|
1415
|
-
}
|
|
1416
|
-
if (spec.unionSources.some((s) => s.join && s.join.length > 0) && (!spec.sources || spec.sources.length === 0)) {
|
|
1417
|
-
throw new MaterializedViewConfigError(
|
|
1418
|
-
`withMaterializedView "${spec.name}": a unionSources arm declares join(s) but no \`sources\` are listed \u2014 declare sources: [...] with the right-side (join-target) collection names so writes to them trigger MV refresh`
|
|
1419
|
-
);
|
|
1420
|
-
}
|
|
1421
|
-
if (spec.predicates) {
|
|
1422
|
-
throw new MaterializedViewConfigError(
|
|
1423
|
-
`withMaterializedView "${spec.name}": predicates are not supported on UNION strategies \u2014 UNION mode does not use a Query<T> chain, so .wherePredicate() cannot fire. Use the query() form, or open an issue if per-arm predicates are needed`
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
if (spec.i18nLocale !== void 0 && spec.i18nFields === void 0) {
|
|
1428
|
-
throw new MaterializedViewConfigError(
|
|
1429
|
-
`withMaterializedView "${spec.name}": i18nLocale requires i18nFields \u2014 declare the i18nText descriptors of the group-key fields so they can be resolved at the mv layer before bucketing.`
|
|
1430
|
-
);
|
|
1431
|
-
}
|
|
1432
|
-
if (typeof spec.rowKey !== "function") {
|
|
1433
|
-
throw new ValidationError("withMaterializedView: rowKey is required (no default; see spec \xA7 Type surface)");
|
|
1434
|
-
}
|
|
1435
|
-
if (spec.refresh !== "eager" && spec.refresh !== "lazy" && spec.refresh !== "manual") {
|
|
1436
|
-
throw new ValidationError(
|
|
1437
|
-
`withMaterializedView: refresh must be 'eager' | 'lazy' | 'manual', got "${String(spec.refresh)}"`
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
return {
|
|
1441
|
-
__noydb_strategy: "materialized-view",
|
|
1442
|
-
spec
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// src/materialized-views/index.ts
|
|
1447
|
-
init_registry();
|
|
1448
|
-
init_executor();
|
|
1449
|
-
init_dependency_analyzer();
|
|
1450
|
-
init_query_hash();
|
|
1451
|
-
|
|
1452
|
-
// src/materialized-views/stale.ts
|
|
1453
|
-
var _staleByRegistry = /* @__PURE__ */ new WeakMap();
|
|
1454
|
-
function markMVStale(registry, mvName) {
|
|
1455
|
-
let set = _staleByRegistry.get(registry);
|
|
1456
|
-
if (!set) {
|
|
1457
|
-
set = /* @__PURE__ */ new Set();
|
|
1458
|
-
_staleByRegistry.set(registry, set);
|
|
1459
|
-
}
|
|
1460
|
-
set.add(mvName);
|
|
1461
|
-
}
|
|
1462
|
-
function isMVStale(registry, mvName) {
|
|
1463
|
-
return _staleByRegistry.get(registry)?.has(mvName) ?? false;
|
|
1464
|
-
}
|
|
1465
|
-
async function resolveStaleMVOnRead(accessor, outputCollection) {
|
|
1466
|
-
const registry = accessor.registry();
|
|
1467
|
-
const pending = _staleByRegistry.get(registry);
|
|
1468
|
-
if (!pending || pending.size === 0) return;
|
|
1469
|
-
const candidates = [];
|
|
1470
|
-
for (const mv of registry.all()) {
|
|
1471
|
-
if (mv.outputCollection !== outputCollection) continue;
|
|
1472
|
-
if (!pending.has(mv.spec.name)) continue;
|
|
1473
|
-
candidates.push(mv.spec.name);
|
|
1474
|
-
}
|
|
1475
|
-
if (candidates.length === 0) return;
|
|
1476
|
-
let executor = null;
|
|
1477
|
-
for (const name of candidates) {
|
|
1478
|
-
const reg = registry.byName(name);
|
|
1479
|
-
if (!reg) {
|
|
1480
|
-
pending.delete(name);
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
if (executor === null) {
|
|
1484
|
-
({ MaterializedViewExecutor: executor } = await Promise.resolve().then(() => (init_executor(), executor_exports)));
|
|
1485
|
-
}
|
|
1486
|
-
await executor.refresh(reg, {
|
|
1487
|
-
getCollection: (n) => accessor.getCollection(n),
|
|
1488
|
-
getActiveTxContext: () => accessor.getActiveTxContext(),
|
|
1489
|
-
getQueryContext: () => accessor.getQueryContext()
|
|
1490
|
-
});
|
|
1491
|
-
pending.delete(name);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
function clearMVStale(registry, mvName) {
|
|
1495
|
-
_staleByRegistry.get(registry)?.delete(mvName);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
// src/materialized-views/index.ts
|
|
1499
|
-
init_errors();
|
|
1500
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1501
|
-
0 && (module.exports = {
|
|
1502
|
-
MaterializedViewConfigError,
|
|
1503
|
-
MaterializedViewCycleError,
|
|
1504
|
-
MaterializedViewExecutor,
|
|
1505
|
-
MaterializedViewRegistry,
|
|
1506
|
-
MaterializedViewSourceUnknownError,
|
|
1507
|
-
MaterializedViewTooLargeError,
|
|
1508
|
-
analyzeDependencies,
|
|
1509
|
-
canonicalizeQueryPlan,
|
|
1510
|
-
clearMVStale,
|
|
1511
|
-
computeQueryHash,
|
|
1512
|
-
isMVStale,
|
|
1513
|
-
markMVStale,
|
|
1514
|
-
resolveStaleMVOnRead,
|
|
1515
|
-
summarizeQueryPlan,
|
|
1516
|
-
withMaterializedView
|
|
1517
|
-
});
|
|
1518
|
-
//# sourceMappingURL=index.cjs.map
|