@klum-db/lobby 0.2.0-pre.31 → 0.2.0-pre.32

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.
@@ -1,4 +1,4 @@
1
- import { V as VaultGroup } from '../vault-group-DqEyXbN1.cjs';
1
+ import { V as VaultGroup } from '../vault-group-BchRCDm5.cjs';
2
2
  import '@noy-db/hub/kernel';
3
3
  import '@noy-db/hub';
4
4
  import '@noy-db/hub/bundle';
@@ -1,4 +1,4 @@
1
- import { V as VaultGroup } from '../vault-group-DqEyXbN1.js';
1
+ import { V as VaultGroup } from '../vault-group-BchRCDm5.js';
2
2
  import '@noy-db/hub/kernel';
3
3
  import '@noy-db/hub';
4
4
  import '@noy-db/hub/bundle';
package/dist/index.cjs CHANGED
@@ -1416,6 +1416,73 @@ var init_aggregate_across = __esm({
1416
1416
  }
1417
1417
  });
1418
1418
 
1419
+ // src/federation/retrieve-across.ts
1420
+ async function retrieveAcross(group, collectionName, query, opts = {}) {
1421
+ const { eligible, skipped } = await group.resolveEligible({
1422
+ ...opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {},
1423
+ ...opts.failFast !== void 0 ? { failFast: opts.failFast } : {}
1424
+ });
1425
+ const across = await group.db.queryAcross(
1426
+ eligible.map((r) => r.vaultId),
1427
+ async (vault) => {
1428
+ group.template.configure(vault);
1429
+ const coll = vault.collection(collectionName);
1430
+ let within;
1431
+ if (opts.where?.length) {
1432
+ let q = coll.query();
1433
+ for (const [f, op, v] of opts.where) q = q.where(f, op, v);
1434
+ within = q;
1435
+ }
1436
+ return coll.retrieve(query, {
1437
+ ...opts.mode ? { mode: opts.mode } : {},
1438
+ ...opts.limit !== void 0 ? { limit: opts.limit } : {},
1439
+ ...opts.minScore !== void 0 ? { minScore: opts.minScore } : {},
1440
+ ...opts.fields ? { fields: opts.fields } : {},
1441
+ ...opts.match ? { match: opts.match } : {},
1442
+ ...opts.prefix ? { prefix: opts.prefix } : {},
1443
+ ...opts.snippetWindow !== void 0 ? { snippetWindow: opts.snippetWindow } : {},
1444
+ ...opts.includeRecord ? { includeRecord: true } : {},
1445
+ ...within ? { within } : {}
1446
+ });
1447
+ },
1448
+ { concurrency: opts.concurrency ?? 1, create: false }
1449
+ );
1450
+ const skippedVaults = [...skipped];
1451
+ const perVault = [];
1452
+ for (const r of across) {
1453
+ if (r.error) {
1454
+ if (opts.failFast) throw r.error;
1455
+ skippedVaults.push({ vaultId: r.vault, reason: classifyShardSkip(r.error), error: r.error });
1456
+ } else {
1457
+ perVault.push({ vault: r.vault, hits: r.result });
1458
+ }
1459
+ }
1460
+ const lists = perVault.map((pv) => {
1461
+ if (pv.vault.includes(SEP)) {
1462
+ throw new Error(`retrieveAcross: vault id "${pv.vault}" contains the reserved NUL separator`);
1463
+ }
1464
+ return pv.hits.map((h) => ({ ...h, id: pv.vault + SEP + h.id }));
1465
+ });
1466
+ const fused = (0, import_kernel10.fuseRetrieval)(lists, {
1467
+ ...opts.limit !== void 0 ? { limit: opts.limit } : {},
1468
+ ...opts.rrfK !== void 0 ? { k: opts.rrfK } : {}
1469
+ });
1470
+ const hits = fused.map((h) => {
1471
+ const i = h.id.indexOf(SEP);
1472
+ return { ...h, vault: h.id.slice(0, i), id: h.id.slice(i + 1) };
1473
+ });
1474
+ return { hits, skippedVaults };
1475
+ }
1476
+ var import_kernel10, SEP;
1477
+ var init_retrieve_across = __esm({
1478
+ "src/federation/retrieve-across.ts"() {
1479
+ "use strict";
1480
+ import_kernel10 = require("@noy-db/hub/kernel");
1481
+ init_classify_skip();
1482
+ SEP = "\0";
1483
+ }
1484
+ });
1485
+
1419
1486
  // src/federation/vault-group.ts
