@cloudbitmaps/core 0.11.2 → 0.12.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 (42) hide show
  1. package/dist/{chunk-5OMC5U7U.js → chunk-5W3XS2IU.js} +292 -22
  2. package/dist/chunk-5W3XS2IU.js.map +7 -0
  3. package/dist/core/audit.d.ts +6 -0
  4. package/dist/core/codec.d.ts +33 -1
  5. package/dist/core/consistency.d.ts +21 -2
  6. package/dist/core/cost.d.ts +91 -10
  7. package/dist/core/crbm/format.d.ts +31 -1
  8. package/dist/core/crbm/reader.d.ts +44 -2
  9. package/dist/core/crbm/writer.d.ts +32 -3
  10. package/dist/core/crbm-storage-source.d.ts +203 -22
  11. package/dist/core/crypto.d.ts +11 -7
  12. package/dist/core/determinism.d.ts +7 -0
  13. package/dist/core/engine.d.ts +17 -5
  14. package/dist/core/erase-id.d.ts +20 -0
  15. package/dist/core/erasure.d.ts +8 -4
  16. package/dist/core/generation-gc.d.ts +102 -4
  17. package/dist/core/load-input.d.ts +55 -0
  18. package/dist/core/load.d.ts +65 -10
  19. package/dist/core/metadata.d.ts +42 -0
  20. package/dist/core/pinned-storage-source.d.ts +2 -1
  21. package/dist/core/ports.d.ts +119 -13
  22. package/dist/core/registry-scan.d.ts +6 -0
  23. package/dist/core/retention-sweep.d.ts +47 -2
  24. package/dist/core/retry.d.ts +14 -0
  25. package/dist/core/rollback.d.ts +34 -4
  26. package/dist/core/summary.d.ts +61 -0
  27. package/dist/core/token.d.ts +36 -0
  28. package/dist/core/validate.d.ts +2 -0
  29. package/dist/driver-kit.js +39 -20
  30. package/dist/driver-kit.js.map +2 -2
  31. package/dist/drivers/_shared/entropy.d.ts +5 -0
  32. package/dist/drivers/_shared/object-registry.d.ts +36 -6
  33. package/dist/drivers/_shared/registry.d.ts +47 -19
  34. package/dist/drivers/localfs/registry.d.ts +10 -0
  35. package/dist/drivers/memory.d.ts +9 -3
  36. package/dist/drivers/retry/retrying-chunk-source.d.ts +2 -1
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +1794 -461
  39. package/dist/index.js.map +4 -4
  40. package/dist/testing/fuzz-core.d.ts +6 -5
  41. package/package.json +1 -1
  42. package/dist/chunk-5OMC5U7U.js.map +0 -7
