@cloudbitmaps/core 0.22.0 → 0.23.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 (53) hide show
  1. package/README.md +8 -4
  2. package/dist/chunk-7GT632UF.js +641 -0
  3. package/dist/chunk-7GT632UF.js.map +7 -0
  4. package/dist/chunk-CGILDXGP.js +73 -0
  5. package/dist/chunk-CGILDXGP.js.map +7 -0
  6. package/dist/chunk-GKHERVVQ.js +121 -0
  7. package/dist/chunk-GKHERVVQ.js.map +7 -0
  8. package/dist/{chunk-OSU7W2Y3.js → chunk-MVUD4R72.js} +200 -756
  9. package/dist/chunk-MVUD4R72.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 +49 -0
  15. package/dist/core/combine-many.d.ts +11 -0
  16. package/dist/core/consistency.d.ts +11 -2
  17. package/dist/core/cooperative.d.ts +7 -0
  18. package/dist/core/crbm-storage-source.d.ts +7 -4
  19. package/dist/core/crypto.d.ts +5 -0
  20. package/dist/core/discover.d.ts +5 -3
  21. package/dist/core/engine.d.ts +4 -4
  22. package/dist/core/erase-id.d.ts +35 -12
  23. package/dist/core/erasure.d.ts +61 -27
  24. package/dist/core/errors.d.ts +30 -0
  25. package/dist/core/leases.d.ts +2 -0
  26. package/dist/core/load-input.d.ts +48 -0
  27. package/dist/core/load.d.ts +23 -2
  28. package/dist/core/metrics.d.ts +21 -0
  29. package/dist/core/pinned-storage-source.d.ts +27 -8
  30. package/dist/core/pointer-id.d.ts +9 -0
  31. package/dist/core/ports.d.ts +100 -7
  32. package/dist/core/preflight.d.ts +80 -0
  33. package/dist/core/retention-sweep.d.ts +60 -15
  34. package/dist/core/retention.d.ts +17 -6
  35. package/dist/core/retry.d.ts +1 -2
  36. package/dist/core/rollback.d.ts +22 -12
  37. package/dist/core/validate.d.ts +33 -2
  38. package/dist/driver-kit.d.ts +3 -3
  39. package/dist/driver-kit.js +61 -20
  40. package/dist/driver-kit.js.map +2 -2
  41. package/dist/drivers/_shared/object-registry.d.ts +24 -1
  42. package/dist/drivers/_shared/options.d.ts +15 -7
  43. package/dist/drivers/backends.d.ts +17 -2
  44. package/dist/drivers/crypto.d.ts +17 -2
  45. package/dist/export/index.d.ts +16 -2
  46. package/dist/index.d.ts +10 -9
  47. package/dist/index.js +1743 -1701
  48. package/dist/index.js.map +4 -4
  49. package/dist/node.d.ts +14 -0
  50. package/dist/node.js +1064 -0
  51. package/dist/node.js.map +7 -0
  52. package/package.json +11 -2
  53. package/dist/chunk-OSU7W2Y3.js.map +0 -7
