@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.
Files changed (143) hide show
  1. package/dist/adapter/index.d.ts +9 -0
  2. package/dist/adapter/index.js +14 -0
  3. package/dist/attestation/index.d.ts +1 -1
  4. package/dist/attestation/index.js +3 -3
  5. package/dist/blobs/index.d.ts +3 -3
  6. package/dist/bundle/index.d.ts +3 -3
  7. package/dist/bundle/index.js +1 -1
  8. package/dist/{chunk-Y6YUWZTS.js → chunk-L5HHBOMS.js} +2 -2
  9. package/dist/{chunk-BVKLJBJJ.js → chunk-W5NVNJDX.js} +2 -1
  10. package/dist/{chunk-BVKLJBJJ.js.map → chunk-W5NVNJDX.js.map} +1 -1
  11. package/dist/consent/index.d.ts +2 -2
  12. package/dist/{decrypt-partition-Mhjh6nnG.d.ts → decrypt-partition-CXS5KopB.d.ts} +1 -1
  13. package/dist/derivations/index.d.ts +3 -3
  14. package/dist/{dev-unlock-DJ3IhgY9.d.ts → dev-unlock-CnHTTVea.d.ts} +1 -1
  15. package/dist/guards/index.d.ts +3 -3
  16. package/dist/{hash-DLwkYf1d.d.ts → hash-oc5paYl0.d.ts} +1 -1
  17. package/dist/history/index.d.ts +3 -3
  18. package/dist/i18n/index.d.ts +2 -2
  19. package/dist/{index-CMRIx_zx.d.ts → index-BmhGztUi.d.ts} +1 -1
  20. package/dist/index.d.ts +11 -11
  21. package/dist/index.js +2 -2
  22. package/dist/kernel/index.d.ts +2 -2
  23. package/dist/materialized-views/index.d.ts +3 -3
  24. package/dist/{mime-magic-CacDBwYp.d.ts → mime-magic-JeLKHRng.d.ts} +1 -1
  25. package/dist/{noydb-5MIBD77B.js → noydb-NAU2DTFP.js} +2 -2
  26. package/dist/noydb-NAU2DTFP.js.map +1 -0
  27. package/dist/overlay-views/index.d.ts +3 -3
  28. package/dist/periods/index.d.ts +2 -2
  29. package/dist/session/index.d.ts +3 -3
  30. package/dist/shadow/index.d.ts +2 -2
  31. package/dist/snapshots/index.d.ts +2 -2
  32. package/dist/store/index.d.ts +2 -2
  33. package/dist/sync/index.d.ts +1 -1
  34. package/dist/team/index.d.ts +2 -2
  35. package/dist/{transition-guard-J04-jime.d.ts → transition-guard-Dy3NioQr.d.ts} +1 -1
  36. package/dist/tx/index.d.ts +2 -2
  37. package/dist/{with-materialized-view-DNfzC5lH.d.ts → with-materialized-view-DIVeez0W.d.ts} +1 -1
  38. package/dist/{with-overlayed-view-qk3lbhOd.d.ts → with-overlayed-view-DVZrc5Hb.d.ts} +1 -1
  39. package/dist/{with-rollup-Dd3_ZG4b.d.ts → with-rollup-CbU-O4wP.d.ts} +1 -1
  40. package/package.json +62 -221
  41. package/dist/aggregate/index.cjs +0 -1144
  42. package/dist/aggregate/index.cjs.map +0 -1
  43. package/dist/aggregate/index.d.cts +0 -39
  44. package/dist/attestation/index.cjs +0 -305
  45. package/dist/attestation/index.cjs.map +0 -1
  46. package/dist/attestation/index.d.cts +0 -54
  47. package/dist/blobs/index.cjs +0 -1967
  48. package/dist/blobs/index.cjs.map +0 -1
  49. package/dist/blobs/index.d.cts +0 -48
  50. package/dist/bundle/index.cjs +0 -41045
  51. package/dist/bundle/index.cjs.map +0 -1
  52. package/dist/bundle/index.d.cts +0 -187
  53. package/dist/consent/index.cjs +0 -204
  54. package/dist/consent/index.cjs.map +0 -1
  55. package/dist/consent/index.d.cts +0 -27
  56. package/dist/crdt/index.cjs +0 -152
  57. package/dist/crdt/index.cjs.map +0 -1
  58. package/dist/crdt/index.d.cts +0 -30
  59. package/dist/decrypt-partition-C0iDpbSd.d.cts +0 -558
  60. package/dist/derivations/index.cjs +0 -469
  61. package/dist/derivations/index.cjs.map +0 -1
  62. package/dist/derivations/index.d.cts +0 -74
  63. package/dist/dev-unlock-0Z6yxfS7.d.cts +0 -263
  64. package/dist/discriminant-BN9REW3o.d.cts +0 -60
  65. package/dist/errors-BAWO5Z5a.d.cts +0 -1500
  66. package/dist/forget/index.cjs +0 -43
  67. package/dist/forget/index.cjs.map +0 -1
  68. package/dist/forget/index.d.cts +0 -1
  69. package/dist/guards/index.cjs +0 -455
  70. package/dist/guards/index.cjs.map +0 -1
  71. package/dist/guards/index.d.cts +0 -39
  72. package/dist/hash-Db8ncXGr.d.cts +0 -63
  73. package/dist/history/index.cjs +0 -1245
  74. package/dist/history/index.cjs.map +0 -1
  75. package/dist/history/index.d.cts +0 -65
  76. package/dist/i18n/index.cjs +0 -1280
  77. package/dist/i18n/index.cjs.map +0 -1
  78. package/dist/i18n/index.d.cts +0 -41
  79. package/dist/index-BMmajblo.d.cts +0 -362
  80. package/dist/index-C1SC1EPe.d.cts +0 -1325
  81. package/dist/index-xJEPkvMb.d.cts +0 -93
  82. package/dist/index.cjs +0 -48100
  83. package/dist/index.cjs.map +0 -1
  84. package/dist/index.d.cts +0 -1050
  85. package/dist/indexing/index.cjs +0 -803
  86. package/dist/indexing/index.cjs.map +0 -1
  87. package/dist/indexing/index.d.cts +0 -36
  88. package/dist/kernel/index.cjs +0 -751
  89. package/dist/kernel/index.cjs.map +0 -1
  90. package/dist/kernel/index.d.cts +0 -11
  91. package/dist/lazy-builder-eYZzLEL1.d.cts +0 -304
  92. package/dist/materialized-views/index.cjs +0 -1518
  93. package/dist/materialized-views/index.cjs.map +0 -1
  94. package/dist/materialized-views/index.d.cts +0 -187
  95. package/dist/mime-magic-sdMzsfVK.d.cts +0 -103
  96. package/dist/overlay-views/index.cjs +0 -399
  97. package/dist/overlay-views/index.cjs.map +0 -1
  98. package/dist/overlay-views/index.d.cts +0 -102
  99. package/dist/periods/index.cjs +0 -1041
  100. package/dist/periods/index.cjs.map +0 -1
  101. package/dist/periods/index.d.cts +0 -24
  102. package/dist/predicate-BmhBSPCH.d.cts +0 -267
  103. package/dist/query/index.cjs +0 -3480
  104. package/dist/query/index.cjs.map +0 -1
  105. package/dist/query/index.d.cts +0 -4
  106. package/dist/sealed-record/index.cjs +0 -139
  107. package/dist/sealed-record/index.cjs.map +0 -1
  108. package/dist/sealed-record/index.d.cts +0 -123
  109. package/dist/session/index.cjs +0 -495
  110. package/dist/session/index.cjs.map +0 -1
  111. package/dist/session/index.d.cts +0 -48
  112. package/dist/shadow/index.cjs +0 -133
  113. package/dist/shadow/index.cjs.map +0 -1
  114. package/dist/shadow/index.d.cts +0 -19
  115. package/dist/snapshots/index.cjs +0 -937
  116. package/dist/snapshots/index.cjs.map +0 -1
  117. package/dist/snapshots/index.d.cts +0 -30
  118. package/dist/store/index.cjs +0 -1091
  119. package/dist/store/index.cjs.map +0 -1
  120. package/dist/store/index.d.cts +0 -501
  121. package/dist/strategy-BSxFXGzb.d.cts +0 -110
  122. package/dist/strategy-mJExw4LQ.d.cts +0 -1069
  123. package/dist/sync/index.cjs +0 -1062
  124. package/dist/sync/index.cjs.map +0 -1
  125. package/dist/sync/index.d.cts +0 -45
  126. package/dist/team/index.cjs +0 -2805
  127. package/dist/team/index.cjs.map +0 -1
  128. package/dist/team/index.d.cts +0 -120
  129. package/dist/transition-guard-tWZIsaXe.d.cts +0 -165
  130. package/dist/tx/index.cjs +0 -614
  131. package/dist/tx/index.cjs.map +0 -1
  132. package/dist/tx/index.d.cts +0 -39
  133. package/dist/types-CdVfiQgt.d.cts +0 -15275
  134. package/dist/ulid-DRH25k3y.d.cts +0 -66
  135. package/dist/util/index.cjs +0 -237
  136. package/dist/util/index.cjs.map +0 -1
  137. package/dist/util/index.d.cts +0 -79
  138. package/dist/with-materialized-view-DfgPcvi9.d.cts +0 -27
  139. package/dist/with-overlayed-view-Cvfkx1lg.d.cts +0 -13
  140. package/dist/with-rollup-nIxo7h9z.d.cts +0 -47
  141. /package/dist/{noydb-5MIBD77B.js.map → adapter/index.js.map} +0 -0
  142. /package/dist/{chunk-Y6YUWZTS.js.map → chunk-L5HHBOMS.js.map} +0 -0
  143. /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