@@ -116,6 +116,9 @@ function decodeNameFromPath(encoded) {
116
116
  var CHUNK_KEY_MAX = 65535;
117
117
  var MAX_NAME_LENGTH = 256;
118
118
  var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
119
+ function isWellFormedString(value) {
120
+ return !LONE_SURROGATE.test(value);
121
+ }
119
122
  function encodedNameLength(value) {
120
123
  return Math.max(encodeNameForKey(value).length, encodeNameForPath(value).length);
121
124
  }
@@ -203,6 +206,13 @@ async function mapWithConcurrency(items, limit, fn) {
203
206
  }
204
207
 
205
208
  // src/core/ports.ts
209
+ function assertRegistryCanWrite(registry, what) {
210
+ if (registry.capabilities().canWrite === false) {
211
+ throw new UnsupportedError(
212
+ `${what}: the registry reports it cannot write a row in this runtime (capabilities().canWrite is false), so nothing is written`
213
+ );
214
+ }
215
+ }
206
216
  var STORAGE_BACKEND_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.storage-backend");
207
217
  var STORAGE_BACKEND = STORAGE_BACKEND_BRAND;
208
218
  function brandAsBackend(target) {
@@ -247,6 +257,96 @@ function namespacePathPart(namespace) {
247
257
  return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
248
258
  }
249
259
 
260
+ // src/core/metadata.ts
261
+ var MAX_METADATA_BYTES = 1024;
262
+ var MAX_METADATA_KEY_BYTES = 128;
263
+ var utf8Length = (s) => new TextEncoder().encode(s).length;
264
+ function canonicalMetadataJson(value, fail) {
265
+ if (value === null || typeof value !== "object") {
266
+ fail(`metadata must be a plain object (got ${value === null ? "null" : typeof value})`);
267
+ }
268
+ const proto = Object.getPrototypeOf(value);
269
+ if (Object.prototype.toString.call(value) !== "[object Object]" || proto !== Object.prototype && proto !== null) {
270
+ fail("metadata must be a plain object, not an array, a Map, a boxed value or a class instance");
271
+ }
272
+ const entries = [];
273
+ const keys = Reflect.ownKeys(value);
274
+ for (const key of keys) {
275
+ if (typeof key !== "string") fail("metadata keys must be strings, not symbols");
276
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
277
+ if (descriptor === void 0 || !("value" in descriptor) || descriptor.enumerable !== true) {
278
+ fail(`metadata key ${JSON.stringify(key)} must be an enumerable data property`);
279
+ }
280
+ if (key.length === 0) fail("metadata keys must be non-empty");
281
+ if (key === "__proto__") fail('metadata may not use the key "__proto__"');
282
+ if (!isWellFormedString(key)) fail("metadata keys must be well-formed UTF-16");
283
+ if (key.length * 3 > MAX_METADATA_KEY_BYTES) {
284
+ const keyBytes = utf8Length(key);
285
+ if (keyBytes > MAX_METADATA_KEY_BYTES) {
286
+ fail(`metadata key is ${keyBytes}B, over the ${MAX_METADATA_KEY_BYTES}B cap`);
287
+ }
288
+ }
289
+ const v = descriptor.value;
290
+ if (typeof v === "string") {
291
+ if (!isWellFormedString(v)) {
292
+ fail(`metadata value of ${JSON.stringify(key)} must be well-formed UTF-16`);
293
+ }
294
+ } else if (typeof v !== "number" || !Number.isFinite(v)) {
295
+ fail(`metadata value of ${JSON.stringify(key)} must be a string or a finite number`);
296
+ }
297
+ entries.push([key, v]);
298
+ }
299
+ entries.sort(([a], [b]) => a < b ? -1 : 1);
300
+ const json = `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${JSON.stringify(v)}`).join(",")}}`;
301
+ const bytes = utf8Length(json);
302
+ if (bytes > MAX_METADATA_BYTES) {
303
+ fail(`metadata is ${bytes}B as canonical JSON, over the ${MAX_METADATA_BYTES}B cap`);
304
+ }
305
+ return json;
306
+ }
307
+ function metadataBytes(value, fail) {
308
+ if (value === void 0) return void 0;
309
+ const json = canonicalMetadataJson(value, fail);
310
+ return json === "{}" ? void 0 : new TextEncoder().encode(json);
311
+ }
312
+ function copiedMetadata(value, fail) {
313
+ const bytes = metadataBytes(value, fail);
314
+ return bytes === void 0 ? void 0 : metadataFromBytes(bytes, fail);
315
+ }
316
+ function metadataFromBytes(bytes, fail) {
317
+ if (bytes.length > MAX_METADATA_BYTES) {
318
+ fail(`metadata is ${bytes.length}B, over the ${MAX_METADATA_BYTES}B cap`);
319
+ }
320
+ let text;
321
+ try {
322
+ text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
323
+ } catch {
324
+ fail("metadata is not valid UTF-8");
325
+ }
326
+ let parsed;
327
+ try {
328
+ parsed = JSON.parse(text);
329
+ } catch {
330
+ fail("metadata is not JSON");
331
+ }
332
+ const json = canonicalMetadataJson(parsed, fail);
333
+ if (json === "{}") fail("metadata is empty; a generation without metadata carries none");
334
+ if (json !== text) fail("metadata is not in canonical form");
335
+ return Object.freeze(parsed);
336
+ }
337
+
338
+ // src/core/token.ts
339
+ var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
340
+ function incarnationOf(token) {
341
+ return INCARNATION_TOKEN.exec(token)?.[1];
342
+ }
343
+ function sameIncarnation(a, b) {
344
+ const ia = incarnationOf(a.token);
345
+ const ib = incarnationOf(b.token);
346
+ if (ia !== void 0 || ib !== void 0) return ia === ib;
347
+ return a.createdAt === b.createdAt;
348
+ }
349
+
250
350
  // src/drivers/_shared/registry.ts
251
351
  var STATUSES = ["active", "destroyed"];
252
352
  var RECORD_FIELDS = [
@@ -258,14 +358,20 @@ var RECORD_FIELDS = [
258
358
  "status",
259
359
  "retention",
260
360
  "residency",
361
+ "summary",
261
362
  "createdAt",
262
363
  "updatedAt",
263
364
  "token"
264
365
  ];
366
+ var SCHEMA_1_RECORD_FIELDS = RECORD_FIELDS.filter((f) => f !== "summary");
265
367
  var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
266
368
  var MAX_GOVERNANCE_BYTES = 64 * 1024;
267
369
  var MAX_WRAPPED_DEKS = 8;
268
370
  var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
371
+ var MAX_SUMMARY_CARDINALITY = 2 ** 32;
372
+ var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 16;
373
+ var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
374
+ var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
269
375
  function validateGeneration(gen) {
270
376
  if (gen === null) return;
271
377
  if (!Number.isInteger(gen) || gen < 0) {
@@ -326,12 +432,93 @@ function validateWrappedDeks(value, isStored) {
326
432
  fail(`wrappedDeks entry is ${wrapped.length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);
