@cloudbitmaps/core 0.16.0 → 0.18.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/dist/{chunk-LFUNPMXZ.js → chunk-CCP4U32U.js} +380 -23
- package/dist/chunk-CCP4U32U.js.map +7 -0
- package/dist/core/chunk-checks.d.ts +34 -0
- package/dist/core/codec.d.ts +1 -1
- package/dist/core/combine-expr.d.ts +75 -0
- package/dist/core/combine-feed.d.ts +142 -0
- package/dist/core/combine-held.d.ts +43 -0
- package/dist/core/combine-ledger.d.ts +43 -0
- package/dist/core/combine-many.d.ts +319 -0
- package/dist/core/cost.d.ts +3 -3
- package/dist/core/crbm-storage-source.d.ts +81 -66
- package/dist/core/engine.d.ts +24 -30
- package/dist/core/erasure.d.ts +2 -1
- package/dist/core/errors.d.ts +39 -0
- package/dist/core/generation-gc.d.ts +69 -45
- package/dist/core/kept-generations.d.ts +40 -0
- package/dist/core/leases.d.ts +119 -0
- package/dist/core/load-input.d.ts +14 -1
- package/dist/core/load.d.ts +13 -11
- package/dist/core/metrics.d.ts +24 -0
- package/dist/core/ports.d.ts +99 -2
- package/dist/core/registry-reap.d.ts +22 -0
- package/dist/core/retention.d.ts +2 -1
- package/dist/core/rollback.d.ts +2 -1
- package/dist/driver-kit.d.ts +1 -0
- package/dist/driver-kit.js +100 -1
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +19 -1
- package/dist/drivers/_shared/registry.d.ts +7 -9
- package/dist/export/index.d.ts +20 -8
- package/dist/index.d.ts +13 -3
- package/dist/index.js +2913 -243
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-LFUNPMXZ.js.map +0 -7
|
@@ -17,6 +17,31 @@ var IntegrityError = class extends CloudRoaringError {
|
|
|
17
17
|
};
|
|
18
18
|
var NotFoundError = class extends CloudRoaringError {
|
|
19
19
|
};
|
|
20
|
+
var LeaseExpiredError = class extends CloudRoaringError {
|
|
21
|
+
/** Epoch-ms the lease was taken until. */
|
|
22
|
+
until;
|
|
23
|
+
/** `'expired'` when the clock reached `until`, `'released'` after `release()`. */
|
|
24
|
+
reason;
|
|
25
|
+
constructor(message, until, reason) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.until = until;
|
|
28
|
+
this.reason = reason;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var LeaseLimitError = class extends CloudRoaringError {
|
|
32
|
+
};
|
|
33
|
+
var StaleOperandError = class extends CloudRoaringError {
|
|
34
|
+
code = "stale-operand";
|
|
35
|
+
/** The operand's name in the call that found it stale. */
|
|
36
|
+
operand;
|
|
37
|
+
/** Why it is stale. */
|
|
38
|
+
reason;
|
|
39
|
+
constructor(message, operand, reason) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.operand = operand;
|
|
42
|
+
this.reason = reason;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
20
45
|
var UnsupportedError = class extends CloudRoaringError {
|
|
21
46
|
};
|
|
22
47
|
var CapabilityError = class extends CloudRoaringError {
|
|
@@ -45,6 +70,15 @@ function isWriteConflictError(err) {
|
|
|
45
70
|
function isTransientError(err) {
|
|
46
71
|
return hasBrand(err, TRANSIENT_BRAND);
|
|
47
72
|
}
|
|
73
|
+
function isLeaseExpiredError(err) {
|
|
74
|
+
return isCloudRoaringError(err) && err.name === "LeaseExpiredError";
|
|
75
|
+
}
|
|
76
|
+
function isStaleOperandError(err) {
|
|
77
|
+
return isCloudRoaringError(err) && err.name === "StaleOperandError";
|
|
78
|
+
}
|
|
79
|
+
function isLeaseLimitError(err) {
|
|
80
|
+
return isCloudRoaringError(err) && err.name === "LeaseLimitError";
|
|
81
|
+
}
|
|
48
82
|
function isNotFoundError(err) {
|
|
49
83
|
return isCloudRoaringError(err) && err.name === "NotFoundError";
|
|
50
84
|
}
|
|
@@ -112,6 +146,183 @@ function decodeNameFromPath(encoded) {
|
|
|
112
146
|
return decodePercent(encoded);
|
|
113
147
|
}
|
|
114
148
|
|
|
149
|
+
// src/core/token.ts
|
|
150
|
+
var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
|
|
151
|
+
function incarnationOf(token) {
|
|
152
|
+
return INCARNATION_TOKEN.exec(token)?.[1];
|
|
153
|
+
}
|
|
154
|
+
function sameIncarnation(a, b) {
|
|
155
|
+
const ia = incarnationOf(a.token);
|
|
156
|
+
const ib = incarnationOf(b.token);
|
|
157
|
+
if (ia !== void 0 || ib !== void 0) return ia === ib;
|
|
158
|
+
return a.createdAt === b.createdAt;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/core/leases.ts
|
|
162
|
+
var MAX_LEASE_MS = 14 * 864e5;
|
|
163
|
+
var LEASE_SKEW_MS = 6e4;
|
|
164
|
+
var MAX_LEASES_PER_SEGMENT = 64;
|
|
165
|
+
var MAX_STORED_LEASES = 256;
|
|
166
|
+
var LEASE_CAS_ATTEMPTS = MAX_LEASES_PER_SEGMENT + 8;
|
|
167
|
+
var UNANSWERED_RESENDS = 3;
|
|
168
|
+
var UNANSWERED_BASE_MS = 500;
|
|
169
|
+
var CONFLICT_BASE_MS = 25;
|
|
170
|
+
var CONFLICT_CAP_MS = 400;
|
|
171
|
+
function isLive(entry, now) {
|
|
172
|
+
return now < entry.until + LEASE_SKEW_MS && entry.until <= now + MAX_LEASE_MS + LEASE_SKEW_MS;
|
|
173
|
+
}
|
|
174
|
+
function heldGenerations(record, now) {
|
|
175
|
+
const out = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const e of record?.leases ?? []) if (isLive(e, now)) out.add(e.generation);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
function liveLeases(record, now) {
|
|
180
|
+
return (record?.leases ?? []).filter((e) => isLive(e, now));
|
|
181
|
+
}
|
|
182
|
+
var LEASE_ONLY_RETRIES = 2 * MAX_LEASES_PER_SEGMENT + 8;
|
|
183
|
+
var BOOKKEEPING = /* @__PURE__ */ new Set(["token", "updatedAt", "leases"]);
|
|
184
|
+
function stable(value) {
|
|
185
|
+
if (value === void 0) return "u";
|
|
186
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
187
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
188
|
+
const o = value;
|
|
189
|
+
const keys = Object.keys(o).filter((k) => o[k] !== void 0).sort();
|
|
190
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stable(o[k])}`).join(",")}}`;
|
|
191
|
+
}
|
|
192
|
+
function onlyLeasesDiffer(held, now, alsoIgnore = []) {
|
|
193
|
+
if (!sameIncarnation(held, now)) return false;
|
|
194
|
+
const skip = /* @__PURE__ */ new Set([...BOOKKEEPING, ...alsoIgnore]);
|
|
195
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(held), ...Object.keys(now)]);
|
|
196
|
+
for (const k of keys) {
|
|
197
|
+
if (skip.has(k)) continue;
|
|
198
|
+
if (stable(held[k]) !== stable(now[k])) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
function leaseChurn(deps) {
|
|
205
|
+
let used = 0;
|
|
206
|
+
return {
|
|
207
|
+
async settle(held, now, reread, alsoIgnore) {
|
|
208
|
+
if (held === null || held === void 0 || now === null) return void 0;
|
|
209
|
+
if (used >= LEASE_ONLY_RETRIES || !onlyLeasesDiffer(held, now, alsoIgnore)) return void 0;
|
|
210
|
+
const bound = Math.min(CONFLICT_BASE_MS * 2 ** Math.min(used, 4), CONFLICT_CAP_MS);
|
|
211
|
+
used += 1;
|
|
212
|
+
const sleep = deps.clock?.sleep;
|
|
213
|
+
if (sleep !== void 0) {
|
|
214
|
+
await sleep.call(deps.clock, Math.floor((deps.rng?.next() ?? 1) * bound));
|
|
215
|
+
}
|
|
216
|
+
return { row: await reread() };
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function listOf(entries) {
|
|
221
|
+
if (entries.length === 0) return void 0;
|
|
222
|
+
return [...entries].sort(
|
|
223
|
+
(a, b) => a.generation !== b.generation ? a.generation - b.generation : a.holder < b.holder ? -1 : a.holder > b.holder ? 1 : 0
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
var wait = (deps, bound) => deps.clock.sleep(Math.floor((deps.rng?.next() ?? 1) * bound));
|
|
227
|
+
var heldBy = (row, take) => (row?.leases ?? []).some(
|
|
228
|
+
(e) => e.holder === take.holder && e.generation === take.generation && e.until === take.until
|
|
229
|
+
);
|
|
230
|
+
async function takeLease(ref, deps, take) {
|
|
231
|
+
let row = take.row;
|
|
232
|
+
let held = true;
|
|
233
|
+
let resends = 0;
|
|
234
|
+
let unanswered;
|
|
235
|
+
for (let attempt = 0; attempt < LEASE_CAS_ATTEMPTS; attempt++) {
|
|
236
|
+
if (row === null) row = await deps.registry.get(ref);
|
|
237
|
+
if (row === null || row.status === "destroyed" || row.currentGen === null) {
|
|
238
|
+
throw new NotFoundError(`segment "${ref.segment}" has no generation to lease`);
|
|
239
|
+
}
|
|
240
|
+
if (take.generation > row.currentGen) {
|
|
241
|
+
throw new NotFoundError(
|
|
242
|
+
`segment "${ref.segment}" generation ${take.generation} cannot be leased: it is not a published generation`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (take.current && row.currentGen !== take.generation) return "moved";
|
|
246
|
+
if (heldBy(row, take)) return { token: row.token, row };
|
|
247
|
+
const live = liveLeases(row, deps.clock.now()).filter((e) => e.holder !== take.holder);
|
|
248
|
+
if (live.length >= MAX_LEASES_PER_SEGMENT) {
|
|
249
|
+
throw new LeaseLimitError(
|
|
250
|
+
`segment "${ref.segment}" already has ${live.length} live leases, the most one segment records`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const leases = listOf([
|
|
254
|
+
...live,
|
|
255
|
+
{ holder: take.holder, generation: take.generation, until: take.until }
|
|
256
|
+
]);
|
|
257
|
+
const against = row;
|
|
258
|
+
try {
|
|
259
|
+
const { token } = await deps.registry.compareAndSwap(
|
|
260
|
+
ref,
|
|
261
|
+
against.token,
|
|
262
|
+
{ leases },
|
|
263
|
+
held ? { held: against } : void 0
|
|
264
|
+
);
|
|
265
|
+
return { token, row: against };
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (isWriteConflictError(err)) {
|
|
268
|
+
row = null;
|
|
269
|
+
held = false;
|
|
270
|
+
await wait(deps, Math.min(CONFLICT_BASE_MS * 2 ** attempt, CONFLICT_CAP_MS));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (!isTransientError(err)) throw err;
|
|
274
|
+
const now = await deps.registry.get(ref);
|
|
275
|
+
if (heldBy(now, take)) return { token: now.token, row: against };
|
|
276
|
+
if (now !== null && now.token === against.token) {
|
|
277
|
+
if (resends >= UNANSWERED_RESENDS) throw err;
|
|
278
|
+
await wait(deps, UNANSWERED_BASE_MS * 2 ** resends);
|
|
279
|
+
resends += 1;
|
|
280
|
+
unanswered = err;
|
|
281
|
+
}
|
|
282
|
+
row = now;
|
|
283
|
+
held = false;
|
|
284
|
+
if (row === null)
|
|
285
|
+
throw new NotFoundError(`segment "${ref.segment}" has no generation to lease`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (unanswered !== void 0) throw unanswered;
|
|
289
|
+
throw new WriteConflictError(`lease: contention taking a lease on segment "${ref.segment}"`);
|
|
290
|
+
}
|
|
291
|
+
async function releaseLease(ref, deps, holder) {
|
|
292
|
+
let resends = 0;
|
|
293
|
+
let unanswered;
|
|
294
|
+
let row;
|
|
295
|
+
for (let attempt = 0; attempt < LEASE_CAS_ATTEMPTS; attempt++) {
|
|
296
|
+
if (row === void 0) row = await deps.registry.get(ref);
|
|
297
|
+
if (row === null || row.status === "destroyed") return;
|
|
298
|
+
if (!(row.leases ?? []).some((e) => e.holder === holder)) return;
|
|
299
|
+
const against = row;
|
|
300
|
+
const keep = liveLeases(against, deps.clock.now()).filter((e) => e.holder !== holder);
|
|
301
|
+
try {
|
|
302
|
+
await deps.registry.compareAndSwap(ref, against.token, { leases: listOf(keep) });
|
|
303
|
+
return;
|
|
304
|
+
} catch (err) {
|
|
305
|
+
if (isWriteConflictError(err)) {
|
|
306
|
+
row = void 0;
|
|
307
|
+
await wait(deps, Math.min(CONFLICT_BASE_MS * 2 ** attempt, CONFLICT_CAP_MS));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (!isTransientError(err)) throw err;
|
|
311
|
+
const now = await deps.registry.get(ref);
|
|
312
|
+
if (now === null || !(now.leases ?? []).some((e) => e.holder === holder)) return;
|
|
313
|
+
if (now.token === against.token) {
|
|
314
|
+
if (resends >= UNANSWERED_RESENDS) throw err;
|
|
315
|
+
await wait(deps, UNANSWERED_BASE_MS * 2 ** resends);
|
|
316
|
+
resends += 1;
|
|
317
|
+
unanswered = err;
|
|
318
|
+
}
|
|
319
|
+
row = now;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (unanswered !== void 0) throw unanswered;
|
|
323
|
+
throw new WriteConflictError(`lease: contention releasing a lease on segment "${ref.segment}"`);
|
|
324
|
+
}
|
|
325
|
+
|
|
115
326
|
// src/core/validate.ts
|
|
116
327
|
var CHUNK_KEY_MAX = 65535;
|
|
117
328
|
var MAX_NAME_LENGTH = 256;
|
|
@@ -263,6 +474,27 @@ function namespacePathPart(namespace) {
|
|
|
263
474
|
return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
|
|
264
475
|
}
|
|
265
476
|
|
|
477
|
+
// src/core/kept-generations.ts
|
|
478
|
+
var MAX_KEPT_GENERATIONS = 64;
|
|
479
|
+
var MAX_STORED_KEPT_GENERATIONS = 256;
|
|
480
|
+
function usableKeptGens(record) {
|
|
481
|
+
const list = record?.keptGens;
|
|
482
|
+
if (record === null || list === void 0 || record.currentGen === null) return void 0;
|
|
483
|
+
for (const g of list) if (g >= record.currentGen) return void 0;
|
|
484
|
+
return list;
|
|
485
|
+
}
|
|
486
|
+
function nextKept(prev, generation, keep) {
|
|
487
|
+
const none = { list: void 0, evict: [] };
|
|
488
|
+
if (keep === void 0 || keep > MAX_KEPT_GENERATIONS) return none;
|
|
489
|
+
if (generation === 0) return { list: [], evict: [] };
|
|
490
|
+
const previous = prev?.currentGen ?? null;
|
|
491
|
+
const known = usableKeptGens(prev);
|
|
492
|
+
if (previous === null || known === void 0) return none;
|
|
493
|
+
const all = [...known, previous];
|
|
494
|
+
const cut = Math.max(0, all.length - keep);
|
|
495
|
+
return { list: all.slice(cut), evict: all.slice(0, cut) };
|
|
496
|
+
}
|
|
497
|
+
|
|
266
498
|
// src/core/metadata.ts
|
|
267
499
|
var MAX_METADATA_BYTES = 1024;
|
|
268
500
|
var MAX_METADATA_KEY_BYTES = 128;
|
|
@@ -341,21 +573,9 @@ function metadataFromBytes(bytes, fail) {
|
|
|
341
573
|
return Object.freeze(parsed);
|
|
342
574
|
}
|
|
343
575
|
|
|
344
|
-
// src/core/token.ts
|
|
345
|
-
var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
|
|
346
|
-
function incarnationOf(token) {
|
|
347
|
-
return INCARNATION_TOKEN.exec(token)?.[1];
|
|
348
|
-
}
|
|
349
|
-
function sameIncarnation(a, b) {
|
|
350
|
-
const ia = incarnationOf(a.token);
|
|
351
|
-
const ib = incarnationOf(b.token);
|
|
352
|
-
if (ia !== void 0 || ib !== void 0) return ia === ib;
|
|
353
|
-
return a.createdAt === b.createdAt;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
576
|
// src/drivers/_shared/registry.ts
|
|
357
577
|
var STATUSES = ["active", "destroyed"];
|
|
358
|
-
var
|
|
578
|
+
var BASE_FIELDS = [
|
|
359
579
|
"namespace",
|
|
360
580
|
"segment",
|
|
361
581
|
"currentGen",
|
|
@@ -369,7 +589,13 @@ var RECORD_FIELDS = [
|
|
|
369
589
|
"updatedAt",
|
|
370
590
|
"token"
|
|
371
591
|
];
|
|
372
|
-
var
|
|
592
|
+
var SCHEMA_3_FIELDS = ["keptGens", "leases"];
|
|
593
|
+
var RECORD_FIELDS = [...BASE_FIELDS, ...SCHEMA_3_FIELDS];
|
|
594
|
+
function fieldsOf(schemaVersion) {
|
|
595
|
+
if (schemaVersion >= 3) return RECORD_FIELDS;
|
|
596
|
+
const base = BASE_FIELDS;
|
|
597
|
+
return schemaVersion === 2 ? base : base.filter((f) => f !== "summary");
|
|
598
|
+
}
|
|
373
599
|
var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
|
|
374
600
|
var MAX_GOVERNANCE_BYTES = 64 * 1024;
|
|
375
601
|
var MAX_WRAPPED_DEKS = 8;
|
|
@@ -514,17 +740,103 @@ function validateSummaryKeys(summary, wrappedDeks) {
|
|
|
514
740
|
);
|
|
515
741
|
}
|
|
516
742
|
}
|
|
743
|
+
function validateKeptGens(value, isStored, ctx) {
|
|
744
|
+
if (value === void 0) return void 0;
|
|
745
|
+
const fail = (msg) => {
|
|
746
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
747
|
+
throw isStored ? new IntegrityError(`registry record keptGens: ${msg}${where}`) : new ValidationError(`keptGens: ${msg}`);
|
|
748
|
+
};
|
|
749
|
+
if (!Array.isArray(value)) fail("must be an array");
|
|
750
|
+
const list = value;
|
|
751
|
+
const max = isStored ? MAX_STORED_KEPT_GENERATIONS : MAX_KEPT_GENERATIONS;
|
|
752
|
+
if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
|
|
753
|
+
let last = -1;
|
|
754
|
+
for (const g of list) {
|
|
755
|
+
if (!Number.isSafeInteger(g) || g < 0) {
|
|
756
|
+
fail(`entries must be non-negative safe integers (got ${String(g)})`);
|
|
757
|
+
}
|
|
758
|
+
if (g <= last) fail("entries must be strictly ascending");
|
|
759
|
+
last = g;
|
|
760
|
+
}
|
|
761
|
+
return isStored ? list : Object.freeze([...list]);
|
|
762
|
+
}
|
|
763
|
+
var HOLDER = /^[0-9a-f]{16}$/;
|
|
764
|
+
var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
|
|
765
|
+
function validateLeases(value, isStored, ctx) {
|
|
766
|
+
if (value === void 0) return void 0;
|
|
767
|
+
const fail = (msg) => {
|
|
768
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
769
|
+
throw isStored ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
|
|
770
|
+
};
|
|
771
|
+
if (!Array.isArray(value)) fail("must be an array");
|
|
772
|
+
const list = value;
|
|
773
|
+
const max = MAX_STORED_LEASES;
|
|
774
|
+
if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
|
|
775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
776
|
+
const out = [];
|
|
777
|
+
for (const e of list) {
|
|
778
|
+
if (typeof e !== "object" || e === null || Array.isArray(e)) fail("an entry must be an object");
|
|
779
|
+
const entry = e;
|
|
780
|
+
const extra = Object.keys(entry).filter((k) => !LEASE_ENTRY_KEYS.includes(k));
|
|
781
|
+
if (extra.length > 0) fail(`an entry carries an undeclared field (${extra.join(", ")})`);
|
|
782
|
+
const { holder, generation, until } = entry;
|
|
783
|
+
if (typeof holder !== "string" || !HOLDER.test(holder)) {
|
|
784
|
+
fail("a holder must be 16 lowercase hex digits");
|
|
785
|
+
}
|
|
786
|
+
if (seen.has(holder)) fail("holders must be unique");
|
|
787
|
+
seen.add(holder);
|
|
788
|
+
for (const [name, n] of [
|
|
789
|
+
["generation", generation],
|
|
790
|
+
["until", until]
|
|
791
|
+
]) {
|
|
792
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
793
|
+
fail(`${name} must be a non-negative safe integer (got ${String(n)})`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
out.push(
|
|
797
|
+
isStored ? entry : Object.freeze({
|
|
798
|
+
holder,
|
|
799
|
+
generation,
|
|
800
|
+
until
|
|
801
|
+
})
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
if (isStored) return out;
|
|
805
|
+
return out.length === 0 ? void 0 : Object.freeze(out);
|
|
806
|
+
}
|
|
807
|
+
function validateLeasesPointer(leases, currentGen) {
|
|
808
|
+
if (leases !== void 0 && leases.length > 0 && currentGen === null) {
|
|
809
|
+
throw new ValidationError("leases: a row with no pointer has no generation to lease");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function validateKeptGensBelow(keptGens, currentGen) {
|
|
813
|
+
if (keptGens === void 0) return;
|
|
814
|
+
if (currentGen === null) {
|
|
815
|
+
throw new ValidationError(
|
|
816
|
+
"keptGens: a row with no pointer has no generations below it to keep"
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
const top = keptGens[keptGens.length - 1];
|
|
820
|
+
if (top !== void 0 && top >= currentGen) {
|
|
821
|
+
throw new ValidationError(
|
|
822
|
+
`keptGens names generation ${top}, which is not below the row's pointer ${currentGen}`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
517
826
|
function validateNewRegistryRecord(rec) {
|
|
518
827
|
validateGeneration(rec.currentGen);
|
|
519
828
|
if (rec.status !== void 0) validateStatus(rec.status);
|
|
520
829
|
validateWrappedDeks(rec.wrappedDeks, false);
|
|
521
830
|
validateGovernance(rec.retention, "retention");
|
|
522
831
|
validateGovernance(rec.residency, "residency");
|
|
523
|
-
|
|
832
|
+
const keptGens = validateKeptGens(rec.keptGens, false);
|
|
833
|
+
validateKeptGensBelow(keptGens, rec.currentGen);
|
|
834
|
+
const withKept = keptGens === void 0 ? rec : { ...rec, keptGens };
|
|
835
|
+
if (!("summary" in rec)) return withKept;
|
|
524
836
|
const summary = validateSummary(rec.summary, false);
|
|
525
837
|
validateSummaryNames(summary, rec.currentGen);
|
|
526
838
|
validateSummaryKeys(summary, rec.wrappedDeks);
|
|
527
|
-
return { ...
|
|
839
|
+
return { ...withKept, summary };
|
|
528
840
|
}
|
|
529
841
|
function validateRegistryPatch(patch) {
|
|
530
842
|
validatePatchGeneration(patch);
|
|
@@ -532,10 +844,12 @@ function validateRegistryPatch(patch) {
|
|
|
532
844
|
if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
|
|
533
845
|
if ("retention" in patch) validateGovernance(patch.retention, "retention");
|
|
534
846
|
if ("residency" in patch) validateGovernance(patch.residency, "residency");
|
|
535
|
-
|
|
536
|
-
|
|
847
|
+
const withKept = patch.keptGens === void 0 ? patch : { ...patch, keptGens: validateKeptGens(patch.keptGens, false) };
|
|
848
|
+
const withLeases = "leases" in withKept ? { ...withKept, leases: validateLeases(withKept.leases, false) } : withKept;
|
|
849
|
+
if (!("summary" in patch)) return withLeases;
|
|
850
|
+
return { ...withLeases, summary: validateSummary(patch.summary, false) };
|
|
537
851
|
}
|
|
538
|
-
var REGISTRY_SCHEMA_VERSION =
|
|
852
|
+
var REGISTRY_SCHEMA_VERSION = 3;
|
|
539
853
|
var OLDEST_REGISTRY_SCHEMA_VERSION = 1;
|
|
540
854
|
function assertRegistrySchemaVersion(raw, ctx) {
|
|
541
855
|
if (raw === void 0) {
|
|
@@ -644,6 +958,9 @@ function recordFromNew(ref, rec, now, token) {
|
|
|
644
958
|
retention: rec.retention,
|
|
645
959
|
residency: rec.residency,
|
|
646
960
|
summary: rec.summary,
|
|
961
|
+
keptGens: rec.keptGens,
|
|
962
|
+
// A new row has no holder: a lease is written only against a row that already exists.
|
|
963
|
+
leases: void 0,
|
|
647
964
|
createdAt: now,
|
|
648
965
|
updatedAt: now,
|
|
649
966
|
token
|
|
@@ -659,7 +976,7 @@ function assertStoredRecordShape(r, ctx, schemaVersion = REGISTRY_SCHEMA_VERSION
|
|
|
659
976
|
if (!STATUSES.includes(r.status)) {
|
|
660
977
|
throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);
|
|
661
978
|
}
|
|
662
|
-
const declared = schemaVersion
|
|
979
|
+
const declared = fieldsOf(schemaVersion);
|
|
663
980
|
const extra = Object.keys(r).filter((k) => !declared.includes(k));
|
|
664
981
|
if (extra.length > 0) {
|
|
665
982
|
throw new IntegrityError(
|
|
@@ -677,6 +994,8 @@ function assertStoredRecordShape(r, ctx, schemaVersion = REGISTRY_SCHEMA_VERSION
|
|
|
677
994
|
}
|
|
678
995
|
}
|
|
679
996
|
validateSummary(r.summary, true, ctx);
|
|
997
|
+
validateKeptGens(r.keptGens, true, ctx);
|
|
998
|
+
validateLeases(r.leases, true, ctx);
|
|
680
999
|
}
|
|
681
1000
|
function applyRegistryPatch(prev, patch, now, token) {
|
|
682
1001
|
const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
|
|
@@ -690,6 +1009,24 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
690
1009
|
const kept = currentGen === prev.currentGen ? prev.summary : void 0;
|
|
691
1010
|
summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
|
|
692
1011
|
}
|
|
1012
|
+
let keptGens;
|
|
1013
|
+
if ("keptGens" in patch) {
|
|
1014
|
+
validateKeptGensBelow(patch.keptGens, currentGen);
|
|
1015
|
+
keptGens = patch.keptGens;
|
|
1016
|
+
} else {
|
|
1017
|
+
keptGens = currentGen === prev.currentGen ? prev.keptGens : void 0;
|
|
1018
|
+
}
|
|
1019
|
+
let leases;
|
|
1020
|
+
if ("leases" in patch) {
|
|
1021
|
+
leases = patch.leases;
|
|
1022
|
+
validateLeasesPointer(leases, currentGen);
|
|
1023
|
+
const most = Math.max(MAX_LEASES_PER_SEGMENT, prev.leases?.length ?? 0);
|
|
1024
|
+
if (leases !== void 0 && leases.length > most) {
|
|
1025
|
+
throw new ValidationError(`leases: has ${leases.length} entries, cap ${most}`);
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
leases = prev.leases;
|
|
1029
|
+
}
|
|
693
1030
|
return {
|
|
694
1031
|
namespace: prev.namespace,
|
|
695
1032
|
segment: prev.segment,
|
|
@@ -700,6 +1037,8 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
700
1037
|
retention: "retention" in patch ? patch.retention : prev.retention,
|
|
701
1038
|
residency: "residency" in patch ? patch.residency : prev.residency,
|
|
702
1039
|
summary,
|
|
1040
|
+
keptGens,
|
|
1041
|
+
leases,
|
|
703
1042
|
createdAt: prev.createdAt,
|
|
704
1043
|
updatedAt: now,
|
|
705
1044
|
token
|
|
@@ -730,6 +1069,9 @@ export {
|
|
|
730
1069
|
WriteConflictError,
|
|
731
1070
|
IntegrityError,
|
|
732
1071
|
NotFoundError,
|
|
1072
|
+
LeaseExpiredError,
|
|
1073
|
+
LeaseLimitError,
|
|
1074
|
+
StaleOperandError,
|
|
733
1075
|
UnsupportedError,
|
|
734
1076
|
CapabilityError,
|
|
735
1077
|
BudgetExceededError,
|
|
@@ -738,6 +1080,9 @@ export {
|
|
|
738
1080
|
isCloudRoaringError,
|
|
739
1081
|
isWriteConflictError,
|
|
740
1082
|
isTransientError,
|
|
1083
|
+
isLeaseExpiredError,
|
|
1084
|
+
isStaleOperandError,
|
|
1085
|
+
isLeaseLimitError,
|
|
741
1086
|
isNotFoundError,
|
|
742
1087
|
isIntegrityError,
|
|
743
1088
|
isValidationError,
|
|
@@ -745,6 +1090,20 @@ export {
|
|
|
745
1090
|
decodeNameFromKey,
|
|
746
1091
|
encodeNameForPath,
|
|
747
1092
|
decodeNameFromPath,
|
|
1093
|
+
MAX_KEPT_GENERATIONS,
|
|
1094
|
+
usableKeptGens,
|
|
1095
|
+
nextKept,
|
|
1096
|
+
incarnationOf,
|
|
1097
|
+
sameIncarnation,
|
|
1098
|
+
MAX_LEASE_MS,
|
|
1099
|
+
LEASE_SKEW_MS,
|
|
1100
|
+
MAX_LEASES_PER_SEGMENT,
|
|
1101
|
+
isLive,
|
|
1102
|
+
heldGenerations,
|
|
1103
|
+
onlyLeasesDiffer,
|
|
1104
|
+
leaseChurn,
|
|
1105
|
+
takeLease,
|
|
1106
|
+
releaseLease,
|
|
748
1107
|
MAX_NAME_LENGTH,
|
|
749
1108
|
encodedNameLength,
|
|
750
1109
|
validateSegmentRef,
|
|
@@ -764,8 +1123,6 @@ export {
|
|
|
764
1123
|
metadataBytes,
|
|
765
1124
|
copiedMetadata,
|
|
766
1125
|
metadataFromBytes,
|
|
767
|
-
incarnationOf,
|
|
768
|
-
sameIncarnation,
|
|
769
1126
|
validateNewRegistryRecord,
|
|
770
1127
|
validateRegistryPatch,
|
|
771
1128
|
serializeRegistryEnvelope,
|
|
@@ -782,4 +1139,4 @@ export {
|
|
|
782
1139
|
namespaceKeyPart,
|
|
783
1140
|
namespacePathPart
|
|
784
1141
|
};
|
|
785
|
-
//# sourceMappingURL=chunk-
|
|
1142
|
+
//# sourceMappingURL=chunk-CCP4U32U.js.map
|