@klum-db/lobby 0.3.0-pre.1 → 0.4.0-pre.1
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/README.md +7 -4
- package/dist/bin/klum.d.cts +1 -2
- package/dist/bin/klum.d.ts +1 -2
- package/dist/index.cjs +662 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +325 -9
- package/dist/index.d.ts +325 -9
- package/dist/index.js +588 -113
- package/dist/index.js.map +1 -1
- package/dist/{vault-group-D0EC-D5c.d.cts → vault-group-DJtzjwZK.d.cts} +23 -3
- package/dist/{vault-group-D0EC-D5c.d.ts → vault-group-DJtzjwZK.d.ts} +23 -3
- package/package.json +16 -11
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,396 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/portal/share-link.ts
|
|
34
|
+
var share_link_exports = {};
|
|
35
|
+
__export(share_link_exports, {
|
|
36
|
+
ShareLinkResolutionError: () => ShareLinkResolutionError,
|
|
37
|
+
buildShareLinkForClient: () => buildShareLinkForClient,
|
|
38
|
+
resolveShareLinkAgainstRegistry: () => resolveShareLinkAgainstRegistry
|
|
39
|
+
});
|
|
40
|
+
async function registryRows(registry) {
|
|
41
|
+
await registry.list();
|
|
42
|
+
return registry.query().toArray();
|
|
43
|
+
}
|
|
44
|
+
async function resolveShareLinkAgainstRegistry(link, registry) {
|
|
45
|
+
const parsed = typeof link === "string" || link instanceof URL ? (0, import_share_link.parseShareLink)(link) : link;
|
|
46
|
+
const rows = await registryRows(registry);
|
|
47
|
+
const row = rows.find((r) => r.handle === parsed.vaultHandle);
|
|
48
|
+
if (!row) {
|
|
49
|
+
throw new ShareLinkResolutionError(
|
|
50
|
+
"UNKNOWN_VAULT_HANDLE",
|
|
51
|
+
`share link addresses vault handle "${parsed.vaultHandle}", which is not registered in this fleet.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
link: parsed,
|
|
56
|
+
row,
|
|
57
|
+
consoleRoute: {
|
|
58
|
+
vaultId: row.vaultId,
|
|
59
|
+
collection: parsed.collection,
|
|
60
|
+
recordId: parsed.recordId,
|
|
61
|
+
...parsed.period !== void 0 ? { period: parsed.period } : {},
|
|
62
|
+
...parsed.version !== void 0 ? { version: parsed.version } : {}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function buildShareLinkForClient(opts, registry) {
|
|
67
|
+
const rows = await registryRows(registry);
|
|
68
|
+
const row = rows.find((r) => r.partitionKey === opts.client && r.group === opts.group);
|
|
69
|
+
if (!row) {
|
|
70
|
+
throw new ShareLinkResolutionError(
|
|
71
|
+
"UNKNOWN_CLIENT",
|
|
72
|
+
`client "${opts.client}" is not registered in group "${opts.group}".`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (row.handle === void 0) {
|
|
76
|
+
throw new ShareLinkResolutionError(
|
|
77
|
+
"NO_PORTAL_HANDLE",
|
|
78
|
+
`client "${opts.client}" has no portal vault handle \u2014 provision the portal before minting share links.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return (0, import_share_link.buildShareLink)(
|
|
82
|
+
{
|
|
83
|
+
vaultHandle: row.handle,
|
|
84
|
+
collection: opts.collection,
|
|
85
|
+
recordId: opts.record,
|
|
86
|
+
...opts.period !== void 0 ? { period: opts.period } : {},
|
|
87
|
+
...opts.version !== void 0 ? { version: opts.version } : {},
|
|
88
|
+
...opts.grantToken !== void 0 ? { grantToken: opts.grantToken } : {}
|
|
89
|
+
},
|
|
90
|
+
opts.base
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
var import_share_link, ShareLinkResolutionError;
|
|
94
|
+
var init_share_link = __esm({
|
|
95
|
+
"src/portal/share-link.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
import_share_link = require("@noy-db/hub/share-link");
|
|
98
|
+
ShareLinkResolutionError = class extends Error {
|
|
99
|
+
constructor(code, message) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.code = code;
|
|
102
|
+
}
|
|
103
|
+
code;
|
|
104
|
+
name = "ShareLinkResolutionError";
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// src/federation/insight-auto-push.ts
|
|
110
|
+
var InsightAutoPush;
|
|
111
|
+
var init_insight_auto_push = __esm({
|
|
112
|
+
"src/federation/insight-auto-push.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
InsightAutoPush = class {
|
|
115
|
+
constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err), debounceMs) {
|
|
116
|
+
this.recompute = recompute;
|
|
117
|
+
this.isSource = isSource;
|
|
118
|
+
this.onError = onError;
|
|
119
|
+
this.debounceMs = debounceMs;
|
|
120
|
+
}
|
|
121
|
+
recompute;
|
|
122
|
+
isSource;
|
|
123
|
+
onError;
|
|
124
|
+
debounceMs;
|
|
125
|
+
dirty = /* @__PURE__ */ new Set();
|
|
126
|
+
pending = null;
|
|
127
|
+
timer = null;
|
|
128
|
+
resolvePending = null;
|
|
129
|
+
flushing = false;
|
|
130
|
+
/** Called from a shard's onAfterWrite hook. Marks the shard dirty + schedules a flush. */
|
|
131
|
+
noteWrite(partitionKey, collection) {
|
|
132
|
+
if (!this.isSource(collection)) return;
|
|
133
|
+
this.dirty.add(partitionKey);
|
|
134
|
+
if (this.debounceMs !== void 0) this.scheduleDebounced();
|
|
135
|
+
else this.schedule();
|
|
136
|
+
}
|
|
137
|
+
/** Resolve once no flush is pending (drains rescheduled flushes too). */
|
|
138
|
+
async whenSettled() {
|
|
139
|
+
while (this.pending) await this.pending;
|
|
140
|
+
}
|
|
141
|
+
// ── microtask path (unchanged default) ──
|
|
142
|
+
schedule() {
|
|
143
|
+
if (this.pending) return;
|
|
144
|
+
this.pending = this.runFlush().finally(() => {
|
|
145
|
+
this.pending = null;
|
|
146
|
+
if (this.dirty.size > 0) this.schedule();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// ── debounce path (#13) ──
|
|
150
|
+
scheduleDebounced() {
|
|
151
|
+
if (!this.pending) this.pending = new Promise((r) => {
|
|
152
|
+
this.resolvePending = r;
|
|
153
|
+
});
|
|
154
|
+
if (this.timer) clearTimeout(this.timer);
|
|
155
|
+
this.timer = setTimeout(() => {
|
|
156
|
+
this.timer = null;
|
|
157
|
+
if (this.flushing) return;
|
|
158
|
+
this.flushing = true;
|
|
159
|
+
void this.runFlush().finally(() => {
|
|
160
|
+
this.flushing = false;
|
|
161
|
+
if (this.dirty.size > 0) {
|
|
162
|
+
this.scheduleDebounced();
|
|
163
|
+
} else {
|
|
164
|
+
const resolve = this.resolvePending;
|
|
165
|
+
this.resolvePending = null;
|
|
166
|
+
this.pending = null;
|
|
167
|
+
resolve?.();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}, this.debounceMs);
|
|
171
|
+
}
|
|
172
|
+
async runFlush() {
|
|
173
|
+
await Promise.resolve();
|
|
174
|
+
const pks = [...this.dirty];
|
|
175
|
+
this.dirty.clear();
|
|
176
|
+
for (const pk of pks) {
|
|
177
|
+
try {
|
|
178
|
+
await this.recompute(pk);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
this.onError(err, pk);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// src/federation/read-model.ts
|
|
189
|
+
var read_model_exports = {};
|
|
190
|
+
__export(read_model_exports, {
|
|
191
|
+
PostureViolationError: () => PostureViolationError,
|
|
192
|
+
ReadModel: () => ReadModel,
|
|
193
|
+
deriveShardSummary: () => deriveShardSummary,
|
|
194
|
+
openReadModel: () => openReadModel
|
|
195
|
+
});
|
|
196
|
+
function deriveShardSummary(spec, records, row) {
|
|
197
|
+
const ctx = {
|
|
198
|
+
vaultId: row.vaultId,
|
|
199
|
+
partitionKey: row.partitionKey,
|
|
200
|
+
schemaVersion: row.schemaVersion
|
|
201
|
+
};
|
|
202
|
+
return spec.derive(records, ctx);
|
|
203
|
+
}
|
|
204
|
+
function checkPosture(model, row, shard) {
|
|
205
|
+
const declared = new Set(model.posture.surface);
|
|
206
|
+
const undeclared = Object.keys(row).filter((k) => !declared.has(k) && !PROVENANCE_FIELDS.has(k));
|
|
207
|
+
if (undeclared.length > 0) throw new PostureViolationError(model.name, undeclared, shard);
|
|
208
|
+
}
|
|
209
|
+
async function openReadModel(group, opts) {
|
|
210
|
+
const target = opts.vault;
|
|
211
|
+
if (target === group.name || target.startsWith(`${group.name}--`)) {
|
|
212
|
+
throw new import_cargo.ValidationError(
|
|
213
|
+
`openReadModel: vault "${target}" is the "${group.name}" group itself or one of its shards \u2014 the read-model must live outside the group it composes.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (opts.models.length === 0) {
|
|
217
|
+
throw new import_cargo.ValidationError("openReadModel: at least one model is required.");
|
|
218
|
+
}
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const m of opts.models) {
|
|
221
|
+
if (seen.has(m.name)) throw new import_cargo.ValidationError(`openReadModel: duplicate model name "${m.name}".`);
|
|
222
|
+
seen.add(m.name);
|
|
223
|
+
if (m.posture.surface.length === 0) {
|
|
224
|
+
throw new import_cargo.ValidationError(`openReadModel: model "${m.name}" declares an empty posture.surface.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const vault = await group.db.openVault(target);
|
|
228
|
+
const rm = new ReadModel(group, target, vault, opts.models, opts.shards);
|
|
229
|
+
const autoPush = opts.freshness?.autoPush;
|
|
230
|
+
if (autoPush) rm._armAutoPush(autoPush === true ? {} : autoPush);
|
|
231
|
+
return rm;
|
|
232
|
+
}
|
|
233
|
+
var import_cargo, PostureViolationError, PROVENANCE_FIELDS, ReadModel;
|
|
234
|
+
var init_read_model = __esm({
|
|
235
|
+
"src/federation/read-model.ts"() {
|
|
236
|
+
"use strict";
|
|
237
|
+
import_cargo = require("@noy-db/hub/cargo");
|
|
238
|
+
init_insight_auto_push();
|
|
239
|
+
PostureViolationError = class extends Error {
|
|
240
|
+
constructor(model, fields, shard) {
|
|
241
|
+
super(
|
|
242
|
+
`read-model "${model}": derive emitted undeclared field(s) [${fields.join(", ")}] for shard "${shard}" \u2014 every emitted field must be listed in posture.surface (fail closed; nothing was written).`
|
|
243
|
+
);
|
|
244
|
+
this.model = model;
|
|
245
|
+
this.fields = fields;
|
|
246
|
+
this.shard = shard;
|
|
247
|
+
}
|
|
248
|
+
model;
|
|
249
|
+
fields;
|
|
250
|
+
shard;
|
|
251
|
+
name = "PostureViolationError";
|
|
252
|
+
};
|
|
253
|
+
PROVENANCE_FIELDS = /* @__PURE__ */ new Set(["_shard", "_sourceId", "_sourceVersion"]);
|
|
254
|
+
ReadModel = class {
|
|
255
|
+
/** @internal */
|
|
256
|
+
constructor(group, vaultName, vault, models, shards) {
|
|
257
|
+
this.group = group;
|
|
258
|
+
this.vaultName = vaultName;
|
|
259
|
+
this.vault = vault;
|
|
260
|
+
this.models = models;
|
|
261
|
+
this.shards = shards;
|
|
262
|
+
}
|
|
263
|
+
group;
|
|
264
|
+
vaultName;
|
|
265
|
+
vault;
|
|
266
|
+
models;
|
|
267
|
+
shards;
|
|
268
|
+
/** @internal — auto-push controller; armed by {@link _armAutoPush}. */
|
|
269
|
+
autoPush;
|
|
270
|
+
/**
|
|
271
|
+
* @internal — wire the #13 controller (spec § 3). Trigger is the
|
|
272
|
+
* Noydb-level change stream filtered to this group's shard vaults
|
|
273
|
+
* (`<group>--<pk>`); the read-model vault can never match that prefix
|
|
274
|
+
* (guarded at open), so its own writes can't loop back. Recompute is
|
|
275
|
+
* a per-shard `refresh({ only })` — rollup re-reduces and mirror
|
|
276
|
+
* reconciles just that shard; other shards' rows are untouched
|
|
277
|
+
* (registered shards are preserved by the reconcile asymmetry).
|
|
278
|
+
*/
|
|
279
|
+
_armAutoPush(cfg) {
|
|
280
|
+
const controller = new InsightAutoPush(
|
|
281
|
+
async (partitionKey) => {
|
|
282
|
+
await this.refresh({
|
|
283
|
+
only: [partitionKey],
|
|
284
|
+
...cfg.minVersion !== void 0 ? { minVersion: cfg.minVersion } : {}
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
(collection) => this.models.some((m) => m.source === collection),
|
|
288
|
+
void 0,
|
|
289
|
+
cfg.debounceMs
|
|
290
|
+
);
|
|
291
|
+
this.autoPush = controller;
|
|
292
|
+
const prefix = `${this.group.name}--`;
|
|
293
|
+
this.group.db.on("change", (e) => {
|
|
294
|
+
if (!e.vault.startsWith(prefix)) return;
|
|
295
|
+
controller.noteWrite(e.vault.slice(prefix.length), e.collection);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Await any pending auto-push flush. Resolves immediately when
|
|
300
|
+
* `freshness.autoPush` is off or nothing is pending.
|
|
301
|
+
*/
|
|
302
|
+
async whenSettled() {
|
|
303
|
+
if (this.autoPush) await this.autoPush.whenSettled();
|
|
304
|
+
}
|
|
305
|
+
/** An output collection of the read-model vault (keyed by model `name`). */
|
|
306
|
+
collection(model) {
|
|
307
|
+
return this.vault.collection(model);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Explicit refresh: for each model, read every eligible shard's
|
|
311
|
+
* `source`, posture-check, and write — rollup: one summary row per
|
|
312
|
+
* shard (id = partitionKey); mirror: one row per source record
|
|
313
|
+
* (id = `${shard}:${idOf(row)}`). Every row is stamped with `_shard`
|
|
314
|
+
* + `_sourceVersion` (mirror rows additionally `_sourceId`).
|
|
315
|
+
*
|
|
316
|
+
* Reconciliation (spec § 4): rows whose source record is gone, and
|
|
317
|
+
* all rows of a shard that LEFT the group, are deleted (`retracted`).
|
|
318
|
+
* Rows of a merely-unreachable shard are PRESERVED — offline is not
|
|
319
|
+
* departure; those shards land in `skippedVaults` instead.
|
|
320
|
+
*/
|
|
321
|
+
async refresh(options = {}) {
|
|
322
|
+
const resolved = await this.group.resolveEligible({
|
|
323
|
+
...options.minVersion !== void 0 ? { minVersion: options.minVersion } : {},
|
|
324
|
+
...options.only !== void 0 ? { only: options.only } : {},
|
|
325
|
+
...options.failFast !== void 0 ? { failFast: options.failFast } : {}
|
|
326
|
+
});
|
|
327
|
+
const eligible = this.shards ? resolved.eligible.filter((r) => this.shards(r)) : resolved.eligible;
|
|
328
|
+
const skipped = resolved.skipped;
|
|
329
|
+
let written = 0;
|
|
330
|
+
let retracted = 0;
|
|
331
|
+
for (const model of this.models) {
|
|
332
|
+
const results = await this.group.db.queryAcross(
|
|
333
|
+
eligible.map((r) => r.vaultId),
|
|
334
|
+
async (vault) => {
|
|
335
|
+
this.group.template.configure(vault);
|
|
336
|
+
const coll = vault.collection(model.source);
|
|
337
|
+
const records = await coll.list();
|
|
338
|
+
if (model.kind === "rollup") return records.map((record) => ({ record, id: "", version: 0 }));
|
|
339
|
+
return Promise.all(records.map(async (record) => {
|
|
340
|
+
const id = model.idOf(record);
|
|
341
|
+
const meta = await coll.getMetadata(id);
|
|
342
|
+
return { record, id, version: meta?.version ?? 0 };
|
|
343
|
+
}));
|
|
344
|
+
},
|
|
345
|
+
{ create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
|
|
346
|
+
);
|
|
347
|
+
const out = this.vault.collection(model.name);
|
|
348
|
+
const expected = /* @__PURE__ */ new Set();
|
|
349
|
+
const healthyShards = /* @__PURE__ */ new Set();
|
|
350
|
+
for (let i = 0; i < eligible.length; i++) {
|
|
351
|
+
const row = eligible[i];
|
|
352
|
+
const res = results[i];
|
|
353
|
+
if (!res || res.result === void 0) {
|
|
354
|
+
if (options.failFast && res?.error) throw res.error;
|
|
355
|
+
if (!skipped.some((s) => s.vaultId === row.vaultId)) {
|
|
356
|
+
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
healthyShards.add(row.partitionKey);
|
|
361
|
+
if (model.kind === "rollup") {
|
|
362
|
+
const summary = deriveShardSummary(model, res.result.map((e) => e.record), row);
|
|
363
|
+
checkPosture(model, summary, row.partitionKey);
|
|
364
|
+
await out.put(row.partitionKey, {
|
|
365
|
+
...summary,
|
|
366
|
+
_shard: row.partitionKey,
|
|
367
|
+
_sourceVersion: row.schemaVersion
|
|
368
|
+
});
|
|
369
|
+
expected.add(row.partitionKey);
|
|
370
|
+
written++;
|
|
371
|
+
} else {
|
|
372
|
+
for (const entry of res.result) {
|
|
373
|
+
checkPosture(model, entry.record, row.partitionKey);
|
|
374
|
+
const id = `${row.partitionKey}:${entry.id}`;
|
|
375
|
+
await out.put(id, {
|
|
376
|
+
...entry.record,
|
|
377
|
+
_shard: row.partitionKey,
|
|
378
|
+
_sourceId: entry.id,
|
|
379
|
+
_sourceVersion: entry.version
|
|
380
|
+
});
|
|
381
|
+
expected.add(id);
|
|
382
|
+
written++;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
retracted += await this.reconcile(out, model, expected, healthyShards);
|
|
387
|
+
}
|
|
388
|
+
return { written, retracted, skippedVaults: skipped };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Delete stale rows: id not in `expected` AND the row's shard is
|
|
392
|
+
* healthy this refresh (source record deleted), no longer in the
|
|
393
|
+
* group registry (shard departed), or outside the audience predicate
|
|
394
|
+
* (scope narrowed — out-of-scope data never lingers, spec § 5).
|
|
395
|
+
* Unreachable-but-registered in-scope shards are preserved.
|
|
396
|
+
*/
|
|
397
|
+
async reconcile(out, model, expected, healthyShards) {
|
|
398
|
+
await this.group.registry.list();
|
|
399
|
+
const registryRows2 = new Map(
|
|
400
|
+
this.group.registry.query().toArray().filter((r) => r.group === this.group.name).map((r) => [r.partitionKey, r])
|
|
401
|
+
);
|
|
402
|
+
await out.list();
|
|
403
|
+
const rows = out.query().toArray();
|
|
404
|
+
let retracted = 0;
|
|
405
|
+
for (const row of rows) {
|
|
406
|
+
const shard = row["_shard"];
|
|
407
|
+
if (shard === void 0) continue;
|
|
408
|
+
const id = model.kind === "rollup" ? shard : `${shard}:${String(row["_sourceId"])}`;
|
|
409
|
+
if (expected.has(id)) continue;
|
|
410
|
+
const registryRow = registryRows2.get(shard);
|
|
411
|
+
const outOfScope = registryRow !== void 0 && this.shards !== void 0 && !this.shards(registryRow);
|
|
412
|
+
if (healthyShards.has(shard) || registryRow === void 0 || outOfScope) {
|
|
413
|
+
await out.delete(id);
|
|
414
|
+
retracted++;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return retracted;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
33
423
|
// src/bundle/uint32.ts
|
|
34
424
|
function readUint32BE(bytes, offset) {
|
|
35
425
|
return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
|
|
@@ -72,6 +462,11 @@ function encodeMultiBundle(manifest, inner) {
|
|
|
72
462
|
}
|
|
73
463
|
return out;
|
|
74
464
|
}
|
|
465
|
+
function hasPodMagic(bytes) {
|
|
466
|
+
if (bytes.length < import_pod.NOYDB_BUNDLE_MAGIC.length) return false;
|
|
467
|
+
for (let i = 0; i < import_pod.NOYDB_BUNDLE_MAGIC.length; i++) if (bytes[i] !== import_pod.NOYDB_BUNDLE_MAGIC[i]) return false;
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
75
470
|
function hasMultiMagic(bytes) {
|
|
76
471
|
if (bytes.length < NOYDB_MULTI_BUNDLE_MAGIC.length) return false;
|
|
77
472
|
for (let i = 0; i < NOYDB_MULTI_BUNDLE_MAGIC.length; i++) if (bytes[i] !== NOYDB_MULTI_BUNDLE_MAGIC[i]) return false;
|
|
@@ -135,7 +530,7 @@ async function writeMultiVaultBundle(compartments, opts = {}) {
|
|
|
135
530
|
handle: header.handle,
|
|
136
531
|
exportedAt: c.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
137
532
|
innerBytes: innerBytes.length,
|
|
138
|
-
innerSha256: await (0,
|
|
533
|
+
innerSha256: await (0, import_cargo2.sha256Hex)(innerBytes)
|
|
139
534
|
};
|
|
140
535
|
if (c.roleTag !== void 0) entry.roleTag = c.roleTag;
|
|
141
536
|
if (c.disclose?.name !== void 0 && c.disclose.name !== false) {
|
|
@@ -148,7 +543,7 @@ async function writeMultiVaultBundle(compartments, opts = {}) {
|
|
|
148
543
|
);
|
|
149
544
|
}
|
|
150
545
|
if (c.disclose?.publicEnvelope === true) {
|
|
151
|
-
const env = (0,
|
|
546
|
+
const env = (0, import_pod.readPodCover)(innerBytes);
|
|
152
547
|
if (env !== void 0) entry.publicEnvelope = env;
|
|
153
548
|
}
|
|
154
549
|
const fence = await c.vault.schemaFenceState();
|
|
@@ -158,20 +553,20 @@ async function writeMultiVaultBundle(compartments, opts = {}) {
|
|
|
158
553
|
}
|
|
159
554
|
const manifest = {
|
|
160
555
|
multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
|
|
161
|
-
handle: opts.handle ?? (0,
|
|
556
|
+
handle: opts.handle ?? (0, import_cargo2.generateULID)(),
|
|
162
557
|
compartments: entries
|
|
163
558
|
};
|
|
164
559
|
return encodeMultiBundle(manifest, inner);
|
|
165
560
|
}
|
|
166
561
|
async function readNoydbBundleManifest(bytes) {
|
|
167
562
|
if (hasMultiMagic(bytes)) return [...decodeMultiBundle(bytes).manifest.compartments];
|
|
168
|
-
if ((
|
|
563
|
+
if (hasPodMagic(bytes)) {
|
|
169
564
|
const header = (0, import_pod.readPodHeader)(bytes);
|
|
170
|
-
const env = (0,
|
|
565
|
+
const env = (0, import_pod.readPodCover)(bytes);
|
|
171
566
|
const entry = {
|
|
172
567
|
handle: header.handle,
|
|
173
568
|
innerBytes: bytes.length,
|
|
174
|
-
innerSha256: await (0,
|
|
569
|
+
innerSha256: await (0, import_cargo2.sha256Hex)(bytes)
|
|
175
570
|
};
|
|
176
571
|
if (env !== void 0) entry.publicEnvelope = env;
|
|
177
572
|
return [entry];
|
|
@@ -182,7 +577,7 @@ function readMultiVaultBundleCompartment(bytes, selector) {
|
|
|
182
577
|
if (typeof selector === "number" && !Number.isInteger(selector)) {
|
|
183
578
|
throw new Error(`readMultiVaultBundleCompartment: numeric selector must be an integer, got ${selector}.`);
|
|
184
579
|
}
|
|
185
|
-
if ((
|
|
580
|
+
if (hasPodMagic(bytes) && !hasMultiMagic(bytes)) {
|
|
186
581
|
const header = (0, import_pod.readPodHeader)(bytes);
|
|
187
582
|
if (selector === 0 || selector === header.handle) return bytes;
|
|
188
583
|
throw new Error(`readMultiVaultBundleCompartment: single v1 bundle has only compartment "${header.handle}".`);
|
|
@@ -192,13 +587,12 @@ function readMultiVaultBundleCompartment(bytes, selector) {
|
|
|
192
587
|
if (idx < 0 || idx >= inner.length) throw new Error(`readMultiVaultBundleCompartment: no compartment ${typeof selector === "number" ? `at index ${selector}` : `"${selector}"`}.`);
|
|
193
588
|
return inner[idx];
|
|
194
589
|
}
|
|
195
|
-
var
|
|
590
|
+
var import_cargo2, import_pod, NOYDB_MULTI_BUNDLE_MAGIC, NOYDB_MULTI_BUNDLE_PREFIX_BYTES, NOYDB_MULTI_BUNDLE_VERSION;
|
|
196
591
|
var init_multi_bundle = __esm({
|
|
197
592
|
"src/bundle/multi-bundle.ts"() {
|
|
198
593
|
"use strict";
|
|
199
|
-
|
|
594
|
+
import_cargo2 = require("@noy-db/hub/cargo");
|
|
200
595
|
import_pod = require("@noy-db/hub/pod");
|
|
201
|
-
import_hub = require("@noy-db/hub");
|
|
202
596
|
init_uint32();
|
|
203
597
|
NOYDB_MULTI_BUNDLE_MAGIC = new Uint8Array([78, 68, 66, 77]);
|
|
204
598
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES = 10;
|
|
@@ -271,7 +665,7 @@ async function walkCrossVaultClosure(openVault, opts) {
|
|
|
271
665
|
for (const vaultName of batch) {
|
|
272
666
|
const seeds = perVaultSeeds.get(vaultName);
|
|
273
667
|
const v = await openVault(vaultName);
|
|
274
|
-
const { closure } = await (0,
|
|
668
|
+
const { closure } = await (0, import_cargo3.walkClosure)(v, {
|
|
275
669
|
seeds,
|
|
276
670
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {}
|
|
277
671
|
});
|
|
@@ -323,7 +717,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
323
717
|
const sealIds = {};
|
|
324
718
|
for (const [vaultName, seeds] of plan.perVaultSeeds) {
|
|
325
719
|
const v = await openVault(vaultName);
|
|
326
|
-
const { bundleBytes, transferKey, sealId } = await (0,
|
|
720
|
+
const { bundleBytes, transferKey, sealId } = await (0, import_cargo3.extractPartition)(v, {
|
|
327
721
|
seeds,
|
|
328
722
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {},
|
|
329
723
|
carrySchemas: opts.carrySchemas ?? true,
|
|
@@ -336,7 +730,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
336
730
|
handle: header.handle,
|
|
337
731
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
338
732
|
innerBytes: bundleBytes.length,
|
|
339
|
-
innerSha256: await (0,
|
|
733
|
+
innerSha256: await (0, import_cargo4.sha256Hex)(bundleBytes)
|
|
340
734
|
};
|
|
341
735
|
if (meta?.roleTag !== void 0) entry.roleTag = meta.roleTag;
|
|
342
736
|
if (meta?.disclose?.name !== void 0 && meta.disclose.name !== false) {
|
|
@@ -355,7 +749,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
355
749
|
}
|
|
356
750
|
const manifest = {
|
|
357
751
|
multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
|
|
358
|
-
handle: (0,
|
|
752
|
+
handle: (0, import_cargo4.generateULID)(),
|
|
359
753
|
compartments
|
|
360
754
|
};
|
|
361
755
|
return { bundle: encodeMultiBundle(manifest, inner), transferKeys, sealIds };
|
|
@@ -365,7 +759,7 @@ async function describeCrossVaultExtraction(openVault, opts) {
|
|
|
365
759
|
const compartments = [];
|
|
366
760
|
for (const [vaultName, seeds] of plan.perVaultSeeds) {
|
|
367
761
|
const v = await openVault(vaultName);
|
|
368
|
-
const preview = await (0,
|
|
762
|
+
const preview = await (0, import_cargo3.describeExtraction)(v, {
|
|
369
763
|
seeds,
|
|
370
764
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {}
|
|
371
765
|
});
|
|
@@ -373,13 +767,13 @@ async function describeCrossVaultExtraction(openVault, opts) {
|
|
|
373
767
|
}
|
|
374
768
|
return { compartments, dangling: plan.dangling };
|
|
375
769
|
}
|
|
376
|
-
var
|
|
770
|
+
var import_cargo3, import_pod2, import_cargo4, CrossVaultDanglingRefError;
|
|
377
771
|
var init_extract_cross_vault = __esm({
|
|
378
772
|
"src/interchange/extract-cross-vault.ts"() {
|
|
379
773
|
"use strict";
|
|
380
|
-
|
|
774
|
+
import_cargo3 = require("@noy-db/hub/cargo");
|
|
381
775
|
import_pod2 = require("@noy-db/hub/pod");
|
|
382
|
-
|
|
776
|
+
import_cargo4 = require("@noy-db/hub/cargo");
|
|
383
777
|
init_multi_bundle();
|
|
384
778
|
CrossVaultDanglingRefError = class extends Error {
|
|
385
779
|
constructor(dangling) {
|
|
@@ -465,7 +859,7 @@ async function mergeDecryptedRecords(receiver, decrypted, opts) {
|
|
|
465
859
|
incomingSource.set(coll, srcMap);
|
|
466
860
|
incomingSourceTs.set(coll, stsMap);
|
|
467
861
|
}
|
|
468
|
-
const diff = await (0,
|
|
862
|
+
const diff = await (0, import_cargo5.diffVault)(receiver, candidate);
|
|
469
863
|
const byCollection = {};
|
|
470
864
|
const conflicts = [];
|
|
471
865
|
const writes = [];
|
|
@@ -581,15 +975,15 @@ async function mergeDecryptedRecords(receiver, decrypted, opts) {
|
|
|
581
975
|
};
|
|
582
976
|
}
|
|
583
977
|
async function mergeCompartment(receiver, compartmentBytes, opts) {
|
|
584
|
-
const incoming = await (0,
|
|
978
|
+
const incoming = await (0, import_cargo6.decryptExtractedPartition)(compartmentBytes, opts.transferKey);
|
|
585
979
|
return mergeDecryptedRecords(receiver, incoming, opts);
|
|
586
980
|
}
|
|
587
|
-
var
|
|
981
|
+
var import_cargo5, import_cargo6, FieldLevelDeferredError;
|
|
588
982
|
var init_merge_compartment = __esm({
|
|
589
983
|
"src/interchange/merge-compartment.ts"() {
|
|
590
984
|
"use strict";
|
|
591
|
-
|
|
592
|
-
|
|
985
|
+
import_cargo5 = require("@noy-db/hub/cargo");
|
|
986
|
+
import_cargo6 = require("@noy-db/hub/cargo");
|
|
593
987
|
init_field_authority();
|
|
594
988
|
FieldLevelDeferredError = class extends Error {
|
|
595
989
|
constructor(collection) {
|
|
@@ -685,7 +1079,7 @@ async function graduate(noydb, docked, opts) {
|
|
|
685
1079
|
await mergeDecryptedRecords(vault, staged, { strategy: "take-incoming", reason: "dock:graduate" });
|
|
686
1080
|
let deedSealed = false;
|
|
687
1081
|
if (opts.deed) {
|
|
688
|
-
await (0,
|
|
1082
|
+
await (0, import_cargo8.createDeedOwner)(noydb._store, opts.vaultName, opts.deed.ownerId, opts.deed.sealingProvider);
|
|
689
1083
|
deedSealed = true;
|
|
690
1084
|
}
|
|
691
1085
|
if (opts.stateVault) {
|
|
@@ -705,11 +1099,11 @@ async function graduate(noydb, docked, opts) {
|
|
|
705
1099
|
event: { type: "unit-graduated", unitId: docked.unitId, vault: opts.vaultName }
|
|
706
1100
|
};
|
|
707
1101
|
}
|
|
708
|
-
var
|
|
1102
|
+
var import_cargo8, UnitGraduationError, ISO_EPOCH;
|
|
709
1103
|
var init_graduate = __esm({
|
|
710
1104
|
"src/dock/graduate.ts"() {
|
|
711
1105
|
"use strict";
|
|
712
|
-
|
|
1106
|
+
import_cargo8 = require("@noy-db/hub/cargo");
|
|
713
1107
|
init_merge_compartment();
|
|
714
1108
|
init_stage_records();
|
|
715
1109
|
UnitGraduationError = class extends Error {
|
|
@@ -739,7 +1133,7 @@ __export(surface_exports, {
|
|
|
739
1133
|
async function proposeSurface(smv, def, proposedBy, now) {
|
|
740
1134
|
const row = {
|
|
741
1135
|
...def,
|
|
742
|
-
id: def.id ?? (0,
|
|
1136
|
+
id: def.id ?? (0, import_cargo9.generateULID)(),
|
|
743
1137
|
status: "proposed",
|
|
744
1138
|
proposedBy,
|
|
745
1139
|
createdAt: now
|
|
@@ -760,7 +1154,7 @@ async function exportSurface(source, surface) {
|
|
|
760
1154
|
throw new SurfaceStateError(surface.id, surface.status, "agreed");
|
|
761
1155
|
}
|
|
762
1156
|
const seeds = Object.fromEntries(surface.collections.map((c) => [c, () => true]));
|
|
763
|
-
const { bundleBytes, transferKey } = await (0,
|
|
1157
|
+
const { bundleBytes, transferKey } = await (0, import_cargo10.extractPartition)(source, {
|
|
764
1158
|
seeds,
|
|
765
1159
|
maxDepth: 0,
|
|
766
1160
|
...surface.fields ? { fieldProjection: surface.fields } : {},
|
|
@@ -798,12 +1192,12 @@ async function markSynced(smv, id, now) {
|
|
|
798
1192
|
nextSyncDueAt: now + cadenceMs
|
|
799
1193
|
});
|
|
800
1194
|
}
|
|
801
|
-
var
|
|
1195
|
+
var import_cargo9, import_cargo10, SurfaceNotFoundError, SurfaceStateError, SurfaceCadenceScheduler;
|
|
802
1196
|
var init_surface = __esm({
|
|
803
1197
|
"src/interchange/surface.ts"() {
|
|
804
1198
|
"use strict";
|
|
805
|
-
|
|
806
|
-
|
|
1199
|
+
import_cargo9 = require("@noy-db/hub/cargo");
|
|
1200
|
+
import_cargo10 = require("@noy-db/hub/cargo");
|
|
807
1201
|
init_merge_compartment();
|
|
808
1202
|
SurfaceNotFoundError = class extends Error {
|
|
809
1203
|
name = "SurfaceNotFoundError";
|
|
@@ -908,38 +1302,38 @@ function canonical(value) {
|
|
|
908
1302
|
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
909
1303
|
}
|
|
910
1304
|
async function fingerprintBlueprint(bp) {
|
|
911
|
-
return (0,
|
|
1305
|
+
return (0, import_cargo11.sha256Hex)(new TextEncoder().encode(canonical(bp)));
|
|
912
1306
|
}
|
|
913
|
-
var
|
|
1307
|
+
var import_cargo11;
|
|
914
1308
|
var init_schema_manifest = __esm({
|
|
915
1309
|
"src/federation/schema-manifest.ts"() {
|
|
916
1310
|
"use strict";
|
|
917
|
-
|
|
1311
|
+
import_cargo11 = require("@noy-db/hub/cargo");
|
|
918
1312
|
}
|
|
919
1313
|
});
|
|
920
1314
|
|
|
921
1315
|
// src/federation/constants.ts
|
|
922
|
-
var
|
|
1316
|
+
var import_cargo12;
|
|
923
1317
|
var init_constants = __esm({
|
|
924
1318
|
"src/federation/constants.ts"() {
|
|
925
1319
|
"use strict";
|
|
926
|
-
|
|
1320
|
+
import_cargo12 = require("@noy-db/hub/cargo");
|
|
927
1321
|
}
|
|
928
1322
|
});
|
|
929
1323
|
|
|
930
1324
|
// src/federation/state-vault.ts
|
|
931
1325
|
var state_vault_exports = {};
|
|
932
1326
|
__export(state_vault_exports, {
|
|
933
|
-
STATE_VAULT_NAME: () =>
|
|
1327
|
+
STATE_VAULT_NAME: () => import_cargo12.STATE_VAULT_NAME,
|
|
934
1328
|
StateManagementVault: () => StateManagementVault
|
|
935
1329
|
});
|
|
936
|
-
var
|
|
1330
|
+
var import_cargo13, REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, SURFACES, StateManagementVault;
|
|
937
1331
|
var init_state_vault = __esm({
|
|
938
1332
|
"src/federation/state-vault.ts"() {
|
|
939
1333
|
"use strict";
|
|
940
1334
|
init_schema_manifest();
|
|
941
1335
|
init_constants();
|
|
942
|
-
|
|
1336
|
+
import_cargo13 = require("@noy-db/hub/cargo");
|
|
943
1337
|
init_constants();
|
|
944
1338
|
REGISTRY = "vaultRegistry";
|
|
945
1339
|
MANIFEST = "schemaManifest";
|
|
@@ -969,7 +1363,7 @@ var init_state_vault = __esm({
|
|
|
969
1363
|
#surfaces;
|
|
970
1364
|
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
971
1365
|
static async open(db) {
|
|
972
|
-
const vault = await db.openVault(
|
|
1366
|
+
const vault = await db.openVault(import_cargo12.STATE_VAULT_NAME);
|
|
973
1367
|
return new _StateManagementVault(
|
|
974
1368
|
vault.collection(REGISTRY),
|
|
975
1369
|
vault.collection(MANIFEST),
|
|
@@ -1029,7 +1423,7 @@ var init_state_vault = __esm({
|
|
|
1029
1423
|
*/
|
|
1030
1424
|
async appendEvent(event) {
|
|
1031
1425
|
const ts = event.ts ?? Date.now();
|
|
1032
|
-
const id = (0,
|
|
1426
|
+
const id = (0, import_cargo13.generateULID)();
|
|
1033
1427
|
await this.#events.put(id, { ...event, id, ts });
|
|
1034
1428
|
}
|
|
1035
1429
|
/**
|
|
@@ -1071,13 +1465,13 @@ var init_state_vault = __esm({
|
|
|
1071
1465
|
|
|
1072
1466
|
// src/federation/classify-skip.ts
|
|
1073
1467
|
function classifyShardSkip(err) {
|
|
1074
|
-
return err instanceof
|
|
1468
|
+
return err instanceof import_cargo14.NoAccessError ? "no-grant" : "error";
|
|
1075
1469
|
}
|
|
1076
|
-
var
|
|
1470
|
+
var import_cargo14;
|
|
1077
1471
|
var init_classify_skip = __esm({
|
|
1078
1472
|
"src/federation/classify-skip.ts"() {
|
|
1079
1473
|
"use strict";
|
|
1080
|
-
|
|
1474
|
+
import_cargo14 = require("@noy-db/hub/cargo");
|
|
1081
1475
|
}
|
|
1082
1476
|
});
|
|
1083
1477
|
|
|
@@ -1102,7 +1496,7 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1102
1496
|
for (const leg of legs) {
|
|
1103
1497
|
const map = /* @__PURE__ */ new Map();
|
|
1104
1498
|
for (const rec of await leg.from.list()) {
|
|
1105
|
-
const k = coerceKey((0,
|
|
1499
|
+
const k = coerceKey((0, import_cargo15.readPath)(rec, leg.on));
|
|
1106
1500
|
if (k !== null && !map.has(k)) map.set(k, rec);
|
|
1107
1501
|
}
|
|
1108
1502
|
indexes.push({ leg, map });
|
|
@@ -1110,7 +1504,7 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1110
1504
|
return rows.map((row) => {
|
|
1111
1505
|
const out = { ...row };
|
|
1112
1506
|
for (const { leg, map } of indexes) {
|
|
1113
|
-
const key = coerceKey((0,
|
|
1507
|
+
const key = coerceKey((0, import_cargo15.readPath)(row, leg.field));
|
|
1114
1508
|
const match = key === null ? null : map.get(key) ?? null;
|
|
1115
1509
|
if (match === null && leg.mode === "warn") {
|
|
1116
1510
|
warnOnceBroadcastMiss(leg.field, leg.as, key ?? "<null>");
|
|
@@ -1120,11 +1514,11 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1120
1514
|
return out;
|
|
1121
1515
|
});
|
|
1122
1516
|
}
|
|
1123
|
-
var
|
|
1517
|
+
var import_cargo15, warnedBroadcastKeys;
|
|
1124
1518
|
var init_cross_shard_join = __esm({
|
|
1125
1519
|
"src/federation/cross-shard-join.ts"() {
|
|
1126
1520
|
"use strict";
|
|
1127
|
-
|
|
1521
|
+
import_cargo15 = require("@noy-db/hub/cargo");
|
|
1128
1522
|
warnedBroadcastKeys = /* @__PURE__ */ new Set();
|
|
1129
1523
|
}
|
|
1130
1524
|
});
|
|
@@ -1218,85 +1612,6 @@ var init_cross_vault_live = __esm({
|
|
|
1218
1612
|
}
|
|
1219
1613
|
});
|
|
1220
1614
|
|
|
1221
|
-
// src/federation/insight-auto-push.ts
|
|
1222
|
-
var InsightAutoPush;
|
|
1223
|
-
var init_insight_auto_push = __esm({
|
|
1224
|
-
"src/federation/insight-auto-push.ts"() {
|
|
1225
|
-
"use strict";
|
|
1226
|
-
InsightAutoPush = class {
|
|
1227
|
-
constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err), debounceMs) {
|
|
1228
|
-
this.recompute = recompute;
|
|
1229
|
-
this.isSource = isSource;
|
|
1230
|
-
this.onError = onError;
|
|
1231
|
-
this.debounceMs = debounceMs;
|
|
1232
|
-
}
|
|
1233
|
-
recompute;
|
|
1234
|
-
isSource;
|
|
1235
|
-
onError;
|
|
1236
|
-
debounceMs;
|
|
1237
|
-
dirty = /* @__PURE__ */ new Set();
|
|
1238
|
-
pending = null;
|
|
1239
|
-
timer = null;
|
|
1240
|
-
resolvePending = null;
|
|
1241
|
-
flushing = false;
|
|
1242
|
-
/** Called from a shard's onAfterWrite hook. Marks the shard dirty + schedules a flush. */
|
|
1243
|
-
noteWrite(partitionKey, collection) {
|
|
1244
|
-
if (!this.isSource(collection)) return;
|
|
1245
|
-
this.dirty.add(partitionKey);
|
|
1246
|
-
if (this.debounceMs !== void 0) this.scheduleDebounced();
|
|
1247
|
-
else this.schedule();
|
|
1248
|
-
}
|
|
1249
|
-
/** Resolve once no flush is pending (drains rescheduled flushes too). */
|
|
1250
|
-
async whenSettled() {
|
|
1251
|
-
while (this.pending) await this.pending;
|
|
1252
|
-
}
|
|
1253
|
-
// ── microtask path (unchanged default) ──
|
|
1254
|
-
schedule() {
|
|
1255
|
-
if (this.pending) return;
|
|
1256
|
-
this.pending = this.runFlush().finally(() => {
|
|
1257
|
-
this.pending = null;
|
|
1258
|
-
if (this.dirty.size > 0) this.schedule();
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
// ── debounce path (#13) ──
|
|
1262
|
-
scheduleDebounced() {
|
|
1263
|
-
if (!this.pending) this.pending = new Promise((r) => {
|
|
1264
|
-
this.resolvePending = r;
|
|
1265
|
-
});
|
|
1266
|
-
if (this.timer) clearTimeout(this.timer);
|
|
1267
|
-
this.timer = setTimeout(() => {
|
|
1268
|
-
this.timer = null;
|
|
1269
|
-
if (this.flushing) return;
|
|
1270
|
-
this.flushing = true;
|
|
1271
|
-
void this.runFlush().finally(() => {
|
|
1272
|
-
this.flushing = false;
|
|
1273
|
-
if (this.dirty.size > 0) {
|
|
1274
|
-
this.scheduleDebounced();
|
|
1275
|
-
} else {
|
|
1276
|
-
const resolve = this.resolvePending;
|
|
1277
|
-
this.resolvePending = null;
|
|
1278
|
-
this.pending = null;
|
|
1279
|
-
resolve?.();
|
|
1280
|
-
}
|
|
1281
|
-
});
|
|
1282
|
-
}, this.debounceMs);
|
|
1283
|
-
}
|
|
1284
|
-
async runFlush() {
|
|
1285
|
-
await Promise.resolve();
|
|
1286
|
-
const pks = [...this.dirty];
|
|
1287
|
-
this.dirty.clear();
|
|
1288
|
-
for (const pk of pks) {
|
|
1289
|
-
try {
|
|
1290
|
-
await this.recompute(pk);
|
|
1291
|
-
} catch (err) {
|
|
1292
|
-
this.onError(err, pk);
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
|
-
|
|
1300
1615
|
// src/federation/partial-reduce.ts
|
|
1301
1616
|
function canPartialReduce(spec) {
|
|
1302
1617
|
return Object.values(spec).every((r) => typeof r.merge === "function");
|
|
@@ -1335,12 +1650,12 @@ var init_partial_reduce = __esm({
|
|
|
1335
1650
|
});
|
|
1336
1651
|
|
|
1337
1652
|
// src/federation/aggregate-across.ts
|
|
1338
|
-
var
|
|
1653
|
+
var import_cargo16, import_cargo17, CrossVaultAggregation, CrossVaultGroupedAggregation;
|
|
1339
1654
|
var init_aggregate_across = __esm({
|
|
1340
1655
|
"src/federation/aggregate-across.ts"() {
|
|
1341
1656
|
"use strict";
|
|
1342
|
-
|
|
1343
|
-
|
|
1657
|
+
import_cargo16 = require("@noy-db/hub/cargo");
|
|
1658
|
+
import_cargo17 = require("@noy-db/hub/cargo");
|
|
1344
1659
|
init_cross_vault_live();
|
|
1345
1660
|
init_partial_reduce();
|
|
1346
1661
|
CrossVaultAggregation = class {
|
|
@@ -1358,7 +1673,7 @@ var init_aggregate_across = __esm({
|
|
|
1358
1673
|
return { result: finalizePartial(this.spec, mergePartials(this.spec, partials)), skippedVaults: skippedVaults2 };
|
|
1359
1674
|
}
|
|
1360
1675
|
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
1361
|
-
return { result: (0,
|
|
1676
|
+
return { result: (0, import_cargo16.reduceRecords)(records, this.spec), skippedVaults };
|
|
1362
1677
|
}
|
|
1363
1678
|
live(options = {}) {
|
|
1364
1679
|
if (!this.bind) throw new Error("CrossVaultAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.aggregate()");
|
|
@@ -1369,7 +1684,7 @@ var init_aggregate_across = __esm({
|
|
|
1369
1684
|
isRelevant: this.bind.isRelevant,
|
|
1370
1685
|
compute: async () => {
|
|
1371
1686
|
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
1372
|
-
return { value: (0,
|
|
1687
|
+
return { value: (0, import_cargo16.reduceRecords)(records, spec), skipped: skippedVaults };
|
|
1373
1688
|
},
|
|
1374
1689
|
initialSnapshot: { value: void 0, skipped: [] },
|
|
1375
1690
|
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
@@ -1404,7 +1719,7 @@ var init_aggregate_across = __esm({
|
|
|
1404
1719
|
async run(options = {}) {
|
|
1405
1720
|
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
1406
1721
|
return {
|
|
1407
|
-
results: (0,
|
|
1722
|
+
results: (0, import_cargo17.groupAndReduce)(records, this.field, this.spec),
|
|
1408
1723
|
skippedVaults
|
|
1409
1724
|
};
|
|
1410
1725
|
}
|
|
@@ -1419,7 +1734,7 @@ var init_aggregate_across = __esm({
|
|
|
1419
1734
|
compute: async () => {
|
|
1420
1735
|
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
1421
1736
|
return {
|
|
1422
|
-
records: (0,
|
|
1737
|
+
records: (0, import_cargo17.groupAndReduce)(records, field, spec),
|
|
1423
1738
|
skipped: skippedVaults
|
|
1424
1739
|
};
|
|
1425
1740
|
},
|
|
@@ -1492,7 +1807,7 @@ async function retrieveAcross(group, collectionName, query, opts = {}) {
|
|
|
1492
1807
|
}
|
|
1493
1808
|
return pv.hits.map((h) => ({ ...h, id: pv.vault + SEP + h.id }));
|
|
1494
1809
|
});
|
|
1495
|
-
const fused = (0,
|
|
1810
|
+
const fused = (0, import_cargo18.fuseRetrieval)(lists, {
|
|
1496
1811
|
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
1497
1812
|
...opts.rrfK !== void 0 ? { k: opts.rrfK } : {}
|
|
1498
1813
|
});
|
|
@@ -1502,11 +1817,11 @@ async function retrieveAcross(group, collectionName, query, opts = {}) {
|
|
|
1502
1817
|
});
|
|
1503
1818
|
return { hits, skippedVaults };
|
|
1504
1819
|
}
|
|
1505
|
-
var
|
|
1820
|
+
var import_cargo18, SEP;
|
|
1506
1821
|
var init_retrieve_across = __esm({
|
|
1507
1822
|
"src/federation/retrieve-across.ts"() {
|
|
1508
1823
|
"use strict";
|
|
1509
|
-
|
|
1824
|
+
import_cargo18 = require("@noy-db/hub/cargo");
|
|
1510
1825
|
init_classify_skip();
|
|
1511
1826
|
SEP = "\0";
|
|
1512
1827
|
}
|
|
@@ -1526,30 +1841,31 @@ function autoPushConfig(spec) {
|
|
|
1526
1841
|
}
|
|
1527
1842
|
function assertSafePartitionKey(partitionKey) {
|
|
1528
1843
|
if (partitionKey.length === 0) {
|
|
1529
|
-
throw new
|
|
1844
|
+
throw new import_cargo19.ValidationError("partitionKey must be a non-empty string");
|
|
1530
1845
|
}
|
|
1531
|
-
if (partitionKey ===
|
|
1532
|
-
throw new
|
|
1846
|
+
if (partitionKey === import_cargo12.STATE_VAULT_NAME) {
|
|
1847
|
+
throw new import_cargo19.ReservedVaultNameError(partitionKey);
|
|
1533
1848
|
}
|
|
1534
1849
|
if (!SAFE_PARTITION_KEY.test(partitionKey)) {
|
|
1535
|
-
throw new
|
|
1850
|
+
throw new import_cargo19.ValidationError(
|
|
1536
1851
|
`partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
|
|
1537
1852
|
);
|
|
1538
1853
|
}
|
|
1539
1854
|
if (partitionKey.includes(SHARD_SEPARATOR)) {
|
|
1540
|
-
throw new
|
|
1855
|
+
throw new import_cargo19.ValidationError(
|
|
1541
1856
|
`partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
|
|
1542
1857
|
);
|
|
1543
1858
|
}
|
|
1544
1859
|
}
|
|
1545
|
-
var
|
|
1860
|
+
var import_cargo19, SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
|
|
1546
1861
|
var init_vault_group = __esm({
|
|
1547
1862
|
"src/federation/vault-group.ts"() {
|
|
1548
1863
|
"use strict";
|
|
1549
1864
|
init_state_vault();
|
|
1550
|
-
|
|
1865
|
+
import_cargo19 = require("@noy-db/hub/cargo");
|
|
1551
1866
|
init_constants();
|
|
1552
1867
|
init_classify_skip();
|
|
1868
|
+
init_read_model();
|
|
1553
1869
|
init_cross_shard_join();
|
|
1554
1870
|
init_cross_vault_live();
|
|
1555
1871
|
init_insight_auto_push();
|
|
@@ -1568,7 +1884,7 @@ var init_vault_group = __esm({
|
|
|
1568
1884
|
this.cutoverOnOpen = cutoverOnOpen;
|
|
1569
1885
|
this.meta = meta;
|
|
1570
1886
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
1571
|
-
throw new
|
|
1887
|
+
throw new import_cargo19.ValidationError(
|
|
1572
1888
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
1573
1889
|
);
|
|
1574
1890
|
}
|
|
@@ -1673,11 +1989,11 @@ var init_vault_group = __esm({
|
|
|
1673
1989
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1674
1990
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1675
1991
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1676
|
-
if (row && !provisioned) throw new
|
|
1992
|
+
if (row && !provisioned) throw new import_cargo19.ShardProvisioningError(vaultId, partitionKey);
|
|
1677
1993
|
if (row && provisioned) return this.openShard(partitionKey);
|
|
1678
1994
|
if (region !== void 0) {
|
|
1679
1995
|
const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
|
|
1680
|
-
if (backendRegion !== region) throw new
|
|
1996
|
+
if (backendRegion !== region) throw new import_cargo19.DataResidencyError(vaultId, region, backendRegion);
|
|
1681
1997
|
}
|
|
1682
1998
|
const vault = await this.db.openVault(vaultId);
|
|
1683
1999
|
this.template.configure(vault);
|
|
@@ -1711,9 +2027,9 @@ var init_vault_group = __esm({
|
|
|
1711
2027
|
async shard(partitionKey) {
|
|
1712
2028
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1713
2029
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1714
|
-
if (!row) throw new
|
|
2030
|
+
if (!row) throw new import_cargo19.UnknownShardError(partitionKey, this.name);
|
|
1715
2031
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1716
|
-
if (!provisioned) throw new
|
|
2032
|
+
if (!provisioned) throw new import_cargo19.ShardProvisioningError(vaultId, partitionKey);
|
|
1717
2033
|
return this.openShard(partitionKey);
|
|
1718
2034
|
}
|
|
1719
2035
|
/** A sharded view over one logical collection across all shards. */
|
|
@@ -1745,7 +2061,7 @@ var init_vault_group = __esm({
|
|
|
1745
2061
|
for (const p of probes) {
|
|
1746
2062
|
if ("error" in p) skipped.push({ vaultId: p.row.vaultId, reason: "error", error: p.error });
|
|
1747
2063
|
else if (p.provisioned) eligible.push(p.row);
|
|
1748
|
-
else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new
|
|
2064
|
+
else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new import_cargo19.ShardProvisioningError(p.row.vaultId, p.row.partitionKey) });
|
|
1749
2065
|
}
|
|
1750
2066
|
return { eligible, skipped };
|
|
1751
2067
|
}
|
|
@@ -1782,7 +2098,7 @@ var init_vault_group = __esm({
|
|
|
1782
2098
|
withCrossVaultDerivation(spec) {
|
|
1783
2099
|
const target = spec.target.vault;
|
|
1784
2100
|
if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
|
|
1785
|
-
throw new
|
|
2101
|
+
throw new import_cargo19.ValidationError(
|
|
1786
2102
|
`withCrossVaultDerivation: target.vault "${target}" is the "${this.name}" group itself or one of its shards \u2014 an Insight summary must target a SEPARATE analytics vault, never write back into client-shard data (it would breach the per-shard DEK boundary). Use a distinct vault name.`
|
|
1787
2103
|
);
|
|
1788
2104
|
}
|
|
@@ -1842,12 +2158,7 @@ var init_vault_group = __esm({
|
|
|
1842
2158
|
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
1843
2159
|
continue;
|
|
1844
2160
|
}
|
|
1845
|
-
const
|
|
1846
|
-
vaultId: row.vaultId,
|
|
1847
|
-
partitionKey: row.partitionKey,
|
|
1848
|
-
schemaVersion: row.schemaVersion
|
|
1849
|
-
};
|
|
1850
|
-
const summary = spec.derive(res.result, ctx);
|
|
2161
|
+
const summary = deriveShardSummary(spec, res.result, row);
|
|
1851
2162
|
await out.put(row.partitionKey, summary);
|
|
1852
2163
|
written++;
|
|
1853
2164
|
}
|
|
@@ -1867,17 +2178,12 @@ var init_vault_group = __esm({
|
|
|
1867
2178
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1868
2179
|
if (!row) return;
|
|
1869
2180
|
const shard = await this.openShard(partitionKey);
|
|
1870
|
-
const ctx = {
|
|
1871
|
-
vaultId: row.vaultId,
|
|
1872
|
-
partitionKey,
|
|
1873
|
-
schemaVersion: row.schemaVersion
|
|
1874
|
-
};
|
|
1875
2181
|
for (const spec of this.crossVaultDerivations) {
|
|
1876
2182
|
if (!spec.autoPush) continue;
|
|
1877
2183
|
const min = autoPushConfig(spec)?.minVersion;
|
|
1878
2184
|
if (min !== void 0 && row.schemaVersion < min) continue;
|
|
1879
2185
|
const records = await shard.collection(spec.source).list();
|
|
1880
|
-
const summary = spec
|
|
2186
|
+
const summary = deriveShardSummary(spec, records, row);
|
|
1881
2187
|
const insight = await this.db.openVault(spec.target.vault);
|
|
1882
2188
|
await insight.collection(spec.target.collection).put(partitionKey, summary);
|
|
1883
2189
|
}
|
|
@@ -1908,7 +2214,7 @@ var init_vault_group = __esm({
|
|
|
1908
2214
|
async cutoverShard(partitionKey) {
|
|
1909
2215
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1910
2216
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1911
|
-
if (!row) throw new
|
|
2217
|
+
if (!row) throw new import_cargo19.UnknownShardError(partitionKey, this.name);
|
|
1912
2218
|
const target = this.template.version;
|
|
1913
2219
|
const sv = await this.ensureStateVault();
|
|
1914
2220
|
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
@@ -1991,7 +2297,7 @@ var init_vault_group = __esm({
|
|
|
1991
2297
|
let vault;
|
|
1992
2298
|
if (!row) {
|
|
1993
2299
|
if (this.group.sharding.autoCreate === false) {
|
|
1994
|
-
throw new
|
|
2300
|
+
throw new import_cargo19.UnknownShardError(key, this.group.name);
|
|
1995
2301
|
}
|
|
1996
2302
|
vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
|
|
1997
2303
|
} else {
|
|
@@ -2074,7 +2380,7 @@ var init_vault_group = __esm({
|
|
|
2074
2380
|
this.group.template.configure(probe);
|
|
2075
2381
|
for (const leg of this.coPartitionedLegs) {
|
|
2076
2382
|
if (!probe.resolveRef(this.collectionName, leg.field)) {
|
|
2077
|
-
throw new
|
|
2383
|
+
throw new import_cargo19.CrossShardJoinError(
|
|
2078
2384
|
`crossShardJoin("${leg.field}"): no ref() declared for "${leg.field}" on collection "${this.collectionName}" in template "${this.group.sharding.vaultTemplate}". Add refs: { ${leg.field}: ref('<target>') } to the template's collection options.`
|
|
2079
2385
|
);
|
|
2080
2386
|
}
|
|
@@ -2236,13 +2542,117 @@ var init_vault_group = __esm({
|
|
|
2236
2542
|
}
|
|
2237
2543
|
});
|
|
2238
2544
|
|
|
2545
|
+
// src/portal/provision.ts
|
|
2546
|
+
var provision_exports = {};
|
|
2547
|
+
__export(provision_exports, {
|
|
2548
|
+
provisionPortal: () => provisionPortal,
|
|
2549
|
+
revokePortalInvite: () => revokePortalInvite
|
|
2550
|
+
});
|
|
2551
|
+
async function requireRow(group, client) {
|
|
2552
|
+
const row = await group.registry.get(group.registryId(client));
|
|
2553
|
+
if (!row) throw new Error(`provisionPortal: client "${client}" has no registry row in group.`);
|
|
2554
|
+
return row;
|
|
2555
|
+
}
|
|
2556
|
+
async function provisionPortal(db, group, opts) {
|
|
2557
|
+
const existing = await group.registry.get(group.registryId(opts.client));
|
|
2558
|
+
if (!existing) await group.createShard(opts.client);
|
|
2559
|
+
const row = existing ?? await requireRow(group, opts.client);
|
|
2560
|
+
const vaultId = row.vaultId;
|
|
2561
|
+
for (const g of opts.firmGrants ?? []) {
|
|
2562
|
+
await db.grant(vaultId, {
|
|
2563
|
+
userId: g.userId,
|
|
2564
|
+
displayName: g.displayName,
|
|
2565
|
+
role: g.role,
|
|
2566
|
+
passphrase: g.passphrase
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
const { issueInvite, issuePeerRecovery } = await import("@noy-db/on-magic-link");
|
|
2570
|
+
const mode = opts.mode ?? "invite";
|
|
2571
|
+
const issued = mode === "rebind" ? await issuePeerRecovery(db, vaultId, {
|
|
2572
|
+
userId: opts.invite.userId,
|
|
2573
|
+
displayName: opts.invite.displayName,
|
|
2574
|
+
...opts.invite.role !== void 0 ? { role: opts.invite.role } : {},
|
|
2575
|
+
...opts.invite.ttlMs !== void 0 ? { ttlMs: opts.invite.ttlMs } : {},
|
|
2576
|
+
...opts.invite.tempPhrase !== void 0 ? { tempPhrase: opts.invite.tempPhrase } : {}
|
|
2577
|
+
}) : await issueInvite(db, vaultId, {
|
|
2578
|
+
userId: opts.invite.userId,
|
|
2579
|
+
displayName: opts.invite.displayName,
|
|
2580
|
+
role: opts.invite.role ?? "client",
|
|
2581
|
+
...opts.invite.ttlMs !== void 0 ? { ttlMs: opts.invite.ttlMs } : {},
|
|
2582
|
+
...opts.invite.tempPhrase !== void 0 ? { tempPhrase: opts.invite.tempPhrase } : {}
|
|
2583
|
+
});
|
|
2584
|
+
const auditRef = {
|
|
2585
|
+
tokenId: issued.payload.tokenId,
|
|
2586
|
+
userId: opts.invite.userId,
|
|
2587
|
+
kind: mode,
|
|
2588
|
+
issuedAt: Date.now(),
|
|
2589
|
+
expiresAt: issued.payload.expiresAt
|
|
2590
|
+
};
|
|
2591
|
+
const handle = row.handle ?? (0, import_cargo20.generateULID)();
|
|
2592
|
+
const updated = {
|
|
2593
|
+
...row,
|
|
2594
|
+
handle,
|
|
2595
|
+
portal: {
|
|
2596
|
+
enabledAt: row.portal?.enabledAt ?? Date.now(),
|
|
2597
|
+
invites: [...row.portal?.invites ?? [], auditRef]
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
await group.registry.put(group.registryId(opts.client), updated);
|
|
2601
|
+
return {
|
|
2602
|
+
vaultId,
|
|
2603
|
+
handle,
|
|
2604
|
+
invite: {
|
|
2605
|
+
encoded: issued.encoded,
|
|
2606
|
+
tokenId: issued.payload.tokenId,
|
|
2607
|
+
userId: opts.invite.userId,
|
|
2608
|
+
expiresAt: issued.payload.expiresAt
|
|
2609
|
+
},
|
|
2610
|
+
row: updated
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
async function revokePortalInvite(db, group, opts) {
|
|
2614
|
+
const row = await requireRow(group, opts.client);
|
|
2615
|
+
const invites = row.portal?.invites ?? [];
|
|
2616
|
+
const ref = invites.find((i) => i.tokenId === opts.tokenId);
|
|
2617
|
+
if (!ref) {
|
|
2618
|
+
throw new Error(`revokePortalInvite: no invite with tokenId "${opts.tokenId}" for client "${opts.client}".`);
|
|
2619
|
+
}
|
|
2620
|
+
if (ref.revokedAt !== void 0) return row;
|
|
2621
|
+
const { revokeInvite } = await import("@noy-db/on-magic-link");
|
|
2622
|
+
await revokeInvite(db, row.vaultId, {
|
|
2623
|
+
tokenId: ref.tokenId,
|
|
2624
|
+
vault: row.vaultId,
|
|
2625
|
+
userId: ref.userId,
|
|
2626
|
+
kind: ref.kind === "rebind" ? "peer-recovery" : "invite",
|
|
2627
|
+
issuer: "",
|
|
2628
|
+
tempPhrase: "",
|
|
2629
|
+
expiresAt: ref.expiresAt
|
|
2630
|
+
});
|
|
2631
|
+
const updated = {
|
|
2632
|
+
...row,
|
|
2633
|
+
portal: {
|
|
2634
|
+
enabledAt: row.portal?.enabledAt ?? Date.now(),
|
|
2635
|
+
invites: invites.map((i) => i.tokenId === opts.tokenId ? { ...i, revokedAt: Date.now() } : i)
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
await group.registry.put(group.registryId(opts.client), updated);
|
|
2639
|
+
return updated;
|
|
2640
|
+
}
|
|
2641
|
+
var import_cargo20;
|
|
2642
|
+
var init_provision = __esm({
|
|
2643
|
+
"src/portal/provision.ts"() {
|
|
2644
|
+
"use strict";
|
|
2645
|
+
import_cargo20 = require("@noy-db/hub/cargo");
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
|
|
2239
2649
|
// src/index.ts
|
|
2240
2650
|
var index_exports = {};
|
|
2241
2651
|
__export(index_exports, {
|
|
2242
|
-
CrossShardJoinError: () =>
|
|
2652
|
+
CrossShardJoinError: () => import_cargo22.CrossShardJoinError,
|
|
2243
2653
|
CrossVaultDanglingRefError: () => CrossVaultDanglingRefError,
|
|
2244
|
-
CustodyApi: () =>
|
|
2245
|
-
DataResidencyError: () =>
|
|
2654
|
+
CustodyApi: () => import_cargo23.CustodyApi,
|
|
2655
|
+
DataResidencyError: () => import_cargo22.DataResidencyError,
|
|
2246
2656
|
DockedUnit: () => DockedUnit,
|
|
2247
2657
|
FieldAuthorityPolicyMissingError: () => FieldAuthorityPolicyMissingError,
|
|
2248
2658
|
FieldLevelDeferredError: () => FieldLevelDeferredError,
|
|
@@ -2253,17 +2663,19 @@ __export(index_exports, {
|
|
|
2253
2663
|
NOYDB_MULTI_BUNDLE_MAGIC: () => NOYDB_MULTI_BUNDLE_MAGIC,
|
|
2254
2664
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES: () => NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
|
|
2255
2665
|
NOYDB_MULTI_BUNDLE_VERSION: () => NOYDB_MULTI_BUNDLE_VERSION,
|
|
2256
|
-
|
|
2257
|
-
|
|
2666
|
+
PostureViolationError: () => PostureViolationError,
|
|
2667
|
+
ReservedVaultNameError: () => import_cargo22.ReservedVaultNameError,
|
|
2668
|
+
ShardProvisioningError: () => import_cargo22.ShardProvisioningError,
|
|
2669
|
+
ShareLinkResolutionError: () => ShareLinkResolutionError,
|
|
2258
2670
|
SurfaceCadenceScheduler: () => SurfaceCadenceScheduler,
|
|
2259
2671
|
SurfaceNotFoundError: () => SurfaceNotFoundError,
|
|
2260
2672
|
SurfaceStateError: () => SurfaceStateError,
|
|
2261
2673
|
UnitGraduationError: () => UnitGraduationError,
|
|
2262
|
-
UnknownShardError: () =>
|
|
2263
|
-
VaultTemplateNotFoundError: () =>
|
|
2674
|
+
UnknownShardError: () => import_cargo22.UnknownShardError,
|
|
2675
|
+
VaultTemplateNotFoundError: () => import_cargo22.VaultTemplateNotFoundError,
|
|
2264
2676
|
agreeSurface: () => agreeSurface,
|
|
2265
2677
|
applySurface: () => applySurface,
|
|
2266
|
-
createDeedOwner: () =>
|
|
2678
|
+
createDeedOwner: () => import_cargo23.createDeedOwner,
|
|
2267
2679
|
createLobby: () => createLobby,
|
|
2268
2680
|
decodeMultiBundle: () => decodeMultiBundle,
|
|
2269
2681
|
describeCrossVaultExtraction: () => describeCrossVaultExtraction,
|
|
@@ -2271,11 +2683,11 @@ __export(index_exports, {
|
|
|
2271
2683
|
exportSurface: () => exportSurface,
|
|
2272
2684
|
extractCrossVaultPartition: () => extractCrossVaultPartition,
|
|
2273
2685
|
groupInspector: () => groupInspector,
|
|
2274
|
-
isDeedVault: () =>
|
|
2686
|
+
isDeedVault: () => import_cargo23.isDeedVault,
|
|
2275
2687
|
isSurfaceDue: () => isSurfaceDue,
|
|
2276
|
-
liberateVault: () =>
|
|
2688
|
+
liberateVault: () => import_cargo23.liberateVault,
|
|
2277
2689
|
listDueSurfaces: () => listDueSurfaces,
|
|
2278
|
-
loadDeedMarker: () =>
|
|
2690
|
+
loadDeedMarker: () => import_cargo23.loadDeedMarker,
|
|
2279
2691
|
markSynced: () => markSynced,
|
|
2280
2692
|
mergeCompartment: () => mergeCompartment,
|
|
2281
2693
|
mergeDecryptedRecords: () => mergeDecryptedRecords,
|
|
@@ -2290,7 +2702,7 @@ __export(index_exports, {
|
|
|
2290
2702
|
writeMultiVaultBundle: () => writeMultiVaultBundle
|
|
2291
2703
|
});
|
|
2292
2704
|
module.exports = __toCommonJS(index_exports);
|
|
2293
|
-
var
|
|
2705
|
+
var import_cargo21 = require("@noy-db/hub/cargo");
|
|
2294
2706
|
|
|
2295
2707
|
// src/dock/docked-unit.ts
|
|
2296
2708
|
var DockedUnit = class {
|
|
@@ -2309,6 +2721,10 @@ var DockedUnit = class {
|
|
|
2309
2721
|
}
|
|
2310
2722
|
};
|
|
2311
2723
|
|
|
2724
|
+
// src/index.ts
|
|
2725
|
+
init_share_link();
|
|
2726
|
+
init_read_model();
|
|
2727
|
+
|
|
2312
2728
|
// src/federation/group-inspector.ts
|
|
2313
2729
|
function groupInspector(group) {
|
|
2314
2730
|
let shardIds = /* @__PURE__ */ new Set();
|
|
@@ -2372,13 +2788,13 @@ async function meterGroup(group, opts = {}) {
|
|
|
2372
2788
|
}
|
|
2373
2789
|
|
|
2374
2790
|
// src/index.ts
|
|
2375
|
-
var
|
|
2791
|
+
var import_cargo22 = require("@noy-db/hub/cargo");
|
|
2376
2792
|
init_multi_bundle();
|
|
2377
2793
|
init_extract_cross_vault();
|
|
2378
2794
|
init_merge_compartment();
|
|
2379
2795
|
|
|
2380
2796
|
// src/interchange/migrate-then-merge.ts
|
|
2381
|
-
var
|
|
2797
|
+
var import_cargo7 = require("@noy-db/hub/cargo");
|
|
2382
2798
|
init_merge_compartment();
|
|
2383
2799
|
init_stage_records();
|
|
2384
2800
|
init_stage_records();
|
|
@@ -2403,7 +2819,7 @@ async function migrateThenMerge(receiver, compartmentBytes, opts) {
|
|
|
2403
2819
|
);
|
|
2404
2820
|
}
|
|
2405
2821
|
if (fromVersion > toVersion) throw new MinVersionError(fromVersion, toVersion);
|
|
2406
|
-
const incoming = await (0,
|
|
2822
|
+
const incoming = await (0, import_cargo7.decryptExtractedPartition)(compartmentBytes, opts.transferKey);
|
|
2407
2823
|
const transformsByCollection = {};
|
|
2408
2824
|
const migrationByCollection = {};
|
|
2409
2825
|
for (const [coll, recs] of Object.entries(incoming)) {
|
|
@@ -2426,7 +2842,7 @@ async function migrateThenMerge(receiver, compartmentBytes, opts) {
|
|
|
2426
2842
|
|
|
2427
2843
|
// src/index.ts
|
|
2428
2844
|
init_field_authority();
|
|
2429
|
-
var
|
|
2845
|
+
var import_cargo23 = require("@noy-db/hub/cargo");
|
|
2430
2846
|
|
|
2431
2847
|
// src/dock/unit-driver.ts
|
|
2432
2848
|
var InMemoryUnitDriver = class {
|
|
@@ -2460,10 +2876,10 @@ var Lobby = class {
|
|
|
2460
2876
|
}
|
|
2461
2877
|
async openVaultGroup(name, opts) {
|
|
2462
2878
|
const db = this.noydb;
|
|
2463
|
-
if (db.isClosed) throw new
|
|
2464
|
-
if (name ===
|
|
2879
|
+
if (db.isClosed) throw new import_cargo21.ValidationError("Instance is closed");
|
|
2880
|
+
if (name === import_cargo21.STATE_VAULT_NAME) throw new import_cargo21.ReservedVaultNameError(name);
|
|
2465
2881
|
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
2466
|
-
if (!template) throw new
|
|
2882
|
+
if (!template) throw new import_cargo21.VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
2467
2883
|
const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
|
|
2468
2884
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2469
2885
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(db);
|
|
@@ -2482,7 +2898,7 @@ var Lobby = class {
|
|
|
2482
2898
|
}
|
|
2483
2899
|
async openStateManagementVault() {
|
|
2484
2900
|
const db = this.noydb;
|
|
2485
|
-
if (db.isClosed) throw new
|
|
2901
|
+
if (db.isClosed) throw new import_cargo21.ValidationError("Instance is closed");
|
|
2486
2902
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2487
2903
|
return StateManagementVault2.open(db);
|
|
2488
2904
|
}
|
|
@@ -2502,6 +2918,64 @@ var Lobby = class {
|
|
|
2502
2918
|
const { graduate: graduateFn } = await Promise.resolve().then(() => (init_graduate(), graduate_exports));
|
|
2503
2919
|
return graduateFn(this.noydb, docked, opts);
|
|
2504
2920
|
}
|
|
2921
|
+
// ─── Portal deep-link addressing (#43) ────────────────────────────────────
|
|
2922
|
+
/**
|
|
2923
|
+
* Resolve a portal share link (raw URL/path or a parsed `ShareLink`)
|
|
2924
|
+
* against the fleet registry: `vaultHandle → registry row` plus a
|
|
2925
|
+
* console-route descriptor the firm app turns into its own URL.
|
|
2926
|
+
* Unknown/foreign handles fail closed with `ShareLinkResolutionError`.
|
|
2927
|
+
* Delegates to `resolveShareLinkAgainstRegistry` in `portal/share-link.ts`.
|
|
2928
|
+
*/
|
|
2929
|
+
async resolveShareLink(link, opts) {
|
|
2930
|
+
const { resolveShareLinkAgainstRegistry: resolveShareLinkAgainstRegistry2 } = await Promise.resolve().then(() => (init_share_link(), share_link_exports));
|
|
2931
|
+
return resolveShareLinkAgainstRegistry2(link, opts.registry);
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Mint a portal share link for a fleet client (`client` partition key +
|
|
2935
|
+
* `group`), delegating the link grammar to `@noy-db/hub/share-link`.
|
|
2936
|
+
* Fails closed when the client is unknown or has no portal handle.
|
|
2937
|
+
* Delegates to `buildShareLinkForClient` in `portal/share-link.ts`.
|
|
2938
|
+
*/
|
|
2939
|
+
async buildShareLink(parts, opts) {
|
|
2940
|
+
const { buildShareLinkForClient: buildShareLinkForClient2 } = await Promise.resolve().then(() => (init_share_link(), share_link_exports));
|
|
2941
|
+
return buildShareLinkForClient2(parts, opts.registry);
|
|
2942
|
+
}
|
|
2943
|
+
// ─── Portal provisioning (#42) ────────────────────────────────────────────
|
|
2944
|
+
/**
|
|
2945
|
+
* Provision (or re-invite, `mode: 'rebind'`) a client portal vault in
|
|
2946
|
+
* one audited operation: ensure the shard, assign the portal handle
|
|
2947
|
+
* (#43's link target), apply firm grants, issue the magic-link invite,
|
|
2948
|
+
* stamp the audit trail on the fleet registry row.
|
|
2949
|
+
* Needs `@noy-db/on-magic-link` (optional peer) and a session created
|
|
2950
|
+
* with `teamStrategy: withTeam()`.
|
|
2951
|
+
* Delegates to `provisionPortal` in `portal/provision.ts`.
|
|
2952
|
+
*/
|
|
2953
|
+
async provisionPortal(group, opts) {
|
|
2954
|
+
const { provisionPortal: provisionPortalFn } = await Promise.resolve().then(() => (init_provision(), provision_exports));
|
|
2955
|
+
return provisionPortalFn(this.noydb, group, opts);
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Revoke an outstanding portal invite and stamp `revokedAt` on the
|
|
2959
|
+
* fleet audit trail. Idempotent; fails closed on an unknown tokenId.
|
|
2960
|
+
* Delegates to `revokePortalInvite` in `portal/provision.ts`.
|
|
2961
|
+
*/
|
|
2962
|
+
async revokePortalInvite(group, opts) {
|
|
2963
|
+
const { revokePortalInvite: revokePortalInviteFn } = await Promise.resolve().then(() => (init_provision(), provision_exports));
|
|
2964
|
+
return revokePortalInviteFn(this.noydb, group, opts);
|
|
2965
|
+
}
|
|
2966
|
+
// ─── Federated read-model (#44) ───────────────────────────────────────────
|
|
2967
|
+
/**
|
|
2968
|
+
* Open a maintained federated read-model over `group` (#44 S1): rollup
|
|
2969
|
+
* models reduce each shard's `source` into one summary row per shard in
|
|
2970
|
+
* the read-model vault — deterministic ids, `_shard`/`_sourceVersion`
|
|
2971
|
+
* provenance, fail-closed `posture.surface` allowlist, explicit
|
|
2972
|
+
* `refresh()`. Spec: docs/federated-read-model.md.
|
|
2973
|
+
* Delegates to `openReadModel` in `federation/read-model.ts`.
|
|
2974
|
+
*/
|
|
2975
|
+
async openReadModel(group, opts) {
|
|
2976
|
+
const { openReadModel: openReadModelFn } = await Promise.resolve().then(() => (init_read_model(), read_model_exports));
|
|
2977
|
+
return openReadModelFn(group, opts);
|
|
2978
|
+
}
|
|
2505
2979
|
// ─── FR-7 Surface API ─────────────────────────────────────────────────────
|
|
2506
2980
|
/**
|
|
2507
2981
|
* Export a scoped partition from `vaultName` bounded to the given surface.
|
|
@@ -2584,8 +3058,10 @@ function createLobby(noydb) {
|
|
|
2584
3058
|
NOYDB_MULTI_BUNDLE_MAGIC,
|
|
2585
3059
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
|
|
2586
3060
|
NOYDB_MULTI_BUNDLE_VERSION,
|
|
3061
|
+
PostureViolationError,
|
|
2587
3062
|
ReservedVaultNameError,
|
|
2588
3063
|
ShardProvisioningError,
|
|
3064
|
+
ShareLinkResolutionError,
|
|
2589
3065
|
SurfaceCadenceScheduler,
|
|
2590
3066
|
SurfaceNotFoundError,
|
|
2591
3067
|
SurfaceStateError,
|