327
433
  }
328
434
  }
435
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
436
+ function base64PadBitsAreZero(text) {
437
+ const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
438
+ if (pad === 0) return true;
439
+ const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
440
+ return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
441
+ }
442
+ function validateSummary(value, isStored, ctx) {
443
+ if (value === void 0) return void 0;
444
+ const fail = (msg) => {
445
+ const where = ctx === void 0 ? "" : `: ${ctx}`;
446
+ throw isStored ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
447
+ };
448
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
449
+ fail(
450
+ `must be an object (got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value})`
451
+ );
452
+ }
453
+ const s = value;
454
+ const keys = Object.keys(s);
455
+ const isSealed = keys.includes("sealed");
456
+ const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "metadata"];
457
+ const extra = keys.filter((k) => !allowed.includes(k));
458
+ if (extra.length > 0) {
459
+ fail(
460
+ `has fields its ${isSealed ? "sealed" : "clear"} shape does not declare (${extra.join(", ")})`
461
+ );
462
+ }
463
+ const generation = s.generation;
464
+ if (!Number.isSafeInteger(generation) || generation < 0) {
465
+ fail(`generation must be a non-negative safe integer (got ${String(generation)})`);
466
+ }
467
+ if (isSealed) {
468
+ const sealed = s.sealed;
469
+ if (typeof sealed !== "string" || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {
470
+ fail("sealed must be canonical, padded standard base64");
471
+ }
472
+ const text = sealed;
473
+ const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
474
+ if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
475
+ fail(
476
+ `sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count, a tag, and metadata up to its cap)`
477
+ );
478
+ }
479
+ return Object.freeze({ generation, sealed: text });
480
+ }
481
+ const n = s.cardinality;
482
+ if (!Number.isInteger(n) || n < 0 || n > MAX_SUMMARY_CARDINALITY) {
483
+ fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
484
+ }
485
+ if (s.metadata === void 0) {
486
+ return Object.freeze({ generation, cardinality: n });
487
+ }
488
+ const canonical = canonicalMetadataJson(s.metadata, fail);
489
+ if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
490
+ if (isStored) return s;
491
+ const metadata = Object.freeze(JSON.parse(canonical));
492
+ return Object.freeze({ generation, cardinality: n, metadata });
493
+ }
494
+ function validateSummaryNames(summary, currentGen) {
495
+ if (summary !== void 0 && summary.generation !== currentGen) {
496
+ throw new ValidationError(
497
+ `summary describes generation ${summary.generation}, but the row points at ${String(currentGen)}`
498
+ );
499
+ }
500
+ }
501
+ function summaryAgreesWithKeys(summary, wrappedDeks) {
502
+ return "sealed" in summary === (wrappedDeks !== void 0);
503
+ }
504
+ function validateSummaryKeys(summary, wrappedDeks) {
505
+ if (summary !== void 0 && !summaryAgreesWithKeys(summary, wrappedDeks)) {
506
+ throw new ValidationError(
507
+ 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"
508
+ );
509
+ }
510
+ }
329
511
  function validateNewRegistryRecord(rec) {
330
512
  validateGeneration(rec.currentGen);
331
513
  if (rec.status !== void 0) validateStatus(rec.status);
332
514
  validateWrappedDeks(rec.wrappedDeks, false);
333
515
  validateGovernance(rec.retention, "retention");
334
516
  validateGovernance(rec.residency, "residency");
517
+ if (!("summary" in rec)) return rec;
518
+ const summary = validateSummary(rec.summary, false);
519
+ validateSummaryNames(summary, rec.currentGen);
520
+ validateSummaryKeys(summary, rec.wrappedDeks);
521
+ return { ...rec, summary };
335
522
  }
