@cloudbitmaps/core 0.22.0 → 0.24.0

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.
Files changed (55) hide show
  1. package/README.md +7 -12
  2. package/dist/{chunk-OSU7W2Y3.js → chunk-2B2QF4FU.js} +205 -756
  3. package/dist/chunk-2B2QF4FU.js.map +7 -0
  4. package/dist/chunk-45ML6YU4.js +641 -0
  5. package/dist/chunk-45ML6YU4.js.map +7 -0
  6. package/dist/chunk-BUDAVGBI.js +73 -0
  7. package/dist/chunk-BUDAVGBI.js.map +7 -0
  8. package/dist/chunk-WPER7R2R.js +121 -0
  9. package/dist/chunk-WPER7R2R.js.map +7 -0
  10. package/dist/core/audit.d.ts +53 -15
  11. package/dist/core/bit-route.d.ts +2 -0
  12. package/dist/core/blob.d.ts +4 -0
  13. package/dist/core/budget.d.ts +8 -3
  14. package/dist/core/check.d.ts +50 -0
  15. package/dist/core/codec.d.ts +13 -3
  16. package/dist/core/combine-many.d.ts +11 -0
  17. package/dist/core/consistency.d.ts +11 -2
  18. package/dist/core/cooperative.d.ts +15 -5
  19. package/dist/core/crbm/reader.d.ts +1 -1
  20. package/dist/core/crbm-storage-source.d.ts +7 -4
  21. package/dist/core/crypto.d.ts +5 -0
  22. package/dist/core/discover.d.ts +5 -3
  23. package/dist/core/engine.d.ts +5 -5
  24. package/dist/core/erase-id.d.ts +36 -13
  25. package/dist/core/erasure.d.ts +61 -27
  26. package/dist/core/errors.d.ts +30 -0
  27. package/dist/core/leases.d.ts +2 -0
  28. package/dist/core/load-input.d.ts +49 -1
  29. package/dist/core/load.d.ts +23 -2
  30. package/dist/core/metrics.d.ts +21 -0
  31. package/dist/core/pinned-storage-source.d.ts +27 -8
  32. package/dist/core/pointer-id.d.ts +9 -0
  33. package/dist/core/ports.d.ts +100 -7
  34. package/dist/core/preflight.d.ts +80 -0
  35. package/dist/core/retention-sweep.d.ts +60 -15
  36. package/dist/core/retention.d.ts +17 -6
  37. package/dist/core/retry.d.ts +1 -2
  38. package/dist/core/rollback.d.ts +22 -12
  39. package/dist/core/validate.d.ts +33 -2
  40. package/dist/driver-kit.d.ts +3 -3
  41. package/dist/driver-kit.js +61 -20
  42. package/dist/driver-kit.js.map +2 -2
  43. package/dist/drivers/_shared/object-registry.d.ts +24 -1
  44. package/dist/drivers/_shared/options.d.ts +15 -7
  45. package/dist/drivers/backends.d.ts +17 -2
  46. package/dist/drivers/crypto.d.ts +17 -2
  47. package/dist/export/index.d.ts +16 -2
  48. package/dist/index.d.ts +10 -9
  49. package/dist/index.js +1799 -1700
  50. package/dist/index.js.map +4 -4
  51. package/dist/node.d.ts +14 -0
  52. package/dist/node.js +1064 -0
  53. package/dist/node.js.map +7 -0
  54. package/package.json +11 -3
  55. package/dist/chunk-OSU7W2Y3.js.map +0 -7
