@klum-db/lobby 0.2.0-pre.26 → 0.2.0-pre.28

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/index.js CHANGED
@@ -8,6 +8,188 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/bundle/uint32.ts
12
+ function readUint32BE(bytes, offset) {
13
+ return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
14
+ }
15
+ function writeUint32BE(bytes, offset, value) {
16
+ bytes[offset] = value >>> 24 & 255;
17
+ bytes[offset + 1] = value >>> 16 & 255;
18
+ bytes[offset + 2] = value >>> 8 & 255;
19
+ bytes[offset + 3] = value & 255;
20
+ }
21
+ var init_uint32 = __esm({
22
+ "src/bundle/uint32.ts"() {
23
+ "use strict";
24
+ }
25
+ });
26
+
27
+ // src/bundle/multi-bundle.ts
28
+ import { sha256Hex, generateULID } from "@noy-db/hub/kernel";
29
+ import {
30
+ writeNoydbBundle,
31
+ readNoydbBundleHeader
32
+ } from "@noy-db/hub/bundle";
33
+ import {
34
+ readNoydbBundlePublicEnvelope,
35
+ hasNoydbBundleMagic
36
+ } from "@noy-db/hub";
37
+ function encodeMultiBundle(manifest, inner) {
38
+ validateManifest(manifest);
39
+ if (manifest.compartments.length !== inner.length) {
40
+ throw new Error(`multi-bundle: manifest has ${manifest.compartments.length} compartments but ${inner.length} inner bundles were provided.`);
41
+ }
42
+ for (let i = 0; i < inner.length; i++) {
43
+ if (manifest.compartments[i].innerBytes !== inner[i].length) {
44
+ throw new Error(`multi-bundle: compartment ${i} declares innerBytes ${manifest.compartments[i].innerBytes} but ${inner[i].length} bytes were provided.`);
45
+ }
46
+ }
47
+ const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));
48
+ const bodyLen = inner.reduce((n, b) => n + b.length, 0);
49
+ const out = new Uint8Array(NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestBytes.length + bodyLen);
50
+ out.set(NOYDB_MULTI_BUNDLE_MAGIC, 0);
51
+ out[4] = NOYDB_MULTI_BUNDLE_VERSION;
52
+ out[5] = 0;
53
+ writeUint32BE(out, 6, manifestBytes.length);
54
+ out.set(manifestBytes, NOYDB_MULTI_BUNDLE_PREFIX_BYTES);
55
+ let off = NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestBytes.length;
56
+ for (const b of inner) {
57
+ out.set(b, off);
58
+ off += b.length;
59
+ }
60
+ return out;
61
+ }
62
+ function hasMultiMagic(bytes) {
63
+ if (bytes.length < NOYDB_MULTI_BUNDLE_MAGIC.length) return false;
64
+ for (let i = 0; i < NOYDB_MULTI_BUNDLE_MAGIC.length; i++) if (bytes[i] !== NOYDB_MULTI_BUNDLE_MAGIC[i]) return false;
65
+ return true;
66
+ }
67
+ function validateManifest(parsed) {
68
+ if (parsed === null || typeof parsed !== "object") throw new Error("multi-bundle manifest must be a JSON object.");
69
+ const m = parsed;
70
+ if (m["multiFormatVersion"] !== NOYDB_MULTI_BUNDLE_VERSION) throw new Error(`multi-bundle manifest.multiFormatVersion must be ${NOYDB_MULTI_BUNDLE_VERSION}, got ${String(m["multiFormatVersion"])}.`);
71
+ if (typeof m["handle"] !== "string" || m["handle"].length === 0) throw new Error("multi-bundle manifest.handle must be a non-empty string.");
72
+ if (!Array.isArray(m["compartments"])) throw new Error("multi-bundle manifest.compartments must be an array.");
73
+ const seenHandles = /* @__PURE__ */ new Set();
74
+ for (const c of m["compartments"]) {
75
+ if (c === null || typeof c !== "object") throw new Error("multi-bundle compartment must be an object.");
76
+ const e = c;
77
+ if (typeof e["handle"] !== "string" || e["handle"].length === 0) throw new Error("multi-bundle compartment.handle must be a non-empty string.");
78
+ if (seenHandles.has(e["handle"])) {
79
+ throw new Error(`multi-bundle manifest has a duplicate compartment handle "${e["handle"]}".`);
80
+ }
81
+ seenHandles.add(e["handle"]);
82
+ if (typeof e["innerBytes"] !== "number" || !Number.isInteger(e["innerBytes"]) || e["innerBytes"] < 0) throw new Error("multi-bundle compartment.innerBytes must be a non-negative integer.");
83
+ if (typeof e["innerSha256"] !== "string" || !/^[0-9a-f]{64}$/.test(e["innerSha256"])) throw new Error("multi-bundle compartment.innerSha256 must be 64-char lowercase hex.");
84
+ }
85
+ }
86
+ function decodeMultiBundle(bytes) {
87
+ if (!hasMultiMagic(bytes)) throw new Error("not a NOYDB multi-bundle: missing NDBM magic.");
88
+ if (bytes.length < NOYDB_MULTI_BUNDLE_PREFIX_BYTES) throw new Error("multi-bundle truncated: shorter than the fixed prefix.");
89
+ if (bytes[4] !== NOYDB_MULTI_BUNDLE_VERSION) throw new Error(`unsupported multi-bundle version ${String(bytes[4])}.`);
90
+ const manifestLen = readUint32BE(bytes, 6);
91
+ const manifestEnd = NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestLen;
92
+ if (manifestEnd > bytes.length) throw new Error("multi-bundle truncated: manifest length overruns the buffer.");
93
+ const manifestJson = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(NOYDB_MULTI_BUNDLE_PREFIX_BYTES, manifestEnd));
94
+ let parsed;
95
+ try {
96
+ parsed = JSON.parse(manifestJson);
97
+ } catch (err) {
98
+ throw new Error(`multi-bundle manifest is not valid JSON: ${err.message}`);
99
+ }
100
+ validateManifest(parsed);
101
+ const inner = [];
102
+ let off = manifestEnd;
103
+ for (const c of parsed.compartments) {
104
+ const end = off + c.innerBytes;
105
+ if (end > bytes.length) throw new Error(`multi-bundle truncated: compartment "${c.handle}" innerBytes overruns the buffer.`);
106
+ inner.push(bytes.subarray(off, end));
107
+ off = end;
108
+ }
109
+ if (off !== bytes.length) {
110
+ throw new Error(`multi-bundle: ${bytes.length - off} trailing byte(s) after the last compartment \u2014 buffer may be corrupt.`);
111
+ }
112
+ return { manifest: parsed, inner };
113
+ }
114
+ async function writeMultiVaultBundle(compartments, opts = {}) {
115
+ if (compartments.length === 0) throw new Error("writeMultiVaultBundle: at least one compartment is required.");
116
+ const inner = [];
117
+ const entries = [];
118
+ for (const c of compartments) {
119
+ const innerBytes = await writeNoydbBundle(c.vault, c.bundleOptions ?? {});
120
+ const header = readNoydbBundleHeader(innerBytes);
121
+ const entry = {
122
+ handle: header.handle,
123
+ exportedAt: c.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
124
+ innerBytes: innerBytes.length,
125
+ innerSha256: await sha256Hex(innerBytes)
126
+ };
127
+ if (c.roleTag !== void 0) entry.roleTag = c.roleTag;
128
+ if (c.disclose?.name !== void 0 && c.disclose.name !== false) {
129
+ entry.name = c.disclose.name === true ? c.vault.name : c.disclose.name;
130
+ }
131
+ if (c.disclose?.collections === true) {
132
+ const names = await c.vault.collections();
133
+ entry.collections = await Promise.all(
134
+ names.map(async (n) => ({ name: n, count: await c.vault.collection(n).count() }))
135
+ );
136
+ }
137
+ if (c.disclose?.publicEnvelope === true) {
138
+ const env = readNoydbBundlePublicEnvelope(innerBytes);
139
+ if (env !== void 0) entry.publicEnvelope = env;
140
+ }
141
+ const fence = await c.vault.schemaFenceState();
142
+ entry.schemaVersion = fence.currentSchemaVersion;
143
+ inner.push(innerBytes);
144
+ entries.push(entry);
145
+ }
146
+ const manifest = {
147
+ multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
148
+ handle: opts.handle ?? generateULID(),
149
+ compartments: entries
150
+ };
151
+ return encodeMultiBundle(manifest, inner);
152
+ }
153
+ async function readNoydbBundleManifest(bytes) {
154
+ if (hasMultiMagic(bytes)) return [...decodeMultiBundle(bytes).manifest.compartments];
155
+ if (hasNoydbBundleMagic(bytes)) {
156
+ const header = readNoydbBundleHeader(bytes);
157
+ const env = readNoydbBundlePublicEnvelope(bytes);
158
+ const entry = {
159
+ handle: header.handle,
160
+ innerBytes: bytes.length,
161
+ innerSha256: await sha256Hex(bytes)
162
+ };
163
+ if (env !== void 0) entry.publicEnvelope = env;
164
+ return [entry];
165
+ }
166
+ throw new Error("readNoydbBundleManifest: not a NOYDB bundle (no NDB1 or NDBM magic).");
167
+ }
168
+ function readMultiVaultBundleCompartment(bytes, selector) {
169
+ if (typeof selector === "number" && !Number.isInteger(selector)) {
170
+ throw new Error(`readMultiVaultBundleCompartment: numeric selector must be an integer, got ${selector}.`);
171
+ }
172
+ if (hasNoydbBundleMagic(bytes) && !hasMultiMagic(bytes)) {
173
+ const header = readNoydbBundleHeader(bytes);
174
+ if (selector === 0 || selector === header.handle) return bytes;
175
+ throw new Error(`readMultiVaultBundleCompartment: single v1 bundle has only compartment "${header.handle}".`);
176
+ }
177
+ const { manifest, inner } = decodeMultiBundle(bytes);
178
+ const idx = typeof selector === "number" ? selector : manifest.compartments.findIndex((c) => c.handle === selector);
179
+ if (idx < 0 || idx >= inner.length) throw new Error(`readMultiVaultBundleCompartment: no compartment ${typeof selector === "number" ? `at index ${selector}` : `"${selector}"`}.`);
180
+ return inner[idx];
181
+ }
182
+ var NOYDB_MULTI_BUNDLE_MAGIC, NOYDB_MULTI_BUNDLE_PREFIX_BYTES, NOYDB_MULTI_BUNDLE_VERSION;
183
+ var init_multi_bundle = __esm({
184
+ "src/bundle/multi-bundle.ts"() {
185
+ "use strict";
186
+ init_uint32();
187
+ NOYDB_MULTI_BUNDLE_MAGIC = new Uint8Array([78, 68, 66, 77]);
188
+ NOYDB_MULTI_BUNDLE_PREFIX_BYTES = 10;
189
+ NOYDB_MULTI_BUNDLE_VERSION = 1;
190
+ }
191
+ });
192
+
11
193
  // src/interchange/extract-cross-vault.ts