1420
1487
  var vault_group_exports = {};
1421
1488
  __export(vault_group_exports, {
@@ -1426,47 +1493,49 @@ __export(vault_group_exports, {
1426
1493
  });
1427
1494
  function assertSafePartitionKey(partitionKey) {
1428
1495
  if (partitionKey.length === 0) {
1429
- throw new import_kernel10.ValidationError("partitionKey must be a non-empty string");
1496
+ throw new import_kernel11.ValidationError("partitionKey must be a non-empty string");
1430
1497
  }
1431
1498
  if (partitionKey === import_hub4.STATE_VAULT_NAME) {
1432
- throw new import_kernel10.ReservedVaultNameError(partitionKey);
1499
+ throw new import_kernel11.ReservedVaultNameError(partitionKey);
1433
1500
  }
1434
1501
  if (!SAFE_PARTITION_KEY.test(partitionKey)) {
1435
- throw new import_kernel10.ValidationError(
1502
+ throw new import_kernel11.ValidationError(
1436
1503
  `partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
1437
1504
  );
1438
1505
  }
1439
1506
  if (partitionKey.includes(SHARD_SEPARATOR)) {
1440
- throw new import_kernel10.ValidationError(
1507
+ throw new import_kernel11.ValidationError(
1441
1508
  `partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
1442
1509
  );
1443
1510
  }
1444
1511
  }
1445
- var import_kernel10, SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
1512
+ var import_kernel11, SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
1446
1513
  var init_vault_group = __esm({
1447
1514
  "src/federation/vault-group.ts"() {
1448
1515
  "use strict";
1449
1516
  init_state_vault();
1450
- import_kernel10 = require("@noy-db/hub/kernel");
1517
+ import_kernel11 = require("@noy-db/hub/kernel");
1451
1518
  init_constants();
1452
1519
  init_classify_skip();
1453
1520
  init_cross_shard_join();
1454
1521
  init_cross_vault_live();
1455
1522
  init_insight_auto_push();
1456
1523
  init_aggregate_across();
1524
+ init_retrieve_across();
1457
1525
  init_partial_reduce();
1458
1526
  SHARD_SEPARATOR = "--";
1459
1527
  SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
1460
1528
  VaultGroup = class {
1461
- constructor(db, name, registry, sharding, template, cutoverOnOpen = false) {
1529
+ constructor(db, name, registry, sharding, template, cutoverOnOpen = false, meta) {
1462
1530
  this.db = db;
1463
1531
  this.name = name;
1464
1532
  this.registry = registry;
1465
1533
  this.sharding = sharding;
1466
1534
  this.template = template;
1467
1535
  this.cutoverOnOpen = cutoverOnOpen;
1536
+ this.meta = meta;
1468
1537
  if (name.includes(SHARD_SEPARATOR)) {
1469
- throw new import_kernel10.ValidationError(
1538
+ throw new import_kernel11.ValidationError(
1470
1539
  `VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
1471
1540
  );
1472
1541
  }
@@ -1477,6 +1546,7 @@ var init_vault_group = __esm({
1477
1546
  sharding;
1478
1547
  template;
1479
1548
  cutoverOnOpen;
1549
+ meta;
1480
1550
  /** @internal — set when the group is managed (no explicit registry). */
1481
1551
  stateVault;
1482
1552
  /** @internal */
@@ -1510,6 +1580,30 @@ var init_vault_group = __esm({
1510
1580
  const rows = this.registry.query().toArray();
1511
1581
  return rows.filter((r) => r.group === this.name);
1512
1582
  }
1583
+ /**
1584
+ * Federation-level descriptive metadata (#27): this group's `meta` plus each
1585
+ * member vault's `vaultMeta` (read via `getMeta()`). Best-effort per shard — a
1586
+ * shard that fails to open yields `meta: undefined` for that entry, never
1587
+ * throwing. Member meta is transient per-open (noy-db sets it at `openVault`,
1588
+ * does not persist it); this surfaces whatever each vault was opened with.
1589
+ */
1590
+ async federationMeta() {
1591
+ const rows = await this.allRows();
1592
+ const vaults = await Promise.all(
1593
+ rows.map(async (row) => {
1594
+ let meta;
1595
+ try {
1596
+ const vault = await this.db.openVault(row.vaultId);
1597
+ this.template.configure(vault);
1598
+ meta = vault.getMeta();
1599
+ } catch {
1600
+ meta = void 0;
1601
+ }
1602
+ return { vaultId: row.vaultId, partitionKey: row.partitionKey, meta };
1603
+ })
1604
+ );
1605
+ return { meta: this.meta, vaults };
1606
+ }
1513
1607
  /**
1514
1608
  * Open an existing shard and apply the template. When `cutoverOnOpen` is set
1515
1609
  * (#271) and the shard's registry version is behind the template, its cutover
@@ -1546,11 +1640,11 @@ var init_vault_group = __esm({
1546
1640
  const vaultId = this.shardVaultId(partitionKey);
1547
1641
  const row = await this.registry.get(this.registryId(partitionKey));
1548
1642
  const provisioned = await this.db._shardVaultProvisioned(vaultId);
1549
- if (row && !provisioned) throw new import_kernel10.ShardProvisioningError(vaultId, partitionKey);
1643
+ if (row && !provisioned) throw new import_kernel11.ShardProvisioningError(vaultId, partitionKey);
1550
1644
  if (row && provisioned) return this.openShard(partitionKey);
1551
1645
  if (region !== void 0) {
1552
1646
  const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
1553
- if (backendRegion !== region) throw new import_kernel10.DataResidencyError(vaultId, region, backendRegion);
1647
+ if (backendRegion !== region) throw new import_kernel11.DataResidencyError(vaultId, region, backendRegion);
1554
1648
  }
1555
1649
  const vault = await this.db.openVault(vaultId);
1556
1650
  this.template.configure(vault);
@@ -1584,9 +1678,9 @@ var init_vault_group = __esm({
1584
1678
  async shard(partitionKey) {
1585
1679
  const vaultId = this.shardVaultId(partitionKey);
1586
1680
  const row = await this.registry.get(this.registryId(partitionKey));
1587
- if (!row) throw new import_kernel10.UnknownShardError(partitionKey, this.name);
1681
+ if (!row) throw new import_kernel11.UnknownShardError(partitionKey, this.name);
1588
1682
  const provisioned = await this.db._shardVaultProvisioned(vaultId);
1589
- if (!provisioned) throw new import_kernel10.ShardProvisioningError(vaultId, partitionKey);
1683
+ if (!provisioned) throw new import_kernel11.ShardProvisioningError(vaultId, partitionKey);
1590
1684
  return this.openShard(partitionKey);
1591
1685
  }
1592
1686
  /** A sharded view over one logical collection across all shards. */
@@ -1618,7 +1712,7 @@ var init_vault_group = __esm({
1618
1712
  for (const p of probes) {
1619
1713
  if ("error" in p) skipped.push({ vaultId: p.row.vaultId, reason: "error", error: p.error });
1620
1714
  else if (p.provisioned) eligible.push(p.row);
1621
- else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new import_kernel10.ShardProvisioningError(p.row.vaultId, p.row.partitionKey) });
1715
+ else skipped.push({ vaultId: p.row.vaultId, reason: "error", error: new import_kernel11.ShardProvisioningError(p.row.vaultId, p.row.partitionKey) });
1622
1716
  }
1623
1717
  return { eligible, skipped };
1624
1718
  }
@@ -1655,7 +1749,7 @@ var init_vault_group = __esm({
1655
1749
  withCrossVaultDerivation(spec) {
1656
1750
  const target = spec.target.vault;
1657
1751
  if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
1658
- throw new import_kernel10.ValidationError(
1752
+ throw new import_kernel11.ValidationError(
1659
1753
  `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
1754
  );
1661
1755
  }
@@ -1776,7 +1870,7 @@ var init_vault_group = __esm({
1776
1870
  async cutoverShard(partitionKey) {
1777
1871
  const vaultId = this.shardVaultId(partitionKey);
1778
1872
  const row = await this.registry.get(this.registryId(partitionKey));
1779
- if (!row) throw new import_kernel10.UnknownShardError(partitionKey, this.name);
1873
+ if (!row) throw new import_kernel11.UnknownShardError(partitionKey, this.name);
1780
1874
  const target = this.template.version;
1781
1875
  const sv = await this.ensureStateVault();
1782
1876
  const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
@@ -1859,7 +1953,7 @@ var init_vault_group = __esm({
1859
1953
  let vault;
1860
1954
  if (!row) {
1861
1955
  if (this.group.sharding.autoCreate === false) {
1862
- throw new import_kernel10.UnknownShardError(key, this.group.name);
1956
+ throw new import_kernel11.UnknownShardError(key, this.group.name);
1863
1957
  }
1864
1958
  vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
1865
1959
  } else {
@@ -1871,6 +1965,17 @@ var init_vault_group = __esm({
1871
1965
  query() {
1872
1966
  return new ShardedQuery(this.group, this.collectionName, []);
1873
1967
  }
1968
+ /**
1969
+ * Cross-vault federated retrieval (#26): scatter-gather across all eligible
1970
+ * shards — each shard runs its own trusted-tier `retrieve()`, then results are
1971
+ * RRF-fused by rank only (no cross-vault statistics cross the DEK boundary).
1972
+ * Every hit carries its originating `vault`. An unreachable, schema-drifted, or
1973
+ * un-indexed shard is skipped (`skippedVaults`); pass `failFast` to re-throw
1974
+ * the first per-shard error instead.
1975
+ */
1976
+ async retrieve(query, opts = {}) {
1977
+ return retrieveAcross(this.group, this.collectionName, query, opts);
1978
+ }
1874
1979
  };
1875
1980
  ShardedQuery = class _ShardedQuery {
1876
1981
  constructor(group, collectionName, clauses, coPartitionedLegs = [], broadcastLegs = []) {
@@ -1931,7 +2036,7 @@ var init_vault_group = __esm({
1931
2036
  this.group.template.configure(probe);
1932
2037
  for (const leg of this.coPartitionedLegs) {
1933
2038
  if (!probe.resolveRef(this.collectionName, leg.field)) {
1934
- throw new import_kernel10.CrossShardJoinError(
2039
+ throw new import_kernel11.CrossShardJoinError(
1935
2040
  `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
2041
  );
1937
2042
  }
@@ -2016,7 +2121,7 @@ var init_vault_group = __esm({
2016
2121
  /** @internal — joined queries don't support reactive/aggregate surfaces in v1. */
2017
2122
  assertNoJoinLegs(surface) {
2018
2123
  if (this.coPartitionedLegs.length || this.broadcastLegs.length) {
2019
- throw new import_kernel10.CrossShardJoinError(
2124
+ throw new import_kernel11.CrossShardJoinError(
2020
2125
  `${surface}() is not supported on a ShardedQuery with crossShardJoin/broadcastJoin legs in v1. Use toArray() for joined cross-shard queries.`
2021
2126
  );
2022
2127
  }
@@ -2082,10 +2187,10 @@ var init_vault_group = __esm({
2082
2187
  // src/index.ts
2083
2188
  var index_exports = {};
2084
2189
  __export(index_exports, {
2085
- CrossShardJoinError: () => import_kernel12.CrossShardJoinError,
2190
+ CrossShardJoinError: () => import_kernel13.CrossShardJoinError,
2086
2191
  CrossVaultDanglingRefError: () => CrossVaultDanglingRefError,
2087
2192
  CustodyApi: () => import_hub6.CustodyApi,
2088
- DataResidencyError: () => import_kernel12.DataResidencyError,
2193
+ DataResidencyError: () => import_kernel13.DataResidencyError,
2089
2194
  DockedUnit: () => DockedUnit,
2090
2195
  FieldAuthorityPolicyMissingError: () => FieldAuthorityPolicyMissingError,
2091
2196
  FieldLevelDeferredError: () => FieldLevelDeferredError,
@@ -2096,14 +2201,14 @@ __export(index_exports, {
2096
2201
  NOYDB_MULTI_BUNDLE_MAGIC: () => NOYDB_MULTI_BUNDLE_MAGIC,
2097
2202
  NOYDB_MULTI_BUNDLE_PREFIX_BYTES: () => NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
2098
2203
  NOYDB_MULTI_BUNDLE_VERSION: () => NOYDB_MULTI_BUNDLE_VERSION,
2099
- ReservedVaultNameError: () => import_kernel12.ReservedVaultNameError,
2100
- ShardProvisioningError: () => import_kernel12.ShardProvisioningError,
2204
+ ReservedVaultNameError: () => import_kernel13.ReservedVaultNameError,
2205
+ ShardProvisioningError: () => import_kernel13.ShardProvisioningError,
2101
2206
  SurfaceCadenceScheduler: () => SurfaceCadenceScheduler,
2102
2207
  SurfaceNotFoundError: () => SurfaceNotFoundError,
2103
2208
  SurfaceStateError: () => SurfaceStateError,
2104
2209
  UnitGraduationError: () => UnitGraduationError,
2105
- UnknownShardError: () => import_kernel12.UnknownShardError,
2106
- VaultTemplateNotFoundError: () => import_kernel12.VaultTemplateNotFoundError,
2210
+ UnknownShardError: () => import_kernel13.UnknownShardError,
2211
+ VaultTemplateNotFoundError: () => import_kernel13.VaultTemplateNotFoundError,
2107
2212
  agreeSurface: () => agreeSurface,
2108
2213
  applySurface: () => applySurface,
2109
2214
  createDeedOwner: () => import_hub6.createDeedOwner,
@@ -2133,7 +2238,7 @@ __export(index_exports, {
2133
2238
  writeMultiVaultBundle: () => writeMultiVaultBundle
2134
2239
  });
2135
2240
  module.exports = __toCommonJS(index_exports);
2136
- var import_kernel11 = require("@noy-db/hub/kernel");
2241
+ var import_kernel12 = require("@noy-db/hub/kernel");
2137
2242
  var import_hub5 = require("@noy-db/hub");
2138
2243
 
2139
2244
  // src/dock/docked-unit.ts
@@ -2216,7 +2321,7 @@ async function meterGroup(group, opts = {}) {
2216
2321
  }
2217
2322
 
2218
2323
  // src/index.ts
2219
- var import_kernel12 = require("@noy-db/hub/kernel");
2324
+ var import_kernel13 = require("@noy-db/hub/kernel");
2220
2325
  init_multi_bundle();
2221
2326
  init_extract_cross_vault();
2222
2327
  init_merge_compartment();
@@ -2304,15 +2409,15 @@ var Lobby = class {
2304
2409
  }
2305
2410
  async openVaultGroup(name, opts) {
2306
2411
  const db = this.noydb;
2307
- if (db.isClosed) throw new import_kernel11.ValidationError("Instance is closed");
2308
- if (name === import_hub5.STATE_VAULT_NAME) throw new import_kernel11.ReservedVaultNameError(name);
2412
+ if (db.isClosed) throw new import_kernel12.ValidationError("Instance is closed");
2413
+ if (name === import_hub5.STATE_VAULT_NAME) throw new import_kernel12.ReservedVaultNameError(name);
2309
2414
  const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
2310
- if (!template) throw new import_kernel11.VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
2415
+ if (!template) throw new import_kernel12.VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
2311
2416
  const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
2312
2417
  const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
2313
2418
  const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(db);
2314
2419
  const registry = opts.registry ?? stateVault.registry;
2315
- const group = new VaultGroup2(db, name, registry, opts.sharding, template, opts.cutoverOnOpen ?? false);
2420
+ const group = new VaultGroup2(db, name, registry, opts.sharding, template, opts.cutoverOnOpen ?? false, opts.meta);
2316
2421
  if (stateVault) {
2317
2422
  group._attachStateVault(stateVault);
2318
2423
  await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
@@ -2326,7 +2431,7 @@ var Lobby = class {
2326
2431
  }
2327
2432
  async openStateManagementVault() {
2328
2433
  const db = this.noydb;
2329
- if (db.isClosed) throw new import_kernel11.ValidationError("Instance is closed");
2434
+ if (db.isClosed) throw new import_kernel12.ValidationError("Instance is closed");
2330
2435
  const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
2331
2436
  return StateManagementVault2.open(db);
2332
2437
  }