@klum-db/lobby 0.4.0-pre.0 → 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 +1 -1
- package/dist/bin/klum.d.cts +1 -2
- package/dist/bin/klum.d.ts +1 -2
- package/dist/index.cjs +653 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +322 -5
- package/dist/index.d.ts +322 -5
- package/dist/index.js +576 -104
- 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 +14 -9
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;
|
|
@@ -140,7 +530,7 @@ async function writeMultiVaultBundle(compartments, opts = {}) {
|
|
|
140
530
|
handle: header.handle,
|
|
141
531
|
exportedAt: c.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
532
|
innerBytes: innerBytes.length,
|
|
143
|
-
innerSha256: await (0,
|
|
533
|
+
innerSha256: await (0, import_cargo2.sha256Hex)(innerBytes)
|
|
144
534
|
};
|
|
145
535
|
if (c.roleTag !== void 0) entry.roleTag = c.roleTag;
|
|
146
536
|
if (c.disclose?.name !== void 0 && c.disclose.name !== false) {
|
|
@@ -163,7 +553,7 @@ async function writeMultiVaultBundle(compartments, opts = {}) {
|
|
|
163
553
|
}
|
|
164
554
|
const manifest = {
|
|
165
555
|
multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
|
|
166
|
-
handle: opts.handle ?? (0,
|
|
556
|
+
handle: opts.handle ?? (0, import_cargo2.generateULID)(),
|
|
167
557
|
compartments: entries
|
|
168
558
|
};
|
|
169
559
|
return encodeMultiBundle(manifest, inner);
|
|
@@ -176,7 +566,7 @@ async function readNoydbBundleManifest(bytes) {
|
|
|
176
566
|
const entry = {
|
|
177
567
|
handle: header.handle,
|
|
178
568
|
innerBytes: bytes.length,
|
|
179
|
-
innerSha256: await (0,
|
|
569
|
+
innerSha256: await (0, import_cargo2.sha256Hex)(bytes)
|
|
180
570
|
};
|
|
181
571
|
if (env !== void 0) entry.publicEnvelope = env;
|
|
182
572
|
return [entry];
|
|
@@ -197,11 +587,11 @@ function readMultiVaultBundleCompartment(bytes, selector) {
|
|
|
197
587
|
if (idx < 0 || idx >= inner.length) throw new Error(`readMultiVaultBundleCompartment: no compartment ${typeof selector === "number" ? `at index ${selector}` : `"${selector}"`}.`);
|
|
198
588
|
return inner[idx];
|
|
199
589
|
}
|
|
200
|
-
var
|
|
590
|
+
var import_cargo2, import_pod, NOYDB_MULTI_BUNDLE_MAGIC, NOYDB_MULTI_BUNDLE_PREFIX_BYTES, NOYDB_MULTI_BUNDLE_VERSION;
|
|
201
591
|
var init_multi_bundle = __esm({
|
|
202
592
|
"src/bundle/multi-bundle.ts"() {
|
|
203
593
|
"use strict";
|
|
204
|
-
|
|
594
|
+
import_cargo2 = require("@noy-db/hub/cargo");
|
|
205
595
|
import_pod = require("@noy-db/hub/pod");
|
|
206
596
|
init_uint32();
|
|
207
597
|
NOYDB_MULTI_BUNDLE_MAGIC = new Uint8Array([78, 68, 66, 77]);
|
|
@@ -275,7 +665,7 @@ async function walkCrossVaultClosure(openVault, opts) {
|
|
|
275
665
|
for (const vaultName of batch) {
|
|
276
666
|
const seeds = perVaultSeeds.get(vaultName);
|
|
277
667
|
const v = await openVault(vaultName);
|
|
278
|
-
const { closure } = await (0,
|
|
668
|
+
const { closure } = await (0, import_cargo3.walkClosure)(v, {
|
|
279
669
|
seeds,
|
|
280
670
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {}
|
|
281
671
|
});
|
|
@@ -327,7 +717,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
327
717
|
const sealIds = {};
|
|
328
718
|
for (const [vaultName, seeds] of plan.perVaultSeeds) {
|
|
329
719
|
const v = await openVault(vaultName);
|
|
330
|
-
const { bundleBytes, transferKey, sealId } = await (0,
|
|
720
|
+
const { bundleBytes, transferKey, sealId } = await (0, import_cargo3.extractPartition)(v, {
|
|
331
721
|
seeds,
|
|
332
722
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {},
|
|
333
723
|
carrySchemas: opts.carrySchemas ?? true,
|
|
@@ -340,7 +730,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
340
730
|
handle: header.handle,
|
|
341
731
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
342
732
|
innerBytes: bundleBytes.length,
|
|
343
|
-
innerSha256: await (0,
|
|
733
|
+
innerSha256: await (0, import_cargo4.sha256Hex)(bundleBytes)
|
|
344
734
|
};
|
|
345
735
|
if (meta?.roleTag !== void 0) entry.roleTag = meta.roleTag;
|
|
346
736
|
if (meta?.disclose?.name !== void 0 && meta.disclose.name !== false) {
|
|
@@ -359,7 +749,7 @@ async function extractCrossVaultPartition(openVault, opts) {
|
|
|
359
749
|
}
|
|
360
750
|
const manifest = {
|
|
361
751
|
multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
|
|
362
|
-
handle: (0,
|
|
752
|
+
handle: (0, import_cargo4.generateULID)(),
|
|
363
753
|
compartments
|
|
364
754
|
};
|
|
365
755
|
return { bundle: encodeMultiBundle(manifest, inner), transferKeys, sealIds };
|
|
@@ -369,7 +759,7 @@ async function describeCrossVaultExtraction(openVault, opts) {
|
|
|
369
759
|
const compartments = [];
|
|
370
760
|
for (const [vaultName, seeds] of plan.perVaultSeeds) {
|
|
371
761
|
const v = await openVault(vaultName);
|
|
372
|
-
const preview = await (0,
|
|
762
|
+
const preview = await (0, import_cargo3.describeExtraction)(v, {
|
|
373
763
|
seeds,
|
|
374
764
|
...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {}
|
|
375
765
|
});
|
|
@@ -377,13 +767,13 @@ async function describeCrossVaultExtraction(openVault, opts) {
|
|
|
377
767
|
}
|
|
378
768
|
return { compartments, dangling: plan.dangling };
|
|
379
769
|
}
|
|
380
|
-
var
|
|
770
|
+
var import_cargo3, import_pod2, import_cargo4, CrossVaultDanglingRefError;
|
|
381
771
|
var init_extract_cross_vault = __esm({
|
|
382
772
|
"src/interchange/extract-cross-vault.ts"() {
|
|
383
773
|
"use strict";
|
|
384
|
-
|
|
774
|
+
import_cargo3 = require("@noy-db/hub/cargo");
|
|
385
775
|
import_pod2 = require("@noy-db/hub/pod");
|
|
386
|
-
|
|
776
|
+
import_cargo4 = require("@noy-db/hub/cargo");
|
|
387
777
|
init_multi_bundle();
|
|
388
778
|
CrossVaultDanglingRefError = class extends Error {
|
|
389
779
|
constructor(dangling) {
|
|
@@ -469,7 +859,7 @@ async function mergeDecryptedRecords(receiver, decrypted, opts) {
|
|
|
469
859
|
incomingSource.set(coll, srcMap);
|
|
470
860
|
incomingSourceTs.set(coll, stsMap);
|
|
471
861
|
}
|
|
472
|
-
const diff = await (0,
|
|
862
|
+
const diff = await (0, import_cargo5.diffVault)(receiver, candidate);
|
|
473
863
|
const byCollection = {};
|
|
474
864
|
const conflicts = [];
|
|
475
865
|
const writes = [];
|
|
@@ -585,15 +975,15 @@ async function mergeDecryptedRecords(receiver, decrypted, opts) {
|
|
|
585
975
|
};
|
|
586
976
|
}
|
|
587
977
|
async function mergeCompartment(receiver, compartmentBytes, opts) {
|
|
588
|
-
const incoming = await (0,
|
|
978
|
+
const incoming = await (0, import_cargo6.decryptExtractedPartition)(compartmentBytes, opts.transferKey);
|
|
589
979
|
return mergeDecryptedRecords(receiver, incoming, opts);
|
|
590
980
|
}
|
|
591
|
-
var
|
|
981
|
+
var import_cargo5, import_cargo6, FieldLevelDeferredError;
|
|
592
982
|
var init_merge_compartment = __esm({
|
|
593
983
|
"src/interchange/merge-compartment.ts"() {
|
|
594
984
|
"use strict";
|
|
595
|
-
|
|
596
|
-
|
|
985
|
+
import_cargo5 = require("@noy-db/hub/cargo");
|
|
986
|
+
import_cargo6 = require("@noy-db/hub/cargo");
|
|
597
987
|
init_field_authority();
|
|
598
988
|
FieldLevelDeferredError = class extends Error {
|
|
599
989
|
constructor(collection) {
|
|
@@ -689,7 +1079,7 @@ async function graduate(noydb, docked, opts) {
|
|
|
689
1079
|
await mergeDecryptedRecords(vault, staged, { strategy: "take-incoming", reason: "dock:graduate" });
|
|
690
1080
|
let deedSealed = false;
|
|
691
1081
|
if (opts.deed) {
|
|
692
|
-
await (0,
|
|
1082
|
+
await (0, import_cargo8.createDeedOwner)(noydb._store, opts.vaultName, opts.deed.ownerId, opts.deed.sealingProvider);
|
|
693
1083
|
deedSealed = true;
|
|
694
1084
|
}
|
|
695
1085
|
if (opts.stateVault) {
|
|
@@ -709,11 +1099,11 @@ async function graduate(noydb, docked, opts) {
|
|
|
709
1099
|
event: { type: "unit-graduated", unitId: docked.unitId, vault: opts.vaultName }
|
|
710
1100
|
};
|
|
711
1101
|
}
|
|
712
|
-
var
|
|
1102
|
+
var import_cargo8, UnitGraduationError, ISO_EPOCH;
|
|
713
1103
|
var init_graduate = __esm({
|
|
714
1104
|
"src/dock/graduate.ts"() {
|
|
715
1105
|
"use strict";
|
|
716
|
-
|
|
1106
|
+
import_cargo8 = require("@noy-db/hub/cargo");
|
|
717
1107
|
init_merge_compartment();
|
|
718
1108
|
init_stage_records();
|
|
719
1109
|
UnitGraduationError = class extends Error {
|
|
@@ -743,7 +1133,7 @@ __export(surface_exports, {
|
|
|
743
1133
|
async function proposeSurface(smv, def, proposedBy, now) {
|
|
744
1134
|
const row = {
|
|
745
1135
|
...def,
|
|
746
|
-
id: def.id ?? (0,
|
|
1136
|
+
id: def.id ?? (0, import_cargo9.generateULID)(),
|
|
747
1137
|
status: "proposed",
|
|
748
1138
|
proposedBy,
|
|
749
1139
|
createdAt: now
|
|
@@ -764,7 +1154,7 @@ async function exportSurface(source, surface) {
|
|
|
764
1154
|
throw new SurfaceStateError(surface.id, surface.status, "agreed");
|
|
765
1155
|
}
|
|
766
1156
|
const seeds = Object.fromEntries(surface.collections.map((c) => [c, () => true]));
|
|
767
|
-
const { bundleBytes, transferKey } = await (0,
|
|
1157
|
+
const { bundleBytes, transferKey } = await (0, import_cargo10.extractPartition)(source, {
|
|
768
1158
|
seeds,
|
|
769
1159
|
maxDepth: 0,
|
|
770
1160
|
...surface.fields ? { fieldProjection: surface.fields } : {},
|
|
@@ -802,12 +1192,12 @@ async function markSynced(smv, id, now) {
|
|
|
802
1192
|
nextSyncDueAt: now + cadenceMs
|
|
803
1193
|
});
|
|
804
1194
|
}
|
|
805
|
-
var
|
|
1195
|
+
var import_cargo9, import_cargo10, SurfaceNotFoundError, SurfaceStateError, SurfaceCadenceScheduler;
|
|
806
1196
|
var init_surface = __esm({
|
|
807
1197
|
"src/interchange/surface.ts"() {
|
|
808
1198
|
"use strict";
|
|
809
|
-
|
|
810
|
-
|
|
1199
|
+
import_cargo9 = require("@noy-db/hub/cargo");
|
|
1200
|
+
import_cargo10 = require("@noy-db/hub/cargo");
|
|
811
1201
|
init_merge_compartment();
|
|
812
1202
|
SurfaceNotFoundError = class extends Error {
|
|
813
1203
|
name = "SurfaceNotFoundError";
|
|
@@ -912,38 +1302,38 @@ function canonical(value) {
|
|
|
912
1302
|
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
913
1303
|
}
|
|
914
1304
|
async function fingerprintBlueprint(bp) {
|
|
915
|
-
return (0,
|
|
1305
|
+
return (0, import_cargo11.sha256Hex)(new TextEncoder().encode(canonical(bp)));
|
|
916
1306
|
}
|
|
917
|
-
var
|
|
1307
|
+
var import_cargo11;
|
|
918
1308
|
var init_schema_manifest = __esm({
|
|
919
1309
|
"src/federation/schema-manifest.ts"() {
|
|
920
1310
|
"use strict";
|
|
921
|
-
|
|
1311
|
+
import_cargo11 = require("@noy-db/hub/cargo");
|
|
922
1312
|
}
|
|
923
1313
|
});
|
|
924
1314
|
|
|
925
1315
|
// src/federation/constants.ts
|
|
926
|
-
var
|
|
1316
|
+
var import_cargo12;
|
|
927
1317
|
var init_constants = __esm({
|
|
928
1318
|
"src/federation/constants.ts"() {
|
|
929
1319
|
"use strict";
|
|
930
|
-
|
|
1320
|
+
import_cargo12 = require("@noy-db/hub/cargo");
|
|
931
1321
|
}
|
|
932
1322
|
});
|
|
933
1323
|
|
|
934
1324
|
// src/federation/state-vault.ts
|
|
935
1325
|
var state_vault_exports = {};
|
|
936
1326
|
__export(state_vault_exports, {
|
|
937
|
-
STATE_VAULT_NAME: () =>
|
|
1327
|
+
STATE_VAULT_NAME: () => import_cargo12.STATE_VAULT_NAME,
|
|
938
1328
|
StateManagementVault: () => StateManagementVault
|
|
939
1329
|
});
|
|
940
|
-
var
|
|
1330
|
+
var import_cargo13, REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, SURFACES, StateManagementVault;
|
|
941
1331
|
var init_state_vault = __esm({
|
|
942
1332
|
"src/federation/state-vault.ts"() {
|
|
943
1333
|
"use strict";
|
|
944
1334
|
init_schema_manifest();
|
|
945
1335
|
init_constants();
|
|
946
|
-
|
|
1336
|
+
import_cargo13 = require("@noy-db/hub/cargo");
|
|
947
1337
|
init_constants();
|
|
948
1338
|
REGISTRY = "vaultRegistry";
|
|
949
1339
|
MANIFEST = "schemaManifest";
|
|
@@ -973,7 +1363,7 @@ var init_state_vault = __esm({
|
|
|
973
1363
|
#surfaces;
|
|
974
1364
|
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
975
1365
|
static async open(db) {
|
|
976
|
-
const vault = await db.openVault(
|
|
1366
|
+
const vault = await db.openVault(import_cargo12.STATE_VAULT_NAME);
|
|
977
1367
|
return new _StateManagementVault(
|
|
978
1368
|
vault.collection(REGISTRY),
|
|
979
1369
|
vault.collection(MANIFEST),
|
|
@@ -1033,7 +1423,7 @@ var init_state_vault = __esm({
|
|
|
1033
1423
|
*/
|
|
1034
1424
|
async appendEvent(event) {
|
|
1035
1425
|
const ts = event.ts ?? Date.now();
|
|
1036
|
-
const id = (0,
|
|
1426
|
+
const id = (0, import_cargo13.generateULID)();
|
|
1037
1427
|
await this.#events.put(id, { ...event, id, ts });
|
|
1038
1428
|
}
|
|
1039
1429
|
/**
|
|
@@ -1075,13 +1465,13 @@ var init_state_vault = __esm({
|
|
|
1075
1465
|
|
|
1076
1466
|
// src/federation/classify-skip.ts
|
|
1077
1467
|
function classifyShardSkip(err) {
|
|
1078
|
-
return err instanceof
|
|
1468
|
+
return err instanceof import_cargo14.NoAccessError ? "no-grant" : "error";
|
|
1079
1469
|
}
|
|
1080
|
-
var
|
|
1470
|
+
var import_cargo14;
|
|
1081
1471
|
var init_classify_skip = __esm({
|
|
1082
1472
|
"src/federation/classify-skip.ts"() {
|
|
1083
1473
|
"use strict";
|
|
1084
|
-
|
|
1474
|
+
import_cargo14 = require("@noy-db/hub/cargo");
|
|
1085
1475
|
}
|
|
1086
1476
|
});
|
|
1087
1477
|
|
|
@@ -1106,7 +1496,7 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1106
1496
|
for (const leg of legs) {
|
|
1107
1497
|
const map = /* @__PURE__ */ new Map();
|
|
1108
1498
|
for (const rec of await leg.from.list()) {
|
|
1109
|
-
const k = coerceKey((0,
|
|
1499
|
+
const k = coerceKey((0, import_cargo15.readPath)(rec, leg.on));
|
|
1110
1500
|
if (k !== null && !map.has(k)) map.set(k, rec);
|
|
1111
1501
|
}
|
|
1112
1502
|
indexes.push({ leg, map });
|
|
@@ -1114,7 +1504,7 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1114
1504
|
return rows.map((row) => {
|
|
1115
1505
|
const out = { ...row };
|
|
1116
1506
|
for (const { leg, map } of indexes) {
|
|
1117
|
-
const key = coerceKey((0,
|
|
1507
|
+
const key = coerceKey((0, import_cargo15.readPath)(row, leg.field));
|
|
1118
1508
|
const match = key === null ? null : map.get(key) ?? null;
|
|
1119
1509
|
if (match === null && leg.mode === "warn") {
|
|
1120
1510
|
warnOnceBroadcastMiss(leg.field, leg.as, key ?? "<null>");
|
|
@@ -1124,11 +1514,11 @@ async function applyBroadcastLegs(rows, legs) {
|
|
|
1124
1514
|
return out;
|
|
1125
1515
|
});
|
|
1126
1516
|
}
|
|
1127
|
-
var
|
|
1517
|
+
var import_cargo15, warnedBroadcastKeys;
|
|
1128
1518
|
var init_cross_shard_join = __esm({
|
|
1129
1519
|
"src/federation/cross-shard-join.ts"() {
|
|
1130
1520
|
"use strict";
|
|
1131
|
-
|
|
1521
|
+
import_cargo15 = require("@noy-db/hub/cargo");
|
|
1132
1522
|
warnedBroadcastKeys = /* @__PURE__ */ new Set();
|
|
1133
1523
|
}
|
|
1134
1524
|
});
|
|
@@ -1222,85 +1612,6 @@ var init_cross_vault_live = __esm({
|
|
|
1222
1612
|
}
|
|
1223
1613
|
});
|
|
1224
1614
|
|
|
1225
|
-
// src/federation/insight-auto-push.ts
|
|
1226
|
-
var InsightAutoPush;
|
|
1227
|
-
var init_insight_auto_push = __esm({
|
|
1228
|
-
"src/federation/insight-auto-push.ts"() {
|
|
1229
|
-
"use strict";
|
|
1230
|
-
InsightAutoPush = class {
|
|
1231
|
-
constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err), debounceMs) {
|
|
1232
|
-
this.recompute = recompute;
|
|
1233
|
-
this.isSource = isSource;
|
|
1234
|
-
this.onError = onError;
|
|
1235
|
-
this.debounceMs = debounceMs;
|
|
1236
|
-
}
|
|
1237
|
-
recompute;
|
|
1238
|
-
isSource;
|
|
1239
|
-
onError;
|
|
1240
|
-
debounceMs;
|
|
1241
|
-
dirty = /* @__PURE__ */ new Set();
|
|
1242
|
-
pending = null;
|
|
1243
|
-
timer = null;
|
|
1244
|
-
resolvePending = null;
|
|
1245
|
-
flushing = false;
|
|
1246
|
-
/** Called from a shard's onAfterWrite hook. Marks the shard dirty + schedules a flush. */
|
|
1247
|
-
noteWrite(partitionKey, collection) {
|
|
1248
|
-
if (!this.isSource(collection)) return;
|
|
1249
|
-
this.dirty.add(partitionKey);
|
|
1250
|
-
if (this.debounceMs !== void 0) this.scheduleDebounced();
|
|
1251
|
-
else this.schedule();
|
|
1252
|
-
}
|
|
1253
|
-
/** Resolve once no flush is pending (drains rescheduled flushes too). */
|
|
1254
|
-
async whenSettled() {
|
|
1255
|
-
while (this.pending) await this.pending;
|
|
1256
|
-
}
|
|
1257
|
-
// ── microtask path (unchanged default) ──
|
|
1258
|
-
schedule() {
|
|
1259
|
-
if (this.pending) return;
|
|
1260
|
-
this.pending = this.runFlush().finally(() => {
|
|
1261
|
-
this.pending = null;
|
|
1262
|
-
if (this.dirty.size > 0) this.schedule();
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
// ── debounce path (#13) ──
|
|
1266
|
-
scheduleDebounced() {
|
|
1267
|
-
if (!this.pending) this.pending = new Promise((r) => {
|
|
1268
|
-
this.resolvePending = r;
|
|
1269
|
-
});
|
|
1270
|
-
if (this.timer) clearTimeout(this.timer);
|
|
1271
|
-
this.timer = setTimeout(() => {
|
|
1272
|
-
this.timer = null;
|
|
1273
|
-
if (this.flushing) return;
|
|
1274
|
-
this.flushing = true;
|
|
1275
|
-
void this.runFlush().finally(() => {
|
|
1276
|
-
this.flushing = false;
|
|
1277
|
-
if (this.dirty.size > 0) {
|
|
1278
|
-
this.scheduleDebounced();
|
|
1279
|
-
} else {
|
|
1280
|
-
const resolve = this.resolvePending;
|
|
1281
|
-
this.resolvePending = null;
|
|
1282
|
-
this.pending = null;
|
|
1283
|
-
resolve?.();
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1286
|
-
}, this.debounceMs);
|
|
1287
|
-
}
|
|
1288
|
-
async runFlush() {
|
|
1289
|
-
await Promise.resolve();
|
|
1290
|
-
const pks = [...this.dirty];
|
|
1291
|
-
this.dirty.clear();
|
|
1292
|
-
for (const pk of pks) {
|
|
1293
|
-
try {
|
|
1294
|
-
await this.recompute(pk);
|
|
1295
|
-
} catch (err) {
|
|
1296
|
-
this.onError(err, pk);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
1615
|
// src/federation/partial-reduce.ts
|
|
1305
1616
|
function canPartialReduce(spec) {
|
|
1306
1617
|
return Object.values(spec).every((r) => typeof r.merge === "function");
|
|
@@ -1339,12 +1650,12 @@ var init_partial_reduce = __esm({
|
|
|
1339
1650
|
});
|
|
1340
1651
|
|
|
1341
1652
|
// src/federation/aggregate-across.ts
|
|
1342
|
-
var
|
|
1653
|
+
var import_cargo16, import_cargo17, CrossVaultAggregation, CrossVaultGroupedAggregation;
|
|
1343
1654
|
var init_aggregate_across = __esm({
|
|
1344
1655
|
"src/federation/aggregate-across.ts"() {
|
|
1345
1656
|
"use strict";
|
|
1346
|
-
|
|
1347
|
-
|
|
1657
|
+
import_cargo16 = require("@noy-db/hub/cargo");
|
|
1658
|
+
import_cargo17 = require("@noy-db/hub/cargo");
|
|
1348
1659
|
init_cross_vault_live();
|
|
1349
1660
|
init_partial_reduce();
|
|
1350
1661
|
CrossVaultAggregation = class {
|
|
@@ -1362,7 +1673,7 @@ var init_aggregate_across = __esm({
|
|
|
1362
1673
|
return { result: finalizePartial(this.spec, mergePartials(this.spec, partials)), skippedVaults: skippedVaults2 };
|
|
1363
1674
|
}
|
|
1364
1675
|
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
1365
|
-
return { result: (0,
|
|
1676
|
+
return { result: (0, import_cargo16.reduceRecords)(records, this.spec), skippedVaults };
|
|
1366
1677
|
}
|
|
1367
1678
|
live(options = {}) {
|
|
1368
1679
|
if (!this.bind) throw new Error("CrossVaultAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.aggregate()");
|
|
@@ -1373,7 +1684,7 @@ var init_aggregate_across = __esm({
|
|
|
1373
1684
|
isRelevant: this.bind.isRelevant,
|
|
1374
1685
|
compute: async () => {
|
|
1375
1686
|
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
1376
|
-
return { value: (0,
|
|
1687
|
+
return { value: (0, import_cargo16.reduceRecords)(records, spec), skipped: skippedVaults };
|
|
1377
1688
|
},
|
|
1378
1689
|
initialSnapshot: { value: void 0, skipped: [] },
|
|
1379
1690
|
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
@@ -1408,7 +1719,7 @@ var init_aggregate_across = __esm({
|
|
|
1408
1719
|
async run(options = {}) {
|
|
1409
1720
|
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
1410
1721
|
return {
|
|
1411
|
-
results: (0,
|
|
1722
|
+
results: (0, import_cargo17.groupAndReduce)(records, this.field, this.spec),
|
|
1412
1723
|
skippedVaults
|
|
1413
1724
|
};
|
|
1414
1725
|
}
|
|
@@ -1423,7 +1734,7 @@ var init_aggregate_across = __esm({
|
|
|
1423
1734
|
compute: async () => {
|
|
1424
1735
|
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
1425
1736
|
return {
|
|
1426
|
-
records: (0,
|
|
1737
|
+
records: (0, import_cargo17.groupAndReduce)(records, field, spec),
|
|
1427
1738
|
skipped: skippedVaults
|
|
1428
1739
|
};
|
|
1429
1740
|
},
|
|
@@ -1496,7 +1807,7 @@ async function retrieveAcross(group, collectionName, query, opts = {}) {
|
|
|
1496
1807
|
}
|
|
1497
1808
|
return pv.hits.map((h) => ({ ...h, id: pv.vault + SEP + h.id }));
|
|
1498
1809
|
});
|
|
1499
|
-
const fused = (0,
|
|
1810
|
+
const fused = (0, import_cargo18.fuseRetrieval)(lists, {
|
|
1500
1811
|
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
1501
1812
|
...opts.rrfK !== void 0 ? { k: opts.rrfK } : {}
|
|
1502
1813
|
});
|
|
@@ -1506,11 +1817,11 @@ async function retrieveAcross(group, collectionName, query, opts = {}) {
|
|
|
1506
1817
|
});
|
|
1507
1818
|
return { hits, skippedVaults };
|
|
1508
1819
|
}
|
|
1509
|
-
var
|
|
1820
|
+
var import_cargo18, SEP;
|
|
1510
1821
|
var init_retrieve_across = __esm({
|
|
1511
1822
|
"src/federation/retrieve-across.ts"() {
|
|
1512
1823
|
"use strict";
|
|
1513
|
-
|
|
1824
|
+
import_cargo18 = require("@noy-db/hub/cargo");
|
|
1514
1825
|
init_classify_skip();
|
|
1515
1826
|
SEP = "\0";
|
|
1516
1827
|
}
|
|
@@ -1530,30 +1841,31 @@ function autoPushConfig(spec) {
|
|
|
1530
1841
|
}
|
|
1531
1842
|
function assertSafePartitionKey(partitionKey) {
|
|
1532
1843
|
if (partitionKey.length === 0) {
|
|
1533
|
-
throw new
|
|
1844
|
+
throw new import_cargo19.ValidationError("partitionKey must be a non-empty string");
|
|
1534
1845
|
}
|
|
1535
|
-
if (partitionKey ===
|
|
1536
|
-
throw new
|
|
1846
|
+
if (partitionKey === import_cargo12.STATE_VAULT_NAME) {
|
|
1847
|
+
throw new import_cargo19.ReservedVaultNameError(partitionKey);
|
|
1537
1848
|
}
|
|
1538
1849
|
if (!SAFE_PARTITION_KEY.test(partitionKey)) {
|
|
1539
|
-
throw new
|
|
1850
|
+
throw new import_cargo19.ValidationError(
|
|
1540
1851
|
`partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
|
|
1541
1852
|
);
|
|
1542
1853
|
}
|
|
1543
1854
|
if (partitionKey.includes(SHARD_SEPARATOR)) {
|
|
1544
|
-
throw new
|
|
1855
|
+
throw new import_cargo19.ValidationError(
|
|
1545
1856
|
`partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
|
|
1546
1857
|
);
|
|
1547
1858
|
}
|
|
1548
1859
|
}
|
|
1549
|
-
var
|
|
1860
|
+
var import_cargo19, SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
|
|
1550
1861
|
var init_vault_group = __esm({
|
|
1551
1862
|
"src/federation/vault-group.ts"() {
|
|
1552
1863
|
"use strict";
|
|
1553
1864
|
init_state_vault();
|
|
1554
|
-
|
|
1865
|
+
import_cargo19 = require("@noy-db/hub/cargo");
|
|
1555
1866
|
init_constants();
|
|
1556
1867
|
init_classify_skip();
|
|
1868
|
+
init_read_model();
|
|
1557
1869
|
init_cross_shard_join();
|
|
1558
1870
|
init_cross_vault_live();
|
|
1559
1871
|
init_insight_auto_push();
|
|
@@ -1572,7 +1884,7 @@ var init_vault_group = __esm({
|
|
|
1572
1884
|
this.cutoverOnOpen = cutoverOnOpen;
|
|
1573
1885
|
this.meta = meta;
|
|
1574
1886
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
1575
|
-
throw new
|
|
1887
|
+
throw new import_cargo19.ValidationError(
|
|
1576
1888
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
1577
1889
|
);
|
|
1578
1890
|
}
|
|
@@ -1677,11 +1989,11 @@ var init_vault_group = __esm({
|
|
|
1677
1989
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1678
1990
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1679
1991
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1680
|
-
if (row && !provisioned) throw new
|
|
1992
|
+
if (row && !provisioned) throw new import_cargo19.ShardProvisioningError(vaultId, partitionKey);
|
|
1681
1993
|
if (row && provisioned) return this.openShard(partitionKey);
|
|
1682
1994
|
if (region !== void 0) {
|
|
1683
1995
|
const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
|
|
1684
|
-
if (backendRegion !== region) throw new
|
|
1996
|
+
if (backendRegion !== region) throw new import_cargo19.DataResidencyError(vaultId, region, backendRegion);
|
|
1685
1997
|
}
|
|
1686
1998
|
const vault = await this.db.openVault(vaultId);
|
|
1687
1999
|
this.template.configure(vault);
|
|
@@ -1715,9 +2027,9 @@ var init_vault_group = __esm({
|
|
|
1715
2027
|
async shard(partitionKey) {
|
|
1716
2028
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1717
2029
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1718
|
-
if (!row) throw new
|
|
2030
|
+
if (!row) throw new import_cargo19.UnknownShardError(partitionKey, this.name);
|
|
1719
2031
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1720
|
-
if (!provisioned) throw new
|
|
2032
|
+
if (!provisioned) throw new import_cargo19.ShardProvisioningError(vaultId, partitionKey);
|
|
1721
2033
|
return this.openShard(partitionKey);
|
|
1722
2034
|
}
|
|
1723
2035
|
/** A sharded view over one logical collection across all shards. */
|
|
@@ -1749,7 +2061,7 @@ var init_vault_group = __esm({
|
|
|
1749
2061
|
for (const p of probes) {
|
|
1750
2062
|
if ("error" in p) skipped.push({ vaultId: p.row.vaultId, reason: "error", error: p.error });
|
|
1751
2063
|
else if (p.provisioned) eligible.push(p.row);
|
|
1752
|
-
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) });
|
|
1753
2065
|
}
|
|
1754
2066
|
return { eligible, skipped };
|
|
1755
2067
|
}
|
|
@@ -1786,7 +2098,7 @@ var init_vault_group = __esm({
|
|
|
1786
2098
|
withCrossVaultDerivation(spec) {
|
|
1787
2099
|
const target = spec.target.vault;
|
|
1788
2100
|
if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
|
|
1789
|
-
throw new
|
|
2101
|
+
throw new import_cargo19.ValidationError(
|
|
1790
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.`
|
|
1791
2103
|
);
|
|
1792
2104
|
}
|
|
@@ -1846,12 +2158,7 @@ var init_vault_group = __esm({
|
|
|
1846
2158
|
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
1847
2159
|
continue;
|
|
1848
2160
|
}
|
|
1849
|
-
const
|
|
1850
|
-
vaultId: row.vaultId,
|
|
1851
|
-
partitionKey: row.partitionKey,
|
|
1852
|
-
schemaVersion: row.schemaVersion
|
|
1853
|
-
};
|
|
1854
|
-
const summary = spec.derive(res.result, ctx);
|
|
2161
|
+
const summary = deriveShardSummary(spec, res.result, row);
|
|
1855
2162
|
await out.put(row.partitionKey, summary);
|
|
1856
2163
|
written++;
|
|
1857
2164
|
}
|
|
@@ -1871,17 +2178,12 @@ var init_vault_group = __esm({
|
|
|
1871
2178
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1872
2179
|
if (!row) return;
|
|
1873
2180
|
const shard = await this.openShard(partitionKey);
|
|
1874
|
-
const ctx = {
|
|
1875
|
-
vaultId: row.vaultId,
|
|
1876
|
-
partitionKey,
|
|
1877
|
-
schemaVersion: row.schemaVersion
|
|
1878
|
-
};
|
|
1879
2181
|
for (const spec of this.crossVaultDerivations) {
|
|
1880
2182
|
if (!spec.autoPush) continue;
|
|
1881
2183
|
const min = autoPushConfig(spec)?.minVersion;
|
|
1882
2184
|
if (min !== void 0 && row.schemaVersion < min) continue;
|
|
1883
2185
|
const records = await shard.collection(spec.source).list();
|
|
1884
|
-
const summary = spec
|
|
2186
|
+
const summary = deriveShardSummary(spec, records, row);
|
|
1885
2187
|
const insight = await this.db.openVault(spec.target.vault);
|
|
1886
2188
|
await insight.collection(spec.target.collection).put(partitionKey, summary);
|
|
1887
2189
|
}
|
|
@@ -1912,7 +2214,7 @@ var init_vault_group = __esm({
|
|
|
1912
2214
|
async cutoverShard(partitionKey) {
|
|
1913
2215
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1914
2216
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1915
|
-
if (!row) throw new
|
|
2217
|
+
if (!row) throw new import_cargo19.UnknownShardError(partitionKey, this.name);
|
|
1916
2218
|
const target = this.template.version;
|
|
1917
2219
|
const sv = await this.ensureStateVault();
|
|
1918
2220
|
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
@@ -1995,7 +2297,7 @@ var init_vault_group = __esm({
|
|
|
1995
2297
|
let vault;
|
|
1996
2298
|
if (!row) {
|
|
1997
2299
|
if (this.group.sharding.autoCreate === false) {
|
|
1998
|
-
throw new
|
|
2300
|
+
throw new import_cargo19.UnknownShardError(key, this.group.name);
|
|
1999
2301
|
}
|
|
2000
2302
|
vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
|
|
2001
2303
|
} else {
|
|
@@ -2078,7 +2380,7 @@ var init_vault_group = __esm({
|
|
|
2078
2380
|
this.group.template.configure(probe);
|
|
2079
2381
|
for (const leg of this.coPartitionedLegs) {
|
|
2080
2382
|
if (!probe.resolveRef(this.collectionName, leg.field)) {
|
|
2081
|
-
throw new
|
|
2383
|
+
throw new import_cargo19.CrossShardJoinError(
|
|
2082
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.`
|
|
2083
2385
|
);
|
|
2084
2386
|
}
|
|
@@ -2240,13 +2542,117 @@ var init_vault_group = __esm({
|
|
|
2240
2542
|
}
|
|
2241
2543
|
});
|
|
2242
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
|
+
|
|
2243
2649
|
// src/index.ts
|
|
2244
2650
|
var index_exports = {};
|
|
2245
2651
|
__export(index_exports, {
|
|
2246
|
-
CrossShardJoinError: () =>
|
|
2652
|
+
CrossShardJoinError: () => import_cargo22.CrossShardJoinError,
|
|
2247
2653
|
CrossVaultDanglingRefError: () => CrossVaultDanglingRefError,
|
|
2248
|
-
CustodyApi: () =>
|
|
2249
|
-
DataResidencyError: () =>
|
|
2654
|
+
CustodyApi: () => import_cargo23.CustodyApi,
|
|
2655
|
+
DataResidencyError: () => import_cargo22.DataResidencyError,
|
|
2250
2656
|
DockedUnit: () => DockedUnit,
|
|
2251
2657
|
FieldAuthorityPolicyMissingError: () => FieldAuthorityPolicyMissingError,
|
|
2252
2658
|
FieldLevelDeferredError: () => FieldLevelDeferredError,
|
|
@@ -2257,17 +2663,19 @@ __export(index_exports, {
|
|
|
2257
2663
|
NOYDB_MULTI_BUNDLE_MAGIC: () => NOYDB_MULTI_BUNDLE_MAGIC,
|
|
2258
2664
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES: () => NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
|
|
2259
2665
|
NOYDB_MULTI_BUNDLE_VERSION: () => NOYDB_MULTI_BUNDLE_VERSION,
|
|
2260
|
-
|
|
2261
|
-
|
|
2666
|
+
PostureViolationError: () => PostureViolationError,
|
|
2667
|
+
ReservedVaultNameError: () => import_cargo22.ReservedVaultNameError,
|
|
2668
|
+
ShardProvisioningError: () => import_cargo22.ShardProvisioningError,
|
|
2669
|
+
ShareLinkResolutionError: () => ShareLinkResolutionError,
|
|
2262
2670
|
SurfaceCadenceScheduler: () => SurfaceCadenceScheduler,
|
|
2263
2671
|
SurfaceNotFoundError: () => SurfaceNotFoundError,
|
|
2264
2672
|
SurfaceStateError: () => SurfaceStateError,
|
|
2265
2673
|
UnitGraduationError: () => UnitGraduationError,
|
|
2266
|
-
UnknownShardError: () =>
|
|
2267
|
-
VaultTemplateNotFoundError: () =>
|
|
2674
|
+
UnknownShardError: () => import_cargo22.UnknownShardError,
|
|
2675
|
+
VaultTemplateNotFoundError: () => import_cargo22.VaultTemplateNotFoundError,
|
|
2268
2676
|
agreeSurface: () => agreeSurface,
|
|
2269
2677
|
applySurface: () => applySurface,
|
|
2270
|
-
createDeedOwner: () =>
|
|
2678
|
+
createDeedOwner: () => import_cargo23.createDeedOwner,
|
|
2271
2679
|
createLobby: () => createLobby,
|
|
2272
2680
|
decodeMultiBundle: () => decodeMultiBundle,
|
|
2273
2681
|
describeCrossVaultExtraction: () => describeCrossVaultExtraction,
|
|
@@ -2275,11 +2683,11 @@ __export(index_exports, {
|
|
|
2275
2683
|
exportSurface: () => exportSurface,
|
|
2276
2684
|
extractCrossVaultPartition: () => extractCrossVaultPartition,
|
|
2277
2685
|
groupInspector: () => groupInspector,
|
|
2278
|
-
isDeedVault: () =>
|
|
2686
|
+
isDeedVault: () => import_cargo23.isDeedVault,
|
|
2279
2687
|
isSurfaceDue: () => isSurfaceDue,
|
|
2280
|
-
liberateVault: () =>
|
|
2688
|
+
liberateVault: () => import_cargo23.liberateVault,
|
|
2281
2689
|
listDueSurfaces: () => listDueSurfaces,
|
|
2282
|
-
loadDeedMarker: () =>
|
|
2690
|
+
loadDeedMarker: () => import_cargo23.loadDeedMarker,
|
|
2283
2691
|
markSynced: () => markSynced,
|
|
2284
2692
|
mergeCompartment: () => mergeCompartment,
|
|
2285
2693
|
mergeDecryptedRecords: () => mergeDecryptedRecords,
|
|
@@ -2294,7 +2702,7 @@ __export(index_exports, {
|
|
|
2294
2702
|
writeMultiVaultBundle: () => writeMultiVaultBundle
|
|
2295
2703
|
});
|
|
2296
2704
|
module.exports = __toCommonJS(index_exports);
|
|
2297
|
-
var
|
|
2705
|
+
var import_cargo21 = require("@noy-db/hub/cargo");
|
|
2298
2706
|
|
|
2299
2707
|
// src/dock/docked-unit.ts
|
|
2300
2708
|
var DockedUnit = class {
|
|
@@ -2313,6 +2721,10 @@ var DockedUnit = class {
|
|
|
2313
2721
|
}
|
|
2314
2722
|
};
|
|
2315
2723
|
|
|
2724
|
+
// src/index.ts
|
|
2725
|
+
init_share_link();
|
|
2726
|
+
init_read_model();
|
|
2727
|
+
|
|
2316
2728
|
// src/federation/group-inspector.ts
|
|
2317
2729
|
function groupInspector(group) {
|
|
2318
2730
|
let shardIds = /* @__PURE__ */ new Set();
|
|
@@ -2376,13 +2788,13 @@ async function meterGroup(group, opts = {}) {
|
|
|
2376
2788
|
}
|
|
2377
2789
|
|
|
2378
2790
|
// src/index.ts
|
|
2379
|
-
var
|
|
2791
|
+
var import_cargo22 = require("@noy-db/hub/cargo");
|
|
2380
2792
|
init_multi_bundle();
|
|
2381
2793
|
init_extract_cross_vault();
|
|
2382
2794
|
init_merge_compartment();
|
|
2383
2795
|
|
|
2384
2796
|
// src/interchange/migrate-then-merge.ts
|
|
2385
|
-
var
|
|
2797
|
+
var import_cargo7 = require("@noy-db/hub/cargo");
|
|
2386
2798
|
init_merge_compartment();
|
|
2387
2799
|
init_stage_records();
|
|
2388
2800
|
init_stage_records();
|
|
@@ -2407,7 +2819,7 @@ async function migrateThenMerge(receiver, compartmentBytes, opts) {
|
|
|
2407
2819
|
);
|
|
2408
2820
|
}
|
|
2409
2821
|
if (fromVersion > toVersion) throw new MinVersionError(fromVersion, toVersion);
|
|
2410
|
-
const incoming = await (0,
|
|
2822
|
+
const incoming = await (0, import_cargo7.decryptExtractedPartition)(compartmentBytes, opts.transferKey);
|
|
2411
2823
|
const transformsByCollection = {};
|
|
2412
2824
|
const migrationByCollection = {};
|
|
2413
2825
|
for (const [coll, recs] of Object.entries(incoming)) {
|
|
@@ -2430,7 +2842,7 @@ async function migrateThenMerge(receiver, compartmentBytes, opts) {
|
|
|
2430
2842
|
|
|
2431
2843
|
// src/index.ts
|
|
2432
2844
|
init_field_authority();
|
|
2433
|
-
var
|
|
2845
|
+
var import_cargo23 = require("@noy-db/hub/cargo");
|
|
2434
2846
|
|
|
2435
2847
|
// src/dock/unit-driver.ts
|
|
2436
2848
|
var InMemoryUnitDriver = class {
|
|
@@ -2464,10 +2876,10 @@ var Lobby = class {
|
|
|
2464
2876
|
}
|
|
2465
2877
|
async openVaultGroup(name, opts) {
|
|
2466
2878
|
const db = this.noydb;
|
|
2467
|
-
if (db.isClosed) throw new
|
|
2468
|
-
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);
|
|
2469
2881
|
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
2470
|
-
if (!template) throw new
|
|
2882
|
+
if (!template) throw new import_cargo21.VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
2471
2883
|
const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
|
|
2472
2884
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2473
2885
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(db);
|
|
@@ -2486,7 +2898,7 @@ var Lobby = class {
|
|
|
2486
2898
|
}
|
|
2487
2899
|
async openStateManagementVault() {
|
|
2488
2900
|
const db = this.noydb;
|
|
2489
|
-
if (db.isClosed) throw new
|
|
2901
|
+
if (db.isClosed) throw new import_cargo21.ValidationError("Instance is closed");
|
|
2490
2902
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2491
2903
|
return StateManagementVault2.open(db);
|
|
2492
2904
|
}
|
|
@@ -2506,6 +2918,64 @@ var Lobby = class {
|
|
|
2506
2918
|
const { graduate: graduateFn } = await Promise.resolve().then(() => (init_graduate(), graduate_exports));
|
|
2507
2919
|
return graduateFn(this.noydb, docked, opts);
|
|
2508
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
|
+
}
|
|
2509
2979
|
// ─── FR-7 Surface API ─────────────────────────────────────────────────────
|
|
2510
2980
|
/**
|
|
2511
2981
|
* Export a scoped partition from `vaultName` bounded to the given surface.
|
|
@@ -2588,8 +3058,10 @@ function createLobby(noydb) {
|
|
|
2588
3058
|
NOYDB_MULTI_BUNDLE_MAGIC,
|
|
2589
3059
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
|
|
2590
3060
|
NOYDB_MULTI_BUNDLE_VERSION,
|
|
3061
|
+
PostureViolationError,
|
|
2591
3062
|
ReservedVaultNameError,
|
|
2592
3063
|
ShardProvisioningError,
|
|
3064
|
+
ShareLinkResolutionError,
|
|
2593
3065
|
SurfaceCadenceScheduler,
|
|
2594
3066
|
SurfaceNotFoundError,
|
|
2595
3067
|
SurfaceStateError,
|