@@ -5,6 +5,7 @@ var NAMES = /* @__PURE__ */ new WeakMap();
5
5
  var CloudRoaringError = class extends Error {
6
6
  /** Cross-bundle brand — see {@link errorKind}. Non-enumerable-ish (symbol key ⇒ not in JSON). */
7
7
  [ERROR_BRAND] = true;
8
+ /** Builds the error from `message`, and sets `name` to the class's name as written. */
8
9
  constructor(message) {
9
10
  super(message);
10
11
  this.name = NAMES.get(new.target) ?? new.target.name;
@@ -24,6 +25,7 @@ var LeaseExpiredError = class extends CloudRoaringError {
24
25
  until;
25
26
  /** `'expired'` when the clock reached `until`, `'released'` after `release()`. */
26
27
  reason;
28
+ /** Builds the error from its `message`, the epoch-ms the lease ran until, and why it ended. */
27
29
  constructor(message, until, reason) {
28
30
  super(message);
29
31
  this.until = until;
@@ -33,11 +35,13 @@ var LeaseExpiredError = class extends CloudRoaringError {
33
35
  var LeaseLimitError = class extends CloudRoaringError {
34
36
  };
35
37
  var StaleOperandError = class extends CloudRoaringError {
38
+ /** The stable code `'stale-operand'`, to match on. */
36
39
  code = "stale-operand";
37
40
  /** The operand's name in the call that found it stale. */
38
41
  operand;
39
42
  /** Why it is stale. */
40
43
  reason;
44
+ /** Builds the error from its `message`, the operand's name and why it is stale. */
41
45
  constructor(message, operand, reason) {
42
46
  super(message);
43
47
  this.operand = operand;
@@ -58,6 +62,7 @@ var BudgetExceededError = class extends CloudRoaringError {
58
62
  * lower bound on the resident bytes it needs (`'maxBufferedBytes'`), or `limit + 1` (`'maxScanSegments'`).
59
63
  */
60
64
  spent;
65
+ /** Builds the error from its `message` and the `option`, `limit` and `spent` it reports. */
61
66
  constructor(message, details) {
62
67
  super(message);
63
68
  this.option = details.option;
@@ -70,6 +75,7 @@ var KeyUnavailableError = class extends CloudRoaringError {
70
75
  var TransientError = class extends CloudRoaringError {
71
76
  /** A second brand so a transient fault is classifiable cross-bundle. */
72
77
  [TRANSIENT_BRAND] = true;
78
+ /** Builds the error from `message`; `options.cause`, when given, is kept as `cause`. */
73
79
  constructor(message, options) {
74
80
  super(message);
75
81
  if (options && "cause" in options) this.cause = options.cause;
@@ -97,6 +103,15 @@ function isCloudRoaringError(err) {
97
103
  function isWriteConflictError(err) {
98
104
  return isCloudRoaringError(err) && err.name === "WriteConflictError";
99
105
  }
106
+ var UNSETTLED_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.error.unsettled-conflict");
107
+ function unsettledConflict(message) {
108
+ const err = new WriteConflictError(message);
109
+ Object.defineProperty(err, UNSETTLED_BRAND, { value: true });
110
+ return err;
111
+ }
112
+ function isUnsettledConflict(err) {
113
+ return isWriteConflictError(err) && hasBrand(err, UNSETTLED_BRAND);
114
+ }
100
115
  function isTransientError(err) {
101
116
  return hasBrand(err, TRANSIENT_BRAND);
102
117
  }
@@ -134,39 +149,6 @@ function errorKind(err) {
134
149
  return typeof name === "string" && Object.hasOwn(KIND_NAMES, name) ? name : "CloudRoaringError";
135
150
  }
136
151
 
137
- // src/core/bit-route.ts
138
- var U32_MAX = 4294967295;
139
- var CHUNK_COUNT = 65536;
140
- var LOW_16 = 65535;
141
- var idErrors = /* @__PURE__ */ new WeakSet();
142
- function isBadIdError(err) {
143
- return err instanceof ValidationError && idErrors.has(err);
144
- }
145
- function badId(id) {
146
- let got;
147
- if (id === null) got = "null";
148
- else if (id === void 0) got = "undefined";
149
- else if (typeof id === "object") got = "an object";
150
- else if (typeof id !== "number") got = `${/^[aeiou]/.test(typeof id) ? "an" : "a"} ${typeof id}`;
151
- else if (!Number.isFinite(id)) got = "NaN or an infinity";
152
- else if (id < 0) got = "a negative number";
153
- else if (!Number.isInteger(id)) got = "a number with a fraction";
154
- else got = `a number above ${U32_MAX} (ids are 32-bit)`;
155
- const err = new ValidationError(`an id must be an integer from 0 to ${U32_MAX}; got ${got}`);
156
- idErrors.add(err);
157
- return err;
158
- }
159
- function splitId(id) {
160
- if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
161
- throw badId(id);
162
- }
163
- return { chunkKey: id >>> 16, remainder: id & LOW_16 };
164
- }
165
- var MAX_REMAINDER = CHUNK_COUNT - 1;
166
- function joinId(chunkKey, remainder) {
167
- return ((chunkKey & LOW_16) << 16 >>> 0) + (remainder & LOW_16);
168
- }
169
-
170
152
  // src/core/check.ts
171
153
  function shown(value) {
172
154
  if (typeof value === "string") return JSON.stringify(value);
@@ -190,6 +172,11 @@ function requireInteger(value, bounds) {
190
172
  }
191
173
  return value;
192
174
  }
175
+ function checkRequireEncryption(value) {
176
+ if (value !== void 0 && typeof value !== "boolean") {
177
+ throw new ValidationError(`requireEncryption must be a boolean; got ${shown(value)}`);
178
+ }
179
+ }
193
180
  function requirePositiveInteger(value, what) {
194
181
  return requireInteger(value, { min: 1, what });
195
182
  }
@@ -202,69 +189,71 @@ function requireFiniteNumber(value, bounds) {
202
189
  }
203
190
  return value;
204
191
  }
205
-
206
- // src/core/concurrency.ts
207
- async function mapWithConcurrency(items, limit, fn) {
208
- requirePositiveInteger(limit, "concurrency limit");
209
- const results = new Array(items.length);
210
- let next = 0;
211
- let failed = false;
212
- let firstError;
213
- const worker = async () => {
214
- while (!failed) {
215
- const i = next++;
216
- if (i >= items.length) return;
217
- try {
218
- results[i] = await fn(items[i], i);
219
- } catch (err) {
220
- if (!failed) {
221
- failed = true;
222
- firstError = err;
223
- }
224
- return;
225
- }
226
- }
227
- };
228
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
229
- await Promise.all(workers);
230
- if (failed) throw firstError;
231
- return results;
192
+ function refuseUnknownOptions(name, options, keys, hint) {
193
+ if (options === null || typeof options !== "object") {
194
+ throw new ValidationError(
195
+ `${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
196
+ );
197
+ }
198
+ const unknown = Object.keys(options).filter((k) => !keys.includes(k));
199
+ if (unknown.length > 0) {
200
+ const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
201
+ throw new ValidationError(
202
+ `${name} does not take ${list(unknown)}. It takes ${list(keys)}${hint === void 0 ? "" : `; ${hint}`}.`
203
+ );
204
+ }
232
205
  }
233
- function boundedAll(items, limit, fn) {
234
- try {
235
- requirePositiveInteger(limit, "concurrency limit");
236
- } catch (err) {
237
- return Promise.reject(err);
206
+ function requireBool(name, value) {
207
+ if (value === void 0) return void 0;
208
+ if (typeof value !== "boolean") {
209
+ throw new ValidationError(`${name} must be a boolean; got ${shown(value)}`);
238
210
  }
239
- if (items.length > limit) return inWaves(items, limit, fn);
240
- const calls = [];
241
- for (const item of items) {
242
- try {
243
- calls.push(fn(item));
244
- } catch (err) {
245
- void Promise.allSettled(calls);
246
- return Promise.reject(err);
247
- }
211
+ return value;
212
+ }
213
+ function requireNonEmptyString(name, value) {
214
+ if (value === void 0) return void 0;
215
+ if (typeof value !== "string" || value.trim().length === 0) {
216
+ throw new ValidationError(
217
+ `${name} must be a non-empty string; got ${typeof value === "string" ? "an empty string" : shown(value)}`
218
+ );
248
219
  }
249
- return Promise.all(calls);
220
+ return value;
250
221
  }
251
- async function inWaves(items, limit, fn) {
252
- const results = new Array(items.length);
253
- let next = 0;
254
- let failed = false;
255
- const worker = async () => {
256
- while (!failed && next < items.length) {
257
- const i = next++;
258
- try {
259
- results[i] = await fn(items[i]);
260
- } catch (err) {
261
- failed = true;
262
- throw err;
263
- }
264
- }
265
- };
266
- await Promise.all(Array.from({ length: limit }, () => worker()));
267
- return results;
222
+
223
+ // src/core/bit-route.ts
224
+ var U32_MAX = 4294967295;
225
+ var CHUNK_COUNT = 65536;
226
+ var LOW_16 = 65535;
227
+ var idErrors = /* @__PURE__ */ new WeakSet();
228
+ function isBadIdError(err) {
229
+ return err instanceof ValidationError && idErrors.has(err);
230
+ }
231
+ function badId(id) {
232
+ let got;
233
+ if (id === null) got = "null";
234
+ else if (id === void 0) got = "undefined";
235
+ else if (typeof id === "object") got = "an object";
236
+ else if (typeof id !== "number") got = `${/^[aeiou]/.test(typeof id) ? "an" : "a"} ${typeof id}`;
237
+ else if (!Number.isFinite(id)) got = "NaN or an infinity";
238
+ else if (id < 0) got = "a negative number";
239
+ else if (!Number.isInteger(id)) got = "a number with a fraction";
240
+ else got = `a number above ${U32_MAX} (ids are 32-bit)`;
241
+ const err = new ValidationError(`an id must be an integer from 0 to ${U32_MAX}; got ${got}`);
242
+ idErrors.add(err);
243
+ return err;
244
+ }
245
+ function refusalOfId(id) {
246
+ return badId(id);
247
+ }
248
+ function splitId(id) {
249
+ if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
250
+ throw badId(id);
251
+ }
252
+ return { chunkKey: id >>> 16, remainder: id & LOW_16 };
253
+ }
254
+ var MAX_REMAINDER = CHUNK_COUNT - 1;
255
+ function joinId(chunkKey, remainder) {
256
+ return ((chunkKey & LOW_16) << 16 >>> 0) + (remainder & LOW_16);
268
257
  }
269
258
 
270
259
  // src/core/name-codec.ts
@@ -359,6 +348,7 @@ function validatePart(value, field) {
359
348
  );
360
349
  }
361
350
  }
351
+ var REF_KEYS = ["segment", "namespace", "currentGen", "status"];
362
352
  function requireRefObject(ref, hint = "") {
363
353
  if (typeof ref !== "object" || ref === null) {
364
354
  throw new ValidationError(`a segment ref is an object such as { segment: 'name' }${hint}`);
@@ -366,6 +356,7 @@ function requireRefObject(ref, hint = "") {
366
356
  }
367
357
  function validateSegmentRef(ref) {
368
358
  requireRefObject(ref);
359
+ refuseUnknownKeys(ref);
369
360
  validatePart(ref.segment, "segment");
370
361
  if (ref.namespace !== void 0) validatePart(ref.namespace, "namespace");
371
362
  }
@@ -381,8 +372,56 @@ function validateUserNamespace(namespace) {
381
372
  );
382
373
  }
383
374
  }
384
- function validateUserRef(ref) {
375
+ var PORT_KEYS = [
376
+ "generation",
377
+ "chunkKey",
378
+ "wrappedDeks",
379
+ "keyId",
380
+ "retention",
381
+ "residency",
382
+ "summary",
383
+ "knownGens",
384
+ "leases",
385
+ "createdAt",
386
+ "updatedAt",
387
+ "token",
388
+ "pointerId"
389
+ ];
390
+ var SEGMENT_HANDLE_MARK = /* @__PURE__ */ Symbol.for("cloudbitmaps.roaring.segment/1");
391
+ function unknownKeysOf(ref, extraKeys) {
392
+ return Object.keys(ref).filter(
393
+ (k) => !REF_KEYS.includes(k) && !extraKeys.includes(k)
394
+ );
395
+ }
396
+ function refuseUnknownKeys(ref) {
397
+ const unknown = unknownKeysOf(ref, PORT_KEYS);
398
+ if (unknown.length === 0) return;
399
+ const list = unknown.map((k) => `"${k}"`).join(", ");
400
+ throw new ValidationError(
401
+ `a driver port: unknown ${unknown.length === 1 ? "key" : "keys"} ${list} in the segment ref; it takes { segment, namespace } and a generation key's \`generation\`, a chunk ref's \`chunkKey\`, a \`segments()\` row's \`currentGen\` and \`status\`, or a registry row's own fields (a segment addressed without its namespace is a different segment)`
402
+ );
403
+ }
404
+ function refuseUnknownRefKeys(ref, verb, extraKeys = []) {
405
+ if (ref[SEGMENT_HANDLE_MARK] === true) {
406
+ throw new ValidationError(
407
+ `${verb}: a segment handle is not a ref; pass { segment, namespace }`
408
+ );
409
+ }
410
+ if (Array.isArray(ref)) {
411
+ throw new ValidationError(
412
+ `${verb}: a segment ref is an object such as { segment: 'name' }, not an array`
413
+ );
414
+ }
415
+ const unknown = unknownKeysOf(ref, extraKeys);
416
+ if (unknown.length === 0) return;
417
+ const list = unknown.map((k) => `"${k}"`).join(", ");
418
+ throw new ValidationError(
419
+ `${verb}: unknown ${unknown.length === 1 ? "key" : "keys"} ${list} in the segment ref; a ref takes { segment, namespace } (and a \`segments()\` row's \`currentGen\` and \`status\`, which are read for nothing); a segment addressed without its namespace is a different segment`
420
+ );
421
+ }
422
+ function validateUserRef(ref, verb, extraKeys = []) {
385
423
  requireRefObject(ref, "; store.segment() takes the bare name");
424
+ if (verb !== void 0) refuseUnknownRefKeys(ref, verb, extraKeys);
386
425
  validatePart(ref.segment, "segment");
387
426
  if (ref.namespace !== void 0) validateUserNamespace(ref.namespace);
388
427
  }
@@ -445,6 +484,22 @@ function isStorageBackend(value) {
445
484
  return typeof value === "object" && value !== null && value[STORAGE_BACKEND_BRAND] === true;
446
485
  }
447
486
 
487
+ // src/core/token.ts
488
+ var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
489
+ function incarnationOf(token) {
490
+ return INCARNATION_TOKEN.exec(token)?.[1];
491
+ }
492
+ function incarnationField(token) {
493
+ const incarnation = token === void 0 ? void 0 : incarnationOf(token);
494
+ return incarnation === void 0 ? {} : { incarnation };
495
+ }
496
+ function sameIncarnation(a, b) {
497
+ const ia = incarnationOf(a.token);
498
+ const ib = incarnationOf(b.token);
499
+ if (ia !== void 0 || ib !== void 0) return ia === ib;
500
+ return a.createdAt === b.createdAt;
501
+ }
502
+
448
503
  // src/core/pointer-id.ts
449
504
  var RESOLVED_FIELDS = [
450
505
  "currentGen",
@@ -468,6 +523,11 @@ function pointerIdOf(row) {
468
523
  }
469
524
  return pointerId;
470
525
  }
526
+ function pointerSetBy(now, before, generation, sent) {
527
+ if (now === null || now.status === "destroyed" || now.currentGen !== generation) return false;
528
+ if (before !== null && !sameIncarnation(before, now)) return false;
529
+ return sent.has(now.pointerId);
530
+ }
471
531
 
472
532
  // src/core/crypto.ts
473
533
  var UTF82 = new TextEncoder();
@@ -560,22 +620,6 @@ async function withRetry(op, policy, deps) {
560
620
  throw lastErr;
561
621
  }
562
622
 
563
- // src/core/token.ts
564
- var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
565
- function incarnationOf(token) {
566
- return INCARNATION_TOKEN.exec(token)?.[1];
567
- }
568
- function incarnationField(token) {
569
- const incarnation = token === void 0 ? void 0 : incarnationOf(token);
570
- return incarnation === void 0 ? {} : { incarnation };
571
- }
572
- function sameIncarnation(a, b) {
573
- const ia = incarnationOf(a.token);
574
- const ib = incarnationOf(b.token);
575
- if (ia !== void 0 || ib !== void 0) return ia === ib;
576
- return a.createdAt === b.createdAt;
577
- }
578
-
579
623
  // src/core/leases.ts
580
624
  var MAX_LEASE_MS = 14 * 864e5;
581
625
  var LEASE_SKEW_MS = 6e4;
@@ -742,53 +786,6 @@ async function releaseLease(ref, deps, holder) {
742
786
  );
743
787
  }
744
788
 
745
- // src/drivers/_shared/keys.ts
746
- var DEFAULT_NAMESPACE = "_default";
747
- function namespaceKeyPart(namespace) {
748
- return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForKey(namespace);
749
- }
750
- function namespacePathPart(namespace) {
751
- return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
752
- }
753
-
754
- // src/drivers/_shared/options.ts
755
- function refuseUnknownOptions(name, options, keys, hint) {
756
- if (options === null || typeof options !== "object") {
757
- throw new ValidationError(
758
- `${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
759
- );
760
- }
761
- const unknown = Object.keys(options).filter((k) => !keys.includes(k));
762
- if (unknown.length > 0) {
763
- const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
764
- throw new ValidationError(
765
- `${name} does not take ${list(unknown)}. It takes ${list(keys)}${hint === void 0 ? "" : `; ${hint}`}.`
766
- );
767
- }
768
- }
769
- function checkNow(name, now) {
770
- if (now !== void 0 && typeof now !== "function") {
771
- throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
772
- }
773
- }
774
- function requirePositiveInt(name, value) {
775
- return value === void 0 ? void 0 : requirePositiveInteger(value, name);
776
- }
777
- function requireBool(name, value) {
778
- if (value === void 0) return void 0;
779
- if (typeof value !== "boolean") {
780
- throw new ValidationError(`${name} must be a boolean; got ${shown2(value)}`);
781
- }
782
- return value;
783
- }
784
- function shown2(value) {
785
- if (typeof value === "string") return JSON.stringify(value);
786
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
787
- return String(value);
788
- }
789
- return value === null ? "null" : typeof value;
790
- }
791
-
792
789
  // src/core/crbm/frame.ts
793
790
  var PREAMBLE_BYTES = 8;
794
791
  var FOOTER_BYTES = 104;
@@ -1067,35 +1064,6 @@ function ownSummary(ref, generation, description, aead) {
1067
1064
  return summary;
1068
1065
  }
1069
1066
 
1070
- // src/core/known-generations-shape.ts
1071
- var ENTRY_KEYS = ["generation", "footerCrc"];
1072
- function knownGensShapeFault(value, max, show = String) {
1073
- if (value === void 0) return void 0;
1074
- if (!Array.isArray(value)) return "must be an array";
1075
- const list = value;
1076
- if (list.length > max) return `has ${list.length} entries, cap ${max}`;
1077
- const seen = /* @__PURE__ */ new Set();
1078
- for (const e of list) {
1079
- if (typeof e !== "object" || e === null || Array.isArray(e))
1080
- return "an entry must be an object";
1081
- const entry = e;
1082
- const extra = Object.keys(entry).filter((k) => !ENTRY_KEYS.includes(k));
1083
- if (extra.length > 0) {
1084
- return `an entry carries an undeclared field (${show(extra.join(", "))})`;
1085
- }
1086
- const { generation, footerCrc } = entry;
1087
- if (!Number.isSafeInteger(generation) || generation < 0) {
1088
- return `a generation must be a non-negative safe integer (got ${show(generation)})`;
1089
- }
1090
- if (!Number.isInteger(footerCrc) || footerCrc < 0 || footerCrc > 4294967295) {
1091
- return `a footerCrc must be an integer from 0 to 2^32 - 1 (got ${show(footerCrc)})`;
1092
- }
1093
- if (seen.has(generation)) return `generation ${String(generation)} is repeated`;
1094
- seen.add(generation);
1095
- }
1096
- return void 0;
1097
- }
1098
-
1099
1067
  // src/core/order.ts
1100
1068
  var ascending = (a, b) => a - b;
1101
1069
 
@@ -1248,553 +1216,33 @@ function leasedByRow(record) {
1248
1216
  return new Set((record.leases ?? []).map((e) => e.generation));
1249
1217
  }
1250
1218
 
1251
- // src/drivers/_shared/registry.ts
1252
- var STATUSES = ["active", "destroyed"];
1253
- var RECORD_FIELDS = [
1254
- "namespace",
1255
- "segment",
1256
- "currentGen",
1257
- "wrappedDeks",
1258
- "keyId",
1259
- "status",
1260
- "retention",
1261
- "residency",
1262
- "summary",
1263
- "createdAt",
1264
- "updatedAt",
1265
- "token",
1266
- "knownGens",
1267
- "leases",
1268
- "pointerId"
1269
- ];
1270
- var DRIVER_MANAGED = ["token", "pointerId"];
1271
- var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
1272
- var MAX_GOVERNANCE_BYTES = 64 * 1024;
1273
- var MAX_WRAPPED_DEKS = 8;
1274
- var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
1275
- var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
1276
- var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
1277
- var BASE642 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
1278
- function validateGeneration(gen) {
1279
- if (gen === null) return;
1280
- if (!Number.isSafeInteger(gen) || gen < 0) {
1281
- throw new ValidationError(
1282
- `currentGen must be a non-negative safe integer or null (no Storage generation yet); got ${gen}`
1283
- );
1284
- }
1285
- }
1286
- function validatePatchGeneration(patch) {
1287
- if (!("currentGen" in patch)) return;
1288
- if (patch.currentGen === void 0) {
1289
- throw new ValidationError(
1290
- `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.`
1291
- );
1292
- }
1293
- validateGeneration(patch.currentGen);
1294
- }
1295
- function validateStatus(status) {
1296
- if (!STATUSES.includes(status)) {
1297
- throw new ValidationError(`status must be one of ${STATUSES.join("/")}; got ${status}`);
1298
- }
1299
- }
1300
- function validateGovernance(meta, field) {
1301
- if (meta === void 0) return;
1302
- if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
1303
- throw new ValidationError(
1304
- `${field} must be a plain object (got ${meta === null ? "null" : typeof meta})`
1305
- );
1306
- }
1307
- let json;
1308
- try {
1309
- json = JSON.stringify(meta);
1310
- } catch {
1311
- throw new ValidationError(`${field} must be JSON-serializable`);
1312
- }
1313
- if (json.length > MAX_GOVERNANCE_BYTES) {
1314
- throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
1315
- }
1316
- }
1317
- function validateWrappedDeks(value, origin) {
1318
- if (value === void 0) return;
1319
- const fail = (msg) => {
1320
- throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
1321
- };
1322
- if (!Array.isArray(value)) fail("wrappedDeks must be an array");
1323
- const list = value;
1324
- if (list.length === 0) fail("wrappedDeks must be non-empty when present");
1325
- if (list.length > MAX_WRAPPED_DEKS)
1326
- fail(`wrappedDeks has ${list.length} entries, cap ${MAX_WRAPPED_DEKS}`);
1327
- for (const w of list) {
1328
- if (w === null || typeof w !== "object") fail("wrappedDeks entry must be an object");
1329
- const { keyId, wrapped } = w;
1330
- if (typeof keyId !== "string" || keyId.length === 0)
1331
- fail("wrappedDeks entry needs a non-empty keyId");
1332
- if (typeof wrapped !== "string" || wrapped.length === 0)
1333
- fail("wrappedDeks entry needs a non-empty wrapped blob");
1334
- if (wrapped.length > MAX_WRAPPED_DEK_BYTES)
1335
- fail(`wrappedDeks entry is ${wrapped.length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);
1336
- }
1337
- }
1338
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1339
- function base64PadBitsAreZero(text) {
1340
- const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
1341
- if (pad === 0) return true;
1342
- const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
1343
- return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
1344
- }
1345
- function validateSummary(value, origin, ctx) {
1346
- if (value === void 0) return void 0;
1347
- const fail = (msg) => {
1348
- const where = ctx === void 0 ? "" : `: ${ctx}`;
1349
- throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
1350
- };
1351
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
1352
- fail(
1353
- `must be an object (got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value})`
1354
- );
1355
- }
1356
- const s = value;
1357
- const keys = Object.keys(s);
1358
- const isSealed = keys.includes("sealed");
1359
- const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
1360
- const extra = keys.filter((k) => !allowed.includes(k));
1361
- if (extra.length > 0) {
1362
- fail(
1363
- `has fields its ${isSealed ? "sealed" : "clear"} shape does not declare (${extra.join(", ")})`
1364
- );
1365
- }
1366
- const generation = s.generation;
1367
- if (!Number.isSafeInteger(generation) || generation < 0) {
1368
- fail(`generation must be a non-negative safe integer (got ${String(generation)})`);
1369
- }
1370
- if (isSealed) {
1371
- const sealed = s.sealed;
1372
- if (typeof sealed !== "string" || !BASE642.test(sealed) || !base64PadBitsAreZero(sealed)) {
1373
- fail("sealed must be canonical, padded standard base64");
1374
- }
1375
- const text = sealed;
1376
- const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
1377
- if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
1378
- fail(
1379
- `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)`
1380
- );
1381
- }
1382
- return Object.freeze({ generation, sealed: text });
1383
- }
1384
- const n = s.cardinality;
1385
- if (!Number.isInteger(n) || n < 0 || n > MAX_SUMMARY_CARDINALITY) {
1386
- fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
1387
- }
1388
- const fingerprint = s.fingerprint;
1389
- const parts = fingerprintParts(fingerprint);
1390
- if (parts === void 0) {
1391
- fail(
1392
- `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown3(JSON.stringify(fingerprint))})`
1393
- );
1394
- }
1395
- const tailBytes = s.tailBytes;
1396
- if (!tailBytesFit(tailBytes, parts.size)) {
1397
- fail(
1398
- `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 ${shown3(JSON.stringify(tailBytes))} for an object of ${parts.size} bytes)`
1399
- );
1400
- }
1401
- if (s.metadata === void 0) {
1402
- return Object.freeze({
1403
- generation,
1404
- cardinality: n,
1405
- fingerprint,
1406
- tailBytes
1407
- });
1408
- }
1409
- const canonical = canonicalMetadataJson(s.metadata, fail);
1410
- if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
1411
- if (origin === "stored") return s;
1412
- const metadata = Object.freeze(JSON.parse(canonical));
1413
- return Object.freeze({
1414
- generation,
1415
- cardinality: n,
1416
- fingerprint,
1417
- tailBytes,
1418
- metadata
1419
- });
1420
- }
1421
- function validateSummaryNames(summary, currentGen) {
1422
- if (summary !== void 0 && summary.generation !== currentGen) {
1423
- throw new ValidationError(
1424
- `summary describes generation ${summary.generation}, but the row points at ${String(currentGen)}`
1425
- );
1426
- }
1427
- }
1428
- function summaryAgreesWithKeys(summary, wrappedDeks) {
1429
- return "sealed" in summary === (wrappedDeks !== void 0);
1430
- }
1431
- function validateSummaryKeys(summary, wrappedDeks) {
1432
- if (summary !== void 0 && !summaryAgreesWithKeys(summary, wrappedDeks)) {
1433
- throw new ValidationError(
1434
- 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"
1435
- );
1436
- }
1437
- }
1438
- function validateKnownGens(value, origin, ctx) {
1439
- if (value === void 0) return void 0;
1440
- const fail = (msg) => {
1441
- const where = ctx === void 0 ? "" : `: ${ctx}`;
1442
- throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
1443
- };
1444
- const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
1445
- const fault = knownGensShapeFault(value, max, shown3);
1446
- if (fault !== void 0) fail(fault);
1447
- const out = [];
1448
- for (const entry of value) {
1449
- out.push(
1450
- origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
1451
- );
1452
- }
1453
- return origin === "stored" ? out : Object.freeze(out);
1454
- }
1455
- var HOLDER = /^[0-9a-f]{16}$/;
1456
- var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
1457
- function validateLeases(value, origin, ctx) {
1219
+ // src/core/known-generations-shape.ts
1220
+ var ENTRY_KEYS = ["generation", "footerCrc"];
1221
+ function knownGensShapeFault(value, max, show = String) {
1458
1222
  if (value === void 0) return void 0;
1459
- const fail = (msg) => {
1460
- const where = ctx === void 0 ? "" : `: ${ctx}`;
1461
- throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
1462
- };
1463
- if (!Array.isArray(value)) fail("must be an array");
1223
+ if (!Array.isArray(value)) return "must be an array";
1464
1224
  const list = value;
1465
- const max = MAX_STORED_LEASES;
1466
- if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
1225
+ if (list.length > max) return `has ${list.length} entries, cap ${max}`;
1467
1226
  const seen = /* @__PURE__ */ new Set();
1468
- const out = [];
1469
1227
  for (const e of list) {
1470
- if (typeof e !== "object" || e === null || Array.isArray(e)) fail("an entry must be an object");
1228
+ if (typeof e !== "object" || e === null || Array.isArray(e))
1229
+ return "an entry must be an object";
1471
1230
  const entry = e;
1472
- const extra = Object.keys(entry).filter((k) => !LEASE_ENTRY_KEYS.includes(k));
1473
- if (extra.length > 0) fail(`an entry carries an undeclared field (${extra.join(", ")})`);
1474
- const { holder, generation, until } = entry;
1475
- if (typeof holder !== "string" || !HOLDER.test(holder)) {
1476
- fail("a holder must be 16 lowercase hex digits");
1477
- }
1478
- if (seen.has(holder)) fail("holders must be unique");
1479
- seen.add(holder);
1480
- for (const [name, n] of [
1481
- ["generation", generation],
1482
- ["until", until]
1483
- ]) {
1484
- if (!Number.isSafeInteger(n) || n < 0) {
1485
- fail(`${name} must be a non-negative safe integer (got ${String(n)})`);
1486
- }
1231
+ const extra = Object.keys(entry).filter((k) => !ENTRY_KEYS.includes(k));
1232
+ if (extra.length > 0) {
1233
+ return `an entry carries an undeclared field (${show(extra.join(", "))})`;
1487
1234
  }
1488
- out.push(
1489
- origin === "stored" ? entry : Object.freeze({
1490
- holder,
1491
- generation,
1492
- until
1493
- })
1494
- );
1495
- }
1496
- if (origin === "stored") return out;
1497
- return out.length === 0 ? void 0 : Object.freeze(out);
1498
- }
1499
- function validateLeasesPointer(leases, currentGen) {
1500
- if (leases !== void 0 && leases.length > 0 && currentGen === null) {
1501
- throw new ValidationError("leases: a row with no pointer has no generation to lease");
1502
- }
1503
- }
1504
- function validateKnownGensFit(knownGens, currentGen, summary) {
1505
- if (knownGens === void 0) return;
1506
- if (currentGen === null) {
1507
- throw new ValidationError("knownGens: a row with no pointer has no generation to know");
1508
- }
1509
- const last = knownGens[knownGens.length - 1];
1510
- if (last === void 0 || last.generation !== currentGen) {
1511
- throw new ValidationError(
1512
- `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})`
1513
- );
1514
- }
1515
- if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
1516
- throw new ValidationError(
1517
- `knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
1518
- );
1519
- }
1520
- }
1521
- function refuseDriverManaged(write, what) {
1522
- const named = DRIVER_MANAGED.filter((field) => field in write);
1523
- if (named.length > 0) {
1524
- throw new ValidationError(
1525
- `${what} names ${named.join(" and ")}, which the registry sets on every write and a caller never does`
1526
- );
1527
- }
1528
- }
1529
- function validateKeyId(keyId) {
1530
- if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
1531
- throw new ValidationError("keyId must be a non-empty string, or absent");
1532
- }
1533
- }
1534
- function validateNewRegistryRecord(rec) {
1535
- refuseDriverManaged(rec, "a new record");
1536
- validateKeyId(rec.keyId);
1537
- validateGeneration(rec.currentGen);
1538
- if (rec.status !== void 0) validateStatus(rec.status);
1539
- validateWrappedDeks(rec.wrappedDeks, "caller");
1540
- validateGovernance(rec.retention, "retention");
1541
- validateGovernance(rec.residency, "residency");
1542
- const knownGens = validateKnownGens(rec.knownGens, "caller");
1543
- const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
1544
- const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
1545
- validateKnownGensFit(knownGens, rec.currentGen, summary);
1546
- if (!("summary" in rec)) return withKnown;
1547
- validateSummaryNames(summary, rec.currentGen);
1548
- validateSummaryKeys(summary, rec.wrappedDeks);
1549
- return { ...withKnown, summary };
1550
- }
1551
- function validateRegistryPatch(patch) {
1552
- refuseDriverManaged(patch, "a patch");
1553
- if ("keyId" in patch) validateKeyId(patch.keyId);
1554
- validatePatchGeneration(patch);
1555
- if (patch.status !== void 0) validateStatus(patch.status);
1556
- if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
1557
- if ("retention" in patch) validateGovernance(patch.retention, "retention");
1558
- if ("residency" in patch) validateGovernance(patch.residency, "residency");
1559
- const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
1560
- const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
1561
- if (!("summary" in patch)) return withLeases;
1562
- return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
1563
- }
1564
- var REGISTRY_SCHEMA_VERSION = 5;
1565
- function assertRegistrySchemaVersion(raw, ctx) {
1566
- if (raw === void 0) {
1567
- throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
1568
- }
1569
- if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
1570
- throw new IntegrityError(`registry row has a malformed schemaVersion (${shown3(raw)}): ${ctx}`);
1571
- }
1572
- if (raw < REGISTRY_SCHEMA_VERSION) {
1573
- throw new UnsupportedError(
1574
- `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}`
1575
- );
1576
- }
1577
- if (raw > REGISTRY_SCHEMA_VERSION) {
1578
- throw new UnsupportedError(
1579
- `registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`
1580
- );
1581
- }
1582
- return raw;
1583
- }
1584
- function serializeRegistryEnvelope(env) {
1585
- return JSON.stringify({
1586
- schemaVersion: REGISTRY_SCHEMA_VERSION,
1587
- deleted: env.deleted,
1588
- record: env.record
1589
- });
1590
- }
1591
- var INCARNATION_BYTES = 16;
1592
- var WRITE_BYTES = 8;
1593
- function tokenParts(token, ctx, what = "token") {
1594
- const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
1595
- if (match === null) {
1596
- throw new IntegrityError(
1597
- `registry row ${what} is not one a shipped registry writes (${shown3(JSON.stringify(token))}): ${ctx}`
1598
- );
1599
- }
1600
- const counter = Number(match[2]);
1601
- if (!Number.isSafeInteger(counter)) {
1602
- throw new IntegrityError(`registry row ${what}'s counter is out of safe-integer range: ${ctx}`);
1603
- }
1604
- return { incarnation: match[1], counter };
1605
- }
1606
- function checkPointerId(pointerId, token, ctx) {
1607
- const parts = tokenParts(pointerId, ctx, "pointerId");
1608
- if (parts.incarnation !== token.incarnation) {
1609
- throw new IntegrityError(
1610
- `registry row pointerId names another incarnation than its token: ${ctx}`
1611
- );
1612
- }
1613
- if (parts.counter > token.counter) {
1614
- throw new IntegrityError(`registry row pointerId is a write after its token: ${ctx}`);
1615
- }
1616
- }
1617
- function drawHex(entropy, bytes) {
1618
- const drawn = entropy(bytes);
1619
- if (!(drawn instanceof Uint8Array) || drawn.length !== bytes) {
1620
- throw new ValidationError(
1621
- `the registry's entropy source must return ${bytes} bytes as a Uint8Array`
1622
- );
1623
- }
1624
- let hex = "";
1625
- for (const b of drawn) hex += b.toString(16).padStart(2, "0");
1626
- return hex;
1627
- }
1628
- function drawIncarnation(entropy) {
1629
- return drawHex(entropy, INCARNATION_BYTES);
1630
- }
1631
- function drawWrite(entropy) {
1632
- return drawHex(entropy, WRITE_BYTES);
1633
- }
1634
- function nextRegistryToken(record, entropy) {
1635
- const { incarnation, counter } = tokenParts(record.token, record.segment);
1636
- return `${incarnation}.${counter + 1}.${drawWrite(entropy)}`;
1637
- }
1638
- function newIncarnationToken(entropy, tombstone) {
1639
- const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
1640
- return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
1641
- }
1642
- function shown3(v) {
1643
- const text = String(v);
1644
- return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
1645
- }
1646
- function parseRegistryEnvelope(text, ctx, expected) {
1647
- let parsed;
1648
- try {
1649
- parsed = JSON.parse(text);
1650
- } catch {
1651
- throw new IntegrityError(`registry row is not valid JSON: ${ctx}`);
1652
- }
1653
- if (parsed === null || typeof parsed !== "object") {
1654
- throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
1655
- }
1656
- const env = parsed;
1657
- assertRegistrySchemaVersion(env.schemaVersion, ctx);
1658
- const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
1659
- if (extra.length > 0) {
1660
- throw new IntegrityError(
1661
- `registry row has fields its envelope does not declare (${shown3(extra.join(", "))}): ${ctx}`
1662
- );
1663
- }
1664
- if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
1665
- throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
1666
- }
1667
- const r = env.record;
1668
- assertStoredRecordShape(r, ctx);
1669
- if (typeof r.token !== "string") {
1670
- throw new IntegrityError(`registry row is missing its token: ${ctx}`);
1671
- }
1672
- checkPointerId(r.pointerId, tokenParts(r.token, ctx), ctx);
1673
- if (expected !== void 0 && (r.segment !== expected.segment || (r.namespace ?? void 0) !== (expected.namespace ?? void 0))) {
1674
- throw new IntegrityError(
1675
- `registry row names another segment than the one it is stored for: ${ctx}`
1676
- );
1677
- }
1678
- return { deleted: env.deleted, record: env.record };
1679
- }
1680
- function recordFromNew(ref, rec, now, token) {
1681
- return {
1682
- namespace: ref.namespace,
1683
- segment: ref.segment,
1684
- currentGen: rec.currentGen,
1685
- wrappedDeks: rec.wrappedDeks,
1686
- keyId: rec.keyId,
1687
- status: rec.status ?? "active",
1688
- retention: rec.retention,
1689
- residency: rec.residency,
1690
- summary: rec.summary,
1691
- knownGens: rec.knownGens,
1692
- // A new row has no holder: a lease is written only against a row that already exists.
1693
- leases: void 0,
1694
- createdAt: now,
1695
- updatedAt: now,
1696
- token,
1697
- pointerId: token
1698
- };
1699
- }
1700
- function assertStoredRecordShape(r, ctx) {
1701
- 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") {
1702
- throw new IntegrityError(`registry record is missing required fields: ${ctx}`);
1703
- }
1704
- if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
1705
- throw new IntegrityError(
1706
- `registry record has an invalid currentGen (${shown3(r.currentGen)}): ${ctx}`
1707
- );
1708
- }
1709
- if (!STATUSES.includes(r.status)) {
1710
- throw new IntegrityError(`registry record has an unknown status (${shown3(r.status)}): ${ctx}`);
1711
- }
1712
- const declared = RECORD_FIELDS;
1713
- const extra = Object.keys(r).filter((k) => !declared.includes(k));
1714
- if (extra.length > 0) {
1715
- throw new IntegrityError(
1716
- `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown3(extra.join(", "))}): ${ctx}`
1717
- );
1718
- }
1719
- if (r.keyId !== void 0 && typeof r.keyId !== "string") {
1720
- throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
1721
- }
1722
- validateWrappedDeks(r.wrappedDeks, "stored");
1723
- for (const field of ["retention", "residency"]) {
1724
- const meta = r[field];
1725
- if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
1726
- throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
1235
+ const { generation, footerCrc } = entry;
1236
+ if (!Number.isSafeInteger(generation) || generation < 0) {
1237
+ return `a generation must be a non-negative safe integer (got ${show(generation)})`;
1727
1238
  }
1728
- }
1729
- validateSummary(r.summary, "stored", ctx);
1730
- validateKnownGens(r.knownGens, "stored", ctx);
1731
- validateLeases(r.leases, "stored", ctx);
1732
- }
1733
- function applyRegistryPatch(prev, patch, now, token) {
1734
- const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
1735
- const wrappedDeks = "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks;
1736
- let summary;
1737
- if ("summary" in patch) {
1738
- validateSummaryNames(patch.summary, currentGen);
1739
- validateSummaryKeys(patch.summary, wrappedDeks);
1740
- summary = patch.summary;
1741
- } else {
1742
- const kept = currentGen === prev.currentGen ? prev.summary : void 0;
1743
- summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
1744
- }
1745
- let knownGens;
1746
- if ("knownGens" in patch) {
1747
- validateKnownGensFit(patch.knownGens, currentGen, summary);
1748
- knownGens = patch.knownGens;
1749
- } else {
1750
- knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
1751
- }
1752
- let leases;
1753
- if ("leases" in patch) {
1754
- leases = patch.leases;
1755
- validateLeasesPointer(leases, currentGen);
1756
- const most = Math.max(MAX_LEASES_PER_SEGMENT, prev.leases?.length ?? 0);
1757
- if (leases !== void 0 && leases.length > most) {
1758
- throw new ValidationError(`leases: has ${leases.length} entries, cap ${most}`);
1239
+ if (!Number.isInteger(footerCrc) || footerCrc < 0 || footerCrc > 4294967295) {
1240
+ return `a footerCrc must be an integer from 0 to 2^32 - 1 (got ${show(footerCrc)})`;
1759
1241
  }
1760
- } else {
1761
- leases = prev.leases;
1762
- }
1763
- return {
1764
- namespace: prev.namespace,
1765
- segment: prev.segment,
1766
- currentGen,
1767
- wrappedDeks,
1768
- keyId: "keyId" in patch ? patch.keyId : prev.keyId,
1769
- status: patch.status ?? prev.status,
1770
- retention: "retention" in patch ? patch.retention : prev.retention,
1771
- residency: "residency" in patch ? patch.residency : prev.residency,
1772
- summary,
1773
- knownGens,
1774
- leases,
1775
- createdAt: prev.createdAt,
1776
- updatedAt: now,
1777
- token,
1778
- pointerId: renewsPointer(patch) ? token : prev.pointerId
1779
- };
1780
- }
1781
-
1782
- // src/drivers/_shared/entropy.ts
1783
- function webCrypto() {
1784
- const c = globalThis.crypto;
1785
- return c !== void 0 && typeof c.getRandomValues === "function" ? c : void 0;
1786
- }
1787
- var webCryptoEntropy = (length) => {
1788
- const c = webCrypto();
1789
- if (c === void 0) {
1790
- throw new UnsupportedError(
1791
- "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"
1792
- );
1242
+ if (seen.has(generation)) return `generation ${String(generation)} is repeated`;
1243
+ seen.add(generation);
1793
1244
  }
1794
- return c.getRandomValues(new Uint8Array(length));
1795
- };
1796
- function entropyIsAvailable(entropy) {
1797
- return entropy !== webCryptoEntropy || webCrypto() !== void 0;
1245
+ return void 0;
1798
1246
  }
1799
1247
 
1800
1248
  export {
@@ -1813,23 +1261,29 @@ export {
1813
1261
  KeyUnavailableError,
1814
1262
  TransientError,
1815
1263
  isWriteConflictError,
1264
+ unsettledConflict,
1265
+ isUnsettledConflict,
1816
1266
  isTransientError,
1817
1267
  isNotFoundError,
1818
1268
  isIntegrityError,
1819
1269
  isValidationError,
1820
1270
  isKeyUnavailableError,
1821
1271
  errorKind,
1272
+ shown,
1822
1273
  requireInteger,
1274
+ checkRequireEncryption,
1823
1275
  requirePositiveInteger,
1824
1276
  requireFiniteNumber,
1277
+ refuseUnknownOptions,
1278
+ requireBool,
1279
+ requireNonEmptyString,
1825
1280
  U32_MAX,
1826
1281
  CHUNK_COUNT,
1827
1282
  isBadIdError,
1283
+ refusalOfId,
1828
1284
  splitId,
1829
1285
  MAX_REMAINDER,
1830
1286
  joinId,
1831
- mapWithConcurrency,
1832
- boundedAll,
1833
1287
  PREAMBLE_BYTES,
1834
1288
  FOOTER_BYTES,
1835
1289
  encodeNameForKey,
@@ -1842,6 +1296,8 @@ export {
1842
1296
  RESERVED_NAMESPACE_PREFIX,
1843
1297
  isReservedNamespace,
1844
1298
  validateUserNamespace,
1299
+ PORT_KEYS,
1300
+ refuseUnknownRefKeys,
1845
1301
  validateUserRef,
1846
1302
  validateChunkKeyOrder,
1847
1303
  validateChunkRef,
@@ -1850,15 +1306,7 @@ export {
1850
1306
  STORAGE_BACKEND,
1851
1307
  brandAsBackend,
1852
1308
  isStorageBackend,
1853
- MAX_METADATA_BYTES,
1854
- metadataBytes,
1855
- copiedMetadata,
1856
- metadataFromBytes,
1857
- sizePart,
1858
- fingerprintFor,
1859
- fingerprintParts,
1860
- footerCrcOf,
1861
- compactFingerprint,
1309
+ INCARNATION_TOKEN,
1862
1310
  incarnationOf,
1863
1311
  incarnationField,
1864
1312
  sameIncarnation,
@@ -1866,14 +1314,28 @@ export {
1866
1314
  renewsPointer,
1867
1315
  renewPointer,
1868
1316
  pointerIdOf,
1317
+ pointerSetBy,
1869
1318
  aadFor,
1870
1319
  sameKeys,
1320
+ MAX_METADATA_BYTES,
1321
+ canonicalMetadataJson,
1322
+ metadataBytes,
1323
+ copiedMetadata,
1324
+ metadataFromBytes,
1325
+ sizePart,
1326
+ fingerprintFor,
1327
+ fingerprintParts,
1328
+ footerCrcOf,
1329
+ compactFingerprint,
1330
+ MAX_SUMMARY_CARDINALITY,
1331
+ tailBytesFit,
1871
1332
  sizeOfFingerprint,
1872
1333
  usableSummary,
1873
1334
  summaryAgrees,
1874
1335
  metadataToCarry,
1875
1336
  summaryOf,
1876
1337
  ownSummary,
1338
+ ascending,
1877
1339
  DEFAULT_RETRY_POLICY,
1878
1340
  jitteredWait,
1879
1341
  retryRead,
@@ -1882,6 +1344,7 @@ export {
1882
1344
  MAX_LEASE_MS,
1883
1345
  LEASE_SKEW_MS,
1884
1346
  MAX_LEASES_PER_SEGMENT,
1347
+ MAX_STORED_LEASES,
1885
1348
  UNANSWERED_RESENDS,
1886
1349
  UNANSWERED_BASE_MS,
1887
1350
  isLive,
@@ -1890,8 +1353,7 @@ export {
1890
1353
  leaseChurn,
1891
1354
  takeLease,
1892
1355
  releaseLease,
1893
- knownGensShapeFault,
1894
- ascending,
1356
+ MAX_KNOWN_GENERATIONS,
1895
1357
  MAX_STORED_KNOWN_GENERATIONS,
1896
1358
  usableKnownGens,
1897
1359
  windowOf,
@@ -1903,24 +1365,6 @@ export {
1903
1365
  describingKnown,
1904
1366
  isFullLedger,
1905
1367
  fallbackBound,
1906
- validateNewRegistryRecord,
1907
- validateRegistryPatch,
1908
- serializeRegistryEnvelope,
1909
- drawIncarnation,
1910
- drawWrite,
1911
- nextRegistryToken,
1912
- newIncarnationToken,
1913
- parseRegistryEnvelope,
1914
- recordFromNew,
1915
- applyRegistryPatch,
1916
- webCryptoEntropy,
1917
- entropyIsAvailable,
1918
- DEFAULT_NAMESPACE,
1919
- namespaceKeyPart,
1920
- namespacePathPart,
1921
- refuseUnknownOptions,
1922
- checkNow,
1923
- requirePositiveInt,
1924
- requireBool
1368
+ knownGensShapeFault
1925
1369
  };
1926
- //# sourceMappingURL=chunk-OSU7W2Y3.js.map
1370
+ //# sourceMappingURL=chunk-MVUD4R72.js.map