336
523
  function validateRegistryPatch(patch) {
337
524
  validatePatchGeneration(patch);
@@ -339,13 +526,16 @@ function validateRegistryPatch(patch) {
339
526
  if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
340
527
  if ("retention" in patch) validateGovernance(patch.retention, "retention");
341
528
  if ("residency" in patch) validateGovernance(patch.residency, "residency");
529
+ if (!("summary" in patch)) return patch;
530
+ return { ...patch, summary: validateSummary(patch.summary, false) };
342
531
  }
343
- var REGISTRY_SCHEMA_VERSION = 1;
532
+ var REGISTRY_SCHEMA_VERSION = 2;
533
+ var OLDEST_REGISTRY_SCHEMA_VERSION = 1;
344
534
  function assertRegistrySchemaVersion(raw, ctx) {
345
535
  if (raw === void 0) {
346
536
  throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
347
537
  }
348
- if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
538
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < OLDEST_REGISTRY_SCHEMA_VERSION) {
349
539
  throw new IntegrityError(`registry row has a malformed schemaVersion (${String(raw)}): ${ctx}`);
350
540
  }
351
541
  if (raw > REGISTRY_SCHEMA_VERSION) {
@@ -353,6 +543,7 @@ function assertRegistrySchemaVersion(raw, ctx) {
353
543
  `registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`
354
544
  );
355
545
  }
546
+ return raw;
356
547
  }