12
194
  var extract_cross_vault_exports = {};
13
195
  __export(extract_cross_vault_exports, {
@@ -19,13 +201,10 @@ __export(extract_cross_vault_exports, {
19
201
  import {
20
202
  walkClosure,
21
203
  extractPartition,
22
- encodeMultiBundle,
23
- readNoydbBundleHeader,
24
- NOYDB_MULTI_BUNDLE_VERSION,
25
- generateULID,
204
+ readNoydbBundleHeader as readNoydbBundleHeader2,
26
205
  describeExtraction
27
206
  } from "@noy-db/hub/bundle";
28
- import { sha256Hex } from "@noy-db/hub/kernel";
207
+ import { sha256Hex as sha256Hex2, generateULID as generateULID2 } from "@noy-db/hub/kernel";
29
208
  function asIdArray(v) {
30
209
  if (v === null || v === void 0) return [];
31
210
  if (Array.isArray(v)) return v.filter((x) => typeof x === "string" || typeof x === "number").map(String);
@@ -142,13 +321,13 @@ async function extractCrossVaultPartition(openVault, opts) {
142
321
  carryLedger: opts.carryLedger ?? false,
143
322
  ...opts.compression !== void 0 ? { compression: opts.compression } : {}
144
323
  });
145
- const header = readNoydbBundleHeader(bundleBytes);
324
+ const header = readNoydbBundleHeader2(bundleBytes);
146
325
  const meta = opts.compartmentMeta?.[vaultName];
147
326
  const entry = {
148
327
  handle: header.handle,
149
328
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
150
329
  innerBytes: bundleBytes.length,
151
- innerSha256: await sha256Hex(bundleBytes)
330
+ innerSha256: await sha256Hex2(bundleBytes)
152
331
  };
153
332
  if (meta?.roleTag !== void 0) entry.roleTag = meta.roleTag;
154
333
  if (meta?.disclose?.name !== void 0 && meta.disclose.name !== false) {
@@ -167,7 +346,7 @@ async function extractCrossVaultPartition(openVault, opts) {
167
346
  }
168
347
  const manifest = {
169
348
  multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
170
- handle: generateULID(),
349
+ handle: generateULID2(),
171
350
  compartments
172
351
  };
173
352
  return { bundle: encodeMultiBundle(manifest, inner), transferKeys, sealIds };
@@ -189,6 +368,7 @@ var CrossVaultDanglingRefError;
189
368
  var init_extract_cross_vault = __esm({
190
369
  "src/interchange/extract-cross-vault.ts"() {
191
370
  "use strict";
371
+ init_multi_bundle();
192
372
  CrossVaultDanglingRefError = class extends Error {
193
373
  constructor(dangling) {
194
374
  super(
@@ -544,12 +724,12 @@ __export(surface_exports, {
544
724
  markSynced: () => markSynced,
545
725
  proposeSurface: () => proposeSurface
546
726
  });
547
- import { generateULID as generateULID2 } from "@noy-db/hub/kernel";
727
+ import { generateULID as generateULID3 } from "@noy-db/hub/kernel";
548
728
  import { extractPartition as extractPartition2 } from "@noy-db/hub/bundle";
549
729
  async function proposeSurface(smv, def, proposedBy, now) {
550
730
  const row = {
551
731
  ...def,
552
- id: def.id ?? generateULID2(),
732
+ id: def.id ?? generateULID3(),
553
733
  status: "proposed",
554
734
  proposedBy,
555
735
  createdAt: now
@@ -666,7 +846,7 @@ var init_surface = __esm({
666
846
  });
667
847
 
668
848
  // src/federation/schema-manifest.ts
669
- import { sha256Hex as sha256Hex2 } from "@noy-db/hub/kernel";
849
+ import { sha256Hex as sha256Hex3 } from "@noy-db/hub/kernel";
670
850
  function captureBlueprint(configure) {
671
851
  const recorded = [];
672
852
  const collectionStub = new Proxy(
@@ -717,7 +897,7 @@ function canonical(value) {
717
897
  return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
718
898
  }
719
899
  async function fingerprintBlueprint(bp) {
720
- return sha256Hex2(new TextEncoder().encode(canonical(bp)));
900
+ return sha256Hex3(new TextEncoder().encode(canonical(bp)));
721
901
  }
722
902
  var init_schema_manifest = __esm({
723
903
  "src/federation/schema-manifest.ts"() {
@@ -739,7 +919,7 @@ __export(state_vault_exports, {
739
919
  STATE_VAULT_NAME: () => STATE_VAULT_NAME,
740
920
  StateManagementVault: () => StateManagementVault
741
921
  });
742
- import { generateULID as generateULID3 } from "@noy-db/hub/kernel";
922
+ import { generateULID as generateULID4 } from "@noy-db/hub/kernel";
743
923
  var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, SURFACES, StateManagementVault;
744
924
  var init_state_vault = __esm({
745
925
  "src/federation/state-vault.ts"() {
@@ -835,7 +1015,7 @@ var init_state_vault = __esm({
835
1015
  */
836
1016
  async appendEvent(event) {
837
1017
  const ts = event.ts ?? Date.now();
838
- const id = generateULID3();
1018
+ const id = generateULID4();
839
1019
  await this.#events.put(id, { ...event, id, ts });
840
1020
  }
841
1021
  /**
@@ -1808,7 +1988,70 @@ var DockedUnit = class {
1808
1988
  }
1809
1989
  };
1810
1990
 
1991
+ // src/federation/group-inspector.ts
1992
+ function groupInspector(group) {
1993
+ let shardIds = /* @__PURE__ */ new Set();
1994
+ const refresh = async () => {
1995
+ const rows = await group.allRows();
1996
+ shardIds = new Set(rows.map((r) => r.vaultId));
1997
+ return rows;
1998
+ };
1999
+ return {
2000
+ async listAccessibleVaults() {
2001
+ const rows = await refresh();
2002
+ return rows.map((r) => ({ id: r.vaultId, role: "owner" }));
2003
+ },
2004
+ async openVault(name) {
2005
+ const vault = await group.db.openVault(name);
2006
+ group.template.configure(vault);
2007
+ return vault;
2008
+ },
2009
+ onAfterWrite(handler) {
2010
+ return group.db.onAfterWrite((event) => {
2011
+ if (shardIds.has(event.vault)) return handler(event);
2012
+ });
2013
+ },
2014
+ onWriteConflict(handler) {
2015
+ return group.db.onWriteConflict((c) => {
2016
+ if (shardIds.has(c.vault)) handler(c);
2017
+ });
2018
+ },
2019
+ get writeQueue() {
2020
+ return group.db.writeQueue;
2021
+ }
2022
+ };
2023
+ }
2024
+
2025
+ // src/federation/meter-group.ts
2026
+ async function meterGroup(group, opts = {}) {
2027
+ const { eligible, skipped } = await group.resolveEligible(
2028
+ opts.minVersion !== void 0 ? { minVersion: opts.minVersion } : {}
2029
+ );
2030
+ const perShard = [];
2031
+ const names = /* @__PURE__ */ new Set();
2032
+ let records = 0;
2033
+ for (const row of eligible) {
2034
+ const vault = await group.shard(row.partitionKey);
2035
+ const collNames = await vault.collections();
2036
+ let shardRecords = 0;
2037
+ for (const n of collNames) {
2038
+ names.add(n);
2039
+ shardRecords += await vault.collection(n).count();
2040
+ }
2041
+ records += shardRecords;
2042
+ perShard.push({
2043
+ vaultId: row.vaultId,
2044
+ partitionKey: row.partitionKey,
2045
+ schemaVersion: row.schemaVersion,
2046
+ collections: collNames.length,
2047
+ records: shardRecords
2048
+ });
2049
+ }
2050
+ return { vaults: eligible.length, collections: names.size, records, perShard, skipped };
2051
+ }
2052
+
1811
2053
  // src/index.ts
2054
+ init_multi_bundle();
1812
2055
  init_extract_cross_vault();
1813
2056
  init_merge_compartment();
1814
2057
  import {
@@ -2023,6 +2266,9 @@ export {
2023
2266
  Lobby,
2024
2267
  MigrationTransformRequiredError,
2025
2268
  MinVersionError,
2269
+ NOYDB_MULTI_BUNDLE_MAGIC,
2270
+ NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
2271
+ NOYDB_MULTI_BUNDLE_VERSION,
2026
2272
  ReservedVaultNameError3 as ReservedVaultNameError,
2027
2273
  ShardProvisioningError2 as ShardProvisioningError,
2028
2274
  SurfaceCadenceScheduler,
@@ -2035,9 +2281,12 @@ export {
2035
2281
  applySurface,
2036
2282
  createDeedOwner2 as createDeedOwner,
2037
2283
  createLobby,
2284
+ decodeMultiBundle,
2038
2285
  describeCrossVaultExtraction,
2286
+ encodeMultiBundle,
2039
2287
  exportSurface,
2040
2288
  extractCrossVaultPartition,
2289
+ groupInspector,
2041
2290
  isDeedVault,
2042
2291
  isSurfaceDue,
2043
2292
  liberateVault,
@@ -2046,10 +2295,14 @@ export {
2046
2295
  markSynced,
2047
2296
  mergeCompartment,
2048
2297
  mergeDecryptedRecords,
2298
+ meterGroup,
2049
2299
  migrateThenMerge,
2050
2300
  proposeSurface,
2301
+ readMultiVaultBundleCompartment,
2302
+ readNoydbBundleManifest,
2051
2303
  resolveFieldAuthority,
2052
2304
  resolveRecordByFieldAuthority,
2053
- walkCrossVaultClosure
2305
+ walkCrossVaultClosure,
2306
+ writeMultiVaultBundle
2054
2307
  };
2055
2308
  //# sourceMappingURL=index.js.map