@cloudbitmaps/core 0.20.0 → 0.21.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.
- package/README.md +85 -0
- package/dist/{chunk-YSC7NJWV.js → chunk-IGLIZBR7.js} +740 -137
- package/dist/chunk-IGLIZBR7.js.map +7 -0
- package/dist/core/absent-operands.d.ts +8 -0
- package/dist/core/audit.d.ts +12 -2
- package/dist/core/bit-route.d.ts +2 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/bulk-load.d.ts +148 -0
- package/dist/core/cas-retry.d.ts +27 -0
- package/dist/core/check.d.ts +22 -0
- package/dist/core/chunk-checks.d.ts +6 -1
- package/dist/core/chunk-window.d.ts +11 -4
- package/dist/core/combine-expr.d.ts +1 -3
- package/dist/core/combine-feed.d.ts +2 -16
- package/dist/core/combine-many.d.ts +12 -9
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +69 -17
- package/dist/core/crbm/fingerprint.d.ts +5 -0
- package/dist/core/crbm/format.d.ts +0 -1
- package/dist/core/crbm/plan-reads.d.ts +9 -3
- package/dist/core/crbm/reader.d.ts +52 -6
- package/dist/core/crbm/varint.d.ts +2 -1
- package/dist/core/crbm/writer.d.ts +5 -1
- package/dist/core/crbm-open.d.ts +154 -0
- package/dist/core/crbm-storage-source.d.ts +65 -452
- package/dist/core/crbm-write.d.ts +75 -0
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/engine.d.ts +222 -33
- package/dist/core/erase-id.d.ts +42 -7
- package/dist/core/erasure.d.ts +4 -3
- package/dist/core/errors.d.ts +8 -2
- package/dist/core/generation-gc.d.ts +129 -15
- package/dist/core/generation-list.d.ts +11 -0
- package/dist/core/id-window.d.ts +46 -0
- package/dist/core/invalidation-scope.d.ts +77 -0
- package/dist/core/known-generations-shape.d.ts +13 -0
- package/dist/core/known-generations.d.ts +147 -0
- package/dist/core/leases.d.ts +4 -3
- package/dist/core/ledger-seed.d.ts +17 -0
- package/dist/core/load.d.ts +17 -38
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +4 -2
- package/dist/core/order.d.ts +2 -0
- package/dist/core/pinned-storage-source.d.ts +4 -4
- package/dist/core/ports.d.ts +89 -16
- package/dist/core/publish.d.ts +207 -0
- package/dist/core/registry-scan.d.ts +0 -2
- package/dist/core/retention-sweep.d.ts +69 -65
- package/dist/core/retention.d.ts +1 -1
- package/dist/core/retry.d.ts +12 -19
- package/dist/core/rollback.d.ts +60 -31
- package/dist/core/summary.d.ts +50 -18
- package/dist/core/token.d.ts +2 -2
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +4 -0
- package/dist/driver-kit.js +80 -7
- package/dist/driver-kit.js.map +3 -3
- package/dist/drivers/_shared/bytes.d.ts +6 -0
- package/dist/drivers/_shared/faults.d.ts +15 -0
- package/dist/drivers/_shared/object-keys.d.ts +18 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
- package/dist/drivers/_shared/object-registry.d.ts +2 -0
- package/dist/drivers/_shared/options.d.ts +19 -0
- package/dist/drivers/_shared/registry.d.ts +13 -10
- package/dist/drivers/localfs/exact-case.d.ts +0 -3
- package/dist/drivers/localfs/fs-util.d.ts +22 -0
- package/dist/drivers/localfs/registry.d.ts +10 -4
- package/dist/drivers/localfs/storage.d.ts +9 -2
- package/dist/drivers/memory.d.ts +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3647 -2410
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-YSC7NJWV.js.map +0 -7
- package/dist/core/kept-generations.d.ts +0 -40
|
@@ -115,10 +115,126 @@ function isKeyUnavailableError(err) {
|
|
|
115
115
|
return isCloudRoaringError(err) && err.name === "KeyUnavailableError";
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// src/core/bit-route.ts
|
|
119
|
+
var U32_MAX = 4294967295;
|
|
120
|
+
var CHUNK_COUNT = 65536;
|
|
121
|
+
var LOW_16 = 65535;
|
|
122
|
+
function splitId(id) {
|
|
123
|
+
if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
|
|
124
|
+
throw new ValidationError(`an id must be an integer from 0 to ${U32_MAX}`);
|
|
125
|
+
}
|
|
126
|
+
return { chunkKey: id >>> 16, remainder: id & LOW_16 };
|
|
127
|
+
}
|
|
128
|
+
var MAX_REMAINDER = CHUNK_COUNT - 1;
|
|
129
|
+
function joinId(chunkKey, remainder) {
|
|
130
|
+
return ((chunkKey & LOW_16) << 16 >>> 0) + (remainder & LOW_16);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/core/check.ts
|
|
134
|
+
function shown(value) {
|
|
135
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
136
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
137
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
138
|
+
return value === null ? "null" : typeof value;
|
|
139
|
+
}
|
|
140
|
+
function rangeOf(min, max, note) {
|
|
141
|
+
let range = "";
|
|
142
|
+
if (min !== void 0 && max !== void 0) range = ` from ${min} to ${max}`;
|
|
143
|
+
else if (min !== void 0) range = ` of at least ${min}`;
|
|
144
|
+
else if (max !== void 0) range = ` of at most ${max}`;
|
|
145
|
+
return note === void 0 ? range : `${range} (${note})`;
|
|
146
|
+
}
|
|
147
|
+
function requireInteger(value, bounds) {
|
|
148
|
+
const { min, max, what, note } = bounds;
|
|
149
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || max !== void 0 && value > max) {
|
|
150
|
+
throw new ValidationError(
|
|
151
|
+
`${what} must be an integer${rangeOf(min, max, note)}; got ${shown(value)}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
function requirePositiveInteger(value, what) {
|
|
157
|
+
return requireInteger(value, { min: 1, what });
|
|
158
|
+
}
|
|
159
|
+
function requireFiniteNumber(value, bounds) {
|
|
160
|
+
const { min, max, what, note } = bounds;
|
|
161
|
+
if (typeof value !== "number" || !Number.isFinite(value) || min !== void 0 && value < min || max !== void 0 && value > max) {
|
|
162
|
+
throw new ValidationError(
|
|
163
|
+
`${what} must be a finite number${rangeOf(min, max, note)}; got ${shown(value)}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/core/concurrency.ts
|
|
170
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
171
|
+
requirePositiveInteger(limit, "concurrency limit");
|
|
172
|
+
const results = new Array(items.length);
|
|
173
|
+
let next = 0;
|
|
174
|
+
let failed = false;
|
|
175
|
+
let firstError;
|
|
176
|
+
const worker = async () => {
|
|
177
|
+
while (!failed) {
|
|
178
|
+
const i = next++;
|
|
179
|
+
if (i >= items.length) return;
|
|
180
|
+
try {
|
|
181
|
+
results[i] = await fn(items[i], i);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (!failed) {
|
|
184
|
+
failed = true;
|
|
185
|
+
firstError = err;
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
192
|
+
await Promise.all(workers);
|
|
193
|
+
if (failed) throw firstError;
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
function boundedAll(items, limit, fn) {
|
|
197
|
+
try {
|
|
198
|
+
requirePositiveInteger(limit, "concurrency limit");
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return Promise.reject(err);
|
|
201
|
+
}
|
|
202
|
+
if (items.length > limit) return inWaves(items, limit, fn);
|
|
203
|
+
const calls = [];
|
|
204
|
+
for (const item of items) {
|
|
205
|
+
try {
|
|
206
|
+
calls.push(fn(item));
|
|
207
|
+
} catch (err) {
|
|
208
|
+
void Promise.allSettled(calls);
|
|
209
|
+
return Promise.reject(err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return Promise.all(calls);
|
|
213
|
+
}
|
|
214
|
+
async function inWaves(items, limit, fn) {
|
|
215
|
+
const results = new Array(items.length);
|
|
216
|
+
let next = 0;
|
|
217
|
+
let failed = false;
|
|
218
|
+
const worker = async () => {
|
|
219
|
+
while (!failed && next < items.length) {
|
|
220
|
+
const i = next++;
|
|
221
|
+
try {
|
|
222
|
+
results[i] = await fn(items[i]);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
failed = true;
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
await Promise.all(Array.from({ length: limit }, () => worker()));
|
|
230
|
+
return results;
|
|
231
|
+
}
|
|
232
|
+
|
|
118
233
|
// src/core/name-codec.ts
|
|
119
234
|
var KEY_SAFE = /[A-Za-z0-9._:-]/;
|
|
120
235
|
var PATH_SAFE = /[A-Za-z0-9._-]/;
|
|
121
236
|
var UTF8 = new TextEncoder();
|
|
237
|
+
var UTF8_LENIENT = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
|
|
122
238
|
function escapeChar(ch) {
|
|
123
239
|
let out = "";
|
|
124
240
|
for (const byte of UTF8.encode(ch)) {
|
|
@@ -136,17 +252,17 @@ function encodeWith(name, safe) {
|
|
|
136
252
|
function decodePercent(encoded) {
|
|
137
253
|
const bytes = [];
|
|
138
254
|
for (let i = 0; i < encoded.length; ) {
|
|
139
|
-
if (encoded[i] === "%" && i + 2
|
|
255
|
+
if (encoded[i] === "%" && i + 2 <= encoded.length) {
|
|
140
256
|
const hex = encoded.slice(i + 1, i + 3);
|
|
141
257
|
if (!/^[0-9A-Fa-f]{2}$/.test(hex)) return encoded;
|
|
142
258
|
bytes.push(parseInt(hex, 16));
|
|
143
259
|
i += 3;
|
|
144
260
|
} else {
|
|
145
|
-
for (const b of
|
|
261
|
+
for (const b of UTF8.encode(encoded[i])) bytes.push(b);
|
|
146
262
|
i += 1;
|
|
147
263
|
}
|
|
148
264
|
}
|
|
149
|
-
return
|
|
265
|
+
return UTF8_LENIENT.decode(new Uint8Array(bytes));
|
|
150
266
|
}
|
|
151
267
|
function encodeNameForKey(name) {
|
|
152
268
|
return defuseDotComponent(encodeWith(name, KEY_SAFE));
|
|
@@ -234,41 +350,14 @@ function validateChunkKeyOrder(keys) {
|
|
|
234
350
|
}
|
|
235
351
|
function validateChunkRef(ref) {
|
|
236
352
|
validateSegmentRef(ref);
|
|
237
|
-
|
|
353
|
+
validateChunkKey(ref.chunkKey);
|
|
354
|
+
}
|
|
355
|
+
function validateChunkKey(chunkKey) {
|
|
356
|
+
if (!(typeof chunkKey === "number" && chunkKey >= 0 && chunkKey <= CHUNK_KEY_MAX && (chunkKey | 0) === chunkKey)) {
|
|
238
357
|
throw new ValidationError(`chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]`);
|
|
239
358
|
}
|
|
240
359
|
}
|
|
241
360
|
|
|
242
|
-
// src/core/concurrency.ts
|
|
243
|
-
async function mapWithConcurrency(items, limit, fn) {
|
|
244
|
-
if (!Number.isInteger(limit) || limit < 1) {
|
|
245
|
-
throw new ValidationError(`concurrency limit must be a positive integer; got ${limit}`);
|
|
246
|
-
}
|
|
247
|
-
const results = new Array(items.length);
|
|
248
|
-
let next = 0;
|
|
249
|
-
let failed = false;
|
|
250
|
-
let firstError;
|
|
251
|
-
const worker = async () => {
|
|
252
|
-
while (!failed) {
|
|
253
|
-
const i = next++;
|
|
254
|
-
if (i >= items.length) return;
|
|
255
|
-
try {
|
|
256
|
-
results[i] = await fn(items[i], i);
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (!failed) {
|
|
259
|
-
failed = true;
|
|
260
|
-
firstError = err;
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
267
|
-
await Promise.all(workers);
|
|
268
|
-
if (failed) throw firstError;
|
|
269
|
-
return results;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
361
|
// src/core/ports.ts
|
|
273
362
|
function assertRegistryCanWrite(registry, what) {
|
|
274
363
|
if (registry.capabilities().canWrite === false) {
|
|
@@ -336,6 +425,97 @@ function pointerIdOf(row) {
|
|
|
336
425
|
return pointerId;
|
|
337
426
|
}
|
|
338
427
|
|
|
428
|
+
// src/core/crypto.ts
|
|
429
|
+
var UTF82 = new TextEncoder();
|
|
430
|
+
var AAD_VERSION = 1;
|
|
431
|
+
var SCOPE_CHUNK = 0;
|
|
432
|
+
var SCOPE_INDEX = 1;
|
|
433
|
+
var SCOPE_METADATA = 2;
|
|
434
|
+
var SCOPE_SUMMARY = 3;
|
|
435
|
+
function aadFor(ref, generation, scope) {
|
|
436
|
+
const ns = UTF82.encode(ref.namespace ?? "");
|
|
437
|
+
const seg = UTF82.encode(ref.segment);
|
|
438
|
+
const out = new Uint8Array(1 + 2 + ns.length + 2 + seg.length + 8 + 1 + 4);
|
|
439
|
+
const view = new DataView(out.buffer);
|
|
440
|
+
let o = 0;
|
|
441
|
+
out[o] = AAD_VERSION;
|
|
442
|
+
o += 1;
|
|
443
|
+
view.setUint16(o, ns.length, true);
|
|
444
|
+
o += 2;
|
|
445
|
+
out.set(ns, o);
|
|
446
|
+
o += ns.length;
|
|
447
|
+
view.setUint16(o, seg.length, true);
|
|
448
|
+
o += 2;
|
|
449
|
+
out.set(seg, o);
|
|
450
|
+
o += seg.length;
|
|
451
|
+
view.setBigUint64(o, BigInt(generation), true);
|
|
452
|
+
o += 8;
|
|
453
|
+
out[o] = scope === "index" ? SCOPE_INDEX : scope === "metadata" ? SCOPE_METADATA : scope === "summary" ? SCOPE_SUMMARY : SCOPE_CHUNK;
|
|
454
|
+
o += 1;
|
|
455
|
+
view.setUint32(o, typeof scope === "number" ? scope : 0, true);
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
var sameKeys = (a, b) => a !== void 0 && JSON.stringify(a) === JSON.stringify(b);
|
|
459
|
+
|
|
460
|
+
// src/core/retry.ts
|
|
461
|
+
var DEFAULT_RETRY_POLICY = {
|
|
462
|
+
maxAttempts: 4,
|
|
463
|
+
baseDelayMs: 50,
|
|
464
|
+
maxDelayMs: 2e3,
|
|
465
|
+
backoffFactor: 2,
|
|
466
|
+
jitter: "full"
|
|
467
|
+
};
|
|
468
|
+
function backoffDelayMs(policy, attempt) {
|
|
469
|
+
const raw = policy.baseDelayMs * policy.backoffFactor ** (attempt - 1);
|
|
470
|
+
return Math.min(policy.maxDelayMs, raw);
|
|
471
|
+
}
|
|
472
|
+
function applyJitter(policy, delayMs, rng) {
|
|
473
|
+
if (policy.jitter === "none") return delayMs;
|
|
474
|
+
return rng.next() * delayMs;
|
|
475
|
+
}
|
|
476
|
+
function jitteredWait(clock, rng, boundMs) {
|
|
477
|
+
const sleep = clock?.sleep;
|
|
478
|
+
return sleep === void 0 ? Promise.resolve() : sleep.call(clock, Math.floor((rng?.next() ?? 1) * boundMs));
|
|
479
|
+
}
|
|
480
|
+
function retryRead(op, retry) {
|
|
481
|
+
return retry === void 0 ? op() : withRetry(op, retry.policy ?? DEFAULT_RETRY_POLICY, retry);
|
|
482
|
+
}
|
|
483
|
+
function checkedRetryPolicy(policy) {
|
|
484
|
+
const { maxAttempts, baseDelayMs, maxDelayMs, backoffFactor, jitter } = policy;
|
|
485
|
+
requireInteger(maxAttempts, { min: 1, what: "retry.maxAttempts" });
|
|
486
|
+
requireFiniteNumber(baseDelayMs, { min: 0, what: "retry.baseDelayMs" });
|
|
487
|
+
requireFiniteNumber(maxDelayMs, {
|
|
488
|
+
min: baseDelayMs,
|
|
489
|
+
what: "retry.maxDelayMs",
|
|
490
|
+
note: "retry.baseDelayMs"
|
|
491
|
+
});
|
|
492
|
+
requireFiniteNumber(backoffFactor, { min: 1, what: "retry.backoffFactor" });
|
|
493
|
+
if (jitter !== "full" && jitter !== "none") {
|
|
494
|
+
throw new ValidationError(`retry.jitter must be 'full' or 'none'`);
|
|
495
|
+
}
|
|
496
|
+
return policy;
|
|
497
|
+
}
|
|
498
|
+
async function withRetry(op, policy, deps) {
|
|
499
|
+
const retryable = deps.isRetryable ?? isTransientError;
|
|
500
|
+
const attempts = requireInteger(policy.maxAttempts, { min: 1, what: "retry.maxAttempts" });
|
|
501
|
+
let lastErr;
|
|
502
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
503
|
+
try {
|
|
504
|
+
return await op();
|
|
505
|
+
} catch (err) {
|
|
506
|
+
lastErr = err;
|
|
507
|
+
if (attempt >= attempts || !retryable(err)) throw err;
|
|
508
|
+
const delayMs = applyJitter(policy, backoffDelayMs(policy, attempt), deps.rng);
|
|
509
|
+
try {
|
|
510
|
+
deps.onRetry?.({ attempt, delayMs, err });
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
await deps.clock.sleep(delayMs);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
throw lastErr;
|
|
517
|
+
}
|
|
518
|
+
|
|
339
519
|
// src/core/token.ts
|
|
340
520
|
var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
|
|
341
521
|
function incarnationOf(token) {
|
|
@@ -403,10 +583,7 @@ function leaseChurn(deps) {
|
|
|
403
583
|
if (used >= LEASE_ONLY_RETRIES || !onlyLeasesDiffer(held, now, alsoIgnore)) return void 0;
|
|
404
584
|
const bound = Math.min(CONFLICT_BASE_MS * 2 ** Math.min(used, 4), CONFLICT_CAP_MS);
|
|
405
585
|
used += 1;
|
|
406
|
-
|
|
407
|
-
if (sleep !== void 0) {
|
|
408
|
-
await sleep.call(deps.clock, Math.floor((deps.rng?.next() ?? 1) * bound));
|
|
409
|
-
}
|
|
586
|
+
await jitteredWait(deps.clock, deps.rng, bound);
|
|
410
587
|
return { row: await reread() };
|
|
411
588
|
}
|
|
412
589
|
};
|
|
@@ -417,7 +594,7 @@ function listOf(entries) {
|
|
|
417
594
|
(a, b) => a.generation !== b.generation ? a.generation - b.generation : a.holder < b.holder ? -1 : a.holder > b.holder ? 1 : 0
|
|
418
595
|
);
|
|
419
596
|
}
|
|
420
|
-
var wait = (deps, bound) => deps.clock
|
|
597
|
+
var wait = (deps, bound) => jitteredWait(deps.clock, deps.rng, bound);
|
|
421
598
|
var heldBy = (row, take) => (row?.leases ?? []).some(
|
|
422
599
|
(e) => e.holder === take.holder && e.generation === take.generation && e.until === take.until
|
|
423
600
|
);
|
|
@@ -526,6 +703,44 @@ function namespacePathPart(namespace) {
|
|
|
526
703
|
return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
|
|
527
704
|
}
|
|
528
705
|
|
|
706
|
+
// src/drivers/_shared/options.ts
|
|
707
|
+
function refuseUnknownOptions(name, options, keys, hint) {
|
|
708
|
+
if (options === null || typeof options !== "object") {
|
|
709
|
+
throw new ValidationError(
|
|
710
|
+
`${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const unknown = Object.keys(options).filter((k) => !keys.includes(k));
|
|
714
|
+
if (unknown.length > 0) {
|
|
715
|
+
const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
|
|
716
|
+
throw new ValidationError(
|
|
717
|
+
`${name} does not take ${list(unknown)}. It takes ${list(keys)}${hint === void 0 ? "" : `; ${hint}`}.`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function checkNow(name, now) {
|
|
722
|
+
if (now !== void 0 && typeof now !== "function") {
|
|
723
|
+
throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function requirePositiveInt(name, value) {
|
|
727
|
+
return value === void 0 ? void 0 : requirePositiveInteger(value, name);
|
|
728
|
+
}
|
|
729
|
+
function requireBool(name, value) {
|
|
730
|
+
if (value === void 0) return void 0;
|
|
731
|
+
if (typeof value !== "boolean") {
|
|
732
|
+
throw new ValidationError(`${name} must be a boolean; got ${shown2(value)}`);
|
|
733
|
+
}
|
|
734
|
+
return value;
|
|
735
|
+
}
|
|
736
|
+
function shown2(value) {
|
|
737
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
738
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
739
|
+
return String(value);
|
|
740
|
+
}
|
|
741
|
+
return value === null ? "null" : typeof value;
|
|
742
|
+
}
|
|
743
|
+
|
|
529
744
|
// src/core/crbm/frame.ts
|
|
530
745
|
var PREAMBLE_BYTES = 8;
|
|
531
746
|
var FOOTER_BYTES = 104;
|
|
@@ -533,7 +748,9 @@ var FOOTER_BYTES = 104;
|
|
|
533
748
|
// src/core/metadata.ts
|
|
534
749
|
var MAX_METADATA_BYTES = 1024;
|
|
535
750
|
var MAX_METADATA_KEY_BYTES = 128;
|
|
536
|
-
var
|
|
751
|
+
var UTF83 = new TextEncoder();
|
|
752
|
+
var UTF8_STRICT = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
753
|
+
var utf8Length = (s) => UTF83.encode(s).length;
|
|
537
754
|
function canonicalMetadataJson(value, fail) {
|
|
538
755
|
if (value === null || typeof value !== "object") {
|
|
539
756
|
fail(`metadata must be a plain object (got ${value === null ? "null" : typeof value})`);
|
|
@@ -580,7 +797,7 @@ function canonicalMetadataJson(value, fail) {
|
|
|
580
797
|
function metadataBytes(value, fail) {
|
|
581
798
|
if (value === void 0) return void 0;
|
|
582
799
|
const json = canonicalMetadataJson(value, fail);
|
|
583
|
-
return json === "{}" ? void 0 :
|
|
800
|
+
return json === "{}" ? void 0 : UTF83.encode(json);
|
|
584
801
|
}
|
|
585
802
|
function copiedMetadata(value, fail) {
|
|
586
803
|
const bytes = metadataBytes(value, fail);
|
|
@@ -592,7 +809,7 @@ function metadataFromBytes(bytes, fail) {
|
|
|
592
809
|
}
|
|
593
810
|
let text;
|
|
594
811
|
try {
|
|
595
|
-
text =
|
|
812
|
+
text = UTF8_STRICT.decode(bytes);
|
|
596
813
|
} catch {
|
|
597
814
|
fail("metadata is not valid UTF-8");
|
|
598
815
|
}
|
|
@@ -621,6 +838,13 @@ function fingerprintParts(fingerprint) {
|
|
|
621
838
|
if (footerCrc > 4294967295) return void 0;
|
|
622
839
|
return { size, footerCrc };
|
|
623
840
|
}
|
|
841
|
+
function footerCrcOf(fingerprint) {
|
|
842
|
+
const parts = fingerprintParts(fingerprint);
|
|
843
|
+
if (parts === void 0) {
|
|
844
|
+
throw new IntegrityError(`not an object's fingerprint: ${String(fingerprint).slice(0, 64)}`);
|
|
845
|
+
}
|
|
846
|
+
return parts.footerCrc;
|
|
847
|
+
}
|
|
624
848
|
function compactFingerprint(fingerprint) {
|
|
625
849
|
const colon = fingerprint.indexOf(":");
|
|
626
850
|
const size = Number(fingerprint.slice(0, colon));
|
|
@@ -628,25 +852,352 @@ function compactFingerprint(fingerprint) {
|
|
|
628
852
|
return `${size.toString(36)}.${crc.toString(36)}`;
|
|
629
853
|
}
|
|
630
854
|
|
|
631
|
-
// src/core/
|
|
855
|
+
// src/core/summary.ts
|
|
856
|
+
var NONCE_BYTES = 12;
|
|
857
|
+
var TAG_BYTES = 16;
|
|
858
|
+
var COUNT_BYTES = 8;
|
|
859
|
+
var SIZE_BYTES = 8;
|
|
860
|
+
var CRC_BYTES = 4;
|
|
861
|
+
var TAIL_BYTES = 4;
|
|
862
|
+
var FIXED_BYTES = COUNT_BYTES + SIZE_BYTES + CRC_BYTES + TAIL_BYTES;
|
|
863
|
+
var MAX_SUMMARY_CARDINALITY = U32_MAX + 1;
|
|
864
|
+
var MAX_TAIL_BYTES = 8392820;
|
|
865
|
+
function tailBytesFit(tailBytes, size) {
|
|
866
|
+
return typeof tailBytes === "number" && Number.isInteger(tailBytes) && tailBytes >= FOOTER_BYTES && tailBytes <= size - PREAMBLE_BYTES && tailBytes <= MAX_TAIL_BYTES;
|
|
867
|
+
}
|
|
868
|
+
function sizeOfFingerprint(fingerprint) {
|
|
869
|
+
const parts = fingerprintParts(fingerprint);
|
|
870
|
+
if (parts === void 0)
|
|
871
|
+
throw new IntegrityError("registry record summary: a malformed fingerprint");
|
|
872
|
+
return parts.size;
|
|
873
|
+
}
|
|
874
|
+
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
875
|
+
function toBase64(bytes) {
|
|
876
|
+
let out = "";
|
|
877
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
878
|
+
const a = bytes[i];
|
|
879
|
+
const b = bytes[i + 1];
|
|
880
|
+
const c = bytes[i + 2];
|
|
881
|
+
out += BASE64[a >> 2] + BASE64[(a & 3) << 4 | (b ?? 0) >> 4];
|
|
882
|
+
out += b === void 0 ? "=" : BASE64[(b & 15) << 2 | (c ?? 0) >> 6];
|
|
883
|
+
out += c === void 0 ? "=" : BASE64[c & 63];
|
|
884
|
+
}
|
|
885
|
+
return out;
|
|
886
|
+
}
|
|
887
|
+
function fromBase64(text) {
|
|
888
|
+
if (text.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) return void 0;
|
|
889
|
+
const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
890
|
+
const out = new Uint8Array(text.length / 4 * 3 - pad);
|
|
891
|
+
let o = 0;
|
|
892
|
+
for (let i = 0; i < text.length; i += 4) {
|
|
893
|
+
const n = BASE64.indexOf(text[i]) << 18 | BASE64.indexOf(text[i + 1]) << 12 | Math.max(BASE64.indexOf(text[i + 2]), 0) << 6 | Math.max(BASE64.indexOf(text[i + 3]), 0);
|
|
894
|
+
if (o < out.length) out[o++] = n >> 16 & 255;
|
|
895
|
+
if (o < out.length) out[o++] = n >> 8 & 255;
|
|
896
|
+
if (o < out.length) out[o++] = n & 255;
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
function clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) {
|
|
901
|
+
return metadata === void 0 || Object.keys(metadata).length === 0 ? { generation, cardinality, fingerprint, tailBytes } : { generation, cardinality, fingerprint, tailBytes, metadata };
|
|
902
|
+
}
|
|
903
|
+
function sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata) {
|
|
904
|
+
const meta = metadataBytes(metadata, (message) => {
|
|
905
|
+
throw new IntegrityError(`summary metadata: ${message}`);
|
|
906
|
+
});
|
|
907
|
+
const parts = fingerprintParts(fingerprint);
|
|
908
|
+
if (parts === void 0) throw new IntegrityError("summary: a malformed fingerprint");
|
|
909
|
+
const plain = new Uint8Array(FIXED_BYTES + (meta?.length ?? 0));
|
|
910
|
+
const view = new DataView(plain.buffer);
|
|
911
|
+
view.setBigUint64(0, BigInt(cardinality), true);
|
|
912
|
+
view.setBigUint64(COUNT_BYTES, BigInt(parts.size), true);
|
|
913
|
+
view.setUint32(COUNT_BYTES + SIZE_BYTES, parts.footerCrc, true);
|
|
914
|
+
view.setUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, tailBytes, true);
|
|
915
|
+
if (meta !== void 0) plain.set(meta, FIXED_BYTES);
|
|
916
|
+
const { nonce, ciphertext, tag } = aead.seal(plain, aadFor(ref, generation, "summary"));
|
|
917
|
+
const framed = new Uint8Array(nonce.length + ciphertext.length + tag.length);
|
|
918
|
+
framed.set(nonce, 0);
|
|
919
|
+
framed.set(ciphertext, nonce.length);
|
|
920
|
+
framed.set(tag, nonce.length + ciphertext.length);
|
|
921
|
+
return { generation, sealed: toBase64(framed) };
|
|
922
|
+
}
|
|
923
|
+
function openSummary(aead, ref, summary) {
|
|
924
|
+
const fail = (message) => {
|
|
925
|
+
throw new IntegrityError(`registry record summary: ${message}`);
|
|
926
|
+
};
|
|
927
|
+
const framed = fromBase64(summary.sealed);
|
|
928
|
+
const framing = NONCE_BYTES + TAG_BYTES;
|
|
929
|
+
if (framed === void 0 || framed.length < framing + FIXED_BYTES || framed.length > framing + FIXED_BYTES + MAX_METADATA_BYTES) {
|
|
930
|
+
fail(
|
|
931
|
+
"the sealed summary is not a nonce, a count, a fingerprint, a tail size, up to 1 KiB of metadata and a tag"
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
const bytes = framed;
|
|
935
|
+
let plain;
|
|
936
|
+
try {
|
|
937
|
+
plain = aead.open(
|
|
938
|
+
{
|
|
939
|
+
nonce: bytes.subarray(0, NONCE_BYTES),
|
|
940
|
+
ciphertext: bytes.subarray(NONCE_BYTES, bytes.length - TAG_BYTES),
|
|
941
|
+
tag: bytes.subarray(bytes.length - TAG_BYTES)
|
|
942
|
+
},
|
|
943
|
+
aadFor(ref, summary.generation, "summary")
|
|
944
|
+
);
|
|
945
|
+
} catch (err) {
|
|
946
|
+
if (!isIntegrityError(err)) throw err;
|
|
947
|
+
return fail(
|
|
948
|
+
"the sealed summary does not open: tampered bytes, or a summary moved from another generation"
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (plain.length < FIXED_BYTES)
|
|
952
|
+
fail("the sealed summary is shorter than its count, fingerprint and tail size");
|
|
953
|
+
const view = new DataView(plain.buffer, plain.byteOffset, plain.byteLength);
|
|
954
|
+
const count = view.getBigUint64(0, true);
|
|
955
|
+
if (count > BigInt(MAX_SUMMARY_CARDINALITY)) fail(`cardinality ${count} is outside 0 to 2^32`);
|
|
956
|
+
const size = view.getBigUint64(COUNT_BYTES, true);
|
|
957
|
+
const fingerprint = fingerprintFor(Number(size), view.getUint32(COUNT_BYTES + SIZE_BYTES, true));
|
|
958
|
+
if (size > BigInt(Number.MAX_SAFE_INTEGER) || fingerprintParts(fingerprint) === void 0) {
|
|
959
|
+
fail(`the object size ${size} is not one a .crbm object can have`);
|
|
960
|
+
}
|
|
961
|
+
const tailBytes = view.getUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, true);
|
|
962
|
+
if (!tailBytesFit(tailBytes, Number(size))) {
|
|
963
|
+
fail(`the tail size ${tailBytes} is not one a .crbm object of ${size} bytes can have`);
|
|
964
|
+
}
|
|
965
|
+
const metadata = plain.length === FIXED_BYTES ? void 0 : metadataFromBytes(plain.subarray(FIXED_BYTES), fail);
|
|
966
|
+
return { cardinality: Number(count), metadata, fingerprint, tailBytes };
|
|
967
|
+
}
|
|
968
|
+
function usableSummary(ref, row, aead) {
|
|
969
|
+
const summary = row.summary;
|
|
970
|
+
if (summary === void 0 || row.status !== "active" || summary.generation !== row.currentGen) {
|
|
971
|
+
return void 0;
|
|
972
|
+
}
|
|
973
|
+
const keyed = row.wrappedDeks !== void 0 && row.wrappedDeks.length > 0;
|
|
974
|
+
if ("sealed" in summary) {
|
|
975
|
+
if (!keyed || aead === void 0) return void 0;
|
|
976
|
+
try {
|
|
977
|
+
return openSummary(aead, ref, summary);
|
|
978
|
+
} catch (err) {
|
|
979
|
+
if (isIntegrityError(err)) return void 0;
|
|
980
|
+
throw err;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (keyed) return void 0;
|
|
984
|
+
const parts = fingerprintParts(summary.fingerprint);
|
|
985
|
+
if (parts === void 0 || !tailBytesFit(summary.tailBytes, parts.size)) return void 0;
|
|
986
|
+
return {
|
|
987
|
+
cardinality: summary.cardinality,
|
|
988
|
+
metadata: summary.metadata,
|
|
989
|
+
fingerprint: summary.fingerprint,
|
|
990
|
+
tailBytes: summary.tailBytes
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
function canonicalOrNone(metadata) {
|
|
994
|
+
if (metadata === void 0) return void 0;
|
|
995
|
+
const json = canonicalMetadataJson(metadata, (message) => {
|
|
996
|
+
throw new IntegrityError(`metadata: ${message}`);
|
|
997
|
+
});
|
|
998
|
+
return json === "{}" ? void 0 : json;
|
|
999
|
+
}
|
|
1000
|
+
function summaryAgrees(summary, object) {
|
|
1001
|
+
return summary.cardinality === object.cardinality && canonicalOrNone(summary.metadata) === canonicalOrNone(object.metadata) && (summary.tailBytes === void 0 || object.tailBytes === void 0 || summary.tailBytes === object.tailBytes);
|
|
1002
|
+
}
|
|
1003
|
+
function metadataToCarry(object, authenticated) {
|
|
1004
|
+
const empty = (metadata) => metadata === void 0 || Object.keys(metadata).length === 0;
|
|
1005
|
+
return empty(object) && authenticated !== void 0 && !empty(authenticated.metadata) ? authenticated.metadata : object;
|
|
1006
|
+
}
|
|
1007
|
+
function summaryOf(ref, generation, description, aead) {
|
|
1008
|
+
const { cardinality, fingerprint, tailBytes, metadata } = description;
|
|
1009
|
+
if (tailBytes > MAX_TAIL_BYTES) return void 0;
|
|
1010
|
+
return aead === void 0 ? clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) : sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata);
|
|
1011
|
+
}
|
|
1012
|
+
function ownSummary(ref, generation, description, aead) {
|
|
1013
|
+
const summary = summaryOf(ref, generation, description, aead);
|
|
1014
|
+
if (summary === void 0) {
|
|
1015
|
+
throw new IntegrityError(
|
|
1016
|
+
`the object written for generation ${generation} of "${ref.segment}" has a tail of ${description.tailBytes} bytes, above the ${MAX_TAIL_BYTES} a summary records`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
return summary;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/core/known-generations-shape.ts
|
|
1023
|
+
var ENTRY_KEYS = ["generation", "footerCrc"];
|
|
1024
|
+
function knownGensShapeFault(value, max, show = String) {
|
|
1025
|
+
if (value === void 0) return void 0;
|
|
1026
|
+
if (!Array.isArray(value)) return "must be an array";
|
|
1027
|
+
const list = value;
|
|
1028
|
+
if (list.length > max) return `has ${list.length} entries, cap ${max}`;
|
|
1029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1030
|
+
for (const e of list) {
|
|
1031
|
+
if (typeof e !== "object" || e === null || Array.isArray(e))
|
|
1032
|
+
return "an entry must be an object";
|
|
1033
|
+
const entry = e;
|
|
1034
|
+
const extra = Object.keys(entry).filter((k) => !ENTRY_KEYS.includes(k));
|
|
1035
|
+
if (extra.length > 0) {
|
|
1036
|
+
return `an entry carries an undeclared field (${show(extra.join(", "))})`;
|
|
1037
|
+
}
|
|
1038
|
+
const { generation, footerCrc } = entry;
|
|
1039
|
+
if (!Number.isSafeInteger(generation) || generation < 0) {
|
|
1040
|
+
return `a generation must be a non-negative safe integer (got ${show(generation)})`;
|
|
1041
|
+
}
|
|
1042
|
+
if (!Number.isInteger(footerCrc) || footerCrc < 0 || footerCrc > 4294967295) {
|
|
1043
|
+
return `a footerCrc must be an integer from 0 to 2^32 - 1 (got ${show(footerCrc)})`;
|
|
1044
|
+
}
|
|
1045
|
+
if (seen.has(generation)) return `generation ${String(generation)} is repeated`;
|
|
1046
|
+
seen.add(generation);
|
|
1047
|
+
}
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/core/order.ts
|
|
1052
|
+
var ascending = (a, b) => a - b;
|
|
1053
|
+
|
|
1054
|
+
// src/core/known-generations.ts
|
|
632
1055
|
var MAX_KEPT_GENERATIONS = 64;
|
|
633
|
-
var
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1056
|
+
var KNOWN_FILL = 128;
|
|
1057
|
+
var MAX_KNOWN_GENERATIONS = 192;
|
|
1058
|
+
var MAX_STORED_KNOWN_GENERATIONS = 384;
|
|
1059
|
+
function usableKnownGens(record) {
|
|
1060
|
+
const known = record?.knownGens;
|
|
1061
|
+
if (record === null || known === void 0 || known.length === 0 || record.status !== "active" || record.currentGen === null) {
|
|
1062
|
+
return void 0;
|
|
1063
|
+
}
|
|
1064
|
+
const last = known[known.length - 1];
|
|
1065
|
+
if (last.generation !== record.currentGen) return void 0;
|
|
1066
|
+
const summary = record.summary;
|
|
1067
|
+
if (summary !== void 0 && summary.generation === record.currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
|
|
1068
|
+
return void 0;
|
|
1069
|
+
}
|
|
1070
|
+
return known;
|
|
1071
|
+
}
|
|
1072
|
+
function windowOf(record) {
|
|
1073
|
+
const known = usableKnownGens(record);
|
|
1074
|
+
if (known === void 0) return void 0;
|
|
1075
|
+
const pointer = record.currentGen;
|
|
1076
|
+
return known.filter((e) => e.generation < pointer).map((e) => e.generation);
|
|
1077
|
+
}
|
|
1078
|
+
function carriesWindow(record, leased) {
|
|
1079
|
+
const below = windowOf(record);
|
|
1080
|
+
return below !== void 0 && below.filter((g) => !leased.has(g)).length <= MAX_KEPT_GENERATIONS;
|
|
1081
|
+
}
|
|
1082
|
+
var KNOWN_SPAN = 127;
|
|
1083
|
+
function keptWindow(below, superseded, keep) {
|
|
1084
|
+
const oldestFirst = [...below].sort(ascending);
|
|
1085
|
+
const first = keep > 0 && superseded !== void 0 && oldestFirst.includes(superseded) ? superseded : void 0;
|
|
1086
|
+
const held = new Set(first === void 0 ? [] : [first]);
|
|
1087
|
+
for (let i = oldestFirst.length - 1, room = keep - held.size; i >= 0 && room > 0; i--) {
|
|
1088
|
+
if (oldestFirst[i] === first) continue;
|
|
1089
|
+
held.add(oldestFirst[i]);
|
|
1090
|
+
room -= 1;
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
kept: oldestFirst.filter((g) => held.has(g)),
|
|
1094
|
+
evict: oldestFirst.filter((g) => !held.has(g))
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function capKnown(known, protect, leased = /* @__PURE__ */ new Set()) {
|
|
1098
|
+
const keep = /* @__PURE__ */ new Set();
|
|
1099
|
+
for (const e of known) if (protect.has(e.generation)) keep.add(e.generation);
|
|
1100
|
+
for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
|
|
1101
|
+
if (leased.has(known[i].generation)) keep.add(known[i].generation);
|
|
1102
|
+
}
|
|
1103
|
+
for (let i = known.length - 1; i >= 0 && keep.size < KNOWN_FILL; i--)
|
|
1104
|
+
keep.add(known[i].generation);
|
|
1105
|
+
return known.filter((e) => keep.has(e.generation));
|
|
1106
|
+
}
|
|
1107
|
+
function capInsert(known, protect, leased) {
|
|
1108
|
+
const keep = /* @__PURE__ */ new Set();
|
|
1109
|
+
let unleased = 0;
|
|
1110
|
+
for (const e of known) {
|
|
1111
|
+
if (!protect.has(e.generation)) continue;
|
|
1112
|
+
keep.add(e.generation);
|
|
1113
|
+
if (!leased.has(e.generation)) unleased += 1;
|
|
1114
|
+
}
|
|
1115
|
+
for (let i = known.length - 1; i >= 0 && unleased < KNOWN_FILL; i--) {
|
|
1116
|
+
const g = known[i].generation;
|
|
1117
|
+
if (keep.has(g) || leased.has(g)) continue;
|
|
1118
|
+
keep.add(g);
|
|
1119
|
+
unleased += 1;
|
|
1120
|
+
}
|
|
1121
|
+
for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
|
|
1122
|
+
if (leased.has(known[i].generation)) keep.add(known[i].generation);
|
|
1123
|
+
}
|
|
1124
|
+
return known.filter((e) => keep.has(e.generation));
|
|
1125
|
+
}
|
|
1126
|
+
function makeCurrent(known, entry) {
|
|
1127
|
+
return [...known.filter((e) => e.generation !== entry.generation), entry];
|
|
1128
|
+
}
|
|
1129
|
+
function nextKnown(prev, generation, footerCrc, keep, options = {}) {
|
|
1130
|
+
const self = { generation, footerCrc };
|
|
1131
|
+
const clears = options.clearsLeases === true;
|
|
1132
|
+
const byName = keep !== void 0 && keep <= MAX_KEPT_GENERATIONS;
|
|
1133
|
+
if (options.seed !== void 0 && !clears) {
|
|
1134
|
+
const seed = options.seed.filter((e) => e.generation !== generation);
|
|
1135
|
+
const protect = /* @__PURE__ */ new Set([...seed.map((e) => e.generation), generation]);
|
|
1136
|
+
return {
|
|
1137
|
+
known: capKnown([...seed, self], protect, options.leased ?? /* @__PURE__ */ new Set()),
|
|
1138
|
+
list: void 0,
|
|
1139
|
+
evict: []
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
const found = usableKnownGens(prev);
|
|
1143
|
+
if (found === void 0 && prev !== null && prev.currentGen !== null) {
|
|
1144
|
+
return { known: [self], list: void 0, evict: [] };
|
|
1145
|
+
}
|
|
1146
|
+
const all = found ?? [];
|
|
1147
|
+
const below = all.filter((e) => e.generation < generation);
|
|
1148
|
+
const above = clears ? [] : all.filter((e) => e.generation > generation);
|
|
1149
|
+
const span = clears ? 0 : Math.min(keep ?? Number.MAX_SAFE_INTEGER, KNOWN_SPAN);
|
|
1150
|
+
const vouch = new Set((span === 0 ? [] : below.slice(-span)).map((e) => e.generation));
|
|
1151
|
+
const leased = clears ? /* @__PURE__ */ new Set() : options.leased ?? /* @__PURE__ */ new Set();
|
|
1152
|
+
const held = below.filter((e) => !vouch.has(e.generation) && leased.has(e.generation));
|
|
1153
|
+
const chosen = /* @__PURE__ */ new Set([
|
|
1154
|
+
...vouch,
|
|
1155
|
+
...held.map((e) => e.generation),
|
|
1156
|
+
...above.map((e) => e.generation)
|
|
1157
|
+
]);
|
|
1158
|
+
const known = capKnown(
|
|
1159
|
+
[...all.filter((e) => chosen.has(e.generation)), self],
|
|
1160
|
+
/* @__PURE__ */ new Set([...vouch, generation]),
|
|
1161
|
+
leased
|
|
1162
|
+
);
|
|
1163
|
+
const wide = !clears && found !== void 0 && !carriesWindow(prev, leased);
|
|
1164
|
+
if (!byName || wide) return { known, list: void 0, evict: [] };
|
|
1165
|
+
const named = new Set(known.map((e) => e.generation));
|
|
1166
|
+
return {
|
|
1167
|
+
known,
|
|
1168
|
+
list: known.filter((e) => e.generation < generation).map((e) => e.generation).sort(ascending),
|
|
1169
|
+
evict: below.filter((e) => !named.has(e.generation)).map((e) => e.generation).sort(ascending)
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function rollbackKnown(prev, target) {
|
|
1173
|
+
const found = usableKnownGens(prev);
|
|
1174
|
+
if (found === void 0) return void 0;
|
|
1175
|
+
const moved = makeCurrent(found, target);
|
|
1176
|
+
const inserted = !found.some((e) => e.generation === target.generation);
|
|
1177
|
+
if (!inserted && moved.length <= MAX_KNOWN_GENERATIONS) return moved;
|
|
1178
|
+
return capInsert(moved, /* @__PURE__ */ new Set([target.generation, prev.currentGen]), leasedByRow(prev));
|
|
1179
|
+
}
|
|
1180
|
+
function pruneKnown(record, doomed) {
|
|
1181
|
+
const found = usableKnownGens(record);
|
|
1182
|
+
if (found === void 0) return void 0;
|
|
1183
|
+
const rest = found.filter((e) => e.generation === record.currentGen || !doomed.has(e.generation));
|
|
1184
|
+
if (rest.length <= MAX_KNOWN_GENERATIONS) return rest;
|
|
1185
|
+
return capKnown(rest, /* @__PURE__ */ new Set([record.currentGen]), leasedByRow(record));
|
|
1186
|
+
}
|
|
1187
|
+
function describingKnown(record, movedTo) {
|
|
1188
|
+
const found = usableKnownGens(record);
|
|
1189
|
+
if (found === void 0 || found.length <= MAX_KNOWN_GENERATIONS) return found;
|
|
1190
|
+
return capKnown(found, /* @__PURE__ */ new Set([record.currentGen, movedTo]), leasedByRow(record));
|
|
1191
|
+
}
|
|
1192
|
+
function isFullLedger(known) {
|
|
1193
|
+
return known.length >= KNOWN_FILL;
|
|
1194
|
+
}
|
|
1195
|
+
function fallbackBound(record, known) {
|
|
1196
|
+
const leased = leasedByRow(record);
|
|
1197
|
+
return (known.find((e) => !leased.has(e.generation)) ?? known[0]).generation;
|
|
1198
|
+
}
|
|
1199
|
+
function leasedByRow(record) {
|
|
1200
|
+
return new Set((record.leases ?? []).map((e) => e.generation));
|
|
650
1201
|
}
|
|
651
1202
|
|
|
652
1203
|
// src/drivers/_shared/registry.ts
|
|
@@ -664,7 +1215,7 @@ var RECORD_FIELDS = [
|
|
|
664
1215
|
"createdAt",
|
|
665
1216
|
"updatedAt",
|
|
666
1217
|
"token",
|
|
667
|
-
"
|
|
1218
|
+
"knownGens",
|
|
668
1219
|
"leases",
|
|
669
1220
|
"pointerId"
|
|
670
1221
|
];
|
|
@@ -673,10 +1224,9 @@ var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
|
|
|
673
1224
|
var MAX_GOVERNANCE_BYTES = 64 * 1024;
|
|
674
1225
|
var MAX_WRAPPED_DEKS = 8;
|
|
675
1226
|
var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
|
|
676
|
-
var
|
|
677
|
-
var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 16;
|
|
1227
|
+
var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
|
|
678
1228
|
var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
|
|
679
|
-
var
|
|
1229
|
+
var BASE642 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
680
1230
|
function validateGeneration(gen) {
|
|
681
1231
|
if (gen === null) return;
|
|
682
1232
|
if (!Number.isSafeInteger(gen) || gen < 0) {
|
|
@@ -716,10 +1266,10 @@ function validateGovernance(meta, field) {
|
|
|
716
1266
|
throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
|
|
717
1267
|
}
|
|
718
1268
|
}
|
|
719
|
-
function validateWrappedDeks(value,
|
|
1269
|
+
function validateWrappedDeks(value, origin) {
|
|
720
1270
|
if (value === void 0) return;
|
|
721
1271
|
const fail = (msg) => {
|
|
722
|
-
throw
|
|
1272
|
+
throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
|
|
723
1273
|
};
|
|
724
1274
|
if (!Array.isArray(value)) fail("wrappedDeks must be an array");
|
|
725
1275
|
const list = value;
|
|
@@ -744,11 +1294,11 @@ function base64PadBitsAreZero(text) {
|
|
|
744
1294
|
const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
|
|
745
1295
|
return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
|
|
746
1296
|
}
|
|
747
|
-
function validateSummary(value,
|
|
1297
|
+
function validateSummary(value, origin, ctx) {
|
|
748
1298
|
if (value === void 0) return void 0;
|
|
749
1299
|
const fail = (msg) => {
|
|
750
1300
|
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
751
|
-
throw
|
|
1301
|
+
throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
|
|
752
1302
|
};
|
|
753
1303
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
754
1304
|
fail(
|
|
@@ -758,7 +1308,7 @@ function validateSummary(value, isStored, ctx) {
|
|
|
758
1308
|
const s = value;
|
|
759
1309
|
const keys = Object.keys(s);
|
|
760
1310
|
const isSealed = keys.includes("sealed");
|
|
761
|
-
const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "metadata"];
|
|
1311
|
+
const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
|
|
762
1312
|
const extra = keys.filter((k) => !allowed.includes(k));
|
|
763
1313
|
if (extra.length > 0) {
|
|
764
1314
|
fail(
|
|
@@ -771,14 +1321,14 @@ function validateSummary(value, isStored, ctx) {
|
|
|
771
1321
|
}
|
|
772
1322
|
if (isSealed) {
|
|
773
1323
|
const sealed = s.sealed;
|
|
774
|
-
if (typeof sealed !== "string" || !
|
|
1324
|
+
if (typeof sealed !== "string" || !BASE642.test(sealed) || !base64PadBitsAreZero(sealed)) {
|
|
775
1325
|
fail("sealed must be canonical, padded standard base64");
|
|
776
1326
|
}
|
|
777
1327
|
const text = sealed;
|
|
778
1328
|
const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
|
|
779
1329
|
if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
|
|
780
1330
|
fail(
|
|
781
|
-
`sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count and
|
|
1331
|
+
`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)`
|
|
782
1332
|
);
|
|
783
1333
|
}
|
|
784
1334
|
return Object.freeze({ generation, sealed: text });
|
|
@@ -788,26 +1338,35 @@ function validateSummary(value, isStored, ctx) {
|
|
|
788
1338
|
fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
|
|
789
1339
|
}
|
|
790
1340
|
const fingerprint = s.fingerprint;
|
|
791
|
-
|
|
1341
|
+
const parts = fingerprintParts(fingerprint);
|
|
1342
|
+
if (parts === void 0) {
|
|
1343
|
+
fail(
|
|
1344
|
+
`fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown3(JSON.stringify(fingerprint))})`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
const tailBytes = s.tailBytes;
|
|
1348
|
+
if (!tailBytesFit(tailBytes, parts.size)) {
|
|
792
1349
|
fail(
|
|
793
|
-
`
|
|
1350
|
+
`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)`
|
|
794
1351
|
);
|
|
795
1352
|
}
|
|
796
1353
|
if (s.metadata === void 0) {
|
|
797
1354
|
return Object.freeze({
|
|
798
1355
|
generation,
|
|
799
1356
|
cardinality: n,
|
|
800
|
-
fingerprint
|
|
1357
|
+
fingerprint,
|
|
1358
|
+
tailBytes
|
|
801
1359
|
});
|
|
802
1360
|
}
|
|
803
1361
|
const canonical = canonicalMetadataJson(s.metadata, fail);
|
|
804
1362
|
if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
|
|
805
|
-
if (
|
|
1363
|
+
if (origin === "stored") return s;
|
|
806
1364
|
const metadata = Object.freeze(JSON.parse(canonical));
|
|
807
1365
|
return Object.freeze({
|
|
808
1366
|
generation,
|
|
809
1367
|
cardinality: n,
|
|
810
1368
|
fingerprint,
|
|
1369
|
+
tailBytes,
|
|
811
1370
|
metadata
|
|
812
1371
|
});
|
|
813
1372
|
}
|
|
@@ -828,33 +1387,30 @@ function validateSummaryKeys(summary, wrappedDeks) {
|
|
|
828
1387
|
);
|
|
829
1388
|
}
|
|
830
1389
|
}
|
|
831
|
-
function
|
|
1390
|
+
function validateKnownGens(value, origin, ctx) {
|
|
832
1391
|
if (value === void 0) return void 0;
|
|
833
1392
|
const fail = (msg) => {
|
|
834
1393
|
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
835
|
-
throw
|
|
1394
|
+
throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
|
|
836
1395
|
};
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
}
|
|
846
|
-
if (g <= last) fail("entries must be strictly ascending");
|
|
847
|
-
last = g;
|
|
1396
|
+
const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
|
|
1397
|
+
const fault = knownGensShapeFault(value, max, shown3);
|
|
1398
|
+
if (fault !== void 0) fail(fault);
|
|
1399
|
+
const out = [];
|
|
1400
|
+
for (const entry of value) {
|
|
1401
|
+
out.push(
|
|
1402
|
+
origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
|
|
1403
|
+
);
|
|
848
1404
|
}
|
|
849
|
-
return
|
|
1405
|
+
return origin === "stored" ? out : Object.freeze(out);
|
|
850
1406
|
}
|
|
851
1407
|
var HOLDER = /^[0-9a-f]{16}$/;
|
|
852
1408
|
var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
|
|
853
|
-
function validateLeases(value,
|
|
1409
|
+
function validateLeases(value, origin, ctx) {
|
|
854
1410
|
if (value === void 0) return void 0;
|
|
855
1411
|
const fail = (msg) => {
|
|
856
1412
|
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
857
|
-
throw
|
|
1413
|
+
throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
|
|
858
1414
|
};
|
|
859
1415
|
if (!Array.isArray(value)) fail("must be an array");
|
|
860
1416
|
const list = value;
|
|
@@ -882,14 +1438,14 @@ function validateLeases(value, isStored, ctx) {
|
|
|
882
1438
|
}
|
|
883
1439
|
}
|
|
884
1440
|
out.push(
|
|
885
|
-
|
|
1441
|
+
origin === "stored" ? entry : Object.freeze({
|
|
886
1442
|
holder,
|
|
887
1443
|
generation,
|
|
888
1444
|
until
|
|
889
1445
|
})
|
|
890
1446
|
);
|
|
891
1447
|
}
|
|
892
|
-
if (
|
|
1448
|
+
if (origin === "stored") return out;
|
|
893
1449
|
return out.length === 0 ? void 0 : Object.freeze(out);
|
|
894
1450
|
}
|
|
895
1451
|
function validateLeasesPointer(leases, currentGen) {
|
|
@@ -897,17 +1453,20 @@ function validateLeasesPointer(leases, currentGen) {
|
|
|
897
1453
|
throw new ValidationError("leases: a row with no pointer has no generation to lease");
|
|
898
1454
|
}
|
|
899
1455
|
}
|
|
900
|
-
function
|
|
901
|
-
if (
|
|
1456
|
+
function validateKnownGensFit(knownGens, currentGen, summary) {
|
|
1457
|
+
if (knownGens === void 0) return;
|
|
902
1458
|
if (currentGen === null) {
|
|
1459
|
+
throw new ValidationError("knownGens: a row with no pointer has no generation to know");
|
|
1460
|
+
}
|
|
1461
|
+
const last = knownGens[knownGens.length - 1];
|
|
1462
|
+
if (last === void 0 || last.generation !== currentGen) {
|
|
903
1463
|
throw new ValidationError(
|
|
904
|
-
|
|
1464
|
+
`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})`
|
|
905
1465
|
);
|
|
906
1466
|
}
|
|
907
|
-
|
|
908
|
-
if (top !== void 0 && top >= currentGen) {
|
|
1467
|
+
if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
|
|
909
1468
|
throw new ValidationError(
|
|
910
|
-
`
|
|
1469
|
+
`knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
|
|
911
1470
|
);
|
|
912
1471
|
}
|
|
913
1472
|
}
|
|
@@ -919,45 +1478,52 @@ function refuseDriverManaged(write, what) {
|
|
|
919
1478
|
);
|
|
920
1479
|
}
|
|
921
1480
|
}
|
|
1481
|
+
function validateKeyId(keyId) {
|
|
1482
|
+
if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
|
|
1483
|
+
throw new ValidationError("keyId must be a non-empty string, or absent");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
922
1486
|
function validateNewRegistryRecord(rec) {
|
|
923
1487
|
refuseDriverManaged(rec, "a new record");
|
|
1488
|
+
validateKeyId(rec.keyId);
|
|
924
1489
|
validateGeneration(rec.currentGen);
|
|
925
1490
|
if (rec.status !== void 0) validateStatus(rec.status);
|
|
926
|
-
validateWrappedDeks(rec.wrappedDeks,
|
|
1491
|
+
validateWrappedDeks(rec.wrappedDeks, "caller");
|
|
927
1492
|
validateGovernance(rec.retention, "retention");
|
|
928
1493
|
validateGovernance(rec.residency, "residency");
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
1494
|
+
const knownGens = validateKnownGens(rec.knownGens, "caller");
|
|
1495
|
+
const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
|
|
1496
|
+
const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
|
|
1497
|
+
validateKnownGensFit(knownGens, rec.currentGen, summary);
|
|
1498
|
+
if (!("summary" in rec)) return withKnown;
|
|
934
1499
|
validateSummaryNames(summary, rec.currentGen);
|
|
935
1500
|
validateSummaryKeys(summary, rec.wrappedDeks);
|
|
936
|
-
return { ...
|
|
1501
|
+
return { ...withKnown, summary };
|
|
937
1502
|
}
|
|
938
1503
|
function validateRegistryPatch(patch) {
|
|
939
1504
|
refuseDriverManaged(patch, "a patch");
|
|
1505
|
+
if ("keyId" in patch) validateKeyId(patch.keyId);
|
|
940
1506
|
validatePatchGeneration(patch);
|
|
941
1507
|
if (patch.status !== void 0) validateStatus(patch.status);
|
|
942
|
-
if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks,
|
|
1508
|
+
if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
|
|
943
1509
|
if ("retention" in patch) validateGovernance(patch.retention, "retention");
|
|
944
1510
|
if ("residency" in patch) validateGovernance(patch.residency, "residency");
|
|
945
|
-
const
|
|
946
|
-
const withLeases = "leases" in
|
|
1511
|
+
const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
|
|
1512
|
+
const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
|
|
947
1513
|
if (!("summary" in patch)) return withLeases;
|
|
948
|
-
return { ...withLeases, summary: validateSummary(patch.summary,
|
|
1514
|
+
return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
|
|
949
1515
|
}
|
|
950
|
-
var REGISTRY_SCHEMA_VERSION =
|
|
1516
|
+
var REGISTRY_SCHEMA_VERSION = 5;
|
|
951
1517
|
function assertRegistrySchemaVersion(raw, ctx) {
|
|
952
1518
|
if (raw === void 0) {
|
|
953
1519
|
throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
|
|
954
1520
|
}
|
|
955
1521
|
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
|
|
956
|
-
throw new IntegrityError(`registry row has a malformed schemaVersion (${
|
|
1522
|
+
throw new IntegrityError(`registry row has a malformed schemaVersion (${shown3(raw)}): ${ctx}`);
|
|
957
1523
|
}
|
|
958
1524
|
if (raw < REGISTRY_SCHEMA_VERSION) {
|
|
959
1525
|
throw new UnsupportedError(
|
|
960
|
-
`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 (
|
|
1526
|
+
`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}`
|
|
961
1527
|
);
|
|
962
1528
|
}
|
|
963
1529
|
if (raw > REGISTRY_SCHEMA_VERSION) {
|
|
@@ -980,7 +1546,7 @@ function tokenParts(token, ctx, what = "token") {
|
|
|
980
1546
|
const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
|
|
981
1547
|
if (match === null) {
|
|
982
1548
|
throw new IntegrityError(
|
|
983
|
-
`registry row ${what} is not one a shipped registry writes (${
|
|
1549
|
+
`registry row ${what} is not one a shipped registry writes (${shown3(JSON.stringify(token))}): ${ctx}`
|
|
984
1550
|
);
|
|
985
1551
|
}
|
|
986
1552
|
const counter = Number(match[2]);
|
|
@@ -1025,7 +1591,7 @@ function newIncarnationToken(entropy, tombstone) {
|
|
|
1025
1591
|
const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
|
|
1026
1592
|
return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
|
|
1027
1593
|
}
|
|
1028
|
-
function
|
|
1594
|
+
function shown3(v) {
|
|
1029
1595
|
const text = String(v);
|
|
1030
1596
|
return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
|
|
1031
1597
|
}
|
|
@@ -1044,7 +1610,7 @@ function parseRegistryEnvelope(text, ctx, expected) {
|
|
|
1044
1610
|
const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
|
|
1045
1611
|
if (extra.length > 0) {
|
|
1046
1612
|
throw new IntegrityError(
|
|
1047
|
-
`registry row has fields its envelope does not declare (${
|
|
1613
|
+
`registry row has fields its envelope does not declare (${shown3(extra.join(", "))}): ${ctx}`
|
|
1048
1614
|
);
|
|
1049
1615
|
}
|
|
1050
1616
|
if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
|
|
@@ -1074,7 +1640,7 @@ function recordFromNew(ref, rec, now, token) {
|
|
|
1074
1640
|
retention: rec.retention,
|
|
1075
1641
|
residency: rec.residency,
|
|
1076
1642
|
summary: rec.summary,
|
|
1077
|
-
|
|
1643
|
+
knownGens: rec.knownGens,
|
|
1078
1644
|
// A new row has no holder: a lease is written only against a row that already exists.
|
|
1079
1645
|
leases: void 0,
|
|
1080
1646
|
createdAt: now,
|
|
@@ -1089,32 +1655,32 @@ function assertStoredRecordShape(r, ctx) {
|
|
|
1089
1655
|
}
|
|
1090
1656
|
if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
|
|
1091
1657
|
throw new IntegrityError(
|
|
1092
|
-
`registry record has an invalid currentGen (${
|
|
1658
|
+
`registry record has an invalid currentGen (${shown3(r.currentGen)}): ${ctx}`
|
|
1093
1659
|
);
|
|
1094
1660
|
}
|
|
1095
1661
|
if (!STATUSES.includes(r.status)) {
|
|
1096
|
-
throw new IntegrityError(`registry record has an unknown status (${
|
|
1662
|
+
throw new IntegrityError(`registry record has an unknown status (${shown3(r.status)}): ${ctx}`);
|
|
1097
1663
|
}
|
|
1098
1664
|
const declared = RECORD_FIELDS;
|
|
1099
1665
|
const extra = Object.keys(r).filter((k) => !declared.includes(k));
|
|
1100
1666
|
if (extra.length > 0) {
|
|
1101
1667
|
throw new IntegrityError(
|
|
1102
|
-
`registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${
|
|
1668
|
+
`registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown3(extra.join(", "))}): ${ctx}`
|
|
1103
1669
|
);
|
|
1104
1670
|
}
|
|
1105
1671
|
if (r.keyId !== void 0 && typeof r.keyId !== "string") {
|
|
1106
1672
|
throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
|
|
1107
1673
|
}
|
|
1108
|
-
validateWrappedDeks(r.wrappedDeks,
|
|
1674
|
+
validateWrappedDeks(r.wrappedDeks, "stored");
|
|
1109
1675
|
for (const field of ["retention", "residency"]) {
|
|
1110
1676
|
const meta = r[field];
|
|
1111
1677
|
if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
|
|
1112
1678
|
throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
|
|
1113
1679
|
}
|
|
1114
1680
|
}
|
|
1115
|
-
validateSummary(r.summary,
|
|
1116
|
-
|
|
1117
|
-
validateLeases(r.leases,
|
|
1681
|
+
validateSummary(r.summary, "stored", ctx);
|
|
1682
|
+
validateKnownGens(r.knownGens, "stored", ctx);
|
|
1683
|
+
validateLeases(r.leases, "stored", ctx);
|
|
1118
1684
|
}
|
|
1119
1685
|
function applyRegistryPatch(prev, patch, now, token) {
|
|
1120
1686
|
const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
|
|
@@ -1128,12 +1694,12 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
1128
1694
|
const kept = currentGen === prev.currentGen ? prev.summary : void 0;
|
|
1129
1695
|
summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
|
|
1130
1696
|
}
|
|
1131
|
-
let
|
|
1132
|
-
if ("
|
|
1133
|
-
|
|
1134
|
-
|
|
1697
|
+
let knownGens;
|
|
1698
|
+
if ("knownGens" in patch) {
|
|
1699
|
+
validateKnownGensFit(patch.knownGens, currentGen, summary);
|
|
1700
|
+
knownGens = patch.knownGens;
|
|
1135
1701
|
} else {
|
|
1136
|
-
|
|
1702
|
+
knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
|
|
1137
1703
|
}
|
|
1138
1704
|
let leases;
|
|
1139
1705
|
if ("leases" in patch) {
|
|
@@ -1156,7 +1722,7 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
1156
1722
|
retention: "retention" in patch ? patch.retention : prev.retention,
|
|
1157
1723
|
residency: "residency" in patch ? patch.residency : prev.residency,
|
|
1158
1724
|
summary,
|
|
1159
|
-
|
|
1725
|
+
knownGens,
|
|
1160
1726
|
leases,
|
|
1161
1727
|
createdAt: prev.createdAt,
|
|
1162
1728
|
updatedAt: now,
|
|
@@ -1210,6 +1776,16 @@ export {
|
|
|
1210
1776
|
isCapabilityError,
|
|
1211
1777
|
isBudgetExceededError,
|
|
1212
1778
|
isKeyUnavailableError,
|
|
1779
|
+
requireInteger,
|
|
1780
|
+
requirePositiveInteger,
|
|
1781
|
+
requireFiniteNumber,
|
|
1782
|
+
U32_MAX,
|
|
1783
|
+
CHUNK_COUNT,
|
|
1784
|
+
splitId,
|
|
1785
|
+
MAX_REMAINDER,
|
|
1786
|
+
joinId,
|
|
1787
|
+
mapWithConcurrency,
|
|
1788
|
+
boundedAll,
|
|
1213
1789
|
PREAMBLE_BYTES,
|
|
1214
1790
|
FOOTER_BYTES,
|
|
1215
1791
|
encodeNameForKey,
|
|
@@ -1225,19 +1801,19 @@ export {
|
|
|
1225
1801
|
validateUserRef,
|
|
1226
1802
|
validateChunkKeyOrder,
|
|
1227
1803
|
validateChunkRef,
|
|
1228
|
-
|
|
1804
|
+
validateChunkKey,
|
|
1229
1805
|
assertRegistryCanWrite,
|
|
1230
1806
|
STORAGE_BACKEND,
|
|
1231
1807
|
brandAsBackend,
|
|
1232
1808
|
isStorageBackend,
|
|
1233
1809
|
MAX_METADATA_BYTES,
|
|
1234
|
-
canonicalMetadataJson,
|
|
1235
1810
|
metadataBytes,
|
|
1236
1811
|
copiedMetadata,
|
|
1237
1812
|
metadataFromBytes,
|
|
1238
1813
|
sizePart,
|
|
1239
1814
|
fingerprintFor,
|
|
1240
1815
|
fingerprintParts,
|
|
1816
|
+
footerCrcOf,
|
|
1241
1817
|
compactFingerprint,
|
|
1242
1818
|
incarnationOf,
|
|
1243
1819
|
incarnationField,
|
|
@@ -1246,6 +1822,19 @@ export {
|
|
|
1246
1822
|
renewsPointer,
|
|
1247
1823
|
renewPointer,
|
|
1248
1824
|
pointerIdOf,
|
|
1825
|
+
aadFor,
|
|
1826
|
+
sameKeys,
|
|
1827
|
+
sizeOfFingerprint,
|
|
1828
|
+
usableSummary,
|
|
1829
|
+
summaryAgrees,
|
|
1830
|
+
metadataToCarry,
|
|
1831
|
+
summaryOf,
|
|
1832
|
+
ownSummary,
|
|
1833
|
+
DEFAULT_RETRY_POLICY,
|
|
1834
|
+
jitteredWait,
|
|
1835
|
+
retryRead,
|
|
1836
|
+
checkedRetryPolicy,
|
|
1837
|
+
withRetry,
|
|
1249
1838
|
MAX_LEASE_MS,
|
|
1250
1839
|
LEASE_SKEW_MS,
|
|
1251
1840
|
MAX_LEASES_PER_SEGMENT,
|
|
@@ -1257,9 +1846,19 @@ export {
|
|
|
1257
1846
|
leaseChurn,
|
|
1258
1847
|
takeLease,
|
|
1259
1848
|
releaseLease,
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1849
|
+
knownGensShapeFault,
|
|
1850
|
+
ascending,
|
|
1851
|
+
MAX_STORED_KNOWN_GENERATIONS,
|
|
1852
|
+
usableKnownGens,
|
|
1853
|
+
windowOf,
|
|
1854
|
+
KNOWN_SPAN,
|
|
1855
|
+
keptWindow,
|
|
1856
|
+
nextKnown,
|
|
1857
|
+
rollbackKnown,
|
|
1858
|
+
pruneKnown,
|
|
1859
|
+
describingKnown,
|
|
1860
|
+
isFullLedger,
|
|
1861
|
+
fallbackBound,
|
|
1263
1862
|
validateNewRegistryRecord,
|
|
1264
1863
|
validateRegistryPatch,
|
|
1265
1864
|
serializeRegistryEnvelope,
|
|
@@ -1274,6 +1873,10 @@ export {
|
|
|
1274
1873
|
entropyIsAvailable,
|
|
1275
1874
|
DEFAULT_NAMESPACE,
|
|
1276
1875
|
namespaceKeyPart,
|
|
1277
|
-
namespacePathPart
|
|
1876
|
+
namespacePathPart,
|
|
1877
|
+
refuseUnknownOptions,
|
|
1878
|
+
checkNow,
|
|
1879
|
+
requirePositiveInt,
|
|
1880
|
+
requireBool
|
|
1278
1881
|
};
|
|
1279
|
-
//# sourceMappingURL=chunk-
|
|
1882
|
+
//# sourceMappingURL=chunk-IGLIZBR7.js.map
|