357
548
  function serializeRegistryEnvelope(env) {
358
549
  return JSON.stringify({
@@ -361,15 +552,51 @@ function serializeRegistryEnvelope(env) {
361
552
  record: env.record
362
553
  });
363
554
  }
364
- function registryCounterOf(record) {
365
- if (!/^(0|[1-9]\d*)$/.test(record.token)) {
366
- throw new IntegrityError(`registry row token is not a canonical counter: ${record.token}`);
555
+ var COUNTER_TOKEN = /^(0|[1-9]\d*)$/;
556
+ var WRITTEN_COUNTER_TOKEN = /^(0|[1-9]\d*)\.([0-9a-f]{16})$/;
557
+ var INCARNATION_BYTES = 16;
558
+ var WRITE_BYTES = 8;
559
+ function tokenParts(token, schemaVersion, ctx) {
560
+ const bare = schemaVersion === void 0 || schemaVersion === 1 ? COUNTER_TOKEN.exec(token) : null;
561
+ const written = schemaVersion === void 0 || schemaVersion >= 2 ? WRITTEN_COUNTER_TOKEN.exec(token) ?? INCARNATION_TOKEN.exec(token) : null;
562
+ const match = bare ?? written;
563
+ if (match === null) {
564
+ throw new IntegrityError(
565
+ `registry row token is not one a schema-${schemaVersion ?? REGISTRY_SCHEMA_VERSION} row holds (${JSON.stringify(token)}): ${ctx}`
566
+ );
367
567
  }
368
- const n = Number(record.token);
369
- if (!Number.isSafeInteger(n)) {
370
- throw new IntegrityError(`registry row token is out of safe-integer range: ${record.token}`);
568
+ const born = match.length === 4;
569
+ const counter = Number(born ? match[2] : match[1]);
570
+ if (!Number.isSafeInteger(counter)) {
571
+ throw new IntegrityError(`registry row token's counter is out of safe-integer range: ${ctx}`);
371
572
  }
372
- return n;
573
+ return { incarnation: born ? match[1] : void 0, counter };
574
+ }
575
+ function drawHex(entropy, bytes) {
576
+ const drawn = entropy(bytes);
577
+ if (!(drawn instanceof Uint8Array) || drawn.length !== bytes) {
578
+ throw new ValidationError(
579
+ `the registry's entropy source must return ${bytes} bytes as a Uint8Array`
580
+ );
581
+ }
582
+ let hex = "";
583
+ for (const b of drawn) hex += b.toString(16).padStart(2, "0");
584
+ return hex;
585
+ }
586
+ function drawIncarnation(entropy) {
587
+ return drawHex(entropy, INCARNATION_BYTES);
588
+ }
589
+ function drawWrite(entropy) {
590
+ return drawHex(entropy, WRITE_BYTES);
591
+ }
592
+ function nextRegistryToken(record, entropy) {
593
+ const { incarnation, counter } = tokenParts(record.token, void 0, record.segment);
594
+ const tail = `${counter + 1}.${drawWrite(entropy)}`;
595
+ return incarnation === void 0 ? tail : `${incarnation}.${tail}`;
596
+ }
597
+ function newIncarnationToken(entropy, tombstone) {
598
+ const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, void 0, tombstone.segment).counter + 1;
599
+ return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
373
600
  }
374
601
  function parseRegistryEnvelope(text, ctx) {
375
602
  let parsed;
@@ -382,7 +609,7 @@ function parseRegistryEnvelope(text, ctx) {
382
609
  throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
383
610
  }
384
611
  const env = parsed;
385
- assertRegistrySchemaVersion(env.schemaVersion, ctx);
612
+ const schemaVersion = assertRegistrySchemaVersion(env.schemaVersion, ctx);
386
613
  const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
387
614
  if (extra.length > 0) {
388
615
  throw new IntegrityError(
@@ -393,10 +620,11 @@ function parseRegistryEnvelope(text, ctx) {
393
620
  throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
394
621
  }
395
622
  const r = env.record;
396
- assertStoredRecordShape(r, ctx);
623
+ assertStoredRecordShape(r, ctx, schemaVersion);
397
624
  if (typeof r.token !== "string") {
398
625
  throw new IntegrityError(`registry row is missing its token: ${ctx}`);
399
626
  }
627
+ tokenParts(r.token, schemaVersion, ctx);
400
628
  return { deleted: env.deleted, record: env.record };
401
629
  }
402
630
  function recordFromNew(ref, rec, now, token) {
@@ -409,12 +637,13 @@ function recordFromNew(ref, rec, now, token) {
409
637
  status: rec.status ?? "active",
410
638
  retention: rec.retention,
411
639
  residency: rec.residency,
640
+ summary: rec.summary,
412
641
  createdAt: now,
413
642
  updatedAt: now,
414
643
  token
415
644
  };
416
645
  }
417
- function assertStoredRecordShape(r, ctx) {
646
+ function assertStoredRecordShape(r, ctx, schemaVersion = REGISTRY_SCHEMA_VERSION) {
418
647
  if (typeof r.segment !== "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") {
419
648
  throw new IntegrityError(`registry record is missing required fields: ${ctx}`);
420
649
  }
@@ -424,10 +653,11 @@ function assertStoredRecordShape(r, ctx) {
424
653
  if (!STATUSES.includes(r.status)) {
425
654
  throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);
426
655
  }
427
- const extra = Object.keys(r).filter((k) => !RECORD_FIELDS.includes(k));
656
+ const declared = schemaVersion >= 2 ? RECORD_FIELDS : SCHEMA_1_RECORD_FIELDS;
657
+ const extra = Object.keys(r).filter((k) => !declared.includes(k));
428
658
  if (extra.length > 0) {
429
659
  throw new IntegrityError(
430
- `registry record has fields it does not declare (${extra.join(", ")}): ${ctx}`
660
+ `registry record has fields a schema-${schemaVersion} row does not declare (${extra.join(", ")}): ${ctx}`
431
661
  );
432
662
  }
433
663
  if (r.keyId !== void 0 && typeof r.keyId !== "string") {
@@ -440,27 +670,54 @@ function assertStoredRecordShape(r, ctx) {
440
670
  throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
441
671
  }
442
672
  }
673
+ validateSummary(r.summary, true, ctx);
443
674
  }
444
675
  function applyRegistryPatch(prev, patch, now, token) {
676
+ const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
677
+ const wrappedDeks = "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks;
678
+ let summary;
679
+ if ("summary" in patch) {
680
+ validateSummaryNames(patch.summary, currentGen);
681
+ validateSummaryKeys(patch.summary, wrappedDeks);
682
+ summary = patch.summary;
683
+ } else {
684
+ const kept = currentGen === prev.currentGen ? prev.summary : void 0;
685
+ summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
686
+ }
445
687
  return {
446
688
  namespace: prev.namespace,
447
689
  segment: prev.segment,
448
- // `'currentGen' in patch`, NOT `patch.currentGen ?? prev.currentGen`. `null` is a legal, meaningful value
449
- // here (no Storage generation yet), and `??` treats it as absent — so the nullish form would silently ignore a
450
- // patch that clears the pointer and leave the old generation in place. The same reason `wrappedDeks` and
451
- // `keyId` below use presence: any field whose null is a *value* cannot be merged with `??`.
452
- currentGen: "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen,
453
- wrappedDeks: "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks,
690
+ currentGen,
691
+ wrappedDeks,
454
692
  keyId: "keyId" in patch ? patch.keyId : prev.keyId,
455
693
  status: patch.status ?? prev.status,
456
694
  retention: "retention" in patch ? patch.retention : prev.retention,
457
695
  residency: "residency" in patch ? patch.residency : prev.residency,
696
+ summary,
458
697
  createdAt: prev.createdAt,
459
698
  updatedAt: now,
460
699
  token
461
700
  };
462
701
  }
463
702
 
703
+ // src/drivers/_shared/entropy.ts
704
+ function webCrypto() {
705
+ const c = globalThis.crypto;
706
+ return c !== void 0 && typeof c.getRandomValues === "function" ? c : void 0;
707
+ }
708
+ var webCryptoEntropy = (length) => {
709
+ const c = webCrypto();
710
+ if (c === void 0) {
711
+ throw new UnsupportedError(
712
+ "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"
713
+ );
714
+ }
715
+ return c.getRandomValues(new Uint8Array(length));
716
+ };
717
+ function entropyIsAvailable(entropy) {
718
+ return entropy !== webCryptoEntropy || webCrypto() !== void 0;
719
+ }
720
+
464
721
  export {
465
722
  CloudRoaringError,
466
723
  ValidationError,
@@ -491,18 +748,31 @@ export {
491
748
  validateUserRef,
492
749
  validateChunkRef,
493
750
  mapWithConcurrency,
751
+ assertRegistryCanWrite,
494
752
  STORAGE_BACKEND,
495
753
  brandAsBackend,
496
754
  isStorageBackend,
755
+ MAX_METADATA_BYTES,
756
+ canonicalMetadataJson,
757
+ metadataBytes,
758
+ copiedMetadata,
759
+ metadataFromBytes,
760
+ incarnationOf,
761
+ sameIncarnation,
497
762
  validateNewRegistryRecord,
498
763
  validateRegistryPatch,
499
764
  serializeRegistryEnvelope,
500
- registryCounterOf,
765
+ drawIncarnation,
766
+ drawWrite,
767
+ nextRegistryToken,
768
+ newIncarnationToken,
501
769
  parseRegistryEnvelope,
502
770
  recordFromNew,
503
771
  applyRegistryPatch,
772
+ webCryptoEntropy,
773
+ entropyIsAvailable,
504
774
  DEFAULT_NAMESPACE,
505
775
  namespaceKeyPart,
506
776
  namespacePathPart
507
777
  };
508
- //# sourceMappingURL=chunk-5OMC5U7U.js.map
778
+ //# sourceMappingURL=chunk-5W3XS2IU.js.map