@cloudbitmaps/core 0.15.0 → 0.17.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-B7IGS3W6.js → chunk-CV7GJO3B.js} +363 -23
- package/dist/chunk-CV7GJO3B.js.map +7 -0
- package/dist/core/codec.d.ts +10 -0
- package/dist/core/cost.d.ts +4 -4
- package/dist/core/crbm-storage-source.d.ts +76 -68
- package/dist/core/engine.d.ts +59 -6
- package/dist/core/erasure.d.ts +2 -1
- package/dist/core/errors.d.ts +23 -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 +21 -0
- package/dist/core/load.d.ts +33 -12
- package/dist/core/metrics.d.ts +24 -0
- package/dist/core/ports.d.ts +128 -6
- 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 +142 -10
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +34 -3
- package/dist/drivers/_shared/registry.d.ts +7 -9
- package/dist/drivers/localfs/registry.d.ts +4 -0
- package/dist/drivers/memory.d.ts +4 -0
- package/dist/export/index.d.ts +20 -8
- package/dist/index.d.ts +10 -4
- package/dist/index.js +745 -123
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-B7IGS3W6.js.map +0 -7
|
@@ -17,6 +17,19 @@ 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
|
+
};
|
|
20
33
|
var UnsupportedError = class extends CloudRoaringError {
|
|
21
34
|
};
|
|
22
35
|
var CapabilityError = class extends CloudRoaringError {
|
|
@@ -45,6 +58,12 @@ function isWriteConflictError(err) {
|
|
|
45
58
|
function isTransientError(err) {
|
|
46
59
|
return hasBrand(err, TRANSIENT_BRAND);
|
|
47
60
|
}
|
|
61
|
+
function isLeaseExpiredError(err) {
|
|
62
|
+
return isCloudRoaringError(err) && err.name === "LeaseExpiredError";
|
|
63
|
+
}
|
|
64
|
+
function isLeaseLimitError(err) {
|
|
65
|
+
return isCloudRoaringError(err) && err.name === "LeaseLimitError";
|
|
66
|
+
}
|
|
48
67
|
function isNotFoundError(err) {
|
|
49
68
|
return isCloudRoaringError(err) && err.name === "NotFoundError";
|
|
50
69
|
}
|
|
@@ -112,6 +131,183 @@ function decodeNameFromPath(encoded) {
|
|
|
112
131
|
return decodePercent(encoded);
|
|
113
132
|
}
|
|
114
133
|
|
|
134
|
+
// src/core/token.ts
|
|
135
|
+
var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
|
|
136
|
+
function incarnationOf(token) {
|
|
137
|
+
return INCARNATION_TOKEN.exec(token)?.[1];
|
|
138
|
+
}
|
|
139
|
+
function sameIncarnation(a, b) {
|
|
140
|
+
const ia = incarnationOf(a.token);
|
|
141
|
+
const ib = incarnationOf(b.token);
|
|
142
|
+
if (ia !== void 0 || ib !== void 0) return ia === ib;
|
|
143
|
+
return a.createdAt === b.createdAt;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/core/leases.ts
|
|
147
|
+
var MAX_LEASE_MS = 14 * 864e5;
|
|
148
|
+
var LEASE_SKEW_MS = 6e4;
|
|
149
|
+
var MAX_LEASES_PER_SEGMENT = 64;
|
|
150
|
+
var MAX_STORED_LEASES = 256;
|
|
151
|
+
var LEASE_CAS_ATTEMPTS = MAX_LEASES_PER_SEGMENT + 8;
|
|
152
|
+
var UNANSWERED_RESENDS = 3;
|
|
153
|
+
var UNANSWERED_BASE_MS = 500;
|
|
154
|
+
var CONFLICT_BASE_MS = 25;
|
|
155
|
+
var CONFLICT_CAP_MS = 400;
|
|
156
|
+
function isLive(entry, now) {
|
|
157
|
+
return now < entry.until + LEASE_SKEW_MS && entry.until <= now + MAX_LEASE_MS + LEASE_SKEW_MS;
|
|
158
|
+
}
|
|
159
|
+
function heldGenerations(record, now) {
|
|
160
|
+
const out = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const e of record?.leases ?? []) if (isLive(e, now)) out.add(e.generation);
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function liveLeases(record, now) {
|
|
165
|
+
return (record?.leases ?? []).filter((e) => isLive(e, now));
|
|
166
|
+
}
|
|
167
|
+
var LEASE_ONLY_RETRIES = 2 * MAX_LEASES_PER_SEGMENT + 8;
|
|
168
|
+
var BOOKKEEPING = /* @__PURE__ */ new Set(["token", "updatedAt", "leases"]);
|
|
169
|
+
function stable(value) {
|
|
170
|
+
if (value === void 0) return "u";
|
|
171
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
172
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
173
|
+
const o = value;
|
|
174
|
+
const keys = Object.keys(o).filter((k) => o[k] !== void 0).sort();
|
|
175
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stable(o[k])}`).join(",")}}`;
|
|
176
|
+
}
|
|
177
|
+
function onlyLeasesDiffer(held, now, alsoIgnore = []) {
|
|
178
|
+
if (!sameIncarnation(held, now)) return false;
|
|
179
|
+
const skip = /* @__PURE__ */ new Set([...BOOKKEEPING, ...alsoIgnore]);
|
|
180
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(held), ...Object.keys(now)]);
|
|
181
|
+
for (const k of keys) {
|
|
182
|
+
if (skip.has(k)) continue;
|
|
183
|
+
if (stable(held[k]) !== stable(now[k])) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
function leaseChurn(deps) {
|
|
190
|
+
let used = 0;
|
|
191
|
+
return {
|
|
192
|
+
async settle(held, now, reread, alsoIgnore) {
|
|
193
|
+
if (held === null || held === void 0 || now === null) return void 0;
|
|
194
|
+
if (used >= LEASE_ONLY_RETRIES || !onlyLeasesDiffer(held, now, alsoIgnore)) return void 0;
|
|
195
|
+
const bound = Math.min(CONFLICT_BASE_MS * 2 ** Math.min(used, 4), CONFLICT_CAP_MS);
|
|
196
|
+
used += 1;
|
|
197
|
+
const sleep = deps.clock?.sleep;
|
|
198
|
+
if (sleep !== void 0) {
|
|
199
|
+
await sleep.call(deps.clock, Math.floor((deps.rng?.next() ?? 1) * bound));
|
|
200
|
+
}
|
|
201
|
+
return { row: await reread() };
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function listOf(entries) {
|
|
206
|
+
if (entries.length === 0) return void 0;
|
|
207
|
+
return [...entries].sort(
|
|
208
|
+
(a, b) => a.generation !== b.generation ? a.generation - b.generation : a.holder < b.holder ? -1 : a.holder > b.holder ? 1 : 0
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
var wait = (deps, bound) => deps.clock.sleep(Math.floor((deps.rng?.next() ?? 1) * bound));
|
|
212
|
+
var heldBy = (row, take) => (row?.leases ?? []).some(
|
|
213
|
+
(e) => e.holder === take.holder && e.generation === take.generation && e.until === take.until
|
|
214
|
+
);
|
|
215
|
+
async function takeLease(ref, deps, take) {
|
|
216
|
+
let row = take.row;
|
|
217
|
+
let held = true;
|
|
218
|
+
let resends = 0;
|
|
219
|
+
let unanswered;
|
|
220
|
+
for (let attempt = 0; attempt < LEASE_CAS_ATTEMPTS; attempt++) {
|
|
221
|
+
if (row === null) row = await deps.registry.get(ref);
|
|
222
|
+
if (row === null || row.status === "destroyed" || row.currentGen === null) {
|
|
223
|
+
throw new NotFoundError(`segment "${ref.segment}" has no generation to lease`);
|
|
224
|
+
}
|
|
225
|
+
if (take.generation > row.currentGen) {
|
|
226
|
+
throw new NotFoundError(
|
|
227
|
+
`segment "${ref.segment}" generation ${take.generation} cannot be leased: it is not a published generation`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (take.current && row.currentGen !== take.generation) return "moved";
|
|
231
|
+
if (heldBy(row, take)) return { token: row.token, row };
|
|
232
|
+
const live = liveLeases(row, deps.clock.now()).filter((e) => e.holder !== take.holder);
|
|
233
|
+
if (live.length >= MAX_LEASES_PER_SEGMENT) {
|
|
234
|
+
throw new LeaseLimitError(
|
|
235
|
+
`segment "${ref.segment}" already has ${live.length} live leases, the most one segment records`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const leases = listOf([
|
|
239
|
+
...live,
|
|
240
|
+
{ holder: take.holder, generation: take.generation, until: take.until }
|
|
241
|
+
]);
|
|
242
|
+
const against = row;
|
|
243
|
+
try {
|
|
244
|
+
const { token } = await deps.registry.compareAndSwap(
|
|
245
|
+
ref,
|
|
246
|
+
against.token,
|
|
247
|
+
{ leases },
|
|
248
|
+
held ? { held: against } : void 0
|
|
249
|
+
);
|
|
250
|
+
return { token, row: against };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (isWriteConflictError(err)) {
|
|
253
|
+
row = null;
|
|
254
|
+
held = false;
|
|
255
|
+
await wait(deps, Math.min(CONFLICT_BASE_MS * 2 ** attempt, CONFLICT_CAP_MS));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (!isTransientError(err)) throw err;
|
|
259
|
+
const now = await deps.registry.get(ref);
|
|
260
|
+
if (heldBy(now, take)) return { token: now.token, row: against };
|
|
261
|
+
if (now !== null && now.token === against.token) {
|
|
262
|
+
if (resends >= UNANSWERED_RESENDS) throw err;
|
|
263
|
+
await wait(deps, UNANSWERED_BASE_MS * 2 ** resends);
|
|
264
|
+
resends += 1;
|
|
265
|
+
unanswered = err;
|
|
266
|
+
}
|
|
267
|
+
row = now;
|
|
268
|
+
held = false;
|
|
269
|
+
if (row === null)
|
|
270
|
+
throw new NotFoundError(`segment "${ref.segment}" has no generation to lease`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (unanswered !== void 0) throw unanswered;
|
|
274
|
+
throw new WriteConflictError(`lease: contention taking a lease on segment "${ref.segment}"`);
|
|
275
|
+
}
|
|
276
|
+
async function releaseLease(ref, deps, holder) {
|
|
277
|
+
let resends = 0;
|
|
278
|
+
let unanswered;
|
|
279
|
+
let row;
|
|
280
|
+
for (let attempt = 0; attempt < LEASE_CAS_ATTEMPTS; attempt++) {
|
|
281
|
+
if (row === void 0) row = await deps.registry.get(ref);
|
|
282
|
+
if (row === null || row.status === "destroyed") return;
|
|
283
|
+
if (!(row.leases ?? []).some((e) => e.holder === holder)) return;
|
|
284
|
+
const against = row;
|
|
285
|
+
const keep = liveLeases(against, deps.clock.now()).filter((e) => e.holder !== holder);
|
|
286
|
+
try {
|
|
287
|
+
await deps.registry.compareAndSwap(ref, against.token, { leases: listOf(keep) });
|
|
288
|
+
return;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (isWriteConflictError(err)) {
|
|
291
|
+
row = void 0;
|
|
292
|
+
await wait(deps, Math.min(CONFLICT_BASE_MS * 2 ** attempt, CONFLICT_CAP_MS));
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (!isTransientError(err)) throw err;
|
|
296
|
+
const now = await deps.registry.get(ref);
|
|
297
|
+
if (now === null || !(now.leases ?? []).some((e) => e.holder === holder)) return;
|
|
298
|
+
if (now.token === against.token) {
|
|
299
|
+
if (resends >= UNANSWERED_RESENDS) throw err;
|
|
300
|
+
await wait(deps, UNANSWERED_BASE_MS * 2 ** resends);
|
|
301
|
+
resends += 1;
|
|
302
|
+
unanswered = err;
|
|
303
|
+
}
|
|
304
|
+
row = now;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (unanswered !== void 0) throw unanswered;
|
|
308
|
+
throw new WriteConflictError(`lease: contention releasing a lease on segment "${ref.segment}"`);
|
|
309
|
+
}
|
|
310
|
+
|
|
115
311
|
// src/core/validate.ts
|
|
116
312
|
var CHUNK_KEY_MAX = 65535;
|
|
117
313
|
var MAX_NAME_LENGTH = 256;
|
|
@@ -263,6 +459,27 @@ function namespacePathPart(namespace) {
|
|
|
263
459
|
return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
|
|
264
460
|
}
|
|
265
461
|
|
|
462
|
+
// src/core/kept-generations.ts
|
|
463
|
+
var MAX_KEPT_GENERATIONS = 64;
|
|
464
|
+
var MAX_STORED_KEPT_GENERATIONS = 256;
|
|
465
|
+
function usableKeptGens(record) {
|
|
466
|
+
const list = record?.keptGens;
|
|
467
|
+
if (record === null || list === void 0 || record.currentGen === null) return void 0;
|
|
468
|
+
for (const g of list) if (g >= record.currentGen) return void 0;
|
|
469
|
+
return list;
|
|
470
|
+
}
|
|
471
|
+
function nextKept(prev, generation, keep) {
|
|
472
|
+
const none = { list: void 0, evict: [] };
|
|
473
|
+
if (keep === void 0 || keep > MAX_KEPT_GENERATIONS) return none;
|
|
474
|
+
if (generation === 0) return { list: [], evict: [] };
|
|
475
|
+
const previous = prev?.currentGen ?? null;
|
|
476
|
+
const known = usableKeptGens(prev);
|
|
477
|
+
if (previous === null || known === void 0) return none;
|
|
478
|
+
const all = [...known, previous];
|
|
479
|
+
const cut = Math.max(0, all.length - keep);
|
|
480
|
+
return { list: all.slice(cut), evict: all.slice(0, cut) };
|
|
481
|
+
}
|
|
482
|
+
|
|
266
483
|
// src/core/metadata.ts
|
|
267
484
|
var MAX_METADATA_BYTES = 1024;
|
|
268
485
|
var MAX_METADATA_KEY_BYTES = 128;
|
|
@@ -341,21 +558,9 @@ function metadataFromBytes(bytes, fail) {
|
|
|
341
558
|
return Object.freeze(parsed);
|
|
342
559
|
}
|
|
343
560
|
|
|
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
561
|
// src/drivers/_shared/registry.ts
|
|
357
562
|
var STATUSES = ["active", "destroyed"];
|
|
358
|
-
var
|
|
563
|
+
var BASE_FIELDS = [
|
|
359
564
|
"namespace",
|
|
360
565
|
"segment",
|
|
361
566
|
"currentGen",
|
|
@@ -369,7 +574,13 @@ var RECORD_FIELDS = [
|
|
|
369
574
|
"updatedAt",
|
|
370
575
|
"token"
|
|
371
576
|
];
|
|
372
|
-
var
|
|
577
|
+
var SCHEMA_3_FIELDS = ["keptGens", "leases"];
|
|
578
|
+
var RECORD_FIELDS = [...BASE_FIELDS, ...SCHEMA_3_FIELDS];
|
|
579
|
+
function fieldsOf(schemaVersion) {
|
|
580
|
+
if (schemaVersion >= 3) return RECORD_FIELDS;
|
|
581
|
+
const base = BASE_FIELDS;
|
|
582
|
+
return schemaVersion === 2 ? base : base.filter((f) => f !== "summary");
|
|
583
|
+
}
|
|
373
584
|
var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
|
|
374
585
|
var MAX_GOVERNANCE_BYTES = 64 * 1024;
|
|
375
586
|
var MAX_WRAPPED_DEKS = 8;
|
|
@@ -514,17 +725,103 @@ function validateSummaryKeys(summary, wrappedDeks) {
|
|
|
514
725
|
);
|
|
515
726
|
}
|
|
516
727
|
}
|
|
728
|
+
function validateKeptGens(value, isStored, ctx) {
|
|
729
|
+
if (value === void 0) return void 0;
|
|
730
|
+
const fail = (msg) => {
|
|
731
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
732
|
+
throw isStored ? new IntegrityError(`registry record keptGens: ${msg}${where}`) : new ValidationError(`keptGens: ${msg}`);
|
|
733
|
+
};
|
|
734
|
+
if (!Array.isArray(value)) fail("must be an array");
|
|
735
|
+
const list = value;
|
|
736
|
+
const max = isStored ? MAX_STORED_KEPT_GENERATIONS : MAX_KEPT_GENERATIONS;
|
|
737
|
+
if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
|
|
738
|
+
let last = -1;
|
|
739
|
+
for (const g of list) {
|
|
740
|
+
if (!Number.isSafeInteger(g) || g < 0) {
|
|
741
|
+
fail(`entries must be non-negative safe integers (got ${String(g)})`);
|
|
742
|
+
}
|
|
743
|
+
if (g <= last) fail("entries must be strictly ascending");
|
|
744
|
+
last = g;
|
|
745
|
+
}
|
|
746
|
+
return isStored ? list : Object.freeze([...list]);
|
|
747
|
+
}
|
|
748
|
+
var HOLDER = /^[0-9a-f]{16}$/;
|
|
749
|
+
var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
|
|
750
|
+
function validateLeases(value, isStored, ctx) {
|
|
751
|
+
if (value === void 0) return void 0;
|
|
752
|
+
const fail = (msg) => {
|
|
753
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
754
|
+
throw isStored ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
|
|
755
|
+
};
|
|
756
|
+
if (!Array.isArray(value)) fail("must be an array");
|
|
757
|
+
const list = value;
|
|
758
|
+
const max = MAX_STORED_LEASES;
|
|
759
|
+
if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
|
|
760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
761
|
+
const out = [];
|
|
762
|
+
for (const e of list) {
|
|
763
|
+
if (typeof e !== "object" || e === null || Array.isArray(e)) fail("an entry must be an object");
|
|
764
|
+
const entry = e;
|
|
765
|
+
const extra = Object.keys(entry).filter((k) => !LEASE_ENTRY_KEYS.includes(k));
|
|
766
|
+
if (extra.length > 0) fail(`an entry carries an undeclared field (${extra.join(", ")})`);
|
|
767
|
+
const { holder, generation, until } = entry;
|
|
768
|
+
if (typeof holder !== "string" || !HOLDER.test(holder)) {
|
|
769
|
+
fail("a holder must be 16 lowercase hex digits");
|
|
770
|
+
}
|
|
771
|
+
if (seen.has(holder)) fail("holders must be unique");
|
|
772
|
+
seen.add(holder);
|
|
773
|
+
for (const [name, n] of [
|
|
774
|
+
["generation", generation],
|
|
775
|
+
["until", until]
|
|
776
|
+
]) {
|
|
777
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
778
|
+
fail(`${name} must be a non-negative safe integer (got ${String(n)})`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
out.push(
|
|
782
|
+
isStored ? entry : Object.freeze({
|
|
783
|
+
holder,
|
|
784
|
+
generation,
|
|
785
|
+
until
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
if (isStored) return out;
|
|
790
|
+
return out.length === 0 ? void 0 : Object.freeze(out);
|
|
791
|
+
}
|
|
792
|
+
function validateLeasesPointer(leases, currentGen) {
|
|
793
|
+
if (leases !== void 0 && leases.length > 0 && currentGen === null) {
|
|
794
|
+
throw new ValidationError("leases: a row with no pointer has no generation to lease");
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function validateKeptGensBelow(keptGens, currentGen) {
|
|
798
|
+
if (keptGens === void 0) return;
|
|
799
|
+
if (currentGen === null) {
|
|
800
|
+
throw new ValidationError(
|
|
801
|
+
"keptGens: a row with no pointer has no generations below it to keep"
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
const top = keptGens[keptGens.length - 1];
|
|
805
|
+
if (top !== void 0 && top >= currentGen) {
|
|
806
|
+
throw new ValidationError(
|
|
807
|
+
`keptGens names generation ${top}, which is not below the row's pointer ${currentGen}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
517
811
|
function validateNewRegistryRecord(rec) {
|
|
518
812
|
validateGeneration(rec.currentGen);
|
|
519
813
|
if (rec.status !== void 0) validateStatus(rec.status);
|
|
520
814
|
validateWrappedDeks(rec.wrappedDeks, false);
|
|
521
815
|
validateGovernance(rec.retention, "retention");
|
|
522
816
|
validateGovernance(rec.residency, "residency");
|
|
523
|
-
|
|
817
|
+
const keptGens = validateKeptGens(rec.keptGens, false);
|
|
818
|
+
validateKeptGensBelow(keptGens, rec.currentGen);
|
|
819
|
+
const withKept = keptGens === void 0 ? rec : { ...rec, keptGens };
|
|
820
|
+
if (!("summary" in rec)) return withKept;
|
|
524
821
|
const summary = validateSummary(rec.summary, false);
|
|
525
822
|
validateSummaryNames(summary, rec.currentGen);
|
|
526
823
|
validateSummaryKeys(summary, rec.wrappedDeks);
|
|
527
|
-
return { ...
|
|
824
|
+
return { ...withKept, summary };
|
|
528
825
|
}
|
|
529
826
|
function validateRegistryPatch(patch) {
|
|
530
827
|
validatePatchGeneration(patch);
|
|
@@ -532,10 +829,12 @@ function validateRegistryPatch(patch) {
|
|
|
532
829
|
if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
|
|
533
830
|
if ("retention" in patch) validateGovernance(patch.retention, "retention");
|
|
534
831
|
if ("residency" in patch) validateGovernance(patch.residency, "residency");
|
|
535
|
-
|
|
536
|
-
|
|
832
|
+
const withKept = patch.keptGens === void 0 ? patch : { ...patch, keptGens: validateKeptGens(patch.keptGens, false) };
|
|
833
|
+
const withLeases = "leases" in withKept ? { ...withKept, leases: validateLeases(withKept.leases, false) } : withKept;
|
|
834
|
+
if (!("summary" in patch)) return withLeases;
|
|
835
|
+
return { ...withLeases, summary: validateSummary(patch.summary, false) };
|
|
537
836
|
}
|
|
538
|
-
var REGISTRY_SCHEMA_VERSION =
|
|
837
|
+
var REGISTRY_SCHEMA_VERSION = 3;
|
|
539
838
|
var OLDEST_REGISTRY_SCHEMA_VERSION = 1;
|
|
540
839
|
function assertRegistrySchemaVersion(raw, ctx) {
|
|
541
840
|
if (raw === void 0) {
|
|
@@ -644,6 +943,9 @@ function recordFromNew(ref, rec, now, token) {
|
|
|
644
943
|
retention: rec.retention,
|
|
645
944
|
residency: rec.residency,
|
|
646
945
|
summary: rec.summary,
|
|
946
|
+
keptGens: rec.keptGens,
|
|
947
|
+
// A new row has no holder: a lease is written only against a row that already exists.
|
|
948
|
+
leases: void 0,
|
|
647
949
|
createdAt: now,
|
|
648
950
|
updatedAt: now,
|
|
649
951
|
token
|
|
@@ -659,7 +961,7 @@ function assertStoredRecordShape(r, ctx, schemaVersion = REGISTRY_SCHEMA_VERSION
|
|
|
659
961
|
if (!STATUSES.includes(r.status)) {
|
|
660
962
|
throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);
|
|
661
963
|
}
|
|
662
|
-
const declared = schemaVersion
|
|
964
|
+
const declared = fieldsOf(schemaVersion);
|
|
663
965
|
const extra = Object.keys(r).filter((k) => !declared.includes(k));
|
|
664
966
|
if (extra.length > 0) {
|
|
665
967
|
throw new IntegrityError(
|
|
@@ -677,6 +979,8 @@ function assertStoredRecordShape(r, ctx, schemaVersion = REGISTRY_SCHEMA_VERSION
|
|
|
677
979
|
}
|
|
678
980
|
}
|
|
679
981
|
validateSummary(r.summary, true, ctx);
|
|
982
|
+
validateKeptGens(r.keptGens, true, ctx);
|
|
983
|
+
validateLeases(r.leases, true, ctx);
|
|
680
984
|
}
|
|
681
985
|
function applyRegistryPatch(prev, patch, now, token) {
|
|
682
986
|
const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
|
|
@@ -690,6 +994,24 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
690
994
|
const kept = currentGen === prev.currentGen ? prev.summary : void 0;
|
|
691
995
|
summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
|
|
692
996
|
}
|
|
997
|
+
let keptGens;
|
|
998
|
+
if ("keptGens" in patch) {
|
|
999
|
+
validateKeptGensBelow(patch.keptGens, currentGen);
|
|
1000
|
+
keptGens = patch.keptGens;
|
|
1001
|
+
} else {
|
|
1002
|
+
keptGens = currentGen === prev.currentGen ? prev.keptGens : void 0;
|
|
1003
|
+
}
|
|
1004
|
+
let leases;
|
|
1005
|
+
if ("leases" in patch) {
|
|
1006
|
+
leases = patch.leases;
|
|
1007
|
+
validateLeasesPointer(leases, currentGen);
|
|
1008
|
+
const most = Math.max(MAX_LEASES_PER_SEGMENT, prev.leases?.length ?? 0);
|
|
1009
|
+
if (leases !== void 0 && leases.length > most) {
|
|
1010
|
+
throw new ValidationError(`leases: has ${leases.length} entries, cap ${most}`);
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
leases = prev.leases;
|
|
1014
|
+
}
|
|
693
1015
|
return {
|
|
694
1016
|
namespace: prev.namespace,
|
|
695
1017
|
segment: prev.segment,
|
|
@@ -700,6 +1022,8 @@ function applyRegistryPatch(prev, patch, now, token) {
|
|
|
700
1022
|
retention: "retention" in patch ? patch.retention : prev.retention,
|
|
701
1023
|
residency: "residency" in patch ? patch.residency : prev.residency,
|
|
702
1024
|
summary,
|
|
1025
|
+
keptGens,
|
|
1026
|
+
leases,
|
|
703
1027
|
createdAt: prev.createdAt,
|
|
704
1028
|
updatedAt: now,
|
|
705
1029
|
token
|
|
@@ -730,6 +1054,8 @@ export {
|
|
|
730
1054
|
WriteConflictError,
|
|
731
1055
|
IntegrityError,
|
|
732
1056
|
NotFoundError,
|
|
1057
|
+
LeaseExpiredError,
|
|
1058
|
+
LeaseLimitError,
|
|
733
1059
|
UnsupportedError,
|
|
734
1060
|
CapabilityError,
|
|
735
1061
|
BudgetExceededError,
|
|
@@ -738,6 +1064,8 @@ export {
|
|
|
738
1064
|
isCloudRoaringError,
|
|
739
1065
|
isWriteConflictError,
|
|
740
1066
|
isTransientError,
|
|
1067
|
+
isLeaseExpiredError,
|
|
1068
|
+
isLeaseLimitError,
|
|
741
1069
|
isNotFoundError,
|
|
742
1070
|
isIntegrityError,
|
|
743
1071
|
isValidationError,
|
|
@@ -745,6 +1073,20 @@ export {
|
|
|
745
1073
|
decodeNameFromKey,
|
|
746
1074
|
encodeNameForPath,
|
|
747
1075
|
decodeNameFromPath,
|
|
1076
|
+
MAX_KEPT_GENERATIONS,
|
|
1077
|
+
usableKeptGens,
|
|
1078
|
+
nextKept,
|
|
1079
|
+
incarnationOf,
|
|
1080
|
+
sameIncarnation,
|
|
1081
|
+
MAX_LEASE_MS,
|
|
1082
|
+
LEASE_SKEW_MS,
|
|
1083
|
+
MAX_LEASES_PER_SEGMENT,
|
|
1084
|
+
isLive,
|
|
1085
|
+
heldGenerations,
|
|
1086
|
+
onlyLeasesDiffer,
|
|
1087
|
+
leaseChurn,
|
|
1088
|
+
takeLease,
|
|
1089
|
+
releaseLease,
|
|
748
1090
|
MAX_NAME_LENGTH,
|
|
749
1091
|
encodedNameLength,
|
|
750
1092
|
validateSegmentRef,
|
|
@@ -764,8 +1106,6 @@ export {
|
|
|
764
1106
|
metadataBytes,
|
|
765
1107
|
copiedMetadata,
|
|
766
1108
|
metadataFromBytes,
|
|
767
|
-
incarnationOf,
|
|
768
|
-
sameIncarnation,
|
|
769
1109
|
validateNewRegistryRecord,
|
|
770
1110
|
validateRegistryPatch,
|
|
771
1111
|
serializeRegistryEnvelope,
|
|
@@ -782,4 +1122,4 @@ export {
|
|
|
782
1122
|
namespaceKeyPart,
|
|
783
1123
|
namespacePathPart
|
|
784
1124
|
};
|
|
785
|
-
//# sourceMappingURL=chunk-
|
|
1125
|
+
//# sourceMappingURL=chunk-CV7GJO3B.js.map
|