@@ -0,0 +1,641 @@
1
+ import {
2
+ INCARNATION_TOKEN,
3
+ IntegrityError,
4
+ MAX_KNOWN_GENERATIONS,
5
+ MAX_LEASES_PER_SEGMENT,
6
+ MAX_METADATA_BYTES,
7
+ MAX_STORED_KNOWN_GENERATIONS,
8
+ MAX_STORED_LEASES,
9
+ MAX_SUMMARY_CARDINALITY,
10
+ UnsupportedError,
11
+ ValidationError,
12
+ canonicalMetadataJson,
13
+ encodeNameForKey,
14
+ encodeNameForPath,
15
+ fingerprintParts,
16
+ knownGensShapeFault,
17
+ refuseUnknownOptions,
18
+ renewsPointer,
19
+ requirePositiveInteger,
20
+ shown,
21
+ tailBytesFit
22
+ } from "./chunk-2B2QF4FU.js";
23
+
24
+ // src/drivers/_shared/options.ts
25
+ function checkNow(name, now) {
26
+ if (now !== void 0 && typeof now !== "function") {
27
+ throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
28
+ }
29
+ }
30
+ function requirePositiveInt(name, value) {
31
+ return value === void 0 ? void 0 : requirePositiveInteger(value, name);
32
+ }
33
+ var DELETE_OPTION_KEYS = ["ifVersion"];
34
+ var WRITE_OPTION_KEYS = ["held", "onToken"];
35
+ function checkDeleteOptions(options) {
36
+ if (options === void 0) return void 0;
37
+ refuseUnknownOptions("delete options", options, DELETE_OPTION_KEYS);
38
+ const { ifVersion } = options;
39
+ if (ifVersion !== void 0 && typeof ifVersion !== "string") {
40
+ throw new ValidationError(
41
+ `delete options: \`ifVersion\` must be a string; got ${shown(ifVersion)}`
42
+ );
43
+ }
44
+ return ifVersion;
45
+ }
46
+ function checkRegistryWriteOptions(options) {
47
+ if (options === void 0) return;
48
+ refuseUnknownOptions("write options", options, WRITE_OPTION_KEYS);
49
+ const { held, onToken } = options;
50
+ if (held !== void 0 && held !== null && typeof held !== "object") {
51
+ throw new ValidationError(
52
+ `write options: \`held\` must be a registry record or null; got ${shown(held)}`
53
+ );
54
+ }
55
+ if (onToken !== void 0 && typeof onToken !== "function") {
56
+ throw new ValidationError(
57
+ `write options: \`onToken\` must be a function; got ${shown(onToken)}`
58
+ );
59
+ }
60
+ }
61
+
62
+ // src/drivers/_shared/keys.ts
63
+ var DEFAULT_NAMESPACE = "_default";
64
+ function namespaceKeyPart(namespace) {
65
+ return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForKey(namespace);
66
+ }
67
+ function namespacePathPart(namespace) {
68
+ return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
69
+ }
70
+
71
+ // src/drivers/_shared/registry.ts
72
+ var STATUSES = ["active", "destroyed"];
73
+ var RECORD_FIELDS = [
74
+ "namespace",
75
+ "segment",
76
+ "currentGen",
77
+ "wrappedDeks",
78
+ "keyId",
79
+ "status",
80
+ "retention",
81
+ "residency",
82
+ "summary",
83
+ "createdAt",
84
+ "updatedAt",
85
+ "token",
86
+ "knownGens",
87
+ "leases",
88
+ "pointerId"
89
+ ];
90
+ var DRIVER_MANAGED = ["token", "pointerId"];
91
+ var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
92
+ var MAX_GOVERNANCE_BYTES = 64 * 1024;
93
+ var MAX_WRAPPED_DEKS = 8;
94
+ var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
95
+ var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
96
+ var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
97
+ var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
98
+ function validateGeneration(gen) {
99
+ if (gen === null) return;
100
+ if (!Number.isSafeInteger(gen) || gen < 0) {
101
+ throw new ValidationError(
102
+ `currentGen must be a non-negative safe integer or null (no Storage generation yet); got ${gen}`
103
+ );
104
+ }
105
+ }
106
+ function validatePatchGeneration(patch) {
107
+ if (!("currentGen" in patch)) return;
108
+ if (patch.currentGen === void 0) {
109
+ throw new ValidationError(
110
+ `currentGen was present in the patch but undefined. Pass \`null\` to clear the pointer (the segment has no Storage generation), or omit the key to leave it unchanged \u2014 an undefined value is refused because it would un-publish the segment's Storage data.`
111
+ );
112
+ }
113
+ validateGeneration(patch.currentGen);
114
+ }
115
+ function validateStatus(status) {
116
+ if (!STATUSES.includes(status)) {
117
+ throw new ValidationError(`status must be one of ${STATUSES.join("/")}; got ${status}`);
118
+ }
119
+ }
120
+ function validateGovernance(meta, field) {
121
+ if (meta === void 0) return;
122
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
123
+ throw new ValidationError(
124
+ `${field} must be a plain object (got ${meta === null ? "null" : typeof meta})`
125
+ );
126
+ }
127
+ let json;
128
+ try {
129
+ json = JSON.stringify(meta);
130
+ } catch {
131
+ throw new ValidationError(`${field} must be JSON-serializable`);
132
+ }
133
+ if (json.length > MAX_GOVERNANCE_BYTES) {
134
+ throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
135
+ }
136
+ }
137
+ function validateWrappedDeks(value, origin) {
138
+ if (value === void 0) return;
139
+ const fail = (msg) => {
140
+ throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
141
+ };
142
+ if (!Array.isArray(value)) fail("wrappedDeks must be an array");
143
+ const list = value;
144
+ if (list.length === 0) fail("wrappedDeks must be non-empty when present");
145
+ if (list.length > MAX_WRAPPED_DEKS)
146
+ fail(`wrappedDeks has ${list.length} entries, cap ${MAX_WRAPPED_DEKS}`);
147
+ for (const w of list) {
148
+ if (w === null || typeof w !== "object") fail("wrappedDeks entry must be an object");
149
+ const { keyId, wrapped } = w;
150
+ if (typeof keyId !== "string" || keyId.length === 0)
151
+ fail("wrappedDeks entry needs a non-empty keyId");
152
+ if (typeof wrapped !== "string" || wrapped.length === 0)
153
+ fail("wrappedDeks entry needs a non-empty wrapped blob");
154
+ if (wrapped.length > MAX_WRAPPED_DEK_BYTES)
155
+ fail(`wrappedDeks entry is ${wrapped.length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);
156
+ }
157
+ }
158
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
159
+ function base64PadBitsAreZero(text) {
160
+ const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
161
+ if (pad === 0) return true;
162
+ const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
163
+ return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
164
+ }
165
+ function validateSummary(value, origin, ctx) {
166
+ if (value === void 0) return void 0;
167
+ const fail = (msg) => {
168
+ const where = ctx === void 0 ? "" : `: ${ctx}`;
169
+ throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
170
+ };
171
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
172
+ fail(
173
+ `must be an object (got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value})`
174
+ );
175
+ }
176
+ const s = value;
177
+ const keys = Object.keys(s);
178
+ const isSealed = keys.includes("sealed");
179
+ const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
180
+ const extra = keys.filter((k) => !allowed.includes(k));
181
+ if (extra.length > 0) {
182
+ fail(
183
+ `has fields its ${isSealed ? "sealed" : "clear"} shape does not declare (${extra.join(", ")})`
184
+ );
185
+ }
186
+ const generation = s.generation;
187
+ if (!Number.isSafeInteger(generation) || generation < 0) {
188
+ fail(`generation must be a non-negative safe integer (got ${String(generation)})`);
189
+ }
190
+ if (isSealed) {
191
+ const sealed = s.sealed;
192
+ if (typeof sealed !== "string" || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {
193
+ fail("sealed must be canonical, padded standard base64");
194
+ }
195
+ const text = sealed;
196
+ const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
197
+ if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
198
+ fail(
199
+ `sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count, fingerprint and tail size, a tag, and metadata up to its cap)`
200
+ );
201
+ }
202
+ return Object.freeze({ generation, sealed: text });
203
+ }
204
+ const n = s.cardinality;
205
+ if (!Number.isInteger(n) || n < 0 || n > MAX_SUMMARY_CARDINALITY) {
206
+ fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
207
+ }
208
+ const fingerprint = s.fingerprint;
209
+ const parts = fingerprintParts(fingerprint);
210
+ if (parts === void 0) {
211
+ fail(
212
+ `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown2(JSON.stringify(fingerprint))})`
213
+ );
214
+ }
215
+ const tailBytes = s.tailBytes;
216
+ if (!tailBytesFit(tailBytes, parts.size)) {
217
+ fail(
218
+ `tailBytes must be an integer from the footer's size to the object's size less its preamble, and at most the cap of one open's tail read (got ${shown2(JSON.stringify(tailBytes))} for an object of ${parts.size} bytes)`
219
+ );
220
+ }
221
+ if (s.metadata === void 0) {
222
+ return Object.freeze({
223
+ generation,
224
+ cardinality: n,
225
+ fingerprint,
226
+ tailBytes
227
+ });
228
+ }
229
+ const canonical = canonicalMetadataJson(s.metadata, fail);
230
+ if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
231
+ if (origin === "stored") return s;
232
+ const metadata = Object.freeze(JSON.parse(canonical));
233
+ return Object.freeze({
234
+ generation,
235
+ cardinality: n,
236
+ fingerprint,
237
+ tailBytes,
238
+ metadata
239
+ });
240
+ }
241
+ function validateSummaryNames(summary, currentGen) {
242
+ if (summary !== void 0 && summary.generation !== currentGen) {
243
+ throw new ValidationError(
244
+ `summary describes generation ${summary.generation}, but the row points at ${String(currentGen)}`
245
+ );
246
+ }
247
+ }
248
+ function summaryAgreesWithKeys(summary, wrappedDeks) {
249
+ return "sealed" in summary === (wrappedDeks !== void 0);
250
+ }
251
+ function validateSummaryKeys(summary, wrappedDeks) {
252
+ if (summary !== void 0 && !summaryAgreesWithKeys(summary, wrappedDeks)) {
253
+ throw new ValidationError(
254
+ wrappedDeks === void 0 ? "summary is sealed, but the row has no wrapped keys to open it with" : "summary is in the clear, but the row is encrypted: an encrypted segment's summary must be sealed"
255
+ );
256
+ }
257
+ }
258
+ function validateKnownGens(value, origin, ctx) {
259
+ if (value === void 0) return void 0;
260
+ const fail = (msg) => {
261
+ const where = ctx === void 0 ? "" : `: ${ctx}`;
262
+ throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
263
+ };
264
+ const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
265
+ const fault = knownGensShapeFault(value, max, shown2);
266
+ if (fault !== void 0) fail(fault);
267
+ const out = [];
268
+ for (const entry of value) {
269
+ out.push(
270
+ origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
271
+ );
272
+ }
273
+ return origin === "stored" ? out : Object.freeze(out);
274
+ }
275
+ var HOLDER = /^[0-9a-f]{16}$/;
276
+ var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
277
+ function validateLeases(value, origin, ctx) {
278
+ if (value === void 0) return void 0;
279
+ const fail = (msg) => {
280
+ const where = ctx === void 0 ? "" : `: ${ctx}`;
281
+ throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
282
+ };
283
+ if (!Array.isArray(value)) fail("must be an array");
284
+ const list = value;
285
+ const max = MAX_STORED_LEASES;
286
+ if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
287
+ const seen = /* @__PURE__ */ new Set();
288
+ const out = [];
289
+ for (const e of list) {
290
+ if (typeof e !== "object" || e === null || Array.isArray(e)) fail("an entry must be an object");
291
+ const entry = e;
292
+ const extra = Object.keys(entry).filter((k) => !LEASE_ENTRY_KEYS.includes(k));
293
+ if (extra.length > 0) fail(`an entry carries an undeclared field (${extra.join(", ")})`);
294
+ const { holder, generation, until } = entry;
295
+ if (typeof holder !== "string" || !HOLDER.test(holder)) {
296
+ fail("a holder must be 16 lowercase hex digits");
297
+ }
298
+ if (seen.has(holder)) fail("holders must be unique");
299
+ seen.add(holder);
300
+ for (const [name, n] of [
301
+ ["generation", generation],
302
+ ["until", until]
303
+ ]) {
304
+ if (!Number.isSafeInteger(n) || n < 0) {
305
+ fail(`${name} must be a non-negative safe integer (got ${String(n)})`);
306
+ }
307
+ }
308
+ out.push(
309
+ origin === "stored" ? entry : Object.freeze({
310
+ holder,
311
+ generation,
312
+ until
313
+ })
314
+ );
315
+ }
316
+ if (origin === "stored") return out;
317
+ return out.length === 0 ? void 0 : Object.freeze(out);
318
+ }
319
+ function validateLeasesPointer(leases, currentGen) {
320
+ if (leases !== void 0 && leases.length > 0 && currentGen === null) {
321
+ throw new ValidationError("leases: a row with no pointer has no generation to lease");
322
+ }
323
+ }
324
+ function validateKnownGensFit(knownGens, currentGen, summary) {
325
+ if (knownGens === void 0) return;
326
+ if (currentGen === null) {
327
+ throw new ValidationError("knownGens: a row with no pointer has no generation to know");
328
+ }
329
+ const last = knownGens[knownGens.length - 1];
330
+ if (last === void 0 || last.generation !== currentGen) {
331
+ throw new ValidationError(
332
+ `knownGens must end with the row's pointer ${currentGen}, in the order the generations were made current (its last entry is ${last === void 0 ? "missing" : last.generation})`
333
+ );
334
+ }
335
+ if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
336
+ throw new ValidationError(
337
+ `knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
338
+ );
339
+ }
340
+ }
341
+ function refuseDriverManaged(write, what) {
342
+ const named = DRIVER_MANAGED.filter((field) => field in write);
343
+ if (named.length > 0) {
344
+ throw new ValidationError(
345
+ `${what} names ${named.join(" and ")}, which the registry sets on every write and a caller never does`
346
+ );
347
+ }
348
+ }
349
+ function validateKeyId(keyId) {
350
+ if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
351
+ throw new ValidationError("keyId must be a non-empty string, or absent");
352
+ }
353
+ }
354
+ function validateNewRegistryRecord(rec) {
355
+ refuseDriverManaged(rec, "a new record");
356
+ validateKeyId(rec.keyId);
357
+ validateGeneration(rec.currentGen);
358
+ if (rec.status !== void 0) validateStatus(rec.status);
359
+ validateWrappedDeks(rec.wrappedDeks, "caller");
360
+ validateGovernance(rec.retention, "retention");
361
+ validateGovernance(rec.residency, "residency");
362
+ const knownGens = validateKnownGens(rec.knownGens, "caller");
363
+ const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
364
+ const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
365
+ validateKnownGensFit(knownGens, rec.currentGen, summary);
366
+ if (!("summary" in rec)) return withKnown;
367
+ validateSummaryNames(summary, rec.currentGen);
368
+ validateSummaryKeys(summary, rec.wrappedDeks);
369
+ return { ...withKnown, summary };
370
+ }
371
+ function validateRegistryPatch(patch) {
372
+ refuseDriverManaged(patch, "a patch");
373
+ if ("keyId" in patch) validateKeyId(patch.keyId);
374
+ validatePatchGeneration(patch);
375
+ if (patch.status !== void 0) validateStatus(patch.status);
376
+ if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
377
+ if ("retention" in patch) validateGovernance(patch.retention, "retention");
378
+ if ("residency" in patch) validateGovernance(patch.residency, "residency");
379
+ const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
380
+ const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
381
+ if (!("summary" in patch)) return withLeases;
382
+ return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
383
+ }
384
+ var REGISTRY_SCHEMA_VERSION = 5;
385
+ function assertRegistrySchemaVersion(raw, ctx) {
386
+ if (raw === void 0) {
387
+ throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
388
+ }
389
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
390
+ throw new IntegrityError(`registry row has a malformed schemaVersion (${shown2(raw)}): ${ctx}`);
391
+ }
392
+ if (raw < REGISTRY_SCHEMA_VERSION) {
393
+ throw new UnsupportedError(
394
+ `registry row schemaVersion ${raw} is one this build does not read: it reads schema ${REGISTRY_SCHEMA_VERSION} only. Export the segments with the release that wrote the row and load them with this one into a new prefix (steps: https://github.com/cloudbitmaps/cloudbitmaps/blob/main/CHANGELOG.md, the Breaking entry on registry schema 5): ${ctx}`
395
+ );
396
+ }
397
+ if (raw > REGISTRY_SCHEMA_VERSION) {
398
+ throw new UnsupportedError(
399
+ `registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`
400
+ );
401
+ }
402
+ return raw;
403
+ }
404
+ function serializeRegistryEnvelope(env) {
405
+ return JSON.stringify({
406
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
407
+ deleted: env.deleted,
408
+ record: env.record
409
+ });
410
+ }
411
+ var INCARNATION_BYTES = 16;
412
+ var WRITE_BYTES = 8;
413
+ function tokenParts(token, ctx, what = "token") {
414
+ const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
415
+ if (match === null) {
416
+ throw new IntegrityError(
417
+ `registry row ${what} is not one a shipped registry writes (${shown2(JSON.stringify(token))}): ${ctx}`
418
+ );
419
+ }
420
+ const counter = Number(match[2]);
421
+ if (!Number.isSafeInteger(counter)) {
422
+ throw new IntegrityError(`registry row ${what}'s counter is out of safe-integer range: ${ctx}`);
423
+ }
424
+ return { incarnation: match[1], counter };
425
+ }
426
+ function checkPointerId(pointerId, token, ctx) {
427
+ const parts = tokenParts(pointerId, ctx, "pointerId");
428
+ if (parts.incarnation !== token.incarnation) {
429
+ throw new IntegrityError(
430
+ `registry row pointerId names another incarnation than its token: ${ctx}`
431
+ );
432
+ }
433
+ if (parts.counter > token.counter) {
434
+ throw new IntegrityError(`registry row pointerId is a write after its token: ${ctx}`);
435
+ }
436
+ }
437
+ function drawHex(entropy, bytes) {
438
+ const drawn = entropy(bytes);
439
+ if (!(drawn instanceof Uint8Array) || drawn.length !== bytes) {
440
+ throw new ValidationError(
441
+ `the registry's entropy source must return ${bytes} bytes as a Uint8Array`
442
+ );
443
+ }
444
+ let hex = "";
445
+ for (const b of drawn) hex += b.toString(16).padStart(2, "0");
446
+ return hex;
447
+ }
448
+ function drawIncarnation(entropy) {
449
+ return drawHex(entropy, INCARNATION_BYTES);
450
+ }
451
+ function drawWrite(entropy) {
452
+ return drawHex(entropy, WRITE_BYTES);
453
+ }
454
+ function nextRegistryToken(record, entropy) {
455
+ const { incarnation, counter } = tokenParts(record.token, record.segment);
456
+ return `${incarnation}.${counter + 1}.${drawWrite(entropy)}`;
457
+ }
458
+ function newIncarnationToken(entropy, tombstone) {
459
+ const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
460
+ return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
461
+ }
462
+ function shown2(v) {
463
+ const text = String(v);
464
+ return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
465
+ }
466
+ function parseRegistryEnvelope(text, ctx, expected) {
467
+ let parsed;
468
+ try {
469
+ parsed = JSON.parse(text);
470
+ } catch {
471
+ throw new IntegrityError(`registry row is not valid JSON: ${ctx}`);
472
+ }
473
+ if (parsed === null || typeof parsed !== "object") {
474
+ throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
475
+ }
476
+ const env = parsed;
477
+ assertRegistrySchemaVersion(env.schemaVersion, ctx);
478
+ const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
479
+ if (extra.length > 0) {
480
+ throw new IntegrityError(
481
+ `registry row has fields its envelope does not declare (${shown2(extra.join(", "))}): ${ctx}`
482
+ );
483
+ }
484
+ if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
485
+ throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
486
+ }
487
+ const r = env.record;
488
+ assertStoredRecordShape(r, ctx);
489
+ if (typeof r.token !== "string") {
490
+ throw new IntegrityError(`registry row is missing its token: ${ctx}`);
491
+ }
492
+ checkPointerId(r.pointerId, tokenParts(r.token, ctx), ctx);
493
+ if (expected !== void 0 && (r.segment !== expected.segment || (r.namespace ?? void 0) !== (expected.namespace ?? void 0))) {
494
+ throw new IntegrityError(
495
+ `registry row names another segment than the one it is stored for: ${ctx}`
496
+ );
497
+ }
498
+ return { deleted: env.deleted, record: env.record };
499
+ }
500
+ function recordFromNew(ref, rec, now, token) {
501
+ return {
502
+ namespace: ref.namespace,
503
+ segment: ref.segment,
504
+ currentGen: rec.currentGen,
505
+ wrappedDeks: rec.wrappedDeks,
506
+ keyId: rec.keyId,
507
+ status: rec.status ?? "active",
508
+ retention: rec.retention,
509
+ residency: rec.residency,
510
+ summary: rec.summary,
511
+ knownGens: rec.knownGens,
512
+ // A new row has no holder: a lease is written only against a row that already exists.
513
+ leases: void 0,
514
+ createdAt: now,
515
+ updatedAt: now,
516
+ token,
517
+ pointerId: token
518
+ };
519
+ }
520
+ function assertStoredRecordShape(r, ctx) {
521
+ if (typeof r.segment !== "string" || typeof r.pointerId !== "string" || r.namespace !== void 0 && typeof r.namespace !== "string" || r.currentGen !== null && typeof r.currentGen !== "number" || typeof r.status !== "string" || typeof r.createdAt !== "number" || typeof r.updatedAt !== "number") {
522
+ throw new IntegrityError(`registry record is missing required fields: ${ctx}`);
523
+ }
524
+ if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
525
+ throw new IntegrityError(
526
+ `registry record has an invalid currentGen (${shown2(r.currentGen)}): ${ctx}`
527
+ );
528
+ }
529
+ if (!STATUSES.includes(r.status)) {
530
+ throw new IntegrityError(`registry record has an unknown status (${shown2(r.status)}): ${ctx}`);
531
+ }
532
+ const declared = RECORD_FIELDS;
533
+ const extra = Object.keys(r).filter((k) => !declared.includes(k));
534
+ if (extra.length > 0) {
535
+ throw new IntegrityError(
536
+ `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown2(extra.join(", "))}): ${ctx}`
537
+ );
538
+ }
539
+ if (r.keyId !== void 0 && typeof r.keyId !== "string") {
540
+ throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
541
+ }
542
+ validateWrappedDeks(r.wrappedDeks, "stored");
543
+ for (const field of ["retention", "residency"]) {
544
+ const meta = r[field];
545
+ if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
546
+ throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
547
+ }
548
+ }
549
+ validateSummary(r.summary, "stored", ctx);
550
+ validateKnownGens(r.knownGens, "stored", ctx);
551
+ validateLeases(r.leases, "stored", ctx);
552
+ }
553
+ function applyRegistryPatch(prev, patch, now, token) {
554
+ const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
555
+ const wrappedDeks = "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks;
556
+ let summary;
557
+ if ("summary" in patch) {
558
+ validateSummaryNames(patch.summary, currentGen);
559
+ validateSummaryKeys(patch.summary, wrappedDeks);
560
+ summary = patch.summary;
561
+ } else {
562
+ const kept = currentGen === prev.currentGen ? prev.summary : void 0;
563
+ summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
564
+ }
565
+ let knownGens;
566
+ if ("knownGens" in patch) {
567
+ validateKnownGensFit(patch.knownGens, currentGen, summary);
568
+ knownGens = patch.knownGens;
569
+ } else {
570
+ knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
571
+ }
572
+ let leases;
573
+ if ("leases" in patch) {
574
+ leases = patch.leases;
575
+ validateLeasesPointer(leases, currentGen);
576
+ const most = Math.max(MAX_LEASES_PER_SEGMENT, prev.leases?.length ?? 0);
577
+ if (leases !== void 0 && leases.length > most) {
578
+ throw new ValidationError(`leases: has ${leases.length} entries, cap ${most}`);
579
+ }
580
+ } else {
581
+ leases = prev.leases;
582
+ }
583
+ return {
584
+ namespace: prev.namespace,
585
+ segment: prev.segment,
586
+ currentGen,
587
+ wrappedDeks,
588
+ keyId: "keyId" in patch ? patch.keyId : prev.keyId,
589
+ status: patch.status ?? prev.status,
590
+ retention: "retention" in patch ? patch.retention : prev.retention,
591
+ residency: "residency" in patch ? patch.residency : prev.residency,
592
+ summary,
593
+ knownGens,
594
+ leases,
595
+ createdAt: prev.createdAt,
596
+ updatedAt: now,
597
+ token,
598
+ pointerId: renewsPointer(patch) ? token : prev.pointerId
599
+ };
600
+ }
601
+
602
+ // src/drivers/_shared/entropy.ts
603
+ function webCrypto() {
604
+ const c = globalThis.crypto;
605
+ return c !== void 0 && typeof c.getRandomValues === "function" ? c : void 0;
606
+ }
607
+ var webCryptoEntropy = (length) => {
608
+ const c = webCrypto();
609
+ if (c === void 0) {
610
+ throw new UnsupportedError(
611
+ "this runtime has no Web Crypto (crypto.getRandomValues), which a registry needs for the token of every row it writes: run on a runtime that has it"
612
+ );
613
+ }
614
+ return c.getRandomValues(new Uint8Array(length));
615
+ };
616
+ function entropyIsAvailable(entropy) {
617
+ return entropy !== webCryptoEntropy || webCrypto() !== void 0;
618
+ }
619
+
620
+ export {
621
+ checkNow,
622
+ requirePositiveInt,
623
+ checkDeleteOptions,
624
+ checkRegistryWriteOptions,
625
+ validateNewRegistryRecord,
626
+ validateRegistryPatch,
627
+ serializeRegistryEnvelope,
628
+ drawIncarnation,
629
+ drawWrite,
630
+ nextRegistryToken,
631
+ newIncarnationToken,
632
+ parseRegistryEnvelope,
633
+ recordFromNew,
634
+ applyRegistryPatch,
635
+ webCryptoEntropy,
636
+ entropyIsAvailable,
637
+ DEFAULT_NAMESPACE,
638
+ namespaceKeyPart,
639
+ namespacePathPart
640
+ };
641
+ //# sourceMappingURL=chunk-45ML6YU4.js.map