@klum-db/lobby 0.2.0-pre.31 → 0.2.0-pre.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/klum.d.cts +1 -1
- package/dist/bin/klum.d.ts +1 -1
- package/dist/index.cjs +206 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +178 -22
- package/dist/index.js.map +1 -1
- package/dist/{vault-group-DqEyXbN1.d.cts → vault-group-SfkQJ3CM.d.cts} +87 -7
- package/dist/{vault-group-DqEyXbN1.d.ts → vault-group-SfkQJ3CM.d.ts} +87 -7
- package/package.json +11 -11
package/dist/bin/klum.d.cts
CHANGED
package/dist/bin/klum.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1223,28 +1223,33 @@ var init_insight_auto_push = __esm({
|
|
|
1223
1223
|
"src/federation/insight-auto-push.ts"() {
|
|
1224
1224
|
"use strict";
|
|
1225
1225
|
InsightAutoPush = class {
|
|
1226
|
-
constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err)) {
|
|
1226
|
+
constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err), debounceMs) {
|
|
1227
1227
|
this.recompute = recompute;
|
|
1228
1228
|
this.isSource = isSource;
|
|
1229
1229
|
this.onError = onError;
|
|
1230
|
+
this.debounceMs = debounceMs;
|
|
1230
1231
|
}
|
|
1231
1232
|
recompute;
|
|
1232
1233
|
isSource;
|
|
1233
1234
|
onError;
|
|
1234
|
-
|
|
1235
|
+
debounceMs;
|
|
1235
1236
|
dirty = /* @__PURE__ */ new Set();
|
|
1236
|
-
/** The in-flight flush, or null when idle. */
|
|
1237
1237
|
pending = null;
|
|
1238
|
+
timer = null;
|
|
1239
|
+
resolvePending = null;
|
|
1240
|
+
flushing = false;
|
|
1238
1241
|
/** Called from a shard's onAfterWrite hook. Marks the shard dirty + schedules a flush. */
|
|
1239
1242
|
noteWrite(partitionKey, collection) {
|
|
1240
1243
|
if (!this.isSource(collection)) return;
|
|
1241
1244
|
this.dirty.add(partitionKey);
|
|
1242
|
-
this.
|
|
1245
|
+
if (this.debounceMs !== void 0) this.scheduleDebounced();
|
|
1246
|
+
else this.schedule();
|
|
1243
1247
|
}
|
|
1244
1248
|
/** Resolve once no flush is pending (drains rescheduled flushes too). */
|
|
1245
1249
|
async whenSettled() {
|
|
1246
1250
|
while (this.pending) await this.pending;
|
|
1247
1251
|
}
|
|
1252
|
+
// ── microtask path (unchanged default) ──
|
|
1248
1253
|
schedule() {
|
|
1249
1254
|
if (this.pending) return;
|
|
1250
1255
|
this.pending = this.runFlush().finally(() => {
|
|
@@ -1252,6 +1257,29 @@ var init_insight_auto_push = __esm({
|
|
|
1252
1257
|
if (this.dirty.size > 0) this.schedule();
|
|
1253
1258
|
});
|
|
1254
1259
|
}
|
|
1260
|
+
// ── debounce path (#13) ──
|
|
1261
|
+
scheduleDebounced() {
|
|
1262
|
+
if (!this.pending) this.pending = new Promise((r) => {
|
|
1263
|
+
this.resolvePending = r;
|
|
1264
|
+
});
|
|
1265
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1266
|
+
this.timer = setTimeout(() => {
|
|
1267
|
+
this.timer = null;
|
|
1268
|
+
if (this.flushing) return;
|
|
1269
|
+
this.flushing = true;
|
|
1270
|
+
void this.runFlush().finally(() => {
|
|
1271
|
+
this.flushing = false;
|
|
1272
|
+
if (this.dirty.size > 0) {
|
|
1273
|
+
this.scheduleDebounced();
|
|
1274
|
+
} else {
|
|
1275
|
+
const resolve = this.resolvePending;
|
|
1276
|
+
this.resolvePending = null;
|
|
1277
|
+
this.pending = null;
|
|
1278
|
+
resolve?.();
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
}, this.debounceMs);
|
|
1282
|
+
}
|
|
1255
1283
|
async runFlush() {
|
|
1256
1284
|
await Promise.resolve();
|
|
1257
1285
|
const pks = [...this.dirty];
|
|
@@ -1416,6 +1444,73 @@ var init_aggregate_across = __esm({
|
|
|
1416
1444
|
}
|
|
1417
1445
|
});
|
|
1418
1446
|
|
|
1447
|
+
// src/federation/retrieve-across.ts
|
|
1448
|
+
async function retrieveAcross(group, collectionName, query, opts = {}) {
|
|
1449
|
+
const { eligible, skipped } = await group.resolveEligible({
|
|
1450
|
+
...opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {},
|
|
1451
|
+
...opts.failFast !== void 0 ? { failFast: opts.failFast } : {}
|
|
1452
|
+
});
|
|
1453
|
+
const across = await group.db.queryAcross(
|
|
1454
|
+
eligible.map((r) => r.vaultId),
|
|
1455
|
+
async (vault) => {
|
|
1456
|
+
group.template.configure(vault);
|
|
1457
|
+
const coll = vault.collection(collectionName);
|
|
1458
|
+
let within;
|
|
1459
|
+
if (opts.where?.length) {
|
|
1460
|
+
let q = coll.query();
|
|
1461
|
+
for (const [f, op, v] of opts.where) q = q.where(f, op, v);
|
|
1462
|
+
within = q;
|
|
1463
|
+
}
|
|
1464
|
+
return coll.retrieve(query, {
|
|
1465
|
+
...opts.mode ? { mode: opts.mode } : {},
|
|
1466
|
+
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
1467
|
+
...opts.minScore !== void 0 ? { minScore: opts.minScore } : {},
|
|
1468
|
+
...opts.fields ? { fields: opts.fields } : {},
|
|
1469
|
+
...opts.match ? { match: opts.match } : {},
|
|
1470
|
+
...opts.prefix ? { prefix: opts.prefix } : {},
|
|
1471
|
+
...opts.snippetWindow !== void 0 ? { snippetWindow: opts.snippetWindow } : {},
|
|
1472
|
+
...opts.includeRecord ? { includeRecord: true } : {},
|
|
1473
|
+
...within ? { within } : {}
|
|
1474
|
+
});
|
|
1475
|
+
},
|
|
1476
|
+
{ concurrency: opts.concurrency ?? 1, create: false }
|
|
1477
|
+
);
|
|
1478
|
+
const skippedVaults = [...skipped];
|
|
1479
|
+
const perVault = [];
|
|
1480
|
+
for (const r of across) {
|
|
1481
|
+
if (r.error) {
|
|
1482
|
+
if (opts.failFast) throw r.error;
|
|
1483
|
+
skippedVaults.push({ vaultId: r.vault, reason: classifyShardSkip(r.error), error: r.error });
|
|
1484
|
+
} else {
|
|
1485
|
+
perVault.push({ vault: r.vault, hits: r.result });
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const lists = perVault.map((pv) => {
|
|
1489
|
+
if (pv.vault.includes(SEP)) {
|
|
1490
|
+
throw new Error(`retrieveAcross: vault id "${pv.vault}" contains the reserved NUL separator`);
|
|
1491
|
+
}
|
|
1492
|
+
return pv.hits.map((h) => ({ ...h, id: pv.vault + SEP + h.id }));
|
|
1493
|
+
});
|
|
1494
|
+
const fused = (0, import_kernel10.fuseRetrieval)(lists, {
|
|
1495
|
+
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
1496
|
+
...opts.rrfK !== void 0 ? { k: opts.rrfK } : {}
|
|
1497
|
+
});
|
|
1498
|
+
const hits = fused.map((h) => {
|
|
1499
|
+
const i = h.id.indexOf(SEP);
|
|
1500
|
+
return { ...h, vault: h.id.slice(0, i), id: h.id.slice(i + 1) };
|
|
1501
|
+
});
|
|
1502
|
+
return { hits, skippedVaults };
|
|
1503
|
+
}
|
|
1504
|
+
var import_kernel10, SEP;
|
|
1505
|
+
var init_retrieve_across = __esm({
|
|
1506
|
+
"src/federation/retrieve-across.ts"() {
|
|
1507
|
+
"use strict";
|
|
1508
|
+
import_kernel10 = require("@noy-db/hub/kernel");
|
|
1509
|
+
init_classify_skip();
|
|
1510
|
+
SEP = "\0";
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1419
1514
|
// src/federation/vault-group.ts
|
|
1420
1515
|
var vault_group_exports = {};
|
|
1421
1516
|
__export(vault_group_exports, {
|
|
@@ -1424,49 +1519,55 @@ __export(vault_group_exports, {
|
|
|
1424
1519
|
ShardedQuery: () => ShardedQuery,
|
|
1425
1520
|
VaultGroup: () => VaultGroup
|
|
1426
1521
|
});
|
|
1522
|
+
function autoPushConfig(spec) {
|
|
1523
|
+
if (!spec.autoPush) return null;
|
|
1524
|
+
return spec.autoPush === true ? {} : spec.autoPush;
|
|
1525
|
+
}
|
|
1427
1526
|
function assertSafePartitionKey(partitionKey) {
|
|
1428
1527
|
if (partitionKey.length === 0) {
|
|
1429
|
-
throw new
|
|
1528
|
+
throw new import_kernel11.ValidationError("partitionKey must be a non-empty string");
|
|
1430
1529
|
}
|
|
1431
1530
|
if (partitionKey === import_hub4.STATE_VAULT_NAME) {
|
|
1432
|
-
throw new
|
|
1531
|
+
throw new import_kernel11.ReservedVaultNameError(partitionKey);
|
|
1433
1532
|
}
|
|
1434
1533
|
if (!SAFE_PARTITION_KEY.test(partitionKey)) {
|
|
1435
|
-
throw new
|
|
1534
|
+
throw new import_kernel11.ValidationError(
|
|
1436
1535
|
`partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
|
|
1437
1536
|
);
|
|
1438
1537
|
}
|
|
1439
1538
|
if (partitionKey.includes(SHARD_SEPARATOR)) {
|
|
1440
|
-
throw new
|
|
1539
|
+
throw new import_kernel11.ValidationError(
|
|
1441
1540
|
`partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
|
|
1442
1541
|
);
|
|
1443
1542
|
}
|
|
1444
1543
|
}
|
|
1445
|
-
var
|
|
1544
|
+
var import_kernel11, SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
|
|
1446
1545
|
var init_vault_group = __esm({
|
|
1447
1546
|
"src/federation/vault-group.ts"() {
|
|
1448
1547
|
"use strict";
|
|
1449
1548
|
init_state_vault();
|
|
1450
|
-
|
|
1549
|
+
import_kernel11 = require("@noy-db/hub/kernel");
|
|
1451
1550
|
init_constants();
|
|
1452
1551
|
init_classify_skip();
|
|
1453
1552
|
init_cross_shard_join();
|
|
1454
1553
|
init_cross_vault_live();
|
|
1455
1554
|
init_insight_auto_push();
|
|
1456
1555
|
init_aggregate_across();
|
|
1556
|
+
init_retrieve_across();
|
|
1457
1557
|
init_partial_reduce();
|
|
1458
1558
|
SHARD_SEPARATOR = "--";
|
|
1459
1559
|
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
1460
1560
|
VaultGroup = class {
|
|
1461
|
-
constructor(db, name, registry, sharding, template, cutoverOnOpen = false) {
|
|
1561
|
+
constructor(db, name, registry, sharding, template, cutoverOnOpen = false, meta) {
|
|
1462
1562
|
this.db = db;
|
|
1463
1563
|
this.name = name;
|
|
1464
1564
|
this.registry = registry;
|
|
1465
1565
|
this.sharding = sharding;
|
|
1466
1566
|
this.template = template;
|
|
1467
1567
|
this.cutoverOnOpen = cutoverOnOpen;
|
|
1568
|
+
this.meta = meta;
|
|
1468
1569
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
1469
|
-
throw new
|
|
1570
|
+
throw new import_kernel11.ValidationError(
|
|
1470
1571
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
1471
1572
|
);
|
|
1472
1573
|
}
|
|
@@ -1477,6 +1578,7 @@ var init_vault_group = __esm({
|
|
|
1477
1578
|
sharding;
|
|
1478
1579
|
template;
|
|
1479
1580
|
cutoverOnOpen;
|
|
1581
|
+
meta;
|
|
1480
1582
|
/** @internal — set when the group is managed (no explicit registry). */
|
|
1481
1583
|
stateVault;
|
|
1482
1584
|
/** @internal */
|
|
@@ -1510,6 +1612,30 @@ var init_vault_group = __esm({
|
|
|
1510
1612
|
const rows = this.registry.query().toArray();
|
|
1511
1613
|
return rows.filter((r) => r.group === this.name);
|
|
1512
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Federation-level descriptive metadata (#27): this group's `meta` plus each
|
|
1617
|
+
* member vault's `vaultMeta` (read via `getMeta()`). Best-effort per shard — a
|
|
1618
|
+
* shard that fails to open yields `meta: undefined` for that entry, never
|
|
1619
|
+
* throwing. Member meta is transient per-open (noy-db sets it at `openVault`,
|
|
1620
|
+
* does not persist it); this surfaces whatever each vault was opened with.
|
|
1621
|
+
*/
|
|
1622
|
+
async federationMeta() {
|
|
1623
|
+
const rows = await this.allRows();
|
|
1624
|
+
const vaults = await Promise.all(
|
|
1625
|
+
rows.map(async (row) => {
|
|
1626
|
+
let meta;
|
|
1627
|
+
try {
|
|
1628
|
+
const vault = await this.db.openVault(row.vaultId);
|
|
1629
|
+
this.template.configure(vault);
|
|
1630
|
+
meta = vault.getMeta();
|
|
1631
|
+
} catch {
|
|
1632
|
+
meta = void 0;
|
|
1633
|
+
}
|
|
1634
|
+
return { vaultId: row.vaultId, partitionKey: row.partitionKey, meta };
|
|
1635
|
+
})
|
|
1636
|
+
);
|
|
1637
|
+
return { meta: this.meta, vaults };
|
|
1638
|
+
}
|
|
1513
1639
|
/**
|
|
1514
1640
|
* Open an existing shard and apply the template. When `cutoverOnOpen` is set
|
|
1515
1641
|
* (#271) and the shard's registry version is behind the template, its cutover
|
|
@@ -1546,11 +1672,11 @@ var init_vault_group = __esm({
|
|
|
1546
1672
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1547
1673
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1548
1674
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1549
|
-
if (row && !provisioned) throw new
|
|
1675
|
+
if (row && !provisioned) throw new import_kernel11.ShardProvisioningError(vaultId, partitionKey);
|
|
1550
1676
|
if (row && provisioned) return this.openShard(partitionKey);
|
|
1551
1677
|
if (region !== void 0) {
|
|
1552
1678
|
const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
|
|
1553
|
-
if (backendRegion !== region) throw new
|
|
1679
|
+
if (backendRegion !== region) throw new import_kernel11.DataResidencyError(vaultId, region, backendRegion);
|
|
1554
1680
|
}
|
|
1555
1681
|
const vault = await this.db.openVault(vaultId);
|
|
1556
1682
|
this.template.configure(vault);
|
|
@@ -1584,9 +1710,9 @@ var init_vault_group = __esm({
|
|
|
1584
1710
|
async shard(partitionKey) {
|
|
1585
1711
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1586
1712
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1587
|
-
if (!row) throw new
|
|
1713
|
+
if (!row) throw new import_kernel11.UnknownShardError(partitionKey, this.name);
|
|
1588
1714
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
1589
|
-
if (!provisioned) throw new
|
|
1715
|
+
if (!provisioned) throw new import_kernel11.ShardProvisioningError(vaultId, partitionKey);
|
|
1590
1716
|
return this.openShard(partitionKey);
|
|
1591
1717
|
}
|
|
1592
1718
|
/** A sharded view over one logical collection across all shards. */
|
|
@@ -1618,7 +1744,7 @@ var init_vault_group = __esm({
|
|
|
1618
1744
|
for (const p of probes) {
|
|
1619
1745
|
if ("error" in p) skipped.push({ vaultId: p.row.vaultId, reason: "error", error: p.error });
|
|
1620
1746
|
else if (p.provisioned) eligible.push(p.row);
|
|
1621
|
-
else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new
|
|
1747
|
+
else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new import_kernel11.ShardProvisioningError(p.row.vaultId, p.row.partitionKey) });
|
|
1622
1748
|
}
|
|
1623
1749
|
return { eligible, skipped };
|
|
1624
1750
|
}
|
|
@@ -1655,15 +1781,18 @@ var init_vault_group = __esm({
|
|
|
1655
1781
|
withCrossVaultDerivation(spec) {
|
|
1656
1782
|
const target = spec.target.vault;
|
|
1657
1783
|
if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
|
|
1658
|
-
throw new
|
|
1784
|
+
throw new import_kernel11.ValidationError(
|
|
1659
1785
|
`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.`
|
|
1660
1786
|
);
|
|
1661
1787
|
}
|
|
1662
1788
|
this.crossVaultDerivations.push(spec);
|
|
1663
1789
|
if (spec.autoPush && !this.insightAutoPush) {
|
|
1790
|
+
const cfg = autoPushConfig(spec);
|
|
1664
1791
|
const controller = new InsightAutoPush(
|
|
1665
1792
|
(pk) => this._recomputeShardInsights(pk),
|
|
1666
|
-
(collection) => this.crossVaultDerivations.some((s) => s.autoPush && s.source === collection)
|
|
1793
|
+
(collection) => this.crossVaultDerivations.some((s) => s.autoPush && s.source === collection),
|
|
1794
|
+
void 0,
|
|
1795
|
+
cfg?.debounceMs
|
|
1667
1796
|
);
|
|
1668
1797
|
this.insightAutoPush = controller;
|
|
1669
1798
|
const prefix = `${this.name}${SHARD_SEPARATOR}`;
|
|
@@ -1744,6 +1873,8 @@ var init_vault_group = __esm({
|
|
|
1744
1873
|
};
|
|
1745
1874
|
for (const spec of this.crossVaultDerivations) {
|
|
1746
1875
|
if (!spec.autoPush) continue;
|
|
1876
|
+
const min = autoPushConfig(spec)?.minVersion;
|
|
1877
|
+
if (min !== void 0 && row.schemaVersion < min) continue;
|
|
1747
1878
|
const records = await shard.collection(spec.source).list();
|
|
1748
1879
|
const summary = spec.derive(records, ctx);
|
|
1749
1880
|
const insight = await this.db.openVault(spec.target.vault);
|
|
@@ -1776,7 +1907,7 @@ var init_vault_group = __esm({
|
|
|
1776
1907
|
async cutoverShard(partitionKey) {
|
|
1777
1908
|
const vaultId = this.shardVaultId(partitionKey);
|
|
1778
1909
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
1779
|
-
if (!row) throw new
|
|
1910
|
+
if (!row) throw new import_kernel11.UnknownShardError(partitionKey, this.name);
|
|
1780
1911
|
const target = this.template.version;
|
|
1781
1912
|
const sv = await this.ensureStateVault();
|
|
1782
1913
|
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
@@ -1859,7 +1990,7 @@ var init_vault_group = __esm({
|
|
|
1859
1990
|
let vault;
|
|
1860
1991
|
if (!row) {
|
|
1861
1992
|
if (this.group.sharding.autoCreate === false) {
|
|
1862
|
-
throw new
|
|
1993
|
+
throw new import_kernel11.UnknownShardError(key, this.group.name);
|
|
1863
1994
|
}
|
|
1864
1995
|
vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
|
|
1865
1996
|
} else {
|
|
@@ -1871,6 +2002,17 @@ var init_vault_group = __esm({
|
|
|
1871
2002
|
query() {
|
|
1872
2003
|
return new ShardedQuery(this.group, this.collectionName, []);
|
|
1873
2004
|
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Cross-vault federated retrieval (#26): scatter-gather across all eligible
|
|
2007
|
+
* shards — each shard runs its own trusted-tier `retrieve()`, then results are
|
|
2008
|
+
* RRF-fused by rank only (no cross-vault statistics cross the DEK boundary).
|
|
2009
|
+
* Every hit carries its originating `vault`. An unreachable, schema-drifted, or
|
|
2010
|
+
* un-indexed shard is skipped (`skippedVaults`); pass `failFast` to re-throw
|
|
2011
|
+
* the first per-shard error instead.
|
|
2012
|
+
*/
|
|
2013
|
+
async retrieve(query, opts = {}) {
|
|
2014
|
+
return retrieveAcross(this.group, this.collectionName, query, opts);
|
|
2015
|
+
}
|
|
1874
2016
|
};
|
|
1875
2017
|
ShardedQuery = class _ShardedQuery {
|
|
1876
2018
|
constructor(group, collectionName, clauses, coPartitionedLegs = [], broadcastLegs = []) {
|
|
@@ -1931,7 +2073,7 @@ var init_vault_group = __esm({
|
|
|
1931
2073
|
this.group.template.configure(probe);
|
|
1932
2074
|
for (const leg of this.coPartitionedLegs) {
|
|
1933
2075
|
if (!probe.resolveRef(this.collectionName, leg.field)) {
|
|
1934
|
-
throw new
|
|
2076
|
+
throw new import_kernel11.CrossShardJoinError(
|
|
1935
2077
|
`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.`
|
|
1936
2078
|
);
|
|
1937
2079
|
}
|
|
@@ -2013,23 +2155,23 @@ var init_vault_group = __esm({
|
|
|
2013
2155
|
isRelevant: (e) => e.collection === collectionName && e.vault.startsWith(`${group.name}--`)
|
|
2014
2156
|
};
|
|
2015
2157
|
}
|
|
2016
|
-
/**
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2158
|
+
/**
|
|
2159
|
+
* Returns a reactive cross-shard live query — a facade over CrossVaultLive.
|
|
2160
|
+
*
|
|
2161
|
+
* Joined queries (crossShardJoin / broadcastJoin) are supported (#14): the live
|
|
2162
|
+
* value reflects the fully-joined rows. **v1 limitation:** recomputes only on
|
|
2163
|
+
* writes to the primary (left) collection; writes to a co-partitioned right
|
|
2164
|
+
* collection or a broadcast-dimension collection do NOT trigger a recompute —
|
|
2165
|
+
* re-run the query to pick those up.
|
|
2166
|
+
*/
|
|
2025
2167
|
live(options = {}) {
|
|
2026
|
-
this.assertNoJoinLegs("live");
|
|
2027
2168
|
const bind = this.liveBinding();
|
|
2028
2169
|
const core = new CrossVaultLive({
|
|
2029
2170
|
...bind,
|
|
2030
2171
|
compute: async () => {
|
|
2031
2172
|
const { records, skippedVaults } = await this.fanoutRecords(options);
|
|
2032
|
-
|
|
2173
|
+
const joined = await applyBroadcastLegs(records, this.broadcastLegs);
|
|
2174
|
+
return { records: joined, skipped: skippedVaults };
|
|
2033
2175
|
},
|
|
2034
2176
|
initialSnapshot: { records: [], skipped: [] },
|
|
2035
2177
|
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
@@ -2049,14 +2191,28 @@ var init_vault_group = __esm({
|
|
|
2049
2191
|
stop: () => core.stop()
|
|
2050
2192
|
};
|
|
2051
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* @internal — the FanoutRecordSource for aggregate surfaces. With no join legs,
|
|
2196
|
+
* returns `this` (partial-reduce-eligible, #8). With join legs, returns a
|
|
2197
|
+
* toArray-backed source (fully-joined rows) and NO fanoutReduce, forcing
|
|
2198
|
+
* central-reduce over the joined rows (partial-reduce can't span the central
|
|
2199
|
+
* broadcast join). Used by scalar `aggregate()` and `ShardedGroupedQuery`.
|
|
2200
|
+
*/
|
|
2201
|
+
aggregateSource() {
|
|
2202
|
+
if (!this.coPartitionedLegs.length && !this.broadcastLegs.length) return this;
|
|
2203
|
+
return {
|
|
2204
|
+
fanoutRecords: async (o) => {
|
|
2205
|
+
const { results, skippedVaults } = await this.toArray(o);
|
|
2206
|
+
return { records: results, skippedVaults };
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2052
2210
|
/** One-shot distributed aggregate — central reduce over all shard records. */
|
|
2053
2211
|
aggregate(spec) {
|
|
2054
|
-
this.
|
|
2055
|
-
return new CrossVaultAggregation(this, spec, this.liveBinding());
|
|
2212
|
+
return new CrossVaultAggregation(this.aggregateSource(), spec, this.liveBinding());
|
|
2056
2213
|
}
|
|
2057
2214
|
/** Begin a grouped cross-shard aggregate. */
|
|
2058
2215
|
groupBy(field) {
|
|
2059
|
-
this.assertNoJoinLegs("groupBy");
|
|
2060
2216
|
return new ShardedGroupedQuery(this, field);
|
|
2061
2217
|
}
|
|
2062
2218
|
};
|
|
@@ -2069,7 +2225,7 @@ var init_vault_group = __esm({
|
|
|
2069
2225
|
field;
|
|
2070
2226
|
aggregate(spec) {
|
|
2071
2227
|
return new CrossVaultGroupedAggregation(
|
|
2072
|
-
|
|
2228
|
+
this.query.aggregateSource(),
|
|
2073
2229
|
this.field,
|
|
2074
2230
|
spec,
|
|
2075
2231
|
this.query.liveBinding()
|
|
@@ -2082,10 +2238,10 @@ var init_vault_group = __esm({
|
|
|
2082
2238
|
// src/index.ts
|
|
2083
2239
|
var index_exports = {};
|
|
2084
2240
|
__export(index_exports, {
|
|
2085
|
-
CrossShardJoinError: () =>
|
|
2241
|
+
CrossShardJoinError: () => import_kernel13.CrossShardJoinError,
|
|
2086
2242
|
CrossVaultDanglingRefError: () => CrossVaultDanglingRefError,
|
|
2087
2243
|
CustodyApi: () => import_hub6.CustodyApi,
|
|
2088
|
-
DataResidencyError: () =>
|
|
2244
|
+
DataResidencyError: () => import_kernel13.DataResidencyError,
|
|
2089
2245
|
DockedUnit: () => DockedUnit,
|
|
2090
2246
|
FieldAuthorityPolicyMissingError: () => FieldAuthorityPolicyMissingError,
|
|
2091
2247
|
FieldLevelDeferredError: () => FieldLevelDeferredError,
|
|
@@ -2096,14 +2252,14 @@ __export(index_exports, {
|
|
|
2096
2252
|
NOYDB_MULTI_BUNDLE_MAGIC: () => NOYDB_MULTI_BUNDLE_MAGIC,
|
|
2097
2253
|
NOYDB_MULTI_BUNDLE_PREFIX_BYTES: () => NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
|
|
2098
2254
|
NOYDB_MULTI_BUNDLE_VERSION: () => NOYDB_MULTI_BUNDLE_VERSION,
|
|
2099
|
-
ReservedVaultNameError: () =>
|
|
2100
|
-
ShardProvisioningError: () =>
|
|
2255
|
+
ReservedVaultNameError: () => import_kernel13.ReservedVaultNameError,
|
|
2256
|
+
ShardProvisioningError: () => import_kernel13.ShardProvisioningError,
|
|
2101
2257
|
SurfaceCadenceScheduler: () => SurfaceCadenceScheduler,
|
|
2102
2258
|
SurfaceNotFoundError: () => SurfaceNotFoundError,
|
|
2103
2259
|
SurfaceStateError: () => SurfaceStateError,
|
|
2104
2260
|
UnitGraduationError: () => UnitGraduationError,
|
|
2105
|
-
UnknownShardError: () =>
|
|
2106
|
-
VaultTemplateNotFoundError: () =>
|
|
2261
|
+
UnknownShardError: () => import_kernel13.UnknownShardError,
|
|
2262
|
+
VaultTemplateNotFoundError: () => import_kernel13.VaultTemplateNotFoundError,
|
|
2107
2263
|
agreeSurface: () => agreeSurface,
|
|
2108
2264
|
applySurface: () => applySurface,
|
|
2109
2265
|
createDeedOwner: () => import_hub6.createDeedOwner,
|
|
@@ -2133,7 +2289,7 @@ __export(index_exports, {
|
|
|
2133
2289
|
writeMultiVaultBundle: () => writeMultiVaultBundle
|
|
2134
2290
|
});
|
|
2135
2291
|
module.exports = __toCommonJS(index_exports);
|
|
2136
|
-
var
|
|
2292
|
+
var import_kernel12 = require("@noy-db/hub/kernel");
|
|
2137
2293
|
var import_hub5 = require("@noy-db/hub");
|
|
2138
2294
|
|
|
2139
2295
|
// src/dock/docked-unit.ts
|
|
@@ -2216,7 +2372,7 @@ async function meterGroup(group, opts = {}) {
|
|
|
2216
2372
|
}
|
|
2217
2373
|
|
|
2218
2374
|
// src/index.ts
|
|
2219
|
-
var
|
|
2375
|
+
var import_kernel13 = require("@noy-db/hub/kernel");
|
|
2220
2376
|
init_multi_bundle();
|
|
2221
2377
|
init_extract_cross_vault();
|
|
2222
2378
|
init_merge_compartment();
|
|
@@ -2304,15 +2460,15 @@ var Lobby = class {
|
|
|
2304
2460
|
}
|
|
2305
2461
|
async openVaultGroup(name, opts) {
|
|
2306
2462
|
const db = this.noydb;
|
|
2307
|
-
if (db.isClosed) throw new
|
|
2308
|
-
if (name === import_hub5.STATE_VAULT_NAME) throw new
|
|
2463
|
+
if (db.isClosed) throw new import_kernel12.ValidationError("Instance is closed");
|
|
2464
|
+
if (name === import_hub5.STATE_VAULT_NAME) throw new import_kernel12.ReservedVaultNameError(name);
|
|
2309
2465
|
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
2310
|
-
if (!template) throw new
|
|
2466
|
+
if (!template) throw new import_kernel12.VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
2311
2467
|
const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
|
|
2312
2468
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2313
2469
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(db);
|
|
2314
2470
|
const registry = opts.registry ?? stateVault.registry;
|
|
2315
|
-
const group = new VaultGroup2(db, name, registry, opts.sharding, template, opts.cutoverOnOpen ?? false);
|
|
2471
|
+
const group = new VaultGroup2(db, name, registry, opts.sharding, template, opts.cutoverOnOpen ?? false, opts.meta);
|
|
2316
2472
|
if (stateVault) {
|
|
2317
2473
|
group._attachStateVault(stateVault);
|
|
2318
2474
|
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|
|
@@ -2326,7 +2482,7 @@ var Lobby = class {
|
|
|
2326
2482
|
}
|
|
2327
2483
|
async openStateManagementVault() {
|
|
2328
2484
|
const db = this.noydb;
|
|
2329
|
-
if (db.isClosed) throw new
|
|
2485
|
+
if (db.isClosed) throw new import_kernel12.ValidationError("Instance is closed");
|
|
2330
2486
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
2331
2487
|
return StateManagementVault2.open(db);
|
|
2332
2488
|
}
|