@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
package/dist/index.js
CHANGED
|
@@ -5,10 +5,17 @@ import {
|
|
|
5
5
|
DEFAULT_NAMESPACE,
|
|
6
6
|
IntegrityError,
|
|
7
7
|
KeyUnavailableError,
|
|
8
|
+
LEASE_SKEW_MS,
|
|
9
|
+
LeaseExpiredError,
|
|
10
|
+
LeaseLimitError,
|
|
11
|
+
MAX_KEPT_GENERATIONS,
|
|
12
|
+
MAX_LEASES_PER_SEGMENT,
|
|
13
|
+
MAX_LEASE_MS,
|
|
8
14
|
MAX_METADATA_BYTES,
|
|
9
15
|
MAX_NAME_LENGTH,
|
|
10
16
|
NotFoundError,
|
|
11
17
|
RESERVED_NAMESPACE_PREFIX,
|
|
18
|
+
StaleOperandError,
|
|
12
19
|
TransientError,
|
|
13
20
|
UnsupportedError,
|
|
14
21
|
ValidationError,
|
|
@@ -25,25 +32,36 @@ import {
|
|
|
25
32
|
encodeNameForPath,
|
|
26
33
|
encodedNameLength,
|
|
27
34
|
entropyIsAvailable,
|
|
35
|
+
heldGenerations,
|
|
28
36
|
incarnationOf,
|
|
29
37
|
isCloudRoaringError,
|
|
30
38
|
isIntegrityError,
|
|
39
|
+
isLeaseExpiredError,
|
|
40
|
+
isLeaseLimitError,
|
|
41
|
+
isLive,
|
|
31
42
|
isNotFoundError,
|
|
32
43
|
isReservedNamespace,
|
|
44
|
+
isStaleOperandError,
|
|
33
45
|
isStorageBackend,
|
|
34
46
|
isTransientError,
|
|
35
47
|
isValidationError,
|
|
36
48
|
isWriteConflictError,
|
|
49
|
+
leaseChurn,
|
|
37
50
|
mapWithConcurrency,
|
|
38
51
|
metadataBytes,
|
|
39
52
|
metadataFromBytes,
|
|
40
53
|
namespacePathPart,
|
|
41
54
|
newIncarnationToken,
|
|
55
|
+
nextKept,
|
|
42
56
|
nextRegistryToken,
|
|
57
|
+
onlyLeasesDiffer,
|
|
43
58
|
parseRegistryEnvelope,
|
|
44
59
|
recordFromNew,
|
|
60
|
+
releaseLease,
|
|
45
61
|
sameIncarnation,
|
|
46
62
|
serializeRegistryEnvelope,
|
|
63
|
+
takeLease,
|
|
64
|
+
usableKeptGens,
|
|
47
65
|
validateChunkKeyOrder,
|
|
48
66
|
validateChunkRef,
|
|
49
67
|
validateNewRegistryRecord,
|
|
@@ -52,7 +70,7 @@ import {
|
|
|
52
70
|
validateUserNamespace,
|
|
53
71
|
validateUserRef,
|
|
54
72
|
webCryptoEntropy
|
|
55
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-CCP4U32U.js";
|
|
56
74
|
|
|
57
75
|
// src/core/bit-route.ts
|
|
58
76
|
var U32_MAX = 4294967295;
|
|
@@ -103,6 +121,42 @@ async function collectWithinBudget(source, budget, op) {
|
|
|
103
121
|
return out;
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
// src/core/chunk-checks.ts
|
|
125
|
+
function assertChunkKeyInRange(k) {
|
|
126
|
+
if (!Number.isInteger(k) || k < 0 || k >= CHUNK_COUNT) {
|
|
127
|
+
throw new IntegrityError(`chunk key from a tier is out of range: ${k}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function assertChunkCardinalityInRange(c) {
|
|
131
|
+
if (!Number.isInteger(c) || c < 1 || c > CHUNK_COUNT) {
|
|
132
|
+
throw new IntegrityError(`chunk cardinality from a tier is out of range: ${c}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function checkedChunkKeys(listed) {
|
|
136
|
+
const keys = [...listed];
|
|
137
|
+
for (const k of keys) assertChunkKeyInRange(k);
|
|
138
|
+
keys.sort((a, b) => a - b);
|
|
139
|
+
for (let i = 1; i < keys.length; i++) {
|
|
140
|
+
if (keys[i] === keys[i - 1]) {
|
|
141
|
+
throw new IntegrityError(`chunk key from a tier is listed twice: ${keys[i]}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return keys;
|
|
145
|
+
}
|
|
146
|
+
function assertChunkPayloadInRange(bitmap, chunkKey) {
|
|
147
|
+
const max = bitmap.maximum?.();
|
|
148
|
+
if (max !== void 0 && max > MAX_REMAINDER) {
|
|
149
|
+
throw new IntegrityError(
|
|
150
|
+
`chunk ${chunkKey} payload holds value ${max}, outside the 16-bit remainder range [0, ${MAX_REMAINDER}] \u2014 the stored object is corrupt or was not written by this codec`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function decodeChunkBytes(codec, bytes, chunkKey, maxBitmapBytes) {
|
|
155
|
+
const bitmap = codec.safeDeserialize(bytes, maxBitmapBytes);
|
|
156
|
+
assertChunkPayloadInRange(bitmap, chunkKey);
|
|
157
|
+
return bitmap;
|
|
158
|
+
}
|
|
159
|
+
|
|
106
160
|
// src/core/chunk-stream.ts
|
|
107
161
|
var ChunkStream = class {
|
|
108
162
|
iterator;
|
|
@@ -332,6 +386,8 @@ var CountingMetricsSink = class _CountingMetricsSink {
|
|
|
332
386
|
op.totalMs += event.ms;
|
|
333
387
|
break;
|
|
334
388
|
}
|
|
389
|
+
case "advisory":
|
|
390
|
+
break;
|
|
335
391
|
default: {
|
|
336
392
|
const exhaustive = event;
|
|
337
393
|
return exhaustive;
|
|
@@ -530,7 +586,7 @@ var SegmentEngine = class {
|
|
|
530
586
|
if (cardinalities) {
|
|
531
587
|
let total2 = 0;
|
|
532
588
|
for (const [k, n] of cardinalities) {
|
|
533
|
-
|
|
589
|
+
assertChunkKeyInRange(k);
|
|
534
590
|
total2 += n;
|
|
535
591
|
}
|
|
536
592
|
return total2;
|
|
@@ -675,6 +731,102 @@ var SegmentEngine = class {
|
|
|
675
731
|
chunks.close();
|
|
676
732
|
}
|
|
677
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* The ids at 1-based ranks `n`, `2n`, `3n` … counted over the ids in `range`, ascending. The rank of each boundary
|
|
736
|
+
* is placed from the index's per-chunk counts, so only the chunks that hold a boundary are read, each once however
|
|
737
|
+
* many boundaries it holds, through the same coalesced stream, window and budget unit as {@link iterate}. A range with
|
|
738
|
+
* `after` inside a chunk reads that chunk too, to count the ids at or below `after`. A last partial window yields
|
|
739
|
+
* nothing.
|
|
740
|
+
*
|
|
741
|
+
* The counts place ids, and are trusted as `count()` trusts them: a chunk that is read must decode to the size its
|
|
742
|
+
* index says, or the read throws {@link IntegrityError} (invariant 5), but a chunk that is not read is not checked, so
|
|
743
|
+
* an index that lies consistently about one (on an unencrypted object, the sum still matches the footer's total,
|
|
744
|
+
* which opening the object checks before any boundary is placed) shifts the ranks after it. An encrypted object's
|
|
745
|
+
* index is authenticated and bound to the object, so a wrong count there needs the key. The index and the chunks must come from one
|
|
746
|
+
* generation (invariant 3): the caller reads through a source that holds one, a pinned one, and a source with no
|
|
747
|
+
* per-chunk counts is refused.
|
|
748
|
+
*
|
|
749
|
+
* Nothing is fetched when the chunks in range hold fewer than `n` ids. The budget is charged before the first fetch
|
|
750
|
+
* with an upper bound, the cut chunk plus one chunk per boundary the counts allow (capped by the chunks in range),
|
|
751
|
+
* which can exceed the chunks actually read. A range that cuts its first chunk reads it even when the ids above
|
|
752
|
+
* `after` turn out to be fewer than `n`, and one that cuts its last chunk may read it and yield nothing.
|
|
753
|
+
*/
|
|
754
|
+
async *everyNth(seg, n, range) {
|
|
755
|
+
if (!Number.isSafeInteger(n) || n < 1) {
|
|
756
|
+
throw new ValidationError(`n must be a positive integer; got ${String(n)}`);
|
|
757
|
+
}
|
|
758
|
+
const epoch = this.invalidations;
|
|
759
|
+
const w = windowOf(range);
|
|
760
|
+
if (w === "empty") return;
|
|
761
|
+
const { loKey, loRem, hiKey, hiRem } = w ?? WHOLE_ID_SPACE;
|
|
762
|
+
if (this.storage.cardinalities === void 0) {
|
|
763
|
+
throw new UnsupportedError("everyNth needs a storage source that reports per-chunk counts");
|
|
764
|
+
}
|
|
765
|
+
const counts = await this.storage.cardinalities(seg);
|
|
766
|
+
if (counts === null) return;
|
|
767
|
+
const keys = keysWithin(
|
|
768
|
+
[...counts.keys()].sort((a, b) => a - b),
|
|
769
|
+
{ loKey, loRem, hiKey, hiRem }
|
|
770
|
+
);
|
|
771
|
+
for (const k of keys) {
|
|
772
|
+
assertChunkKeyInRange(k);
|
|
773
|
+
const c = counts.get(k);
|
|
774
|
+
assertChunkCardinalityInRange(c);
|
|
775
|
+
}
|
|
776
|
+
const gen = await this.cacheVersion(seg);
|
|
777
|
+
const cutsFirst = loRem > 0 && keys[0] === loKey;
|
|
778
|
+
let upper = 0;
|
|
779
|
+
for (const k of keys) upper += counts.get(k);
|
|
780
|
+
if (upper < n) return;
|
|
781
|
+
const cut = cutsFirst ? 1 : 0;
|
|
782
|
+
checkBudget(this.budget, cut + Math.min(keys.length - cut, Math.floor(upper / n)), "everyNth");
|
|
783
|
+
let first;
|
|
784
|
+
let below = 0;
|
|
785
|
+
if (cutsFirst) {
|
|
786
|
+
first = this.chunkRemainders(
|
|
787
|
+
await this.storageChunk({ ...seg, chunkKey: loKey }, gen),
|
|
788
|
+
loKey,
|
|
789
|
+
counts
|
|
790
|
+
);
|
|
791
|
+
below = firstAbove(first, loRem - 1);
|
|
792
|
+
}
|
|
793
|
+
const hits = [];
|
|
794
|
+
let before = 0;
|
|
795
|
+
for (const key of keys) {
|
|
796
|
+
const skipped = key === loKey && cutsFirst ? below : 0;
|
|
797
|
+
const size = counts.get(key) - skipped;
|
|
798
|
+
const next = (Math.floor(before / n) + 1) * n;
|
|
799
|
+
if (next <= before + size) hits.push({ key, index: skipped + next - before - 1 });
|
|
800
|
+
before += size;
|
|
801
|
+
}
|
|
802
|
+
if (hits.length === 0) return;
|
|
803
|
+
const streamed = hits.filter((h) => !(cutsFirst && h.key === loKey)).map((h) => h.key);
|
|
804
|
+
const chunks = streamed.length > 0 ? this.chunkSequence(seg, streamed, gen, epoch) : void 0;
|
|
805
|
+
try {
|
|
806
|
+
for (const { key, index } of hits) {
|
|
807
|
+
const rem = cutsFirst && key === loKey && first !== void 0 ? first : this.chunkRemainders(await chunks.take(key), key, counts);
|
|
808
|
+
const base = key * CHUNK_COUNT;
|
|
809
|
+
for (let i = index; i < rem.length; i += n) {
|
|
810
|
+
const remainder = rem[i] & MAX_REMAINDER;
|
|
811
|
+
if (key === hiKey && remainder > hiRem) return;
|
|
812
|
+
yield base + remainder;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} finally {
|
|
816
|
+
chunks?.close();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** A chunk's remainders ascending, once its size is confirmed against the index's count for it (invariant 5). */
|
|
820
|
+
chunkRemainders(chunk, chunkKey, counts) {
|
|
821
|
+
const expected = counts.get(chunkKey);
|
|
822
|
+
if (chunk === null || chunk.size !== expected) {
|
|
823
|
+
const got = chunk === null ? "no bytes" : `${chunk.size} ids`;
|
|
824
|
+
throw new IntegrityError(
|
|
825
|
+
`chunk ${chunkKey} holds ${got}, but the object's index says ${expected}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return chunk.toUint32Array ? chunk.toUint32Array() : Uint32Array.from(chunk);
|
|
829
|
+
}
|
|
678
830
|
/**
|
|
679
831
|
* `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
|
|
680
832
|
*
|
|
@@ -1041,7 +1193,7 @@ var SegmentEngine = class {
|
|
|
1041
1193
|
}
|
|
1042
1194
|
return streamed.stream.take(chunkKey).then((read) => {
|
|
1043
1195
|
if (read.bytes === null) return null;
|
|
1044
|
-
const bitmap = this.
|
|
1196
|
+
const bitmap = decodeChunkBytes(this.codec, read.bytes, chunkKey, this.maxBitmapBytes);
|
|
1045
1197
|
if (this.cache && !streamed.invalidated) {
|
|
1046
1198
|
const version = this.streamedVersion(streamed.gen, read.version);
|
|
1047
1199
|
if (version !== null) {
|
|
@@ -1063,52 +1215,9 @@ var SegmentEngine = class {
|
|
|
1063
1215
|
chunkCacheKey(ref, version) {
|
|
1064
1216
|
return version === void 0 ? chunkRefKey(ref) : chunkGenKey(ref, version);
|
|
1065
1217
|
}
|
|
1066
|
-
/** The segment's chunk keys, ascending
|
|
1218
|
+
/** The segment's chunk keys, ascending, checked as untrusted tier data. */
|
|
1067
1219
|
async chunkKeys(seg) {
|
|
1068
|
-
|
|
1069
|
-
for (const k of keys) this.assertChunkKeyInRange(k);
|
|
1070
|
-
keys.sort((a, b) => a - b);
|
|
1071
|
-
for (let i = 1; i < keys.length; i++) {
|
|
1072
|
-
if (keys[i] === keys[i - 1]) {
|
|
1073
|
-
throw new IntegrityError(`chunk key from a tier is listed twice: ${keys[i]}`);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
return keys;
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Tier-derived keys are untrusted (invariant 5) — fail fast on a corrupt/out-of-range key rather than
|
|
1080
|
-
* letting it flow into the id-routing step and produce a bogus id.
|
|
1081
|
-
*/
|
|
1082
|
-
assertChunkKeyInRange(k) {
|
|
1083
|
-
if (!Number.isInteger(k) || k < 0 || k >= CHUNK_COUNT) {
|
|
1084
|
-
throw new IntegrityError(`chunk key from a tier is out of range: ${k}`);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* The other half of invariant 5: a chunk payload holds **remainders** — 16-bit offsets within one chunk — so
|
|
1089
|
-
* every value must be `<= 0xffff`.
|
|
1090
|
-
*
|
|
1091
|
-
* Nothing upstream establishes that. The byte cap bounds *size*, and CRC/AEAD prove the bytes are the bytes
|
|
1092
|
-
* that were written — which anyone able to write the bucket satisfies trivially. A value `>= 65536` would then
|
|
1093
|
-
* reach the id-routing step, which masks it (`remainder & 0xffff`) and emits a **fabricated id belonging to a different
|
|
1094
|
-
* chunk's id space**: indistinguishable from real data, inflating `count()` and creating spurious `intersect`
|
|
1095
|
-
* matches.
|
|
1096
|
-
*
|
|
1097
|
-
* Costs one `maximum()` per chunk, not per id — `maximum` is optional on the codec seam precisely so a codec
|
|
1098
|
-
* that cannot answer in O(1) opts out instead of making the read path walk every value.
|
|
1099
|
-
*
|
|
1100
|
-
* One value stands for all of them only because the decode is structurally checked. Roaring answers `maximum()`
|
|
1101
|
-
* from its last container, so a payload listing its containers out of order would report the wrong one's
|
|
1102
|
-
* largest value and pass here while holding values above 65,535. The codec's `safeDeserialize` refuses that
|
|
1103
|
-
* payload, and every other shape that would make this answer wrong, before it reaches this check.
|
|
1104
|
-
*/
|
|
1105
|
-
assertChunkPayloadInRange(bitmap, chunkKey) {
|
|
1106
|
-
const max = bitmap.maximum?.();
|
|
1107
|
-
if (max !== void 0 && max > MAX_REMAINDER) {
|
|
1108
|
-
throw new IntegrityError(
|
|
1109
|
-
`chunk ${chunkKey} payload holds value ${max}, outside the 16-bit remainder range [0, ${MAX_REMAINDER}] \u2014 the stored object is corrupt or was not written by this codec`
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1220
|
+
return checkedChunkKeys(await this.storage.listChunkKeys(seg));
|
|
1112
1221
|
}
|
|
1113
1222
|
/**
|
|
1114
1223
|
* Refuse a combine whose operand names a segment that was never created.
|
|
@@ -1222,15 +1331,6 @@ var SegmentEngine = class {
|
|
|
1222
1331
|
entry.read.then(settled2, settled2);
|
|
1223
1332
|
return entry.read;
|
|
1224
1333
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
* A chunk's stored bytes decoded under the size cap (invariant 5) and range-checked: the one place a chunk becomes a
|
|
1227
|
-
* bitmap, whether its bytes came from a request of its own or from a stream of coalesced ranges.
|
|
1228
|
-
*/
|
|
1229
|
-
decodeChunk(bytes, chunkKey) {
|
|
1230
|
-
const bitmap = this.codec.safeDeserialize(bytes, this.maxBitmapBytes);
|
|
1231
|
-
this.assertChunkPayloadInRange(bitmap, chunkKey);
|
|
1232
|
-
return bitmap;
|
|
1233
|
-
}
|
|
1234
1334
|
/**
|
|
1235
1335
|
* One chunk, decoded, range-checked and cached. A source with `getChunks` is read through it, as a one-key stream, so
|
|
1236
1336
|
* a `storage.get` is reported only for a request that was sent (a chunk the source holds in memory sends none);
|
|
@@ -1277,7 +1377,7 @@ var SegmentEngine = class {
|
|
|
1277
1377
|
}
|
|
1278
1378
|
}
|
|
1279
1379
|
if (!bytes) return null;
|
|
1280
|
-
const bitmap = this.
|
|
1380
|
+
const bitmap = decodeChunkBytes(this.codec, bytes, ref.chunkKey, this.maxBitmapBytes);
|
|
1281
1381
|
if (key !== "" && this.openReads.get(cacheKey)?.token === token) this.cache?.set(key, bitmap);
|
|
1282
1382
|
return bitmap;
|
|
1283
1383
|
}
|
|
@@ -1444,63 +1544,121 @@ async function nextLoadGeneration(ref, deps, row) {
|
|
|
1444
1544
|
}
|
|
1445
1545
|
var LIST_COLLECTION_CADENCE = 16;
|
|
1446
1546
|
async function gcOrphanGenerations(ref, deps, options = {}) {
|
|
1547
|
+
return (await listingPass(ref, deps, { keep: options.keep })).deleted;
|
|
1548
|
+
}
|
|
1549
|
+
async function listingPass(ref, deps, options) {
|
|
1447
1550
|
const keep = options.keep ?? 1;
|
|
1551
|
+
const quiet = options.quiet === true;
|
|
1448
1552
|
if (!Number.isInteger(keep) || keep < 0) {
|
|
1449
1553
|
throw new ValidationError(`keep must be a non-negative integer; got ${String(keep)}`);
|
|
1450
1554
|
}
|
|
1451
1555
|
const record = await deps.registry.get(ref);
|
|
1452
|
-
if (record === null) return [];
|
|
1556
|
+
if (record === null) return { deleted: [], kept: [], complete: true };
|
|
1453
1557
|
const current = record.currentGen;
|
|
1454
1558
|
const seen = /* @__PURE__ */ new Set();
|
|
1455
1559
|
for await (const key of deps.storage.list(ref)) seen.add(key.generation);
|
|
1456
1560
|
const gens = [...seen];
|
|
1457
1561
|
const after = await deps.registry.get(ref);
|
|
1458
|
-
if (after === null)
|
|
1562
|
+
if (after === null) {
|
|
1563
|
+
if (quiet) return { deleted: [], kept: [], complete: false };
|
|
1459
1564
|
throw new WriteConflictError(`registry row for segment ${ref.segment} was purged mid-pass`);
|
|
1565
|
+
}
|
|
1460
1566
|
if (record.status === "destroyed" && after.token !== record.token) {
|
|
1567
|
+
if (quiet) return { deleted: [], kept: [], complete: false };
|
|
1461
1568
|
throw new WriteConflictError(
|
|
1462
1569
|
`segment ${ref.segment} changed incarnation while its generations were being listed`
|
|
1463
1570
|
);
|
|
1464
1571
|
}
|
|
1465
1572
|
const cutoff = current === null || after.currentGen === null ? null : Math.min(current, after.currentGen);
|
|
1466
|
-
const
|
|
1573
|
+
const named = record.status === "destroyed" ? void 0 : usableKeptGens(after);
|
|
1574
|
+
const protect = options.protect === void 0 || named === void 0 ? void 0 : /* @__PURE__ */ new Set([...options.protect, ...named]);
|
|
1575
|
+
const stillCollectable = async (generation) => {
|
|
1467
1576
|
const still = await deps.registry.get(ref);
|
|
1468
1577
|
if (still === null) {
|
|
1578
|
+
if (quiet) return "stop";
|
|
1469
1579
|
throw new WriteConflictError(`registry row for segment ${ref.segment} was purged mid-pass`);
|
|
1470
1580
|
}
|
|
1471
1581
|
const ok = record.status === "destroyed" ? still.token === after.token : cutoff === null || still.currentGen !== null && still.currentGen >= cutoff;
|
|
1472
|
-
|
|
1582
|
+
const now = protect === void 0 ? void 0 : usableKeptGens(still);
|
|
1583
|
+
const unnamed = protect === void 0 || record.status === "destroyed" || now !== void 0 && !now.includes(generation);
|
|
1584
|
+
if (!ok || !unnamed) {
|
|
1585
|
+
if (quiet) return "stop";
|
|
1473
1586
|
throw new WriteConflictError(
|
|
1474
1587
|
`segment ${ref.segment} changed incarnation while its generations were being collected`
|
|
1475
1588
|
);
|
|
1476
1589
|
}
|
|
1590
|
+
if (guard !== void 0 && heldGenerations(still, guard.now()).has(generation)) return "skip";
|
|
1591
|
+
return "delete";
|
|
1477
1592
|
};
|
|
1478
|
-
const
|
|
1593
|
+
const guard = record.status === "destroyed" ? void 0 : options.leases;
|
|
1594
|
+
const held = guard === void 0 ? void 0 : heldGenerations(after, guard.now());
|
|
1595
|
+
const below = record.status === "destroyed" || cutoff === null ? [] : gens.filter((g) => g < cutoff).sort((a, b) => b - a);
|
|
1596
|
+
const candidates = record.status === "destroyed" ? gens.sort((a, b) => a - b) : cutoff === null ? (
|
|
1479
1597
|
// No Storage pointer yet, so "below current" selects nothing and there is nothing safe to infer: an object
|
|
1480
1598
|
// here is either a load about to publish or an orphan we cannot tell apart from it. Deleting would race
|
|
1481
1599
|
// that publish into a dangling pointer. It is collected once a pointer exists.
|
|
1482
1600
|
[]
|
|
1601
|
+
) : protect !== void 0 ? (
|
|
1602
|
+
// The row's list is the window: everything below the pointer it does not name is garbage.
|
|
1603
|
+
below.filter((g) => !protect.has(g))
|
|
1483
1604
|
) : (
|
|
1484
1605
|
// Delete generations below the cutoff, except the newest `keep` of them (the grace window).
|
|
1485
|
-
|
|
1606
|
+
below.slice(keep)
|
|
1486
1607
|
);
|
|
1608
|
+
const leasedSpared = new Set(candidates.filter((g) => held?.has(g) === true));
|
|
1609
|
+
const toDelete = candidates.filter((g) => !leasedSpared.has(g));
|
|
1610
|
+
const done = [];
|
|
1487
1611
|
for (const generation of toDelete) {
|
|
1488
|
-
await stillCollectable();
|
|
1612
|
+
const verdict = await stillCollectable(generation);
|
|
1613
|
+
if (verdict === "stop") return { deleted: done, kept: [], complete: false };
|
|
1614
|
+
if (verdict === "skip") {
|
|
1615
|
+
leasedSpared.add(generation);
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1489
1618
|
await deps.storage.delete({ namespace: ref.namespace, segment: ref.segment, generation });
|
|
1619
|
+
done.push(generation);
|
|
1490
1620
|
}
|
|
1491
|
-
|
|
1621
|
+
const deleted = new Set(done);
|
|
1622
|
+
return {
|
|
1623
|
+
deleted: done,
|
|
1624
|
+
kept: below.filter((g) => !deleted.has(g) && !leasedSpared.has(g)).reverse(),
|
|
1625
|
+
complete: true
|
|
1626
|
+
};
|
|
1492
1627
|
}
|
|
1493
1628
|
async function collectAfterLoad(ref, deps, options) {
|
|
1494
|
-
const { generation, keep, byName, proveCurrent } = options;
|
|
1629
|
+
const { generation, keep, byName, currentGone, kept, proveCurrent, leases } = options;
|
|
1495
1630
|
if (keep >= generation) return [];
|
|
1631
|
+
const list = kept?.list;
|
|
1632
|
+
if (list === void 0 || currentGone === true)
|
|
1633
|
+
return reconcile(ref, deps, { keep, kept, leases });
|
|
1496
1634
|
const periodic = generation % LIST_COLLECTION_CADENCE === 0;
|
|
1497
|
-
if (byName &&
|
|
1498
|
-
if (keep
|
|
1499
|
-
return
|
|
1635
|
+
if (byName && !periodic) {
|
|
1636
|
+
if (keep >= 1 && proveCurrent !== void 0 && !await objectIsThere(deps.storage, proveCurrent)) {
|
|
1637
|
+
return reconcile(ref, deps, { keep, kept, leases });
|
|
1638
|
+
}
|
|
1639
|
+
return deleteEvicted(ref, deps, { generation, evict: kept.evict, leases });
|
|
1640
|
+
}
|
|
1641
|
+
return (await listingPass(ref, deps, { keep, protect: list, quiet: true, leases })).deleted;
|
|
1642
|
+
}
|
|
1643
|
+
async function reconcile(ref, deps, options) {
|
|
1644
|
+
const { keep, kept, leases } = options;
|
|
1645
|
+
const {
|
|
1646
|
+
deleted,
|
|
1647
|
+
kept: present,
|
|
1648
|
+
complete
|
|
1649
|
+
} = await listingPass(ref, deps, { keep, quiet: true, leases });
|
|
1650
|
+
const token = kept?.token;
|
|
1651
|
+
if (complete && token !== void 0 && keep <= MAX_KEPT_GENERATIONS && !sameList(kept?.list, present)) {
|
|
1652
|
+
try {
|
|
1653
|
+
await deps.registry.compareAndSwap(ref, token, { keptGens: present });
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
if (!(err instanceof WriteConflictError)) throw err;
|
|
1500
1656
|
}
|
|
1501
|
-
return collectByName(ref, deps, { generation, keep });
|
|
1502
1657
|
}
|
|
1503
|
-
return
|
|
1658
|
+
return deleted;
|
|
1659
|
+
}
|
|
1660
|
+
function sameList(a, b) {
|
|
1661
|
+
return a !== void 0 && a.length === b.length && a.every((g, i) => g === b[i]);
|
|
1504
1662
|
}
|
|
1505
1663
|
async function objectIsThere(storage, key) {
|
|
1506
1664
|
try {
|
|
@@ -1510,21 +1668,20 @@ async function objectIsThere(storage, key) {
|
|
|
1510
1668
|
return false;
|
|
1511
1669
|
}
|
|
1512
1670
|
}
|
|
1513
|
-
async function
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
|
|
1671
|
+
async function deleteEvicted(ref, deps, options) {
|
|
1672
|
+
const deleted = [];
|
|
1673
|
+
for (const generation of options.evict) {
|
|
1674
|
+
const still = await deps.registry.get(ref);
|
|
1675
|
+
if (still === null || still.currentGen === null || still.currentGen < options.generation) break;
|
|
1676
|
+
const named = usableKeptGens(still);
|
|
1677
|
+
if (named === void 0 || generation >= still.currentGen || named.includes(generation)) break;
|
|
1678
|
+
if (options.leases !== void 0 && heldGenerations(still, options.leases.now()).has(generation)) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
await deps.storage.delete({ namespace: ref.namespace, segment: ref.segment, generation });
|
|
1682
|
+
deleted.push(generation);
|
|
1517
1683
|
}
|
|
1518
|
-
|
|
1519
|
-
if (evicted < 0) return [];
|
|
1520
|
-
const still = await deps.registry.get(ref);
|
|
1521
|
-
if (still === null || still.currentGen === null || still.currentGen < generation) return [];
|
|
1522
|
-
await deps.storage.delete({
|
|
1523
|
-
namespace: ref.namespace,
|
|
1524
|
-
segment: ref.segment,
|
|
1525
|
-
generation: evicted
|
|
1526
|
-
});
|
|
1527
|
-
return [evicted];
|
|
1684
|
+
return deleted;
|
|
1528
1685
|
}
|
|
1529
1686
|
|
|
1530
1687
|
// src/core/reader-defaults.ts
|
|
@@ -2030,13 +2187,30 @@ async function runExport(reader, registry, sink, options = {}) {
|
|
|
2030
2187
|
const segments = [];
|
|
2031
2188
|
const failed = [];
|
|
2032
2189
|
let totalIds = 0;
|
|
2190
|
+
if (typeof reader?.segment !== "function") {
|
|
2191
|
+
throw new ValidationError(
|
|
2192
|
+
"runExport: the reader must have a segment(name, { namespace? }) method returning a handle with pin(), as a store's does"
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
let probed = false;
|
|
2196
|
+
let unusable;
|
|
2033
2197
|
const exportRef = async (ref) => {
|
|
2034
2198
|
let writer;
|
|
2035
2199
|
let count = 0;
|
|
2036
2200
|
let bytes = 0;
|
|
2037
2201
|
try {
|
|
2038
2202
|
validateSegmentRef(ref);
|
|
2039
|
-
const
|
|
2203
|
+
const handle = reader.segment(ref.segment, { namespace: ref.namespace });
|
|
2204
|
+
if (!probed) {
|
|
2205
|
+
probed = true;
|
|
2206
|
+
if (typeof handle?.pin !== "function") {
|
|
2207
|
+
unusable = new ValidationError(
|
|
2208
|
+
"runExport: the reader's segment(name, { namespace? }) must return a handle with a pin() method; a segment is exported at one pinned generation"
|
|
2209
|
+
);
|
|
2210
|
+
throw unusable;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
const ids = (await handle.pin()).iterate();
|
|
2040
2214
|
writer = await sink.open(ref, ext);
|
|
2041
2215
|
if (format === "roaring") {
|
|
2042
2216
|
const bm = requireCodec(options.codec, "runExport({ format: 'roaring' })").empty();
|
|
@@ -2067,6 +2241,7 @@ async function runExport(reader, registry, sink, options = {}) {
|
|
|
2067
2241
|
}
|
|
2068
2242
|
}
|
|
2069
2243
|
} catch (err) {
|
|
2244
|
+
if (err === unusable) throw err;
|
|
2070
2245
|
if (writer !== void 0) {
|
|
2071
2246
|
try {
|
|
2072
2247
|
await writer.abort?.();
|
|
@@ -3848,25 +4023,27 @@ function isShared(buffer) {
|
|
|
3848
4023
|
}
|
|
3849
4024
|
function overCap(what, length) {
|
|
3850
4025
|
return new ValidationError(
|
|
3851
|
-
`${what} is ${length} bytes, more than any canonical 32-bit bitmap serializes to (${MAX_SERIALIZED_LOAD_BYTES}).
|
|
4026
|
+
`${what} is ${length} bytes, more than any canonical 32-bit bitmap serializes to (${MAX_SERIALIZED_LOAD_BYTES}). If it was serialized from a bitmap, call runOptimize() on the bitmap first.`
|
|
3852
4027
|
);
|
|
3853
4028
|
}
|
|
3854
|
-
function prepareLoadInput(input, codec) {
|
|
4029
|
+
function prepareLoadInput(input, codec, subject = "a load") {
|
|
3855
4030
|
if (typeof input !== "object" || input === null) {
|
|
3856
4031
|
throw new ValidationError(
|
|
3857
|
-
|
|
4032
|
+
`${subject} takes ids, { serialized } or { bitmap }; got ${String(input)}`
|
|
3858
4033
|
);
|
|
3859
4034
|
}
|
|
3860
4035
|
const tag = tagOf(input);
|
|
3861
4036
|
if (tag === "Uint8Array" || tag === "Uint8ClampedArray") {
|
|
3862
4037
|
throw new ValidationError(
|
|
3863
|
-
|
|
4038
|
+
`${subject}'s ids cannot be a ${tag} or Buffer: each byte would be loaded as an id. Pass portable Roaring bytes as { serialized }, and ids as a Uint32Array or an array of numbers.`
|
|
3864
4039
|
);
|
|
3865
4040
|
}
|
|
3866
4041
|
if (Symbol.iterator in input || Symbol.asyncIterator in input) return input;
|
|
3867
4042
|
const keys = Object.keys(input);
|
|
3868
4043
|
if (keys.length === 1 && keys[0] === "serialized") {
|
|
3869
|
-
return
|
|
4044
|
+
return new DecodedLoadInput(
|
|
4045
|
+
decodeSerialized(input.serialized, codec, "{ serialized }")
|
|
4046
|
+
);
|
|
3870
4047
|
}
|
|
3871
4048
|
if (keys.length === 1 && keys[0] === "bitmap") {
|
|
3872
4049
|
const bitmap = input.bitmap;
|
|
@@ -3878,13 +4055,13 @@ function prepareLoadInput(input, codec) {
|
|
|
3878
4055
|
const size = bitmap.getSerializationSizeInBytes("portable");
|
|
3879
4056
|
if (typeof size === "number" && size > MAX_SERIALIZED_LOAD_BYTES) throw overCap(what, size);
|
|
3880
4057
|
}
|
|
3881
|
-
return
|
|
4058
|
+
return new DecodedLoadInput(decodeSerialized(bitmap.serialize("portable"), codec, what));
|
|
3882
4059
|
}
|
|
3883
4060
|
throw new ValidationError(
|
|
3884
|
-
|
|
4061
|
+
`${subject} takes ids (an iterable of integers), { serialized } or { bitmap }, as the only key; got an object with keys [${keys.join(", ")}].${subject === "a load" ? " Load options go in the next argument." : ""}`
|
|
3885
4062
|
);
|
|
3886
4063
|
}
|
|
3887
|
-
function
|
|
4064
|
+
function decodeSerialized(bytes, codec, what = "the buffer") {
|
|
3888
4065
|
if (typeof bytes !== "object" || bytes === null || tagOf(bytes) !== "Uint8Array") {
|
|
3889
4066
|
throw new ValidationError(`${what} must be a Uint8Array of portable Roaring bytes`);
|
|
3890
4067
|
}
|
|
@@ -3892,9 +4069,7 @@ function decode(bytes, what, codec) {
|
|
|
3892
4069
|
if (view.byteLength > MAX_SERIALIZED_LOAD_BYTES) throw overCap(what, view.byteLength);
|
|
3893
4070
|
const own = isShared(view.buffer) ? new Uint8Array(view) : view;
|
|
3894
4071
|
try {
|
|
3895
|
-
return
|
|
3896
|
-
codec.safeDeserialize(own, MAX_SERIALIZED_LOAD_BYTES, { whole: true })
|
|
3897
|
-
);
|
|
4072
|
+
return codec.safeDeserialize(own, MAX_SERIALIZED_LOAD_BYTES, { whole: true });
|
|
3898
4073
|
} catch (err) {
|
|
3899
4074
|
if (!isIntegrityError(err)) throw err;
|
|
3900
4075
|
throw new ValidationError(`${what} is not a valid bitmap: ${err.message}`);
|
|
@@ -4037,6 +4212,8 @@ var notThePinned = (ref, generation) => new NotFoundError(
|
|
|
4037
4212
|
function versionOf(generation, lineage) {
|
|
4038
4213
|
return lineage === void 0 ? String(generation) : `${generation}:${String(lineage)}`;
|
|
4039
4214
|
}
|
|
4215
|
+
var PinMoved = class extends Error {
|
|
4216
|
+
};
|
|
4040
4217
|
function summaryOfGeneration(generation, description) {
|
|
4041
4218
|
const { cardinality, metadata } = description;
|
|
4042
4219
|
return metadata === void 0 || Object.keys(metadata).length === 0 ? { generation, cardinality } : { generation, cardinality, metadata: Object.freeze({ ...metadata }) };
|
|
@@ -4403,17 +4580,102 @@ var CrbmStorageChunkSource = class {
|
|
|
4403
4580
|
* and a `keep: 0` sweep can land in that gap, and nothing has been handed out yet, so the generation current
|
|
4404
4581
|
* once the swept one is gone is exactly what a pin taken now should hold. One retry, then propagate.
|
|
4405
4582
|
*/
|
|
4406
|
-
async pinGeneration(ref) {
|
|
4583
|
+
async pinGeneration(ref, lease) {
|
|
4584
|
+
for (let moved = 0; ; moved++) {
|
|
4585
|
+
try {
|
|
4586
|
+
try {
|
|
4587
|
+
return await this.pinOnce(ref, lease);
|
|
4588
|
+
} catch (err) {
|
|
4589
|
+
if (!isNotFoundError(err)) throw err;
|
|
4590
|
+
return await this.pinOnce(ref, lease);
|
|
4591
|
+
}
|
|
4592
|
+
} catch (err) {
|
|
4593
|
+
if (!(err instanceof PinMoved)) throw err;
|
|
4594
|
+
if (moved >= 2) {
|
|
4595
|
+
throw new WriteConflictError(
|
|
4596
|
+
`pin: the pointer of "${ref.segment}" moved on every attempt to lease its current generation`
|
|
4597
|
+
);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Open one named generation and verify it is the object `at.fingerprint` names, for a pin the caller reopens. One
|
|
4604
|
+
* row read and one tail read with a registry (none of the row for a store without one): the open is the one a
|
|
4605
|
+
* pinned read makes ({@link readerAt}), memoised under the version the pin will read by, so the pin's first read
|
|
4606
|
+
* finds it open. A generation that is gone, above the row's pointer, on a destroyed or absent row, or another
|
|
4607
|
+
* object than the fingerprint names, throws `NotFoundError`; nothing here reads empty.
|
|
4608
|
+
*
|
|
4609
|
+
* A generation counts as published while the pointer is at or above it. A rollback deletes nothing, so after one
|
|
4610
|
+
* and a later load, a generation it rolled back from can be reopened while its object is still stored and the
|
|
4611
|
+
* fingerprint matches. Like `pinGeneration`'s pinned reads, this opens the object without the live open's check
|
|
4612
|
+
* of the row's summary against it, since that check is made where the row's own generation is read.
|
|
4613
|
+
*/
|
|
4614
|
+
async pinGenerationAt(ref, at, lease) {
|
|
4615
|
+
const { generation, fingerprint } = at;
|
|
4616
|
+
const gone = (why) => new NotFoundError(
|
|
4617
|
+
`segment "${ref.segment}" generation ${generation} cannot be pinned: ${why}`
|
|
4618
|
+
);
|
|
4619
|
+
let version = versionOf(generation, void 0);
|
|
4620
|
+
let installed;
|
|
4621
|
+
let pinKey;
|
|
4622
|
+
let memoised = false;
|
|
4623
|
+
if (this.registry !== void 0) {
|
|
4624
|
+
const record = await this.registry.get(ref);
|
|
4625
|
+
if (record === null || record.status === "destroyed")
|
|
4626
|
+
throw gone("the segment has no readable row");
|
|
4627
|
+
if (record.currentGen === null || generation > record.currentGen) {
|
|
4628
|
+
throw gone("it is not a published generation of this segment");
|
|
4629
|
+
}
|
|
4630
|
+
let lineage = record.token;
|
|
4631
|
+
if (lease !== void 0) {
|
|
4632
|
+
const taken = await lease.take(record);
|
|
4633
|
+
if (taken === "moved") throw gone("the segment moved while its lease was taken");
|
|
4634
|
+
lineage = taken.token;
|
|
4635
|
+
}
|
|
4636
|
+
version = versionOf(generation, lineage);
|
|
4637
|
+
const key = this.pinnedKey(ref, version);
|
|
4638
|
+
memoised = this.snapshots.peek(key) !== void 0;
|
|
4639
|
+
if (!memoised) {
|
|
4640
|
+
installed = this.install(
|
|
4641
|
+
key,
|
|
4642
|
+
Snapshot.eager(
|
|
4643
|
+
this.openForTarget(ref, {
|
|
4644
|
+
generation,
|
|
4645
|
+
lineage,
|
|
4646
|
+
wrappedDeks: record.wrappedDeks
|
|
4647
|
+
})
|
|
4648
|
+
)
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
pinKey = key;
|
|
4652
|
+
}
|
|
4653
|
+
if (this.registry === void 0) {
|
|
4654
|
+
memoised = this.snapshots.peek(this.pinnedKey(ref, version, fingerprint)) !== void 0;
|
|
4655
|
+
}
|
|
4407
4656
|
try {
|
|
4408
|
-
|
|
4657
|
+
await this.readerAt(ref, generation, version, fingerprint);
|
|
4409
4658
|
} catch (err) {
|
|
4410
|
-
if (
|
|
4411
|
-
|
|
4659
|
+
if (installed !== void 0 && pinKey !== void 0 && this.snapshots.peek(pinKey) === installed) {
|
|
4660
|
+
this.snapshots.delete(pinKey);
|
|
4661
|
+
}
|
|
4662
|
+
throw err;
|
|
4663
|
+
}
|
|
4664
|
+
if (!memoised) {
|
|
4665
|
+
const held = this.heldKey(ref, version, fingerprint);
|
|
4666
|
+
this.replacedPins.delete(held);
|
|
4667
|
+
this.checking.delete(held);
|
|
4412
4668
|
}
|
|
4669
|
+
return { generation, version, fingerprint };
|
|
4413
4670
|
}
|
|
4414
|
-
async pinOnce(ref) {
|
|
4415
|
-
|
|
4671
|
+
async pinOnce(ref, lease) {
|
|
4672
|
+
let live = await this.resolveLive(ref);
|
|
4416
4673
|
if (live === null) return null;
|
|
4674
|
+
if (lease !== void 0 && live.row !== void 0) {
|
|
4675
|
+
const taken = await lease.take(live.row);
|
|
4676
|
+
if (taken === "moved") throw new PinMoved();
|
|
4677
|
+
live = { ...live, target: { ...live.target, lineage: taken.token } };
|
|
4678
|
+
}
|
|
4417
4679
|
const target = live.target;
|
|
4418
4680
|
const version = versionOf(target.generation, target.lineage);
|
|
4419
4681
|
let reader;
|
|
@@ -4709,6 +4971,7 @@ var CrbmStorageChunkSource = class {
|
|
|
4709
4971
|
const unwrap = keys === void 0 || keys.length === 0 || this.keystore === void 0 ? void 0 : prior?.unwrap !== void 0 && sameKeys(prior.target.wrappedDeks, keys) ? prior.unwrap : once(() => this.keystore.openDek(keys));
|
|
4710
4972
|
const crypto = once(() => this.cryptoForRead(ref, generation, keys, unwrap));
|
|
4711
4973
|
return {
|
|
4974
|
+
row: record,
|
|
4712
4975
|
target: { generation, lineage: record.token, wrappedDeks: record.wrappedDeks },
|
|
4713
4976
|
crypto,
|
|
4714
4977
|
unwrap,
|
|
@@ -5021,6 +5284,18 @@ var CrbmStorageChunkSource = class {
|
|
|
5021
5284
|
}
|
|
5022
5285
|
}
|
|
5023
5286
|
};
|
|
5287
|
+
async function encodedChunksOfInput(ids, codec, clock) {
|
|
5288
|
+
if (ids instanceof DecodedLoadInput && ids.bitmap.encodeChunks !== void 0) {
|
|
5289
|
+
const pause = yieldEvery(clock, 1);
|
|
5290
|
+
await pause();
|
|
5291
|
+
const chunks = ids.bitmap.encodeChunks();
|
|
5292
|
+
await pause();
|
|
5293
|
+
return chunks;
|
|
5294
|
+
}
|
|
5295
|
+
return encodeEach(
|
|
5296
|
+
await bucketIds(ids instanceof DecodedLoadInput ? ids.bitmap : ids, codec, clock)
|
|
5297
|
+
);
|
|
5298
|
+
}
|
|
5024
5299
|
function* encodeEach(chunks) {
|
|
5025
5300
|
for (const { chunkKey, bitmap } of chunks) {
|
|
5026
5301
|
if (bitmap.isEmpty) continue;
|
|
@@ -5146,11 +5421,24 @@ function refuseCleartextOntoKey(key, record, cleartext) {
|
|
|
5146
5421
|
}
|
|
5147
5422
|
var UNANSWERED_RESENDS = 3;
|
|
5148
5423
|
var UNANSWERED_RESEND_BASE_MS = 500;
|
|
5149
|
-
|
|
5424
|
+
function leasesPatch(record, options) {
|
|
5425
|
+
const held = record.leases;
|
|
5426
|
+
if (held === void 0) return {};
|
|
5427
|
+
if (options.clearLeases === true) return { leases: void 0 };
|
|
5428
|
+
if (options.leasesNow === void 0) return {};
|
|
5429
|
+
const now = options.leasesNow();
|
|
5430
|
+
const live = held.filter((e) => isLive(e, now));
|
|
5431
|
+
if (live.length === held.length) return {};
|
|
5432
|
+
return { leases: live.length === 0 ? void 0 : live };
|
|
5433
|
+
}
|
|
5434
|
+
async function publishGenerationKept(registry, key, options = {}) {
|
|
5150
5435
|
let fresh;
|
|
5151
5436
|
let failedOn;
|
|
5152
5437
|
let unanswered;
|
|
5153
5438
|
let resends = 0;
|
|
5439
|
+
const churn = leaseChurn({ clock: options.clock, rng: options.rng });
|
|
5440
|
+
let throughChurn = false;
|
|
5441
|
+
let waited;
|
|
5154
5442
|
let sawUnanswered = false;
|
|
5155
5443
|
let lastRecord;
|
|
5156
5444
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
@@ -5161,6 +5449,8 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5161
5449
|
fresh = void 0;
|
|
5162
5450
|
unanswered = void 0;
|
|
5163
5451
|
lastRecord = record;
|
|
5452
|
+
let next;
|
|
5453
|
+
let wrote;
|
|
5164
5454
|
if (failedOn !== void 0) {
|
|
5165
5455
|
let landed;
|
|
5166
5456
|
try {
|
|
@@ -5168,57 +5458,78 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5168
5458
|
} catch (proofErr) {
|
|
5169
5459
|
throw outcomeUnknown(key, proofErr);
|
|
5170
5460
|
}
|
|
5171
|
-
if (landed)
|
|
5461
|
+
if (landed)
|
|
5462
|
+
return {
|
|
5463
|
+
published: true,
|
|
5464
|
+
kept: { ...nextKept(failedOn, key.generation, options.keep), token: void 0 }
|
|
5465
|
+
};
|
|
5172
5466
|
if (record !== null && record.status === "active" && record.currentGen === key.generation) {
|
|
5173
|
-
return
|
|
5467
|
+
return REFUSED;
|
|
5174
5468
|
}
|
|
5175
5469
|
failedOn = void 0;
|
|
5176
5470
|
}
|
|
5177
5471
|
try {
|
|
5178
5472
|
if (options.expectFrom !== void 0 && record?.currentGen !== options.expectFrom) {
|
|
5179
|
-
return
|
|
5473
|
+
return REFUSED;
|
|
5180
5474
|
}
|
|
5181
5475
|
if (options.expectAbsent === true && record !== null) {
|
|
5182
|
-
return
|
|
5476
|
+
return REFUSED;
|
|
5183
5477
|
}
|
|
5478
|
+
throughChurn = false;
|
|
5184
5479
|
if (options.expectToken !== void 0 && record?.token !== options.expectToken) {
|
|
5185
|
-
|
|
5480
|
+
const held = options.expectRow;
|
|
5481
|
+
if (record !== null && held != null && record === waited && onlyLeasesDiffer(held, record)) {
|
|
5482
|
+
throughChurn = true;
|
|
5483
|
+
} else {
|
|
5484
|
+
const settled2 = record === waited ? void 0 : await churn.settle(held, record, () => registry.get(key));
|
|
5485
|
+
if (settled2 !== void 0) {
|
|
5486
|
+
waited = settled2.row;
|
|
5487
|
+
fresh = settled2.row;
|
|
5488
|
+
attempt -= 1;
|
|
5489
|
+
continue;
|
|
5490
|
+
}
|
|
5491
|
+
return REFUSED;
|
|
5492
|
+
}
|
|
5186
5493
|
}
|
|
5187
5494
|
if (record === null && options.summary !== void 0 && "sealed" in options.summary && options.wrappedDeks === void 0) {
|
|
5188
5495
|
if (reused) continue;
|
|
5189
|
-
return
|
|
5496
|
+
return REFUSED;
|
|
5190
5497
|
}
|
|
5191
5498
|
if (record === null) {
|
|
5192
|
-
|
|
5499
|
+
next = nextKept(null, key.generation, options.keep);
|
|
5500
|
+
wrote = (await registry.create(
|
|
5193
5501
|
key,
|
|
5194
5502
|
{
|
|
5195
5503
|
currentGen: key.generation,
|
|
5196
5504
|
wrappedDeks: options.wrappedDeks,
|
|
5197
|
-
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5505
|
+
...options.summary === void 0 ? {} : { summary: options.summary },
|
|
5506
|
+
...next.list === void 0 ? {} : { keptGens: next.list }
|
|
5198
5507
|
},
|
|
5199
5508
|
write
|
|
5200
|
-
);
|
|
5509
|
+
)).token;
|
|
5201
5510
|
} else if (record.status === "destroyed") {
|
|
5202
5511
|
throw new ValidationError(
|
|
5203
5512
|
`segment "${key.segment}" was destroyed (crypto-shredded) while generation ${key.generation} was being written \u2014 refusing to publish it; the written object is unreadable. Use a new segment.`
|
|
5204
5513
|
);
|
|
5205
5514
|
} else if (record.currentGen === null) {
|
|
5206
5515
|
refuseCleartextOntoKey(key, record, options.cleartext);
|
|
5207
|
-
|
|
5516
|
+
next = nextKept(record, key.generation, options.keep);
|
|
5517
|
+
wrote = (await registry.compareAndSwap(
|
|
5208
5518
|
key,
|
|
5209
5519
|
record.token,
|
|
5210
5520
|
{
|
|
5211
5521
|
currentGen: key.generation,
|
|
5212
5522
|
...options.wrappedDeks === void 0 ? {} : { wrappedDeks: options.wrappedDeks },
|
|
5213
|
-
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5523
|
+
...options.summary === void 0 ? {} : { summary: options.summary },
|
|
5524
|
+
...next.list === void 0 ? {} : { keptGens: next.list }
|
|
5214
5525
|
},
|
|
5215
5526
|
write
|
|
5216
|
-
);
|
|
5527
|
+
)).token;
|
|
5217
5528
|
} else if (record.currentGen > key.generation) {
|
|
5218
|
-
return
|
|
5529
|
+
return REFUSED;
|
|
5219
5530
|
} else if (record.currentGen === key.generation) {
|
|
5220
5531
|
if (reused) continue;
|
|
5221
|
-
return true;
|
|
5532
|
+
return { published: true };
|
|
5222
5533
|
} else {
|
|
5223
5534
|
if (options.wrappedDeks !== void 0 && options.wrappedDeks.length > 0) {
|
|
5224
5535
|
throw new ValidationError(
|
|
@@ -5226,20 +5537,27 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5226
5537
|
);
|
|
5227
5538
|
}
|
|
5228
5539
|
refuseCleartextOntoKey(key, record, options.cleartext);
|
|
5229
|
-
|
|
5540
|
+
next = nextKept(record, key.generation, options.keep);
|
|
5541
|
+
wrote = (await registry.compareAndSwap(
|
|
5230
5542
|
key,
|
|
5231
5543
|
record.token,
|
|
5232
5544
|
{
|
|
5233
5545
|
currentGen: key.generation,
|
|
5234
|
-
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5546
|
+
...options.summary === void 0 ? {} : { summary: options.summary },
|
|
5547
|
+
...next.list === void 0 ? {} : { keptGens: next.list },
|
|
5548
|
+
...leasesPatch(record, options)
|
|
5235
5549
|
},
|
|
5236
5550
|
write
|
|
5237
|
-
);
|
|
5551
|
+
)).token;
|
|
5238
5552
|
}
|
|
5239
|
-
return
|
|
5553
|
+
return {
|
|
5554
|
+
published: true,
|
|
5555
|
+
kept: { ...next ?? { list: void 0, evict: [] }, token: wrote }
|
|
5556
|
+
};
|
|
5240
5557
|
} catch (err) {
|
|
5241
5558
|
if (isWriteConflictError(err)) {
|
|
5242
5559
|
failedOn = record;
|
|
5560
|
+
if (throughChurn) attempt -= 1;
|
|
5243
5561
|
continue;
|
|
5244
5562
|
}
|
|
5245
5563
|
if (!isTransientError(err)) throw err;
|
|
@@ -5268,10 +5586,12 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5268
5586
|
}
|
|
5269
5587
|
const final = await registry.get(key);
|
|
5270
5588
|
if (final !== null && final.currentGen !== null && final.currentGen >= key.generation) {
|
|
5271
|
-
if (final.currentGen !== key.generation) return
|
|
5272
|
-
if (!sawUnanswered) return true;
|
|
5589
|
+
if (final.currentGen !== key.generation) return REFUSED;
|
|
5590
|
+
if (!sawUnanswered) return { published: true };
|
|
5273
5591
|
try {
|
|
5274
|
-
return
|
|
5592
|
+
return {
|
|
5593
|
+
published: await landedHere(final, lastRecord ?? null, key, options.holdsOwnObject)
|
|
5594
|
+
};
|
|
5275
5595
|
} catch (proofErr) {
|
|
5276
5596
|
throw outcomeUnknown(key, proofErr);
|
|
5277
5597
|
}
|
|
@@ -5281,6 +5601,10 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5281
5601
|
`publishGeneration: contention setting currentGen for ${key.segment}`
|
|
5282
5602
|
);
|
|
5283
5603
|
}
|
|
5604
|
+
var REFUSED = { published: false };
|
|
5605
|
+
async function publishGeneration(registry, key, options = {}) {
|
|
5606
|
+
return (await publishGenerationKept(registry, key, options)).published;
|
|
5607
|
+
}
|
|
5284
5608
|
async function landedHere(now, before, key, holdsOwnObject) {
|
|
5285
5609
|
if (now === null || now.status === "destroyed" || now.currentGen !== key.generation) return false;
|
|
5286
5610
|
if (before !== null && !sameIncarnation(before, now)) return false;
|
|
@@ -5315,17 +5639,10 @@ async function bulkLoadAhead(driver, ref, ahead, ids, options = {}) {
|
|
|
5315
5639
|
}
|
|
5316
5640
|
const codec = requireCodec(options.codec, "bulkLoadCrbmGeneration");
|
|
5317
5641
|
let chunks;
|
|
5318
|
-
if (ids instanceof
|
|
5319
|
-
const pause = yieldEvery(options.clock, 1);
|
|
5320
|
-
await pause();
|
|
5321
|
-
chunks = ids.bitmap.encodeChunks();
|
|
5322
|
-
await pause();
|
|
5323
|
-
} else if (ids instanceof ChunkLoadInput) {
|
|
5642
|
+
if (ids instanceof ChunkLoadInput) {
|
|
5324
5643
|
chunks = encodeEach(rangeCheckedAtWrite(await collectChunks(ids, codec, options.clock)));
|
|
5325
5644
|
} else {
|
|
5326
|
-
chunks =
|
|
5327
|
-
await bucketIds(ids instanceof DecodedLoadInput ? ids.bitmap : ids, codec, options.clock)
|
|
5328
|
-
);
|
|
5645
|
+
chunks = await encodedChunksOfInput(ids, codec, options.clock);
|
|
5329
5646
|
}
|
|
5330
5647
|
if (options.keystore !== void 0 && options.registry === void 0) {
|
|
5331
5648
|
throw new ValidationError("an encrypted load requires a registry to store the wrapped DEK");
|
|
@@ -5838,7 +6155,7 @@ async function runLoad(ref, prepare, deps, options) {
|
|
|
5838
6155
|
const key = { namespace: ref.namespace, segment: ref.segment, generation };
|
|
5839
6156
|
const reclaim = async () => {
|
|
5840
6157
|
const now = await deps.registry.get(ref);
|
|
5841
|
-
if (now !== null && (now.token === fromToken || now.status === "destroyed")) {
|
|
6158
|
+
if (now !== null && (now.token === fromToken || now.status === "destroyed" || row !== null && onlyLeasesDiffer(row, now))) {
|
|
5842
6159
|
await deps.storage.delete(key);
|
|
5843
6160
|
return;
|
|
5844
6161
|
}
|
|
@@ -5880,14 +6197,17 @@ async function runLoad(ref, prepare, deps, options) {
|
|
|
5880
6197
|
if (guard?.minRetained !== void 0 && before !== null) {
|
|
5881
6198
|
if (written.cardinality < before * guard.minRetained) return refuse("min-retained");
|
|
5882
6199
|
}
|
|
6200
|
+
const leasesNow = deps.clock === void 0 ? void 0 : () => deps.clock.now();
|
|
5883
6201
|
let published;
|
|
5884
6202
|
try {
|
|
5885
|
-
published = await
|
|
6203
|
+
published = await publishGenerationKept(deps.registry, key, {
|
|
6204
|
+
keep,
|
|
6205
|
+
leasesNow,
|
|
5886
6206
|
row,
|
|
5887
6207
|
wrappedDeks: written.wrappedDeks,
|
|
5888
6208
|
summary: written.summary,
|
|
5889
6209
|
cleartext: !written.encrypted,
|
|
5890
|
-
...fromToken === void 0 ? {} : { expectToken: fromToken },
|
|
6210
|
+
...fromToken === void 0 ? {} : { expectToken: fromToken, expectRow: row },
|
|
5891
6211
|
...needsBefore && fromGeneration !== void 0 ? { expectFrom: fromGeneration } : {},
|
|
5892
6212
|
// The third case, and the one the two fences above structurally cannot cover: the guard judged a segment
|
|
5893
6213
|
// that had NO ROW. Both `expectFrom` and `expectToken` compare against a value read from a row, so with no
|
|
@@ -5911,7 +6231,7 @@ async function runLoad(ref, prepare, deps, options) {
|
|
|
5911
6231
|
if (isValidationError(err) || err instanceof KeyUnavailableError) await reclaim();
|
|
5912
6232
|
throw err;
|
|
5913
6233
|
}
|
|
5914
|
-
if (!published) return refuse("superseded");
|
|
6234
|
+
if (!published.published) return refuse("superseded");
|
|
5915
6235
|
audit.onEvent({
|
|
5916
6236
|
kind: "segment.publish",
|
|
5917
6237
|
segment: ref.segment,
|
|
@@ -5922,7 +6242,10 @@ async function runLoad(ref, prepare, deps, options) {
|
|
|
5922
6242
|
const collected = await collectAfterLoad(ref, deps, {
|
|
5923
6243
|
generation,
|
|
5924
6244
|
keep,
|
|
5925
|
-
byName: checked &&
|
|
6245
|
+
byName: checked && deps.collectByListing !== true,
|
|
6246
|
+
currentGone: currentObjectGone,
|
|
6247
|
+
...leasesNow === void 0 ? {} : { leases: { now: leasesNow } },
|
|
6248
|
+
kept: published.kept,
|
|
5926
6249
|
...current.fromSummary && fromGeneration !== void 0 ? { proveCurrent: { ...ref, generation: fromGeneration } } : {}
|
|
5927
6250
|
});
|
|
5928
6251
|
return {
|
|
@@ -5937,123 +6260,2390 @@ async function runLoad(ref, prepare, deps, options) {
|
|
|
5937
6260
|
};
|
|
5938
6261
|
}
|
|
5939
6262
|
|
|
5940
|
-
// src/core/
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
for await (const key of deps.storage.list(ref)) seen.add(key.generation);
|
|
5947
|
-
const described = record === null ? void 0 : await currentDescription(ref, record, deps);
|
|
5948
|
-
return [...seen].sort((a, b) => a - b).map(
|
|
5949
|
-
(generation) => generation === current ? { generation, current: true, ...described } : { generation, current: false }
|
|
5950
|
-
);
|
|
5951
|
-
}
|
|
5952
|
-
async function currentDescription(ref, record, deps) {
|
|
5953
|
-
if (record.summary === void 0) return void 0;
|
|
5954
|
-
let aead;
|
|
5955
|
-
if ("sealed" in record.summary && record.wrappedDeks !== void 0 && deps.keystore !== void 0) {
|
|
5956
|
-
try {
|
|
5957
|
-
aead = await deps.keystore.openDek(record.wrappedDeks);
|
|
5958
|
-
} catch {
|
|
5959
|
-
return void 0;
|
|
5960
|
-
}
|
|
5961
|
-
}
|
|
5962
|
-
const usable = usableSummary(ref, record, aead);
|
|
5963
|
-
if (usable === void 0) return void 0;
|
|
5964
|
-
const metadata = usable.metadata;
|
|
5965
|
-
return metadata === void 0 || Object.keys(metadata).length === 0 ? { cardinality: usable.cardinality } : { cardinality: usable.cardinality, metadata: Object.freeze({ ...metadata }) };
|
|
5966
|
-
}
|
|
5967
|
-
async function rollbackSegment(ref, toGeneration, deps, options = {}) {
|
|
5968
|
-
validateUserRef(ref);
|
|
5969
|
-
if (!Number.isInteger(toGeneration) || toGeneration < 0) {
|
|
6263
|
+
// src/core/combine-expr.ts
|
|
6264
|
+
var MAX_EXPR_DEPTH = 64;
|
|
6265
|
+
var MAX_EXPR_NODES = 4096;
|
|
6266
|
+
var OPERATORS = ["and", "or", "andNot"];
|
|
6267
|
+
function compileExpr(expr, path, operands, budget = { remaining: MAX_EXPR_NODES }, depth = 0) {
|
|
6268
|
+
if (--budget.remaining < 0) {
|
|
5970
6269
|
throw new ValidationError(
|
|
5971
|
-
|
|
6270
|
+
`${path}: more than ${MAX_EXPR_NODES} nodes in one output's expression`
|
|
5972
6271
|
);
|
|
5973
6272
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
6273
|
+
if (typeof expr === "string") {
|
|
6274
|
+
const index = operands.get(expr);
|
|
6275
|
+
if (index === void 0) {
|
|
6276
|
+
throw new ValidationError(`${path}: "${expr}" does not name an operand`);
|
|
6277
|
+
}
|
|
6278
|
+
return { kind: "leaf", operand: index };
|
|
5977
6279
|
}
|
|
5978
|
-
if (
|
|
6280
|
+
if (typeof expr !== "object" || expr === null || Array.isArray(expr)) {
|
|
5979
6281
|
throw new ValidationError(
|
|
5980
|
-
|
|
6282
|
+
`${path}: an expression is an operand name or an object with exactly one of and, or, andNot; got ${expr === null ? "null" : Array.isArray(expr) ? "an array" : typeof expr}`
|
|
5981
6283
|
);
|
|
5982
6284
|
}
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
if (!present.has(toGeneration)) {
|
|
5989
|
-
const available = [...present].sort((a, b) => a - b);
|
|
5990
|
-
throw new NotFoundError(
|
|
5991
|
-
`rollback: generation ${toGeneration} of "${ref.segment}" is not in the bucket` + (available.length === 0 ? " (no generations remain)" : ` \u2014 present: ${available.join(", ")}`)
|
|
6285
|
+
const keys = Object.keys(expr);
|
|
6286
|
+
const operator = keys.length === 1 ? OPERATORS.find((op) => op === keys[0]) : void 0;
|
|
6287
|
+
if (operator === void 0) {
|
|
6288
|
+
throw new ValidationError(
|
|
6289
|
+
`${path}: an expression node holds exactly one of and, or, andNot; got ${keys.length === 0 ? "no key" : keys.map((k) => `"${k}"`).join(", ")}`
|
|
5992
6290
|
);
|
|
5993
6291
|
}
|
|
5994
|
-
if (
|
|
6292
|
+
if (depth >= MAX_EXPR_DEPTH) {
|
|
5995
6293
|
throw new ValidationError(
|
|
5996
|
-
|
|
6294
|
+
`${path}: expression nests more than ${MAX_EXPR_DEPTH} operators deep (an object that contains itself is refused here too)`
|
|
5997
6295
|
);
|
|
5998
6296
|
}
|
|
5999
|
-
const
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
try {
|
|
6003
|
-
aead = await deps.keystore.openDek(record.wrappedDeks);
|
|
6004
|
-
} catch {
|
|
6005
|
-
}
|
|
6297
|
+
const list = expr[operator];
|
|
6298
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
6299
|
+
throw new ValidationError(`${path}.${operator}: must be a non-empty array of expressions`);
|
|
6006
6300
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
{ ...ref, generation: toGeneration },
|
|
6011
|
-
crypto
|
|
6012
|
-
);
|
|
6013
|
-
if (encrypted !== keyed) {
|
|
6014
|
-
throw new IntegrityError(
|
|
6015
|
-
`rollback: generation ${toGeneration} of "${ref.segment}" is ${encrypted ? "encrypted" : "cleartext"}, but the segment is ${keyed ? "encrypted" : "cleartext"}, so no read of the segment could use it; it was never one of its generations. Roll back to another generation.`
|
|
6301
|
+
if (operator === "andNot" && list.length < 2) {
|
|
6302
|
+
throw new ValidationError(
|
|
6303
|
+
`${path}.andNot: needs at least two expressions, the one subtracted from and one to subtract`
|
|
6016
6304
|
);
|
|
6017
6305
|
}
|
|
6018
|
-
const
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6306
|
+
const kids = [];
|
|
6307
|
+
for (let i = 0; i < list.length; i++) {
|
|
6308
|
+
kids.push(compileExpr(list[i], `${path}.${operator}[${i}]`, operands, budget, depth + 1));
|
|
6309
|
+
}
|
|
6310
|
+
return { kind: operator, kids };
|
|
6311
|
+
}
|
|
6312
|
+
function depthOf(node) {
|
|
6313
|
+
if (node.kind === "leaf") return 0;
|
|
6314
|
+
let deepest = 0;
|
|
6315
|
+
for (const kid of node.kids) deepest = Math.max(deepest, depthOf(kid));
|
|
6316
|
+
return deepest + 1;
|
|
6317
|
+
}
|
|
6318
|
+
function operandsOf(node, into = /* @__PURE__ */ new Set()) {
|
|
6319
|
+
if (node.kind === "leaf") into.add(node.operand);
|
|
6320
|
+
else for (const kid of node.kids) operandsOf(kid, into);
|
|
6321
|
+
return [...into].sort((a, b) => a - b);
|
|
6322
|
+
}
|
|
6323
|
+
var CHUNK_IDS = 65536;
|
|
6324
|
+
var ANY_KEYS = new Uint16Array(0);
|
|
6325
|
+
var ANY_BOUNDS = { keys: ANY_KEYS, card: new Uint32Array(0) };
|
|
6326
|
+
var EMPTY_BOUNDS = { keys: new Uint16Array(0), card: new Uint32Array(0) };
|
|
6327
|
+
function intersectBounds(a, b) {
|
|
6328
|
+
const keys = new Uint16Array(Math.min(a.keys.length, b.keys.length));
|
|
6329
|
+
const card = new Uint32Array(keys.length);
|
|
6330
|
+
let i = 0;
|
|
6331
|
+
let j = 0;
|
|
6332
|
+
let n = 0;
|
|
6333
|
+
while (i < a.keys.length && j < b.keys.length) {
|
|
6334
|
+
const x = a.keys[i];
|
|
6335
|
+
const y = b.keys[j];
|
|
6336
|
+
if (x < y) i++;
|
|
6337
|
+
else if (y < x) j++;
|
|
6338
|
+
else {
|
|
6339
|
+
keys[n] = x;
|
|
6340
|
+
card[n] = Math.min(a.card[i], b.card[j]);
|
|
6341
|
+
n++;
|
|
6342
|
+
i++;
|
|
6343
|
+
j++;
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
return n === keys.length ? { keys, card } : { keys: keys.slice(0, n), card: card.slice(0, n) };
|
|
6347
|
+
}
|
|
6348
|
+
function unionBounds(a, b) {
|
|
6349
|
+
const keys = new Uint16Array(a.keys.length + b.keys.length);
|
|
6350
|
+
const card = new Uint32Array(keys.length);
|
|
6351
|
+
let i = 0;
|
|
6352
|
+
let j = 0;
|
|
6353
|
+
let n = 0;
|
|
6354
|
+
while (i < a.keys.length || j < b.keys.length) {
|
|
6355
|
+
const x = i < a.keys.length ? a.keys[i] : Infinity;
|
|
6356
|
+
const y = j < b.keys.length ? b.keys[j] : Infinity;
|
|
6357
|
+
if (x < y) {
|
|
6358
|
+
keys[n] = x;
|
|
6359
|
+
card[n++] = a.card[i++];
|
|
6360
|
+
} else if (y < x) {
|
|
6361
|
+
keys[n] = y;
|
|
6362
|
+
card[n++] = b.card[j++];
|
|
6363
|
+
} else {
|
|
6364
|
+
keys[n] = x;
|
|
6365
|
+
card[n++] = Math.min(CHUNK_IDS, a.card[i++] + b.card[j++]);
|
|
6033
6366
|
}
|
|
6034
6367
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6368
|
+
return { keys: keys.slice(0, n), card: card.slice(0, n) };
|
|
6369
|
+
}
|
|
6370
|
+
function intersectKeys(a, b) {
|
|
6371
|
+
if (a === ANY_KEYS) return b;
|
|
6372
|
+
if (b === ANY_KEYS) return a;
|
|
6373
|
+
const out = new Uint16Array(Math.min(a.length, b.length));
|
|
6374
|
+
let i = 0;
|
|
6375
|
+
let j = 0;
|
|
6376
|
+
let n = 0;
|
|
6377
|
+
while (i < a.length && j < b.length) {
|
|
6378
|
+
const x = a[i];
|
|
6379
|
+
const y = b[j];
|
|
6380
|
+
if (x < y) i++;
|
|
6381
|
+
else if (y < x) j++;
|
|
6382
|
+
else {
|
|
6383
|
+
out[n++] = x;
|
|
6384
|
+
i++;
|
|
6385
|
+
j++;
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
return n === out.length ? out : out.slice(0, n);
|
|
6389
|
+
}
|
|
6390
|
+
function planKeys(node, operands, into = /* @__PURE__ */ new Map()) {
|
|
6391
|
+
if (node.kind === "leaf") return operands[node.operand] ?? EMPTY_BOUNDS;
|
|
6392
|
+
let result;
|
|
6393
|
+
if (node.kind === "andNot") {
|
|
6394
|
+
result = planKeys(node.kids[0], operands, into);
|
|
6395
|
+
for (let i = 1; i < node.kids.length; i++) planKeys(node.kids[i], operands, into);
|
|
6396
|
+
} else {
|
|
6397
|
+
const kids = node.kids.map((kid) => planKeys(kid, operands, into));
|
|
6398
|
+
if (node.kind === "and") {
|
|
6399
|
+
const known = kids.filter((kid) => kid.keys !== ANY_KEYS);
|
|
6400
|
+
known.sort((a, b) => a.keys.length - b.keys.length);
|
|
6401
|
+
result = known[0] ?? ANY_BOUNDS;
|
|
6402
|
+
for (let i = 1; i < known.length && result.keys.length > 0; i++) {
|
|
6403
|
+
result = intersectBounds(result, known[i]);
|
|
6404
|
+
}
|
|
6405
|
+
} else if (kids.some((kid) => kid.keys === ANY_KEYS)) {
|
|
6406
|
+
result = ANY_BOUNDS;
|
|
6407
|
+
} else {
|
|
6408
|
+
result = kids[0];
|
|
6409
|
+
for (let i = 1; i < kids.length; i++) result = unionBounds(result, kids[i]);
|
|
6046
6410
|
}
|
|
6047
6411
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6412
|
+
into.set(node, result.keys);
|
|
6413
|
+
return result;
|
|
6414
|
+
}
|
|
6415
|
+
function keysOf(node, operands, planned) {
|
|
6416
|
+
if (node.kind === "leaf") return (operands[node.operand] ?? EMPTY_BOUNDS).keys;
|
|
6417
|
+
return planned.get(node) ?? new Uint16Array(0);
|
|
6418
|
+
}
|
|
6419
|
+
function collectFetch(node, demanded, operands, planned, fetch) {
|
|
6420
|
+
if (demanded.length === 0 && demanded !== ANY_KEYS) return;
|
|
6421
|
+
const own = keysOf(node, operands, planned);
|
|
6422
|
+
const here = intersectKeys(demanded, own);
|
|
6423
|
+
if (here.length === 0 && here !== ANY_KEYS) return;
|
|
6424
|
+
if (node.kind === "leaf") {
|
|
6425
|
+
if (own !== ANY_KEYS) fetch(node.operand, here);
|
|
6426
|
+
return;
|
|
6427
|
+
}
|
|
6428
|
+
for (const kid of node.kids) collectFetch(kid, here, operands, planned, fetch);
|
|
6429
|
+
}
|
|
6430
|
+
function canHold(node, holds) {
|
|
6431
|
+
switch (node.kind) {
|
|
6432
|
+
case "leaf":
|
|
6433
|
+
return holds(node.operand);
|
|
6434
|
+
case "and":
|
|
6435
|
+
return node.kids.every((kid) => canHold(kid, holds));
|
|
6436
|
+
case "or":
|
|
6437
|
+
return node.kids.some((kid) => canHold(kid, holds));
|
|
6438
|
+
case "andNot":
|
|
6439
|
+
return canHold(node.kids[0], holds);
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
var KeyBits = class {
|
|
6443
|
+
words = new Uint32Array(CHUNK_IDS / 32);
|
|
6444
|
+
count = 0;
|
|
6445
|
+
add(keys) {
|
|
6446
|
+
for (let i = 0; i < keys.length; i++) {
|
|
6447
|
+
const key = keys[i];
|
|
6448
|
+
const word = key >>> 5;
|
|
6449
|
+
const bit = 1 << (key & 31);
|
|
6450
|
+
if ((this.words[word] & bit) === 0) {
|
|
6451
|
+
this.words[word] = this.words[word] | bit;
|
|
6452
|
+
this.count++;
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
get size() {
|
|
6457
|
+
return this.count;
|
|
6458
|
+
}
|
|
6459
|
+
has(key) {
|
|
6460
|
+
return (this.words[key >>> 5] & 1 << (key & 31)) !== 0;
|
|
6461
|
+
}
|
|
6462
|
+
/** The keys, ascending. */
|
|
6463
|
+
toKeys() {
|
|
6464
|
+
const out = [];
|
|
6465
|
+
for (let w = 0; w < this.words.length; w++) {
|
|
6466
|
+
let bits = this.words[w];
|
|
6467
|
+
while (bits !== 0) {
|
|
6468
|
+
const low = bits & -bits;
|
|
6469
|
+
out.push(w << 5 | 31 - Math.clz32(low));
|
|
6470
|
+
bits ^= low;
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
return out;
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
|
|
6477
|
+
// src/core/combine-ledger.ts
|
|
6478
|
+
var RESIDENT_BITMAP_OVERHEAD = 512;
|
|
6479
|
+
var MAX_CHUNK_SERIALIZED = 8208;
|
|
6480
|
+
function serializedBound(cardinality) {
|
|
6481
|
+
return Math.min(2 * cardinality + 16, MAX_CHUNK_SERIALIZED);
|
|
6482
|
+
}
|
|
6483
|
+
var MAX_RESIDENT_DATA = 8800;
|
|
6484
|
+
function residentBytes(serialized) {
|
|
6485
|
+
return Math.ceil(
|
|
6486
|
+
RESIDENT_BITMAP_OVERHEAD + Math.min(1.6 * serialized, serialized + 2e3, MAX_RESIDENT_DATA)
|
|
6487
|
+
);
|
|
6488
|
+
}
|
|
6489
|
+
function residentBound(cardinality) {
|
|
6490
|
+
return residentBytes(serializedBound(cardinality));
|
|
6491
|
+
}
|
|
6492
|
+
var ResidentLedger = class {
|
|
6493
|
+
constructor(limit) {
|
|
6494
|
+
this.limit = limit;
|
|
6495
|
+
}
|
|
6496
|
+
limit;
|
|
6497
|
+
held = 0;
|
|
6498
|
+
peak = 0;
|
|
6499
|
+
get used() {
|
|
6500
|
+
return this.held;
|
|
6501
|
+
}
|
|
6502
|
+
get highWater() {
|
|
6503
|
+
return this.peak;
|
|
6504
|
+
}
|
|
6505
|
+
/** Bytes still free. */
|
|
6506
|
+
get room() {
|
|
6507
|
+
return this.limit - this.held;
|
|
6508
|
+
}
|
|
6509
|
+
/** Charge `bytes` if they fit. */
|
|
6510
|
+
tryCharge(bytes) {
|
|
6511
|
+
if (this.held + bytes > this.limit) return false;
|
|
6512
|
+
this.held += bytes;
|
|
6513
|
+
if (this.held > this.peak) this.peak = this.held;
|
|
6514
|
+
return true;
|
|
6515
|
+
}
|
|
6516
|
+
/**
|
|
6517
|
+
* Charge `bytes`, asking `makeRoom` to free some while they do not fit. `makeRoom` releases what it frees (through
|
|
6518
|
+
* {@link release}) and answers whether it freed anything; the charge is refused when it cannot.
|
|
6519
|
+
*/
|
|
6520
|
+
reserve(bytes, makeRoom) {
|
|
6521
|
+
while (!this.tryCharge(bytes)) if (!makeRoom()) return false;
|
|
6522
|
+
return true;
|
|
6523
|
+
}
|
|
6524
|
+
release(bytes) {
|
|
6525
|
+
this.held -= bytes;
|
|
6526
|
+
}
|
|
6527
|
+
};
|
|
6528
|
+
|
|
6529
|
+
// src/core/combine-feed.ts
|
|
6530
|
+
var KEY_IDS = 65536;
|
|
6531
|
+
function checkFeed(feed, stored) {
|
|
6532
|
+
if (typeof feed !== "object" || feed === null || Array.isArray(feed)) {
|
|
6533
|
+
throw new ValidationError("feed must be an object with names, records and counts");
|
|
6534
|
+
}
|
|
6535
|
+
const f = feed;
|
|
6536
|
+
const names = f.names;
|
|
6537
|
+
const records = f.records;
|
|
6538
|
+
const counts = f.counts;
|
|
6539
|
+
const mayBeEmpty = f.mayBeEmpty;
|
|
6540
|
+
const epoch = f.epoch;
|
|
6541
|
+
if (!Array.isArray(names) || names.length === 0) {
|
|
6542
|
+
throw new ValidationError("feed.names must be a non-empty array of the fed operand names");
|
|
6543
|
+
}
|
|
6544
|
+
const declared = /* @__PURE__ */ new Set();
|
|
6545
|
+
for (const name of names) {
|
|
6546
|
+
if (typeof name !== "string" || name === "") {
|
|
6547
|
+
throw new ValidationError("feed.names must hold non-empty strings");
|
|
6548
|
+
}
|
|
6549
|
+
if (declared.has(name)) throw new ValidationError(`feed.names lists "${name}" twice`);
|
|
6550
|
+
if (stored.has(name)) {
|
|
6551
|
+
throw new ValidationError(
|
|
6552
|
+
`"${name}" is both a stored operand and a fed one: a name is one or the other`
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
declared.add(name);
|
|
6556
|
+
}
|
|
6557
|
+
const iterable = records;
|
|
6558
|
+
if (typeof iterable !== "object" && typeof iterable !== "function" || iterable === null || typeof iterable[Symbol.asyncIterator] !== "function") {
|
|
6559
|
+
throw new ValidationError(
|
|
6560
|
+
"feed.records must be an async iterable (an async generator, for one): wrap an array in `async function*`"
|
|
6561
|
+
);
|
|
6562
|
+
}
|
|
6563
|
+
if (typeof counts !== "function" && (typeof counts !== "object" || counts === null || Array.isArray(counts))) {
|
|
6564
|
+
throw new ValidationError(
|
|
6565
|
+
"feed.counts is required: an object of ids per fed operand, or a function returning one"
|
|
6566
|
+
);
|
|
6567
|
+
}
|
|
6568
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
6569
|
+
if (mayBeEmpty !== void 0) {
|
|
6570
|
+
if (!Array.isArray(mayBeEmpty)) {
|
|
6571
|
+
throw new ValidationError("mayBeEmpty must be an array of fed operand names");
|
|
6572
|
+
}
|
|
6573
|
+
for (const name of mayBeEmpty) {
|
|
6574
|
+
if (typeof name !== "string" || !declared.has(name)) {
|
|
6575
|
+
throw new ValidationError(
|
|
6576
|
+
`mayBeEmpty names ${typeof name === "string" ? `"${name}"` : "something that is not a name"}, which is not a fed operand`
|
|
6577
|
+
);
|
|
6578
|
+
}
|
|
6579
|
+
allowed.add(name);
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
let checkedEpoch;
|
|
6583
|
+
if (epoch !== void 0) {
|
|
6584
|
+
const e = epoch;
|
|
6585
|
+
if (typeof e !== "object" || e === null || typeof e.moved !== "function") {
|
|
6586
|
+
throw new ValidationError("feed.epoch must be { moved }");
|
|
6587
|
+
}
|
|
6588
|
+
checkedEpoch = { moved: e.moved };
|
|
6589
|
+
}
|
|
6590
|
+
return {
|
|
6591
|
+
names: [...declared],
|
|
6592
|
+
records,
|
|
6593
|
+
counts,
|
|
6594
|
+
mayBeEmpty: allowed,
|
|
6595
|
+
epoch: checkedEpoch
|
|
6596
|
+
};
|
|
6597
|
+
}
|
|
6598
|
+
var TYPED = Object.getPrototypeOf(Uint8Array.prototype);
|
|
6599
|
+
var typedGetter = (name) => Object.getOwnPropertyDescriptor(TYPED, name)?.get;
|
|
6600
|
+
var brandOf = typedGetter(Symbol.toStringTag);
|
|
6601
|
+
var bufferOf2 = typedGetter("buffer");
|
|
6602
|
+
var byteOffsetOf2 = typedGetter("byteOffset");
|
|
6603
|
+
var byteLengthOf2 = typedGetter("byteLength");
|
|
6604
|
+
var detachedOf = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "detached")?.get;
|
|
6605
|
+
var valuesOf = TYPED.values;
|
|
6606
|
+
function plainUint32(value) {
|
|
6607
|
+
let tag;
|
|
6608
|
+
try {
|
|
6609
|
+
tag = brandOf.call(value);
|
|
6610
|
+
} catch {
|
|
6611
|
+
return void 0;
|
|
6612
|
+
}
|
|
6613
|
+
if (tag !== "Uint32Array") return void 0;
|
|
6614
|
+
const buffer = bufferOf2.call(value);
|
|
6615
|
+
const length = byteLengthOf2.call(value);
|
|
6616
|
+
if (length === 0) {
|
|
6617
|
+
try {
|
|
6618
|
+
if (detachedOf?.call(buffer) === true) return "detached";
|
|
6619
|
+
} catch {
|
|
6620
|
+
}
|
|
6621
|
+
try {
|
|
6622
|
+
valuesOf.call(value);
|
|
6623
|
+
} catch {
|
|
6624
|
+
return "out of bounds";
|
|
6625
|
+
}
|
|
6626
|
+
return new Uint32Array(0);
|
|
6627
|
+
}
|
|
6628
|
+
return new Uint32Array(buffer, byteOffsetOf2.call(value), length >>> 2);
|
|
6629
|
+
}
|
|
6630
|
+
function isPlainObject(value) {
|
|
6631
|
+
const proto = Object.getPrototypeOf(value);
|
|
6632
|
+
if (proto === null) return true;
|
|
6633
|
+
return Object.getPrototypeOf(proto) === null && Object.hasOwn(proto, "hasOwnProperty");
|
|
6634
|
+
}
|
|
6635
|
+
var FeedClosed = class extends Error {
|
|
6636
|
+
};
|
|
6637
|
+
var FeedStale = class extends Error {
|
|
6638
|
+
};
|
|
6639
|
+
var FeedCursor = class {
|
|
6640
|
+
constructor(deps) {
|
|
6641
|
+
this.deps = deps;
|
|
6642
|
+
this.index = new Map(deps.feed.names.map((name, i) => [name, i]));
|
|
6643
|
+
this.seen = new Uint8Array(deps.feed.names.length);
|
|
6644
|
+
this.counts = deps.feed.names.map(() => 0);
|
|
6645
|
+
}
|
|
6646
|
+
deps;
|
|
6647
|
+
iterator;
|
|
6648
|
+
group;
|
|
6649
|
+
look;
|
|
6650
|
+
current;
|
|
6651
|
+
ended = false;
|
|
6652
|
+
closed = false;
|
|
6653
|
+
finished = false;
|
|
6654
|
+
held = 0;
|
|
6655
|
+
returned;
|
|
6656
|
+
prevKey = -1;
|
|
6657
|
+
index;
|
|
6658
|
+
seen;
|
|
6659
|
+
touched = [];
|
|
6660
|
+
counts;
|
|
6661
|
+
scratch = new Uint32Array(KEY_IDS);
|
|
6662
|
+
recordCount = 0;
|
|
6663
|
+
keyCount = 0;
|
|
6664
|
+
idCount = 0;
|
|
6665
|
+
/** Whether the feed can still yield a key: not closed, and neither ended nor drained. */
|
|
6666
|
+
get active() {
|
|
6667
|
+
return !this.closed && (!this.ended || this.group !== void 0 || this.look !== void 0);
|
|
6668
|
+
}
|
|
6669
|
+
/** Whether the whole feed was read and every end-of-feed check passed. */
|
|
6670
|
+
get complete() {
|
|
6671
|
+
return this.finished;
|
|
6672
|
+
}
|
|
6673
|
+
/** The ledger bytes the cursor holds. */
|
|
6674
|
+
get heldBytes() {
|
|
6675
|
+
return this.held;
|
|
6676
|
+
}
|
|
6677
|
+
get stats() {
|
|
6678
|
+
return { records: this.recordCount, keys: this.keyCount, ids: this.idCount };
|
|
6679
|
+
}
|
|
6680
|
+
/** The key the feed holds next, pulling the first record of it if none is held; `undefined` once nothing is left. */
|
|
6681
|
+
async nextKey() {
|
|
6682
|
+
if (this.closed) return void 0;
|
|
6683
|
+
if (this.group === void 0) {
|
|
6684
|
+
if (this.look !== void 0) {
|
|
6685
|
+
this.group = this.look;
|
|
6686
|
+
this.look = void 0;
|
|
6687
|
+
} else {
|
|
6688
|
+
await this.fill();
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
return this.group?.key;
|
|
6692
|
+
}
|
|
6693
|
+
/** Read until a record with a higher key arrives, or the feed ends: the group of {@link nextKey} is then whole. */
|
|
6694
|
+
async completeKey() {
|
|
6695
|
+
const group = this.group;
|
|
6696
|
+
if (group === void 0) return;
|
|
6697
|
+
while (this.look === void 0 && !this.ended) {
|
|
6698
|
+
const record = await this.pull();
|
|
6699
|
+
if (record === void 0) {
|
|
6700
|
+
await this.end();
|
|
6701
|
+
break;
|
|
6702
|
+
}
|
|
6703
|
+
if (record.ops === null) continue;
|
|
6704
|
+
if (record.key === group.key) for (const [i, h] of record.ops) group.ops.set(i, h);
|
|
6705
|
+
else this.look = { key: record.key, ops: record.ops };
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
/** Take the whole group of {@link nextKey}: its bitmaps by operand index, held until {@link release}. */
|
|
6709
|
+
take() {
|
|
6710
|
+
const group = this.group;
|
|
6711
|
+
this.current = group;
|
|
6712
|
+
this.group = void 0;
|
|
6713
|
+
this.keyCount++;
|
|
6714
|
+
return new BitmapView(group.ops);
|
|
6715
|
+
}
|
|
6716
|
+
/** Give back what the taken group holds. */
|
|
6717
|
+
release() {
|
|
6718
|
+
const group = this.current;
|
|
6719
|
+
this.current = void 0;
|
|
6720
|
+
if (group === void 0) return;
|
|
6721
|
+
for (const h of group.ops.values()) this.giveBack(h.charge);
|
|
6722
|
+
}
|
|
6723
|
+
/** Stop reading: release everything held and return the iterator. Safe to call at any time, and again. */
|
|
6724
|
+
close() {
|
|
6725
|
+
if (this.closed) return;
|
|
6726
|
+
this.closed = true;
|
|
6727
|
+
this.deps.release(this.held);
|
|
6728
|
+
this.held = 0;
|
|
6729
|
+
this.group = this.look = this.current = void 0;
|
|
6730
|
+
const iterator = this.iterator;
|
|
6731
|
+
if (iterator !== void 0 && !this.ended && iterator.return !== void 0) {
|
|
6732
|
+
this.returned = Promise.resolve().then(() => iterator.return()).then(
|
|
6733
|
+
() => void 0,
|
|
6734
|
+
() => void 0
|
|
6735
|
+
);
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
/** Resolves once the iterator's `return` has settled. */
|
|
6739
|
+
async drained() {
|
|
6740
|
+
await this.returned;
|
|
6741
|
+
}
|
|
6742
|
+
// ---- reading ----------------------------------------------------------------------------------------------
|
|
6743
|
+
async fill() {
|
|
6744
|
+
while (this.group === void 0 && !this.ended) {
|
|
6745
|
+
const record = await this.pull();
|
|
6746
|
+
if (record === void 0) {
|
|
6747
|
+
await this.end();
|
|
6748
|
+
return;
|
|
6749
|
+
}
|
|
6750
|
+
if (record.ops !== null) this.group = { key: record.key, ops: record.ops };
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
/** The next record, checked and converted (`ops` is `null` when its key is outside the range); `undefined` at the end. */
|
|
6754
|
+
async pull() {
|
|
6755
|
+
const epoch = this.deps.feed.epoch;
|
|
6756
|
+
if (epoch?.moved() === true) throw new FeedStale();
|
|
6757
|
+
this.iterator ??= this.deps.feed.records[Symbol.asyncIterator]();
|
|
6758
|
+
const step = await this.iterator.next();
|
|
6759
|
+
if (this.closed) throw new FeedClosed();
|
|
6760
|
+
if (step.done === true) return void 0;
|
|
6761
|
+
this.recordCount++;
|
|
6762
|
+
return this.convert(step.value);
|
|
6763
|
+
}
|
|
6764
|
+
async end() {
|
|
6765
|
+
this.ended = true;
|
|
6766
|
+
await this.finish();
|
|
6767
|
+
this.finished = true;
|
|
6768
|
+
}
|
|
6769
|
+
// ---- the checks -------------------------------------------------------------------------------------------
|
|
6770
|
+
refuse(key, operand, why) {
|
|
6771
|
+
throw new ValidationError(
|
|
6772
|
+
`materializeMany feed: record at key ${String(key)}${operand === void 0 ? "" : `, operand "${operand}"`}: ${why}`
|
|
6773
|
+
);
|
|
6774
|
+
}
|
|
6775
|
+
charge(bytes) {
|
|
6776
|
+
this.deps.charge(bytes);
|
|
6777
|
+
if (this.closed) {
|
|
6778
|
+
this.deps.release(bytes);
|
|
6779
|
+
throw new FeedClosed();
|
|
6780
|
+
}
|
|
6781
|
+
this.held += bytes;
|
|
6782
|
+
}
|
|
6783
|
+
giveBack(bytes) {
|
|
6784
|
+
if (this.closed) return;
|
|
6785
|
+
this.held -= bytes;
|
|
6786
|
+
this.deps.release(bytes);
|
|
6787
|
+
}
|
|
6788
|
+
async convert(record) {
|
|
6789
|
+
if (typeof record !== "object" || record === null) {
|
|
6790
|
+
this.refuse("?", void 0, "a record is an object with a key and operands");
|
|
6791
|
+
}
|
|
6792
|
+
const key = record.key;
|
|
6793
|
+
const operands = record.operands;
|
|
6794
|
+
if (typeof key !== "number" || !Number.isInteger(key) || key < 0 || key >= KEY_IDS) {
|
|
6795
|
+
this.refuse(
|
|
6796
|
+
typeof key === "number" ? key : "?",
|
|
6797
|
+
void 0,
|
|
6798
|
+
"the key must be an integer from 0 to 65535"
|
|
6799
|
+
);
|
|
6800
|
+
}
|
|
6801
|
+
if (key < this.prevKey) {
|
|
6802
|
+
this.refuse(
|
|
6803
|
+
key,
|
|
6804
|
+
void 0,
|
|
6805
|
+
`the key is below the previous record's (${this.prevKey}): keys never go back`
|
|
6806
|
+
);
|
|
6807
|
+
}
|
|
6808
|
+
if (key !== this.prevKey) {
|
|
6809
|
+
for (const i of this.touched) this.seen[i] = 0;
|
|
6810
|
+
this.touched.length = 0;
|
|
6811
|
+
this.prevKey = key;
|
|
6812
|
+
}
|
|
6813
|
+
if (typeof operands !== "object" || operands === null || !isPlainObject(operands)) {
|
|
6814
|
+
this.refuse(
|
|
6815
|
+
key,
|
|
6816
|
+
void 0,
|
|
6817
|
+
"operands must be a plain object of fed operand name to Uint32Array (not a Map, an array or a class instance)"
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
6820
|
+
const keep = this.deps.inWindow(key);
|
|
6821
|
+
const ops = keep ? /* @__PURE__ */ new Map() : null;
|
|
6822
|
+
const charged = [];
|
|
6823
|
+
try {
|
|
6824
|
+
for (const name of Reflect.ownKeys(operands)) {
|
|
6825
|
+
if (typeof name !== "string") this.refuse(key, void 0, "operands has a symbol key");
|
|
6826
|
+
const at = this.index.get(name);
|
|
6827
|
+
if (at === void 0) {
|
|
6828
|
+
this.refuse(key, name, "not a declared fed operand (it is not in feed.names)");
|
|
6829
|
+
}
|
|
6830
|
+
const value = operands[name];
|
|
6831
|
+
const view = plainUint32(value);
|
|
6832
|
+
if (view === void 0) {
|
|
6833
|
+
this.refuse(
|
|
6834
|
+
key,
|
|
6835
|
+
name,
|
|
6836
|
+
"the ids must be a Uint32Array (any other typed array, array or look-alike is refused)"
|
|
6837
|
+
);
|
|
6838
|
+
}
|
|
6839
|
+
if (view === "detached") this.refuse(key, name, "the array's buffer is detached");
|
|
6840
|
+
if (view === "out of bounds") {
|
|
6841
|
+
this.refuse(
|
|
6842
|
+
key,
|
|
6843
|
+
name,
|
|
6844
|
+
"the array is out of bounds of its resizable buffer, which shrank under it"
|
|
6845
|
+
);
|
|
6846
|
+
}
|
|
6847
|
+
if (view.length === 0) continue;
|
|
6848
|
+
if (this.seen[at] === 1) {
|
|
6849
|
+
this.refuse(key, name, "named twice at one key: an operand appears at most once per key");
|
|
6850
|
+
}
|
|
6851
|
+
const n = this.scan(view, key, name);
|
|
6852
|
+
this.seen[at] = 1;
|
|
6853
|
+
this.touched.push(at);
|
|
6854
|
+
this.counts[at] += n;
|
|
6855
|
+
this.idCount += n;
|
|
6856
|
+
if (ops !== null) {
|
|
6857
|
+
const bytes = residentBound(n);
|
|
6858
|
+
this.charge(bytes);
|
|
6859
|
+
charged.push(bytes);
|
|
6860
|
+
ops.set(this.deps.base + at, {
|
|
6861
|
+
bitmap: this.deps.codec.fromValues(this.scratch.subarray(0, n)),
|
|
6862
|
+
charge: bytes
|
|
6863
|
+
});
|
|
6864
|
+
}
|
|
6865
|
+
const pause = this.deps.tick();
|
|
6866
|
+
if (pause !== null) {
|
|
6867
|
+
await pause;
|
|
6868
|
+
if (this.closed) throw new FeedClosed();
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6871
|
+
} catch (error) {
|
|
6872
|
+
for (const bytes of charged) this.giveBack(bytes);
|
|
6873
|
+
throw error;
|
|
6874
|
+
}
|
|
6875
|
+
return { key, ops };
|
|
6876
|
+
}
|
|
6877
|
+
/**
|
|
6878
|
+
* Check `view` and copy its low 16 bits into the scratch array in one pass, each id read once: a buffer that changes
|
|
6879
|
+
* under the check (a shared one) cannot make the checked value differ from the converted one.
|
|
6880
|
+
*/
|
|
6881
|
+
scan(view, key, name) {
|
|
6882
|
+
const n = view.length;
|
|
6883
|
+
if (n > KEY_IDS) this.refuse(key, name, `${n} ids, more than one chunk key holds`);
|
|
6884
|
+
const scratch = this.scratch;
|
|
6885
|
+
let prev = -1;
|
|
6886
|
+
for (let i = 0; i < n; i++) {
|
|
6887
|
+
const id = view[i];
|
|
6888
|
+
if (id >>> 16 !== key) {
|
|
6889
|
+
this.refuse(key, name, `the id at position ${i} is not inside the key`);
|
|
6890
|
+
}
|
|
6891
|
+
if (id <= prev) {
|
|
6892
|
+
this.refuse(
|
|
6893
|
+
key,
|
|
6894
|
+
name,
|
|
6895
|
+
`the id at position ${i} is not above the one before it: ids are ascending and unique`
|
|
6896
|
+
);
|
|
6897
|
+
}
|
|
6898
|
+
scratch[i] = id & 65535;
|
|
6899
|
+
prev = id;
|
|
6900
|
+
}
|
|
6901
|
+
return n;
|
|
6902
|
+
}
|
|
6903
|
+
// ---- the end of the feed ----------------------------------------------------------------------------------
|
|
6904
|
+
/** The counts and the empty rule, run once, after the iterator has ended and before anything is published. */
|
|
6905
|
+
async finish() {
|
|
6906
|
+
const { names, mayBeEmpty, counts } = this.deps.feed;
|
|
6907
|
+
let given;
|
|
6908
|
+
try {
|
|
6909
|
+
given = typeof counts === "function" ? await counts() : counts;
|
|
6910
|
+
} catch (cause) {
|
|
6911
|
+
throw new ValidationError(
|
|
6912
|
+
`materializeMany feed: feed.counts ${typeof counts === "function" ? "threw" : "could not be read"}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
6913
|
+
);
|
|
6914
|
+
}
|
|
6915
|
+
if (typeof given !== "object" || given === null || Array.isArray(given)) {
|
|
6916
|
+
throw new ValidationError(
|
|
6917
|
+
"materializeMany feed: feed.counts must give an object of ids per fed operand"
|
|
6918
|
+
);
|
|
6919
|
+
}
|
|
6920
|
+
const own = new Set(Object.keys(given));
|
|
6921
|
+
for (const name of names) {
|
|
6922
|
+
if (!own.has(name)) {
|
|
6923
|
+
throw new ValidationError(`materializeMany feed: feed.counts has no count for "${name}"`);
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
for (const name of own) {
|
|
6927
|
+
if (!this.index.has(name)) {
|
|
6928
|
+
throw new ValidationError(
|
|
6929
|
+
`materializeMany feed: feed.counts names "${name}", which is not in feed.names`
|
|
6930
|
+
);
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
names.forEach((name, i) => {
|
|
6934
|
+
const said = given[name];
|
|
6935
|
+
if (typeof said !== "number" || !Number.isSafeInteger(said) || said < 0) {
|
|
6936
|
+
throw new ValidationError(
|
|
6937
|
+
`materializeMany feed: feed.counts["${name}"] must be a non-negative integer`
|
|
6938
|
+
);
|
|
6939
|
+
}
|
|
6940
|
+
if (said !== this.counts[i]) {
|
|
6941
|
+
throw new ValidationError(
|
|
6942
|
+
`materializeMany feed: operand "${name}" was counted at ${said} ids and the feed held ${this.counts[i]}: the feed is short, long or skipped a key, and is refused`
|
|
6943
|
+
);
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
names.forEach((name, i) => {
|
|
6947
|
+
if (this.counts[i] === 0 && !mayBeEmpty.has(name)) {
|
|
6948
|
+
throw new ValidationError(
|
|
6949
|
+
`materializeMany feed: operand "${name}" holds no id anywhere in the feed, so it would contribute nothing \u2014 an exclude would suppress no ids. If that is expected, list it in mayBeEmpty.`
|
|
6950
|
+
);
|
|
6951
|
+
}
|
|
6952
|
+
});
|
|
6953
|
+
}
|
|
6954
|
+
};
|
|
6955
|
+
var BitmapView = class {
|
|
6956
|
+
constructor(ops) {
|
|
6957
|
+
this.ops = ops;
|
|
6958
|
+
}
|
|
6959
|
+
ops;
|
|
6960
|
+
get size() {
|
|
6961
|
+
return this.ops.size;
|
|
6962
|
+
}
|
|
6963
|
+
get(key) {
|
|
6964
|
+
return this.ops.get(key)?.bitmap;
|
|
6965
|
+
}
|
|
6966
|
+
has(key) {
|
|
6967
|
+
return this.ops.has(key);
|
|
6968
|
+
}
|
|
6969
|
+
forEach(fn) {
|
|
6970
|
+
for (const [k, h] of this.ops) fn(h.bitmap, k, this);
|
|
6971
|
+
}
|
|
6972
|
+
*entries() {
|
|
6973
|
+
for (const [k, h] of this.ops) yield [k, h.bitmap];
|
|
6974
|
+
}
|
|
6975
|
+
*keys() {
|
|
6976
|
+
yield* this.ops.keys();
|
|
6977
|
+
}
|
|
6978
|
+
*values() {
|
|
6979
|
+
for (const h of this.ops.values()) yield h.bitmap;
|
|
6980
|
+
}
|
|
6981
|
+
[Symbol.iterator]() {
|
|
6982
|
+
return this.entries();
|
|
6983
|
+
}
|
|
6984
|
+
};
|
|
6985
|
+
|
|
6986
|
+
// src/core/combine-many.ts
|
|
6987
|
+
var MAX_RANGES_IN_FLIGHT2 = 64;
|
|
6988
|
+
var MAX_BATCH_PARALLELISM = 1024;
|
|
6989
|
+
var RANGE_BYTES = 1 << 20;
|
|
6990
|
+
var WORKING_CHUNK = residentBytes(8208);
|
|
6991
|
+
var BUDGET_HEADROOM_FACTOR = 2;
|
|
6992
|
+
var BUDGET_HEADROOM_FLOOR = 1e3;
|
|
6993
|
+
var INDEX_PARALLELISM = 16;
|
|
6994
|
+
var COMPILED = /* @__PURE__ */ new WeakMap();
|
|
6995
|
+
var MAX_CALL_NODES = 2e5;
|
|
6996
|
+
function subtractedOperands(node, under, into) {
|
|
6997
|
+
if (node.kind === "leaf") {
|
|
6998
|
+
if (under) into.add(node.operand);
|
|
6999
|
+
return;
|
|
7000
|
+
}
|
|
7001
|
+
node.kids.forEach(
|
|
7002
|
+
(kid, i) => subtractedOperands(kid, under || node.kind === "andNot" && i > 0, into)
|
|
7003
|
+
);
|
|
7004
|
+
}
|
|
7005
|
+
function integerIn(value, min, max, what) {
|
|
7006
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
7007
|
+
throw new ValidationError(
|
|
7008
|
+
`${what} must be an integer from ${min} to ${max}; got ${typeof value === "number" ? String(value) : `a ${typeof value}`}`
|
|
7009
|
+
);
|
|
7010
|
+
}
|
|
7011
|
+
return value;
|
|
7012
|
+
}
|
|
7013
|
+
function checkedKeep(value, what) {
|
|
7014
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
7015
|
+
throw new ValidationError(
|
|
7016
|
+
`${what} must be a non-negative integer; got ${value === void 0 ? "nothing (it is required)" : String(value)}`
|
|
7017
|
+
);
|
|
7018
|
+
}
|
|
7019
|
+
return value;
|
|
7020
|
+
}
|
|
7021
|
+
function compileCombineMany(request) {
|
|
7022
|
+
const { operands, outputs } = request;
|
|
7023
|
+
if (!Array.isArray(outputs) || outputs.length === 0) {
|
|
7024
|
+
throw new ValidationError("outputs must be a non-empty array");
|
|
7025
|
+
}
|
|
7026
|
+
checkedKeep(request.keep, "keep");
|
|
7027
|
+
integerIn(request.maxBufferedBytes, 1, Number.MAX_SAFE_INTEGER, "maxBufferedBytes");
|
|
7028
|
+
integerIn(request.publishConcurrency, 1, MAX_BATCH_PARALLELISM, "publishConcurrency");
|
|
7029
|
+
integerIn(request.concurrency, 1, MAX_BATCH_PARALLELISM, "concurrency");
|
|
7030
|
+
for (const [name, bound] of [
|
|
7031
|
+
["after", request.after],
|
|
7032
|
+
["through", request.through]
|
|
7033
|
+
]) {
|
|
7034
|
+
if (bound !== void 0) integerIn(bound, 0, U32_MAX, name);
|
|
7035
|
+
}
|
|
7036
|
+
const index = /* @__PURE__ */ new Map();
|
|
7037
|
+
operands.forEach((op, i) => index.set(op.name, i));
|
|
7038
|
+
for (const op of operands) {
|
|
7039
|
+
if (op.held !== void 0 && op.held.chunks.empty && !op.held.mayBeEmpty) {
|
|
7040
|
+
throw new ValidationError(
|
|
7041
|
+
`materializeMany: operand "${op.name}" holds no id, so an include would contribute none and an exclude would subtract none. Check what it was made from, or name it in \`mayBeEmpty\` if it may be empty.`
|
|
7042
|
+
);
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
const feed = request.feed === void 0 ? void 0 : checkFeed(request.feed, new Set(index.keys()));
|
|
7046
|
+
feed?.names.forEach((name, j) => index.set(name, operands.length + j));
|
|
7047
|
+
let totalNodes = 0;
|
|
7048
|
+
const compiled = outputs.map((output, i) => {
|
|
7049
|
+
const where = `outputs[${i}]`;
|
|
7050
|
+
const budget = { remaining: MAX_EXPR_NODES };
|
|
7051
|
+
const root = compileExpr(output.expr, `${where}.expr`, index, budget);
|
|
7052
|
+
let exclude = [];
|
|
7053
|
+
if (output.exclude !== void 0) {
|
|
7054
|
+
const listed = output.exclude;
|
|
7055
|
+
if (!Array.isArray(listed)) {
|
|
7056
|
+
throw new ValidationError(`${where}.exclude must be an array of expressions`);
|
|
7057
|
+
}
|
|
7058
|
+
exclude = listed.map(
|
|
7059
|
+
(e, j) => compileExpr(e, `${where}.exclude[${j}]`, index, budget)
|
|
7060
|
+
);
|
|
7061
|
+
}
|
|
7062
|
+
const node = exclude.length === 0 ? root : { kind: "andNot", kids: [root, ...exclude] };
|
|
7063
|
+
const nodes = MAX_EXPR_NODES - budget.remaining + (exclude.length === 0 ? 0 : 1);
|
|
7064
|
+
totalNodes += nodes;
|
|
7065
|
+
if (totalNodes > MAX_CALL_NODES) {
|
|
7066
|
+
throw new ValidationError(
|
|
7067
|
+
`${where}: the expressions of the call hold more than ${MAX_CALL_NODES} nodes in all; split the outputs across calls`
|
|
7068
|
+
);
|
|
7069
|
+
}
|
|
7070
|
+
const keep = output.keep === void 0 ? request.keep : checkedKeep(output.keep, `${where}.keep`);
|
|
7071
|
+
if (output.allowEmpty !== void 0 && typeof output.allowEmpty !== "boolean") {
|
|
7072
|
+
throw new ValidationError(`${where}.allowEmpty must be a boolean`);
|
|
7073
|
+
}
|
|
7074
|
+
const guard = output.guard;
|
|
7075
|
+
if (guard !== void 0) {
|
|
7076
|
+
if (typeof guard !== "object" || guard === null) {
|
|
7077
|
+
throw new ValidationError(`${where}.guard must be an object`);
|
|
7078
|
+
}
|
|
7079
|
+
const min = guard.minCardinality;
|
|
7080
|
+
if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
|
|
7081
|
+
throw new ValidationError(
|
|
7082
|
+
`${where}.guard.minCardinality must be a non-negative integer; got ${String(min)}`
|
|
7083
|
+
);
|
|
7084
|
+
}
|
|
7085
|
+
const retained = guard.minRetained;
|
|
7086
|
+
if (retained !== void 0 && (!Number.isFinite(retained) || retained < 0 || retained > 1)) {
|
|
7087
|
+
throw new ValidationError(
|
|
7088
|
+
`${where}.guard.minRetained must be a fraction in 0..1; got ${String(retained)}`
|
|
7089
|
+
);
|
|
7090
|
+
}
|
|
7091
|
+
}
|
|
7092
|
+
const metadata = copiedMetadata(output.metadata, (message) => {
|
|
7093
|
+
throw new ValidationError(`${where}.metadata: ${message}`);
|
|
7094
|
+
});
|
|
7095
|
+
const read = operandsOf(node);
|
|
7096
|
+
return {
|
|
7097
|
+
node,
|
|
7098
|
+
operands: read.filter((i2) => i2 < operands.length),
|
|
7099
|
+
fed: read.filter((i2) => i2 >= operands.length),
|
|
7100
|
+
subtracted: subtractedList(node).filter((i2) => i2 < operands.length),
|
|
7101
|
+
depth: depthOf(node),
|
|
7102
|
+
nodes,
|
|
7103
|
+
write: {
|
|
7104
|
+
keep,
|
|
7105
|
+
...output.allowEmpty === void 0 ? {} : { allowEmpty: output.allowEmpty },
|
|
7106
|
+
...guard === void 0 ? {} : { guard },
|
|
7107
|
+
...metadata === void 0 ? {} : { metadata }
|
|
7108
|
+
}
|
|
7109
|
+
};
|
|
7110
|
+
});
|
|
7111
|
+
const result = { request };
|
|
7112
|
+
COMPILED.set(result, { outputs: compiled, feed });
|
|
7113
|
+
return result;
|
|
7114
|
+
}
|
|
7115
|
+
function rebindCombineMany(compiled, operands) {
|
|
7116
|
+
const checked = COMPILED.get(compiled);
|
|
7117
|
+
const before = compiled.request.operands;
|
|
7118
|
+
if (checked === void 0 || operands.length !== before.length || operands.some((op, i) => op.name !== before[i].name)) {
|
|
7119
|
+
throw new ValidationError(
|
|
7120
|
+
"rebindCombineMany takes the operands compileCombineMany was given, by name and order"
|
|
7121
|
+
);
|
|
7122
|
+
}
|
|
7123
|
+
const result = { request: { ...compiled.request, operands } };
|
|
7124
|
+
COMPILED.set(result, checked);
|
|
7125
|
+
return result;
|
|
7126
|
+
}
|
|
7127
|
+
function subtractedList(node) {
|
|
7128
|
+
const into = /* @__PURE__ */ new Set();
|
|
7129
|
+
subtractedOperands(node, false, into);
|
|
7130
|
+
return [...into].sort((a, b) => a - b);
|
|
7131
|
+
}
|
|
7132
|
+
function windowOf2(after, through) {
|
|
7133
|
+
if (after === void 0 && through === void 0) return null;
|
|
7134
|
+
const lo = after === void 0 ? 0 : after + 1;
|
|
7135
|
+
const hi = through ?? U32_MAX;
|
|
7136
|
+
if (lo > hi) return "empty";
|
|
7137
|
+
return {
|
|
7138
|
+
loKey: Math.floor(lo / 65536),
|
|
7139
|
+
loRem: lo % 65536,
|
|
7140
|
+
hiKey: Math.floor(hi / 65536),
|
|
7141
|
+
hiRem: hi % 65536
|
|
7142
|
+
};
|
|
7143
|
+
}
|
|
7144
|
+
var isEdge2 = (key, w) => key === w.loKey && w.loRem > 0 || key === w.hiKey && w.hiRem < MAX_REMAINDER;
|
|
7145
|
+
function firstAbove2(sorted, x) {
|
|
7146
|
+
let lo = 0;
|
|
7147
|
+
let hi = sorted.length;
|
|
7148
|
+
while (lo < hi) {
|
|
7149
|
+
const mid = lo + hi >>> 1;
|
|
7150
|
+
if (sorted[mid] <= x) lo = mid + 1;
|
|
7151
|
+
else hi = mid;
|
|
7152
|
+
}
|
|
7153
|
+
return lo;
|
|
7154
|
+
}
|
|
7155
|
+
function cutToWindow(codec, chunk, key, w) {
|
|
7156
|
+
const from = key === w.loKey ? w.loRem : 0;
|
|
7157
|
+
const to = key === w.hiKey ? w.hiRem : MAX_REMAINDER;
|
|
7158
|
+
const rem = chunk.toUint32Array ? chunk.toUint32Array() : Uint32Array.from(chunk);
|
|
7159
|
+
return codec.fromValues(rem.subarray(firstAbove2(rem, from - 1), firstAbove2(rem, to)));
|
|
7160
|
+
}
|
|
7161
|
+
var Gate = class {
|
|
7162
|
+
constructor(limit) {
|
|
7163
|
+
this.limit = limit;
|
|
7164
|
+
}
|
|
7165
|
+
limit;
|
|
7166
|
+
active = 0;
|
|
7167
|
+
peak = 0;
|
|
7168
|
+
waiting = [];
|
|
7169
|
+
get highWater() {
|
|
7170
|
+
return this.peak;
|
|
7171
|
+
}
|
|
7172
|
+
async run(task) {
|
|
7173
|
+
if (this.active >= this.limit) await new Promise((resolve2) => this.waiting.push(resolve2));
|
|
7174
|
+
else this.active++;
|
|
7175
|
+
if (this.active > this.peak) this.peak = this.active;
|
|
7176
|
+
try {
|
|
7177
|
+
return await task();
|
|
7178
|
+
} finally {
|
|
7179
|
+
const next = this.waiting.shift();
|
|
7180
|
+
if (next === void 0) this.active--;
|
|
7181
|
+
else next();
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
};
|
|
7185
|
+
var PLAN_KEEP_BYTES = 8 * 1024 * 1024;
|
|
7186
|
+
var BYTES_PER_NODE = 320;
|
|
7187
|
+
var BYTES_PER_OUTPUT = 3700;
|
|
7188
|
+
var combineWork = { adds: 0, withs: 0, operandSteps: 0, planBuilds: 0 };
|
|
7189
|
+
var GroupCost = class {
|
|
7190
|
+
constructor(streamBytes, publishConcurrency) {
|
|
7191
|
+
this.streamBytes = streamBytes;
|
|
7192
|
+
this.publishConcurrency = publishConcurrency;
|
|
7193
|
+
}
|
|
7194
|
+
streamBytes;
|
|
7195
|
+
publishConcurrency;
|
|
7196
|
+
outputs = 0;
|
|
7197
|
+
depth = 0;
|
|
7198
|
+
/** The serialized bounds of the largest outputs, descending: at most `publishConcurrency` of them are written at once. */
|
|
7199
|
+
top = [];
|
|
7200
|
+
count = 0;
|
|
7201
|
+
streams = 0;
|
|
7202
|
+
planWork = 0;
|
|
7203
|
+
operands = /* @__PURE__ */ new Set();
|
|
7204
|
+
/** The cost of the group with `o` added. */
|
|
7205
|
+
with(o) {
|
|
7206
|
+
combineWork.withs++;
|
|
7207
|
+
let streams = this.streams;
|
|
7208
|
+
let operands = this.operands.size;
|
|
7209
|
+
for (const i of o.compiled.operands) {
|
|
7210
|
+
combineWork.operandSteps++;
|
|
7211
|
+
if (!this.operands.has(i)) {
|
|
7212
|
+
streams += this.streamBytes(i);
|
|
7213
|
+
operands++;
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
const depth = Math.max(this.depth, o.compiled.depth);
|
|
7217
|
+
const publishing = this.written(o.serializedBound);
|
|
7218
|
+
return this.outputs + o.bound + streams + (operands + depth + 2) * WORKING_CHUNK + publishing + this.planWork + planWorkOf(o);
|
|
7219
|
+
}
|
|
7220
|
+
add(o) {
|
|
7221
|
+
combineWork.adds++;
|
|
7222
|
+
for (const i of o.compiled.operands) {
|
|
7223
|
+
combineWork.operandSteps++;
|
|
7224
|
+
if (!this.operands.has(i)) {
|
|
7225
|
+
this.operands.add(i);
|
|
7226
|
+
this.streams += this.streamBytes(i);
|
|
7227
|
+
}
|
|
7228
|
+
}
|
|
7229
|
+
this.outputs += o.bound;
|
|
7230
|
+
this.depth = Math.max(this.depth, o.compiled.depth);
|
|
7231
|
+
this.top = this.withLargest(o.serializedBound);
|
|
7232
|
+
this.count++;
|
|
7233
|
+
this.planWork += planWorkOf(o);
|
|
7234
|
+
}
|
|
7235
|
+
get size() {
|
|
7236
|
+
return this.count;
|
|
7237
|
+
}
|
|
7238
|
+
withLargest(bound) {
|
|
7239
|
+
const top = [...this.top];
|
|
7240
|
+
let at = top.length;
|
|
7241
|
+
while (at > 0 && top[at - 1] < bound) at--;
|
|
7242
|
+
top.splice(at, 0, bound);
|
|
7243
|
+
if (top.length > this.publishConcurrency) top.length = this.publishConcurrency;
|
|
7244
|
+
return top;
|
|
7245
|
+
}
|
|
7246
|
+
/**
|
|
7247
|
+
* The bytes of objects being written beside the buffers, at most: the `publishConcurrency` largest outputs, each as
|
|
7248
|
+
* large as its bound, with `bound` among the candidates.
|
|
7249
|
+
*/
|
|
7250
|
+
written(bound) {
|
|
7251
|
+
let sum = 0;
|
|
7252
|
+
for (const b of this.withLargest(bound)) sum += b;
|
|
7253
|
+
return sum;
|
|
7254
|
+
}
|
|
7255
|
+
};
|
|
7256
|
+
var planWorkOf = (o) => o.rootKeys.byteLength * (1 + 2 * o.compiled.operands.length);
|
|
7257
|
+
var budgetError = (limit, what) => new BudgetExceededError(
|
|
7258
|
+
`materializeMany: ${what} would hold more than maxBufferedBytes (${limit}) resident \u2014 raise maxBufferedBytes, or split the outputs across calls`
|
|
7259
|
+
);
|
|
7260
|
+
async function runCombineMany(compiled, deps) {
|
|
7261
|
+
const run = new Run(compiled, deps);
|
|
7262
|
+
return run.execute();
|
|
7263
|
+
}
|
|
7264
|
+
var Run = class {
|
|
7265
|
+
req;
|
|
7266
|
+
source;
|
|
7267
|
+
codec;
|
|
7268
|
+
clock;
|
|
7269
|
+
maxBitmapBytes;
|
|
7270
|
+
ledger;
|
|
7271
|
+
gate = new Gate(MAX_RANGES_IN_FLIGHT2);
|
|
7272
|
+
operands;
|
|
7273
|
+
outputs;
|
|
7274
|
+
tick;
|
|
7275
|
+
window = null;
|
|
7276
|
+
groups = 0;
|
|
7277
|
+
rangeReads = 0;
|
|
7278
|
+
rangeBytes = 0;
|
|
7279
|
+
chunkReads = 0;
|
|
7280
|
+
registryReads = 0;
|
|
7281
|
+
publishes = 0;
|
|
7282
|
+
opens = 0;
|
|
7283
|
+
planCharge = 0;
|
|
7284
|
+
attributedGet = 0;
|
|
7285
|
+
attributedPut = 0;
|
|
7286
|
+
planned = 0;
|
|
7287
|
+
pruned = 0;
|
|
7288
|
+
limit = null;
|
|
7289
|
+
checkedFeed;
|
|
7290
|
+
feed;
|
|
7291
|
+
/** The index the first fed operand has among the call's operands: the stored ones come first. */
|
|
7292
|
+
fedBase;
|
|
7293
|
+
/** The outputs of the group that is running, for the ledger to evict from. */
|
|
7294
|
+
running = [];
|
|
7295
|
+
bounds;
|
|
7296
|
+
constructor(compiled, deps) {
|
|
7297
|
+
const checked = COMPILED.get(compiled);
|
|
7298
|
+
if (checked === void 0) {
|
|
7299
|
+
throw new ValidationError("runCombineMany takes the result of compileCombineMany");
|
|
7300
|
+
}
|
|
7301
|
+
const compiledOutputs = checked.outputs;
|
|
7302
|
+
this.checkedFeed = checked.feed;
|
|
7303
|
+
this.fedBase = compiled.request.operands.length;
|
|
7304
|
+
this.req = compiled.request;
|
|
7305
|
+
this.source = deps.source;
|
|
7306
|
+
this.codec = deps.codec;
|
|
7307
|
+
this.clock = deps.clock;
|
|
7308
|
+
this.maxBitmapBytes = deps.maxBitmapBytes ?? DEFAULT_MAX_BITMAP_BYTES;
|
|
7309
|
+
this.ledger = new ResidentLedger(this.req.maxBufferedBytes);
|
|
7310
|
+
this.tick = yieldEvery(deps.clock, 256);
|
|
7311
|
+
this.operands = this.req.operands.map((spec, index) => ({
|
|
7312
|
+
index,
|
|
7313
|
+
spec,
|
|
7314
|
+
bounds: { keys: new Uint16Array(0), card: new Uint32Array(0) },
|
|
7315
|
+
bytes: 0,
|
|
7316
|
+
startGeneration: void 0,
|
|
7317
|
+
endGeneration: void 0,
|
|
7318
|
+
endRow: void 0,
|
|
7319
|
+
startVersion: void 0,
|
|
7320
|
+
indexKeys: 0,
|
|
7321
|
+
chunkReads: 0,
|
|
7322
|
+
read: false,
|
|
7323
|
+
chunkless: false
|
|
7324
|
+
}));
|
|
7325
|
+
this.outputs = this.req.outputs.map((spec, index) => ({
|
|
7326
|
+
index,
|
|
7327
|
+
spec,
|
|
7328
|
+
compiled: compiledOutputs[index],
|
|
7329
|
+
rootKeys: new Uint16Array(0),
|
|
7330
|
+
bound: 0,
|
|
7331
|
+
serializedBound: 0,
|
|
7332
|
+
status: "waiting",
|
|
7333
|
+
chunks: [],
|
|
7334
|
+
charged: 0,
|
|
7335
|
+
peak: 0,
|
|
7336
|
+
chunkCount: 0,
|
|
7337
|
+
serialized: 0,
|
|
7338
|
+
cursor: 0,
|
|
7339
|
+
group: null,
|
|
7340
|
+
startedAt: null,
|
|
7341
|
+
endedAt: null,
|
|
7342
|
+
solo: false
|
|
7343
|
+
}));
|
|
7344
|
+
}
|
|
7345
|
+
async execute() {
|
|
7346
|
+
this.window = windowOf2(this.req.after, this.req.through);
|
|
7347
|
+
if (this.checkedFeed !== void 0) this.feed = this.openFeed(this.checkedFeed);
|
|
7348
|
+
this.indexHeld();
|
|
7349
|
+
await this.readIndexes();
|
|
7350
|
+
await this.refuseAbsent();
|
|
7351
|
+
this.planOutputs();
|
|
7352
|
+
const groups = this.formGroups(
|
|
7353
|
+
this.outputs.filter((o) => o.status === "waiting"),
|
|
7354
|
+
this.feed !== void 0
|
|
7355
|
+
);
|
|
7356
|
+
this.settleBudget(groups);
|
|
7357
|
+
for (const group of groups) await this.runGroup(group.outputs, group);
|
|
7358
|
+
for (; ; ) {
|
|
7359
|
+
const deferred = this.outputs.filter((o) => o.status === "deferred");
|
|
7360
|
+
if (deferred.length === 0) break;
|
|
7361
|
+
for (const output of deferred) {
|
|
7362
|
+
output.status = "waiting";
|
|
7363
|
+
output.solo = true;
|
|
7364
|
+
await this.runGroup([output.index]);
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
await this.readEnds();
|
|
7368
|
+
return { outputs: this.outputs.map((o) => o.outcome ?? this.neverRan()), stats: this.stats() };
|
|
7369
|
+
}
|
|
7370
|
+
openFeed(feed) {
|
|
7371
|
+
const w = this.window;
|
|
7372
|
+
return new FeedCursor({
|
|
7373
|
+
feed,
|
|
7374
|
+
codec: this.codec,
|
|
7375
|
+
base: this.fedBase,
|
|
7376
|
+
charge: (bytes) => {
|
|
7377
|
+
if (!this.ledger.reserve(bytes, () => this.reclaim())) {
|
|
7378
|
+
throw budgetError(this.req.maxBufferedBytes, "the feed");
|
|
7379
|
+
}
|
|
7380
|
+
},
|
|
7381
|
+
release: (bytes) => this.ledger.release(bytes),
|
|
7382
|
+
tick: this.tick,
|
|
7383
|
+
inWindow: (key) => w === "empty" ? false : w === null || key >= w.loKey && key <= w.hiKey
|
|
7384
|
+
});
|
|
7385
|
+
}
|
|
7386
|
+
/** Whether the store's erasure counter has moved since the call began: always `false` without a feed. */
|
|
7387
|
+
erased() {
|
|
7388
|
+
const epoch = this.checkedFeed?.epoch;
|
|
7389
|
+
return epoch?.moved() === true;
|
|
7390
|
+
}
|
|
7391
|
+
/** What an output that read fed operands gets when an erasure ran in the store since the call began. */
|
|
7392
|
+
staleError(o) {
|
|
7393
|
+
const names = this.checkedFeed.names;
|
|
7394
|
+
return new StaleOperandError(
|
|
7395
|
+
"materializeMany: an erasure ran in this store after the call began, so the operands it was fed may hold an erased id and its fed outputs were not published. Run the call again, with a feed read after the erasure.",
|
|
7396
|
+
names[o.compiled.fed[0] - this.fedBase] ?? "",
|
|
7397
|
+
"erased"
|
|
7398
|
+
);
|
|
7399
|
+
}
|
|
7400
|
+
/**
|
|
7401
|
+
* Refuse every fed output not yet published, and stop reading the feed. `error` is what the feed (or the budget, or an
|
|
7402
|
+
* erasure) found; stored-only outputs are untouched.
|
|
7403
|
+
*/
|
|
7404
|
+
failFed(error) {
|
|
7405
|
+
this.feed?.close();
|
|
7406
|
+
for (const o of this.outputs) {
|
|
7407
|
+
if (o.compiled.fed.length === 0 || o.status === "done") continue;
|
|
7408
|
+
this.fail(o, error instanceof FeedStale ? this.staleError(o) : error);
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
/**
|
|
7412
|
+
* Make room in the ledger. A fed output cannot be run again (the feed is read once), so a fed call gives up its fed
|
|
7413
|
+
* outputs and the feed first, as soon as the ledger passes the budget; only then is a stored-only buffer evicted, to run
|
|
7414
|
+
* again alone.
|
|
7415
|
+
*/
|
|
7416
|
+
reclaim() {
|
|
7417
|
+
if (this.feed !== void 0) {
|
|
7418
|
+
let fed = this.feed.heldBytes;
|
|
7419
|
+
for (const o of this.running)
|
|
7420
|
+
if (o.compiled.fed.length > 0 && o.status === "live") fed += o.charged;
|
|
7421
|
+
if (fed > 0) {
|
|
7422
|
+
this.failFed(budgetError(this.req.maxBufferedBytes, "the feed and the fed outputs"));
|
|
7423
|
+
return true;
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
return this.evictLargest(this.running);
|
|
7427
|
+
}
|
|
7428
|
+
neverRan() {
|
|
7429
|
+
return { ok: false, error: new Error("materializeMany: output did not run") };
|
|
7430
|
+
}
|
|
7431
|
+
// ---- index ------------------------------------------------------------------------------------------------
|
|
7432
|
+
/** What an output that reads the held operand `op` gets when an erasure ran in the store since it was made. */
|
|
7433
|
+
staleHeldError(op) {
|
|
7434
|
+
return new StaleOperandError(
|
|
7435
|
+
`materializeMany: an erasure ran in this store after operand "${op.spec.name}" was made, so it may hold an erased id and the outputs that read it were not published. Make it again from the source of truth, after the erasure, and run the call again.`,
|
|
7436
|
+
op.spec.name,
|
|
7437
|
+
"erased"
|
|
7438
|
+
);
|
|
7439
|
+
}
|
|
7440
|
+
/** The error of the first held operand `o` reads that an erasure has made stale, if any. */
|
|
7441
|
+
staleHeld(o) {
|
|
7442
|
+
for (const i of o.compiled.operands) {
|
|
7443
|
+
const op = this.operands[i];
|
|
7444
|
+
if (op.spec.held?.moved() === true) return this.staleHeldError(op);
|
|
7445
|
+
}
|
|
7446
|
+
return void 0;
|
|
7447
|
+
}
|
|
7448
|
+
/**
|
|
7449
|
+
* Index the held operands the outputs read, before any request: their keys and exact cardinalities are in memory, and
|
|
7450
|
+
* one that an erasure has made stale is failed here, so the outputs that read it fail without a read.
|
|
7451
|
+
*/
|
|
7452
|
+
indexHeld() {
|
|
7453
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
7454
|
+
for (const o of this.outputs) for (const op of o.compiled.operands) wanted.add(op);
|
|
7455
|
+
if (this.window === "empty") return;
|
|
7456
|
+
const w = this.window;
|
|
7457
|
+
const charged = /* @__PURE__ */ new Set();
|
|
7458
|
+
let resident = 0;
|
|
7459
|
+
for (const index of wanted) {
|
|
7460
|
+
const op = this.operands[index];
|
|
7461
|
+
const held = op.spec.held;
|
|
7462
|
+
if (held === void 0) continue;
|
|
7463
|
+
op.read = true;
|
|
7464
|
+
if (held.moved()) {
|
|
7465
|
+
op.error = { error: this.staleHeldError(op) };
|
|
7466
|
+
continue;
|
|
7467
|
+
}
|
|
7468
|
+
if (!charged.has(held.chunks)) {
|
|
7469
|
+
charged.add(held.chunks);
|
|
7470
|
+
resident += held.chunks.residentBytes;
|
|
7471
|
+
}
|
|
7472
|
+
const all = held.chunks.keys;
|
|
7473
|
+
const cards = held.chunks.cardinalities;
|
|
7474
|
+
const keep = [];
|
|
7475
|
+
const card = [];
|
|
7476
|
+
for (let i = 0; i < all.length; i++) {
|
|
7477
|
+
const key = all[i];
|
|
7478
|
+
if (w !== null && (key < w.loKey || key > w.hiKey)) continue;
|
|
7479
|
+
keep.push(key);
|
|
7480
|
+
card.push(cards[i]);
|
|
7481
|
+
}
|
|
7482
|
+
op.bounds = { keys: Uint16Array.from(keep), card: Uint32Array.from(card) };
|
|
7483
|
+
op.indexKeys = keep.length;
|
|
7484
|
+
op.chunkless = all.length === 0;
|
|
7485
|
+
}
|
|
7486
|
+
if (resident > 0) this.chargePlan(resident, "the held operands");
|
|
7487
|
+
}
|
|
7488
|
+
async readIndexes() {
|
|
7489
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
7490
|
+
for (const o of this.outputs) for (const op of o.compiled.operands) wanted.add(op);
|
|
7491
|
+
if (this.window === "empty") return;
|
|
7492
|
+
for (const index of wanted) this.operands[index].read = true;
|
|
7493
|
+
const w = this.window;
|
|
7494
|
+
const stored = [...wanted].filter((index) => this.operands[index].spec.held === void 0);
|
|
7495
|
+
await mapWithConcurrency(stored, INDEX_PARALLELISM, async (index) => {
|
|
7496
|
+
const op = this.operands[index];
|
|
7497
|
+
try {
|
|
7498
|
+
const listed = checkedChunkKeys(await this.source.listChunkKeys(op.spec.ref));
|
|
7499
|
+
const keys = w === null ? listed : within2(listed, w);
|
|
7500
|
+
const cards = await this.source.cardinalities?.(op.spec.ref);
|
|
7501
|
+
const card = new Uint32Array(keys.length);
|
|
7502
|
+
let serialized = 0;
|
|
7503
|
+
for (let i = 0; i < keys.length; i++) {
|
|
7504
|
+
const given = cards?.get(keys[i]);
|
|
7505
|
+
if (given !== void 0) assertChunkCardinalityInRange(given);
|
|
7506
|
+
card[i] = given ?? 65536;
|
|
7507
|
+
serialized += serializedBound(card[i]);
|
|
7508
|
+
}
|
|
7509
|
+
this.opens++;
|
|
7510
|
+
const size = (await this.source.sizeOf?.(op.spec.ref))?.sizeBytes;
|
|
7511
|
+
op.startGeneration = await this.source.currentGeneration?.(op.spec.ref);
|
|
7512
|
+
if (op.spec.pinnedGeneration === void 0) {
|
|
7513
|
+
op.startVersion = await this.source.currentVersion?.(op.spec.ref);
|
|
7514
|
+
}
|
|
7515
|
+
op.bounds = { keys: Uint16Array.from(keys), card };
|
|
7516
|
+
op.indexKeys = keys.length;
|
|
7517
|
+
const window = this.req.concurrency * (RANGE_BYTES + 28);
|
|
7518
|
+
op.bytes = Math.min(window, size ?? serialized + 64);
|
|
7519
|
+
op.chunkless = listed.length === 0;
|
|
7520
|
+
} catch (error) {
|
|
7521
|
+
op.error = { error };
|
|
7522
|
+
}
|
|
7523
|
+
});
|
|
7524
|
+
}
|
|
7525
|
+
/**
|
|
7526
|
+
* An operand that names no segment would contribute nothing: refused, as a combine refuses it, for every operand of
|
|
7527
|
+
* the call. One whose index was read is judged only if it has no chunk at all; one whose index was not read (no output
|
|
7528
|
+
* names it, or the range is empty) is asked outright.
|
|
7529
|
+
*/
|
|
7530
|
+
async refuseAbsent() {
|
|
7531
|
+
if (this.req.allowAbsentOperands === true || this.source.exists === void 0) return;
|
|
7532
|
+
const judged = this.operands.filter(
|
|
7533
|
+
(op) => op.spec.held === void 0 && op.error === void 0 && (op.read ? op.chunkless : true)
|
|
7534
|
+
);
|
|
7535
|
+
if (judged.length === 0) return;
|
|
7536
|
+
const exists = this.source.exists.bind(this.source);
|
|
7537
|
+
const checked = await mapWithConcurrency(judged, INDEX_PARALLELISM, async (op) => {
|
|
7538
|
+
this.registryReads++;
|
|
7539
|
+
return { op, exists: await exists(op.spec.ref) };
|
|
7540
|
+
});
|
|
7541
|
+
const absent = checked.filter((c) => !c.exists).map((c) => c.op.spec.name);
|
|
7542
|
+
if (absent.length === 0) return;
|
|
7543
|
+
throw new ValidationError(
|
|
7544
|
+
`materializeMany: operand ${absent.map((n) => `"${n}"`).join(", ")} names a segment that does not exist, so it would contribute nothing \u2014 an exclude would suppress no ids and an include would contribute none. Check the name and the namespace (a segment addressed without its \`namespace\` is a DIFFERENT segment). Pass \`allowAbsentOperands: true\` if you meant it.`
|
|
7545
|
+
);
|
|
7546
|
+
}
|
|
7547
|
+
// ---- plan -------------------------------------------------------------------------------------------------
|
|
7548
|
+
/** The stored operands' bounds, then one that holds any key for each fed operand. */
|
|
7549
|
+
boundsList() {
|
|
7550
|
+
if (this.bounds === void 0) {
|
|
7551
|
+
const fed = this.checkedFeed?.names.length ?? 0;
|
|
7552
|
+
this.bounds = [
|
|
7553
|
+
...this.operands.map((op) => op.bounds),
|
|
7554
|
+
...Array(fed).fill(ANY_BOUNDS)
|
|
7555
|
+
];
|
|
7556
|
+
}
|
|
7557
|
+
return this.bounds;
|
|
7558
|
+
}
|
|
7559
|
+
/** Charge plan state that lives for the whole call, refusing a plan that alone passes the budget. */
|
|
7560
|
+
chargePlan(bytes, what) {
|
|
7561
|
+
if (this.ledger.tryCharge(bytes)) {
|
|
7562
|
+
this.planCharge += bytes;
|
|
7563
|
+
return;
|
|
7564
|
+
}
|
|
7565
|
+
throw new BudgetExceededError(
|
|
7566
|
+
`materializeMany: the plan alone (${what}) passes maxBufferedBytes (${this.req.maxBufferedBytes}) with ${this.planCharge} bytes already held for it, before any chunk is read \u2014 raise maxBufferedBytes, or split the outputs across calls`
|
|
7567
|
+
);
|
|
7568
|
+
}
|
|
7569
|
+
planOutputs() {
|
|
7570
|
+
for (const op of this.operands) {
|
|
7571
|
+
if (op.read && op.error === void 0) {
|
|
7572
|
+
this.chargePlan(
|
|
7573
|
+
op.bounds.keys.byteLength + op.bounds.card.byteLength,
|
|
7574
|
+
"the operand indexes"
|
|
7575
|
+
);
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
const bounds = this.boundsList();
|
|
7579
|
+
for (const o of this.outputs) {
|
|
7580
|
+
const failed = o.compiled.operands.map((i) => this.operands[i]).find((op) => op.error);
|
|
7581
|
+
if (failed?.error !== void 0) {
|
|
7582
|
+
this.fail(o, failed.error.error);
|
|
7583
|
+
continue;
|
|
7584
|
+
}
|
|
7585
|
+
if (this.window === "empty") {
|
|
7586
|
+
o.status = "waiting";
|
|
7587
|
+
continue;
|
|
7588
|
+
}
|
|
7589
|
+
const root = planKeys(o.compiled.node, bounds);
|
|
7590
|
+
o.rootKeys = root.keys;
|
|
7591
|
+
this.chargePlan(
|
|
7592
|
+
root.keys.byteLength + o.compiled.nodes * BYTES_PER_NODE + BYTES_PER_OUTPUT,
|
|
7593
|
+
`${o.index + 1} outputs planned`
|
|
7594
|
+
);
|
|
7595
|
+
let resident = 0;
|
|
7596
|
+
let serialized = 0;
|
|
7597
|
+
for (let i = 0; i < root.keys.length; i++) {
|
|
7598
|
+
resident += residentBound(root.card[i]);
|
|
7599
|
+
serialized += serializedBound(root.card[i]);
|
|
7600
|
+
}
|
|
7601
|
+
o.bound = resident;
|
|
7602
|
+
o.serializedBound = serialized;
|
|
7603
|
+
o.status = "waiting";
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
/** The bytes a group may take: the budget less what the plan holds. */
|
|
7607
|
+
get room() {
|
|
7608
|
+
return this.req.maxBufferedBytes - this.planCharge;
|
|
7609
|
+
}
|
|
7610
|
+
/** Outputs in call order, a group at a time while the ledger has room for the next. */
|
|
7611
|
+
formGroups(waiting, single) {
|
|
7612
|
+
const groups = [];
|
|
7613
|
+
const fresh = () => new GroupCost((i) => this.operands[i].bytes, this.req.publishConcurrency);
|
|
7614
|
+
let cost = fresh();
|
|
7615
|
+
let kept = 0;
|
|
7616
|
+
let current = [];
|
|
7617
|
+
const flush = () => {
|
|
7618
|
+
if (current.length > 0) {
|
|
7619
|
+
const plan = this.buildGroup(current);
|
|
7620
|
+
const summary = {
|
|
7621
|
+
outputs: current.map((o) => o.index),
|
|
7622
|
+
reads: plan.reads,
|
|
7623
|
+
pruned: plan.pruned
|
|
7624
|
+
};
|
|
7625
|
+
if (kept + plan.planBytes <= PLAN_KEEP_BYTES && this.ledger.tryCharge(plan.planBytes)) {
|
|
7626
|
+
kept += plan.planBytes;
|
|
7627
|
+
this.planCharge += plan.planBytes;
|
|
7628
|
+
summary.plan = plan;
|
|
7629
|
+
}
|
|
7630
|
+
groups.push(summary);
|
|
7631
|
+
}
|
|
7632
|
+
current = [];
|
|
7633
|
+
cost = fresh();
|
|
7634
|
+
};
|
|
7635
|
+
for (const o of waiting) {
|
|
7636
|
+
const alone = fresh().with(o);
|
|
7637
|
+
if (alone > this.room) {
|
|
7638
|
+
this.fail(
|
|
7639
|
+
o,
|
|
7640
|
+
new BudgetExceededError(
|
|
7641
|
+
`materializeMany: output ${o.index} needs about ${alone} bytes resident with its operands, and the plan holds ${this.planCharge}, over maxBufferedBytes (${this.req.maxBufferedBytes}) \u2014 raise maxBufferedBytes to at least ${alone + this.planCharge}`
|
|
7642
|
+
)
|
|
7643
|
+
);
|
|
7644
|
+
continue;
|
|
7645
|
+
}
|
|
7646
|
+
if (!single && cost.size > 0 && cost.with(o) > this.room) flush();
|
|
7647
|
+
current.push(o);
|
|
7648
|
+
cost.add(o);
|
|
7649
|
+
}
|
|
7650
|
+
flush();
|
|
7651
|
+
return groups;
|
|
7652
|
+
}
|
|
7653
|
+
/** The keys each operand is fetched at for `members`, and the keys the pass visits. */
|
|
7654
|
+
buildGroup(members) {
|
|
7655
|
+
combineWork.planBuilds++;
|
|
7656
|
+
const bounds = this.boundsList();
|
|
7657
|
+
const bits = /* @__PURE__ */ new Map();
|
|
7658
|
+
const visit = new KeyBits();
|
|
7659
|
+
const demand = /* @__PURE__ */ new Map();
|
|
7660
|
+
let planBytes = 0;
|
|
7661
|
+
for (const o of members) {
|
|
7662
|
+
visit.add(o.rootKeys);
|
|
7663
|
+
const planned = /* @__PURE__ */ new Map();
|
|
7664
|
+
planKeys(o.compiled.node, bounds, planned);
|
|
7665
|
+
const own = /* @__PURE__ */ new Map();
|
|
7666
|
+
collectFetch(o.compiled.node, o.rootKeys, bounds, planned, (operand, keys) => {
|
|
7667
|
+
let set = bits.get(operand);
|
|
7668
|
+
if (set === void 0) bits.set(operand, set = new KeyBits());
|
|
7669
|
+
set.add(keys);
|
|
7670
|
+
let mine = own.get(operand);
|
|
7671
|
+
if (mine === void 0) own.set(operand, mine = new KeyBits());
|
|
7672
|
+
mine.add(keys);
|
|
7673
|
+
});
|
|
7674
|
+
const lists = /* @__PURE__ */ new Map();
|
|
7675
|
+
for (const [operand, set] of own) {
|
|
7676
|
+
const keys = Uint16Array.from(set.toKeys());
|
|
7677
|
+
lists.set(operand, keys);
|
|
7678
|
+
planBytes += keys.byteLength;
|
|
7679
|
+
}
|
|
7680
|
+
demand.set(o.index, lists);
|
|
7681
|
+
}
|
|
7682
|
+
const fetch = /* @__PURE__ */ new Map();
|
|
7683
|
+
let reads = 0;
|
|
7684
|
+
let pruned = 0;
|
|
7685
|
+
let fixed = 0;
|
|
7686
|
+
for (const [operand, set] of [...bits.entries()].sort((a, b) => a[0] - b[0])) {
|
|
7687
|
+
const keys = Uint16Array.from(set.toKeys());
|
|
7688
|
+
fetch.set(operand, keys);
|
|
7689
|
+
visit.add(keys);
|
|
7690
|
+
planBytes += keys.byteLength;
|
|
7691
|
+
reads += set.size;
|
|
7692
|
+
pruned += this.operands[operand].indexKeys - set.size;
|
|
7693
|
+
fixed += this.operands[operand].bytes;
|
|
7694
|
+
}
|
|
7695
|
+
const visited = Uint16Array.from(visit.toKeys());
|
|
7696
|
+
return {
|
|
7697
|
+
outputs: members.map((o) => o.index),
|
|
7698
|
+
demand,
|
|
7699
|
+
fetch,
|
|
7700
|
+
keys: visited,
|
|
7701
|
+
reads,
|
|
7702
|
+
pruned,
|
|
7703
|
+
fixedBytes: fixed,
|
|
7704
|
+
planBytes: planBytes + visited.byteLength
|
|
7705
|
+
};
|
|
7706
|
+
}
|
|
7707
|
+
settleBudget(groups) {
|
|
7708
|
+
this.planned = groups.reduce((sum, g) => sum + g.reads, 0);
|
|
7709
|
+
const given = this.req.budget;
|
|
7710
|
+
if (given === null) {
|
|
7711
|
+
this.limit = null;
|
|
7712
|
+
} else if (given === void 0) {
|
|
7713
|
+
this.limit = this.planned * BUDGET_HEADROOM_FACTOR + BUDGET_HEADROOM_FLOOR;
|
|
7714
|
+
} else {
|
|
7715
|
+
this.limit = given.maxRequests;
|
|
7716
|
+
if (this.planned > this.limit) {
|
|
7717
|
+
throw new BudgetExceededError(
|
|
7718
|
+
`materializeMany would read ${this.planned} chunks over ${groups.length} group(s), over the budget of ${this.limit} \u2014 raise \`budget.maxRequests\`, raise maxBufferedBytes so fewer groups re-read the operands, or set \`budget: false\``
|
|
7719
|
+
);
|
|
7720
|
+
}
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
// ---- outcomes ---------------------------------------------------------------------------------------------
|
|
7724
|
+
fail(o, error) {
|
|
7725
|
+
this.dropBuffer(o);
|
|
7726
|
+
if (o.outcome === void 0) o.outcome = { ok: false, error };
|
|
7727
|
+
o.status = "done";
|
|
7728
|
+
o.endedAt ??= o.startedAt === null ? null : this.clock.now();
|
|
7729
|
+
}
|
|
7730
|
+
dropBuffer(o) {
|
|
7731
|
+
this.ledger.release(o.charged);
|
|
7732
|
+
o.chunks = [];
|
|
7733
|
+
o.charged = 0;
|
|
7734
|
+
o.serialized = 0;
|
|
7735
|
+
}
|
|
7736
|
+
// ---- one group --------------------------------------------------------------------------------------------
|
|
7737
|
+
async runGroup(indices, summary) {
|
|
7738
|
+
const members = indices.map((i) => this.outputs[i]).filter((o) => o.status === "waiting");
|
|
7739
|
+
if (members.length === 0) return;
|
|
7740
|
+
const kept = summary?.plan;
|
|
7741
|
+
if (summary !== void 0) summary.plan = void 0;
|
|
7742
|
+
if (kept !== void 0) {
|
|
7743
|
+
this.ledger.release(kept.planBytes);
|
|
7744
|
+
this.planCharge -= kept.planBytes;
|
|
7745
|
+
}
|
|
7746
|
+
const plan = kept !== void 0 && members.length === indices.length ? kept : this.buildGroup(members);
|
|
7747
|
+
const group = this.groups++;
|
|
7748
|
+
const budget = this.limit;
|
|
7749
|
+
if (budget !== null && this.chunkReads + plan.reads > budget) {
|
|
7750
|
+
const error = new BudgetExceededError(
|
|
7751
|
+
`materializeMany: re-running outputs would read ${plan.reads} more chunks, past the budget of ${budget} (${this.chunkReads} read)`
|
|
7752
|
+
);
|
|
7753
|
+
for (const o of members) this.fail(o, error);
|
|
7754
|
+
return;
|
|
7755
|
+
}
|
|
7756
|
+
const startedAt = this.clock.now();
|
|
7757
|
+
for (const o of members) {
|
|
7758
|
+
o.status = "live";
|
|
7759
|
+
o.group = group;
|
|
7760
|
+
o.startedAt = startedAt;
|
|
7761
|
+
o.cursor = 0;
|
|
7762
|
+
o.chunkCount = 0;
|
|
7763
|
+
o.peak = 0;
|
|
7764
|
+
}
|
|
7765
|
+
this.pruned += plan.pruned;
|
|
7766
|
+
const fixed = plan.fixedBytes + plan.planBytes;
|
|
7767
|
+
if (!this.ledger.tryCharge(fixed)) {
|
|
7768
|
+
const error = budgetError(this.req.maxBufferedBytes, "the operand streams");
|
|
7769
|
+
for (const o of members) this.fail(o, error);
|
|
7770
|
+
return;
|
|
7771
|
+
}
|
|
7772
|
+
this.running = members;
|
|
7773
|
+
try {
|
|
7774
|
+
await this.pass(plan, members);
|
|
7775
|
+
} catch (error) {
|
|
7776
|
+
for (const o of members) if (o.status === "live") this.fail(o, error);
|
|
7777
|
+
} finally {
|
|
7778
|
+
this.ledger.release(fixed);
|
|
7779
|
+
}
|
|
7780
|
+
const survivors = members.filter((o) => o.status === "live");
|
|
7781
|
+
if (survivors.length > 0) await this.publishWave(survivors);
|
|
7782
|
+
}
|
|
7783
|
+
/** Evict the biggest buffer of a group still running so another charge can fit; it is re-run alone later. */
|
|
7784
|
+
evictLargest(members) {
|
|
7785
|
+
const live = members.filter((o) => o.status === "live");
|
|
7786
|
+
if (live.length <= 1) return false;
|
|
7787
|
+
let biggest;
|
|
7788
|
+
for (const o of live) {
|
|
7789
|
+
if (o.charged > 0 && (biggest === void 0 || o.charged > biggest.charged)) biggest = o;
|
|
7790
|
+
}
|
|
7791
|
+
if (biggest === void 0) return false;
|
|
7792
|
+
this.dropBuffer(biggest);
|
|
7793
|
+
biggest.status = "deferred";
|
|
7794
|
+
biggest.group = null;
|
|
7795
|
+
biggest.startedAt = null;
|
|
7796
|
+
return true;
|
|
7797
|
+
}
|
|
7798
|
+
/** The chunks of `key` of every operand a group fetches: a stream per operand, read in step, one key at a time. */
|
|
7799
|
+
async pass(plan, members) {
|
|
7800
|
+
const readers = /* @__PURE__ */ new Map();
|
|
7801
|
+
for (const o of members) {
|
|
7802
|
+
for (const op of o.compiled.operands) {
|
|
7803
|
+
let list = readers.get(op);
|
|
7804
|
+
if (list === void 0) readers.set(op, list = []);
|
|
7805
|
+
list.push(o);
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7808
|
+
const cursors = [];
|
|
7809
|
+
for (const [index, keys] of plan.fetch) {
|
|
7810
|
+
cursors.push({
|
|
7811
|
+
op: this.operands[index],
|
|
7812
|
+
keys,
|
|
7813
|
+
next: 0,
|
|
7814
|
+
stream: void 0,
|
|
7815
|
+
done: false,
|
|
7816
|
+
failure: void 0
|
|
7817
|
+
});
|
|
7818
|
+
}
|
|
7819
|
+
const retry = (request) => this.gate.run(request);
|
|
7820
|
+
const onRequest = (r) => {
|
|
7821
|
+
this.rangeReads++;
|
|
7822
|
+
this.rangeBytes += r.bytes;
|
|
7823
|
+
};
|
|
7824
|
+
const streamOf = (c) => {
|
|
7825
|
+
c.stream ??= new ChunkStream(this.readKeys(c.op, c.keys, retry, onRequest));
|
|
7826
|
+
return c.stream;
|
|
7827
|
+
};
|
|
7828
|
+
const stop = (c) => {
|
|
7829
|
+
c.done = true;
|
|
7830
|
+
c.stream?.close();
|
|
7831
|
+
};
|
|
7832
|
+
const failOperand = (c, error, victims) => {
|
|
7833
|
+
stop(c);
|
|
7834
|
+
c.failure = { error };
|
|
7835
|
+
for (const o of victims) if (o.status === "live") this.fail(o, error);
|
|
7836
|
+
};
|
|
7837
|
+
const w = this.window === "empty" ? null : this.window;
|
|
7838
|
+
const makeRoom = () => this.reclaim();
|
|
7839
|
+
const feed = this.feed !== void 0 && this.feed.active ? this.feed : void 0;
|
|
7840
|
+
const fedBase = this.fedBase;
|
|
7841
|
+
let at = 0;
|
|
7842
|
+
try {
|
|
7843
|
+
for (; ; ) {
|
|
7844
|
+
if (!members.some((o) => o.status === "live")) break;
|
|
7845
|
+
if (feed?.active === true && !members.some((o) => o.status === "live" && o.compiled.fed.length > 0)) {
|
|
7846
|
+
feed.close();
|
|
7847
|
+
}
|
|
7848
|
+
let feedKey;
|
|
7849
|
+
if (feed?.active === true) {
|
|
7850
|
+
try {
|
|
7851
|
+
feedKey = await feed.nextKey();
|
|
7852
|
+
} catch (error) {
|
|
7853
|
+
if (!(error instanceof FeedClosed)) this.failFed(error);
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
const storedKey = at < plan.keys.length ? plan.keys[at] : void 0;
|
|
7857
|
+
const key = storedKey === void 0 ? feedKey : feedKey === void 0 ? storedKey : Math.min(storedKey, feedKey);
|
|
7858
|
+
if (key === void 0) break;
|
|
7859
|
+
if (storedKey === key) at++;
|
|
7860
|
+
let fedHere;
|
|
7861
|
+
if (feedKey === key && feed !== void 0) {
|
|
7862
|
+
try {
|
|
7863
|
+
await feed.completeKey();
|
|
7864
|
+
fedHere = feed.take();
|
|
7865
|
+
} catch (error) {
|
|
7866
|
+
if (!(error instanceof FeedClosed)) this.failFed(error);
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
const holds = (operand) => operand >= fedBase ? fedHere?.has(operand) === true : hasKey(this.operands[operand].bounds.keys, key);
|
|
7870
|
+
const needs = (o) => o.compiled.fed.length === 0 || canHold(o.compiled.node, holds);
|
|
7871
|
+
try {
|
|
7872
|
+
const exact = feed !== void 0 || members.some((o) => o.status !== "live");
|
|
7873
|
+
const cursorsAt = [];
|
|
7874
|
+
const wanting = (c) => (readers.get(c.op.index) ?? []).filter(
|
|
7875
|
+
(o) => o.status === "live" && hasKey(plan.demand.get(o.index)?.get(c.op.index), key) && needs(o)
|
|
7876
|
+
);
|
|
7877
|
+
for (const c of cursors) {
|
|
7878
|
+
if (c.keys[c.next] !== key) continue;
|
|
7879
|
+
const victims = exact || c.failure !== void 0 ? wanting(c) : void 0;
|
|
7880
|
+
if (c.failure !== void 0) {
|
|
7881
|
+
c.next++;
|
|
7882
|
+
for (const o of victims ?? []) this.fail(o, c.failure.error);
|
|
7883
|
+
continue;
|
|
7884
|
+
}
|
|
7885
|
+
if (c.done) {
|
|
7886
|
+
c.next++;
|
|
7887
|
+
continue;
|
|
7888
|
+
}
|
|
7889
|
+
if (!(readers.get(c.op.index) ?? []).some((o) => o.status === "live")) {
|
|
7890
|
+
stop(c);
|
|
7891
|
+
c.next++;
|
|
7892
|
+
continue;
|
|
7893
|
+
}
|
|
7894
|
+
if (victims === void 0 || victims.length > 0) {
|
|
7895
|
+
try {
|
|
7896
|
+
c.op.spec.check?.();
|
|
7897
|
+
} catch (error) {
|
|
7898
|
+
c.next++;
|
|
7899
|
+
failOperand(c, error, victims ?? wanting(c));
|
|
7900
|
+
continue;
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
cursorsAt.push({ c, victims });
|
|
7904
|
+
}
|
|
7905
|
+
const settled2 = await Promise.allSettled(cursorsAt.map(({ c }) => streamOf(c).take(key)));
|
|
7906
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
7907
|
+
let held = 0;
|
|
7908
|
+
try {
|
|
7909
|
+
for (let i = 0; i < cursorsAt.length; i++) {
|
|
7910
|
+
const { c, victims } = cursorsAt[i];
|
|
7911
|
+
const result = settled2[i];
|
|
7912
|
+
c.next++;
|
|
7913
|
+
if (c.done) continue;
|
|
7914
|
+
if (result.status === "rejected") {
|
|
7915
|
+
failOperand(c, result.reason, victims ?? wanting(c));
|
|
7916
|
+
continue;
|
|
7917
|
+
}
|
|
7918
|
+
this.chunkReads++;
|
|
7919
|
+
c.op.chunkReads++;
|
|
7920
|
+
if (victims !== void 0 && victims.length === 0) continue;
|
|
7921
|
+
const bytes = result.value.bytes;
|
|
7922
|
+
if (bytes === null) {
|
|
7923
|
+
failOperand(
|
|
7924
|
+
c,
|
|
7925
|
+
new IntegrityError(
|
|
7926
|
+
`operand "${c.op.spec.name}" lists chunk ${key} but its generation holds no bytes for it`
|
|
7927
|
+
),
|
|
7928
|
+
victims ?? wanting(c)
|
|
7929
|
+
);
|
|
7930
|
+
continue;
|
|
7931
|
+
}
|
|
7932
|
+
try {
|
|
7933
|
+
const resident = residentBytes(bytes.length);
|
|
7934
|
+
if (!this.ledger.reserve(resident, makeRoom)) {
|
|
7935
|
+
throw budgetError(
|
|
7936
|
+
this.req.maxBufferedBytes,
|
|
7937
|
+
`operand "${c.op.spec.name}" at chunk ${key}`
|
|
7938
|
+
);
|
|
7939
|
+
}
|
|
7940
|
+
held += resident;
|
|
7941
|
+
decoded.set(
|
|
7942
|
+
c.op.index,
|
|
7943
|
+
decodeChunkBytes(this.codec, bytes, key, this.maxBitmapBytes)
|
|
7944
|
+
);
|
|
7945
|
+
} catch (error) {
|
|
7946
|
+
failOperand(c, error, victims ?? wanting(c));
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
const chunks = {
|
|
7950
|
+
get: (operand) => operand >= fedBase ? fedHere?.get(operand) : decoded.get(operand)
|
|
7951
|
+
};
|
|
7952
|
+
for (const o of members) {
|
|
7953
|
+
if (o.status !== "live") continue;
|
|
7954
|
+
if (o.compiled.fed.length > 0) {
|
|
7955
|
+
if (!needs(o)) continue;
|
|
7956
|
+
} else {
|
|
7957
|
+
const roots = o.rootKeys;
|
|
7958
|
+
while (o.cursor < roots.length && roots[o.cursor] < key) o.cursor++;
|
|
7959
|
+
if (o.cursor >= roots.length || roots[o.cursor] !== key) continue;
|
|
7960
|
+
o.cursor++;
|
|
7961
|
+
}
|
|
7962
|
+
const kc = new KeyContext(this.ledger, makeRoom, chunks);
|
|
7963
|
+
try {
|
|
7964
|
+
this.evaluateInto(o, key, kc, w, makeRoom);
|
|
7965
|
+
} catch (error) {
|
|
7966
|
+
kc.reset();
|
|
7967
|
+
if (o.status === "live") this.fail(o, error);
|
|
7968
|
+
}
|
|
7969
|
+
const pause = this.tick();
|
|
7970
|
+
if (pause !== null) await pause;
|
|
7971
|
+
}
|
|
7972
|
+
} finally {
|
|
7973
|
+
this.ledger.release(held);
|
|
7974
|
+
}
|
|
7975
|
+
} finally {
|
|
7976
|
+
feed?.release();
|
|
7977
|
+
}
|
|
7978
|
+
}
|
|
7979
|
+
if (feed !== void 0 && !feed.complete) {
|
|
7980
|
+
this.failFed(new IntegrityError("materializeMany: the feed was not read to its end"));
|
|
7981
|
+
}
|
|
7982
|
+
} finally {
|
|
7983
|
+
for (const c of cursors) c.stream?.close();
|
|
7984
|
+
if (feed !== void 0) {
|
|
7985
|
+
feed.close();
|
|
7986
|
+
await feed.drained();
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7989
|
+
}
|
|
7990
|
+
/** The operand's chunks at `keys` as a stream: the source's coalesced ranges, or chunk by chunk where it has none. */
|
|
7991
|
+
readKeys(op, wanted, retry, onRequest) {
|
|
7992
|
+
const source = this.source;
|
|
7993
|
+
const keys = Array.from(wanted);
|
|
7994
|
+
const held = op.spec.held;
|
|
7995
|
+
if (held !== void 0) {
|
|
7996
|
+
const stale = () => this.staleHeldError(op);
|
|
7997
|
+
return {
|
|
7998
|
+
// Each chunk is checked and read in one step with no wait between, so a release or an erasure cannot land after
|
|
7999
|
+
// the check and before the bytes.
|
|
8000
|
+
async *[Symbol.asyncIterator]() {
|
|
8001
|
+
for (const key of keys) {
|
|
8002
|
+
if (held.moved()) throw stale();
|
|
8003
|
+
const bytes = held.chunks.payload(key);
|
|
8004
|
+
yield { key, bytes: bytes === null ? null : new Uint8Array(bytes), version: null };
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
};
|
|
8008
|
+
}
|
|
8009
|
+
const ref = op.spec.ref;
|
|
8010
|
+
if (source.getChunks !== void 0) {
|
|
8011
|
+
return source.getChunks(ref, keys, { concurrency: this.req.concurrency, retry, onRequest });
|
|
8012
|
+
}
|
|
8013
|
+
return {
|
|
8014
|
+
async *[Symbol.asyncIterator]() {
|
|
8015
|
+
for (const key of keys) {
|
|
8016
|
+
const bytes = await retry(() => source.getChunk({ ...ref, chunkKey: key }));
|
|
8017
|
+
onRequest({ bytes: bytes?.length ?? 0, ms: 0 });
|
|
8018
|
+
yield { key, bytes, version: null };
|
|
8019
|
+
}
|
|
8020
|
+
}
|
|
8021
|
+
};
|
|
8022
|
+
}
|
|
8023
|
+
/** Evaluate `o` at `key` and buffer the chunk when it is not empty. */
|
|
8024
|
+
evaluateInto(o, key, kc, w, makeRoom) {
|
|
8025
|
+
const result = evaluate(o.compiled.node, kc);
|
|
8026
|
+
if (result === null) return;
|
|
8027
|
+
let held = kc.owned(result);
|
|
8028
|
+
if (held.bitmap.isEmpty) {
|
|
8029
|
+
kc.drop(held);
|
|
8030
|
+
return;
|
|
8031
|
+
}
|
|
8032
|
+
if (w !== null && isEdge2(key, w)) {
|
|
8033
|
+
const cut = cutToWindow(this.codec, held.bitmap, key, w);
|
|
8034
|
+
kc.drop(held);
|
|
8035
|
+
if (cut.isEmpty) return;
|
|
8036
|
+
const charge = residentBound(cut.size);
|
|
8037
|
+
kc.take(charge);
|
|
8038
|
+
held = { bitmap: cut, owned: true, charge };
|
|
8039
|
+
}
|
|
8040
|
+
const size = held.bitmap.size;
|
|
8041
|
+
const resident = residentBound(size);
|
|
8042
|
+
kc.drop(held);
|
|
8043
|
+
if (!this.ledger.reserve(resident, makeRoom)) {
|
|
8044
|
+
throw budgetError(this.req.maxBufferedBytes, `output ${o.index}`);
|
|
8045
|
+
}
|
|
8046
|
+
if (o.status !== "live") {
|
|
8047
|
+
this.ledger.release(resident);
|
|
8048
|
+
return;
|
|
8049
|
+
}
|
|
8050
|
+
o.charged += resident;
|
|
8051
|
+
if (o.charged > o.peak) o.peak = o.charged;
|
|
8052
|
+
o.serialized += serializedBound(size);
|
|
8053
|
+
o.chunks.push({ chunkKey: key, bitmap: held.bitmap });
|
|
8054
|
+
o.chunkCount = o.chunks.length;
|
|
8055
|
+
}
|
|
8056
|
+
// ---- publish ----------------------------------------------------------------------------------------------
|
|
8057
|
+
/** Re-check the pinned operands the survivors subtract, then publish what is left, `publishConcurrency` at a time. */
|
|
8058
|
+
async publishWave(survivors) {
|
|
8059
|
+
const toCheck = /* @__PURE__ */ new Set();
|
|
8060
|
+
for (const o of survivors) {
|
|
8061
|
+
for (const i of o.compiled.subtracted) {
|
|
8062
|
+
const spec = this.operands[i].spec;
|
|
8063
|
+
if (spec.pinnedGeneration !== void 0 && spec.current !== void 0) toCheck.add(i);
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
const verdict = /* @__PURE__ */ new Map();
|
|
8067
|
+
await mapWithConcurrency([...toCheck], INDEX_PARALLELISM, async (i) => {
|
|
8068
|
+
const op = this.operands[i];
|
|
8069
|
+
try {
|
|
8070
|
+
const now = await this.readRow(op, true);
|
|
8071
|
+
if (!pinnedStillCurrent(op.spec, now)) {
|
|
8072
|
+
verdict.set(
|
|
8073
|
+
i,
|
|
8074
|
+
new StaleOperandError(
|
|
8075
|
+
`materializeMany: operand "${op.spec.name}", subtracted by an output, was replaced while the call ran (pinned at generation ${String(op.spec.pinnedGeneration)}; the registry now holds ${now === null ? "no row" : `generation ${String(now.generation)}, and its row or its object is not the pinned one`}), so its outputs were not published. Run the call again to subtract the current one.`,
|
|
8076
|
+
op.spec.name,
|
|
8077
|
+
"moved"
|
|
8078
|
+
)
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
} catch (error) {
|
|
8082
|
+
verdict.set(i, error);
|
|
8083
|
+
}
|
|
8084
|
+
});
|
|
8085
|
+
for (const o of survivors) {
|
|
8086
|
+
for (const i of o.compiled.subtracted) {
|
|
8087
|
+
if (verdict.has(i)) {
|
|
8088
|
+
this.fail(o, verdict.get(i));
|
|
8089
|
+
break;
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
const queue = survivors.filter((o) => o.status === "live");
|
|
8094
|
+
let next = 0;
|
|
8095
|
+
let inFlight = 0;
|
|
8096
|
+
let waiters = [];
|
|
8097
|
+
const wake = () => {
|
|
8098
|
+
const now = waiters;
|
|
8099
|
+
waiters = [];
|
|
8100
|
+
for (const resolve2 of now) resolve2();
|
|
8101
|
+
};
|
|
8102
|
+
const worker = async () => {
|
|
8103
|
+
for (; ; ) {
|
|
8104
|
+
const o = queue[next++];
|
|
8105
|
+
if (o === void 0) return;
|
|
8106
|
+
if (o.compiled.fed.length > 0 && this.erased()) {
|
|
8107
|
+
this.fail(o, this.staleError(o));
|
|
8108
|
+
continue;
|
|
8109
|
+
}
|
|
8110
|
+
try {
|
|
8111
|
+
o.spec.beforePublish?.();
|
|
8112
|
+
} catch (error) {
|
|
8113
|
+
this.fail(o, error);
|
|
8114
|
+
continue;
|
|
8115
|
+
}
|
|
8116
|
+
const transient = o.serialized;
|
|
8117
|
+
let admitted = this.ledger.tryCharge(transient);
|
|
8118
|
+
while (!admitted && inFlight > 0) {
|
|
8119
|
+
await new Promise((resolve2) => waiters.push(resolve2));
|
|
8120
|
+
admitted = this.ledger.tryCharge(transient);
|
|
8121
|
+
}
|
|
8122
|
+
if (!admitted) {
|
|
8123
|
+
if (o.solo || o.compiled.fed.length > 0) {
|
|
8124
|
+
this.fail(o, budgetError(this.req.maxBufferedBytes, `publishing output ${o.index}`));
|
|
8125
|
+
} else {
|
|
8126
|
+
this.dropBuffer(o);
|
|
8127
|
+
o.status = "deferred";
|
|
8128
|
+
o.group = null;
|
|
8129
|
+
o.startedAt = null;
|
|
8130
|
+
}
|
|
8131
|
+
wake();
|
|
8132
|
+
continue;
|
|
8133
|
+
}
|
|
8134
|
+
const staleHeld = this.staleHeld(o);
|
|
8135
|
+
if (staleHeld !== void 0) {
|
|
8136
|
+
this.ledger.release(transient);
|
|
8137
|
+
this.fail(o, staleHeld);
|
|
8138
|
+
wake();
|
|
8139
|
+
continue;
|
|
8140
|
+
}
|
|
8141
|
+
if (o.compiled.fed.length > 0 && this.erased()) {
|
|
8142
|
+
this.ledger.release(transient);
|
|
8143
|
+
this.fail(o, this.staleError(o));
|
|
8144
|
+
wake();
|
|
8145
|
+
continue;
|
|
8146
|
+
}
|
|
8147
|
+
inFlight++;
|
|
8148
|
+
this.publishes++;
|
|
8149
|
+
const chunks = o.chunks;
|
|
8150
|
+
try {
|
|
8151
|
+
const value = await o.spec.publish(
|
|
8152
|
+
{
|
|
8153
|
+
async *[Symbol.asyncIterator]() {
|
|
8154
|
+
for (const c of chunks) yield c;
|
|
8155
|
+
}
|
|
8156
|
+
},
|
|
8157
|
+
o.compiled.write
|
|
8158
|
+
);
|
|
8159
|
+
o.outcome = { ok: true, value };
|
|
8160
|
+
const proven = o.spec.requestsOf?.(value);
|
|
8161
|
+
if (proven !== void 0) {
|
|
8162
|
+
this.attributedGet += proven.get;
|
|
8163
|
+
this.attributedPut += proven.put;
|
|
8164
|
+
}
|
|
8165
|
+
} catch (error) {
|
|
8166
|
+
o.outcome = { ok: false, error };
|
|
8167
|
+
} finally {
|
|
8168
|
+
inFlight--;
|
|
8169
|
+
this.ledger.release(transient);
|
|
8170
|
+
this.dropBuffer(o);
|
|
8171
|
+
o.status = "done";
|
|
8172
|
+
o.endedAt = this.clock.now();
|
|
8173
|
+
wake();
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
};
|
|
8177
|
+
await Promise.all(
|
|
8178
|
+
Array.from({ length: Math.min(this.req.publishConcurrency, queue.length) }, () => worker())
|
|
8179
|
+
);
|
|
8180
|
+
}
|
|
8181
|
+
/** A fresh read of the operand's row, remembered as where the operand stood last. */
|
|
8182
|
+
async readRow(op, fingerprint = false) {
|
|
8183
|
+
this.registryReads++;
|
|
8184
|
+
const row = await op.spec.current({ fingerprint }) ?? null;
|
|
8185
|
+
op.endRow = row;
|
|
8186
|
+
op.endGeneration = row?.generation ?? null;
|
|
8187
|
+
return row;
|
|
8188
|
+
}
|
|
8189
|
+
/** Where every operand stands when the call ends: one row read each, the re-checked ones already read. */
|
|
8190
|
+
async readEnds() {
|
|
8191
|
+
const pending = this.operands.filter(
|
|
8192
|
+
(op) => op.endRow === void 0 && op.spec.current !== void 0
|
|
8193
|
+
);
|
|
8194
|
+
await mapWithConcurrency(pending, INDEX_PARALLELISM, async (op) => {
|
|
8195
|
+
try {
|
|
8196
|
+
await this.readRow(op);
|
|
8197
|
+
} catch {
|
|
8198
|
+
}
|
|
8199
|
+
});
|
|
8200
|
+
}
|
|
8201
|
+
// ---- stats ------------------------------------------------------------------------------------------------
|
|
8202
|
+
stats() {
|
|
8203
|
+
const operands = /* @__PURE__ */ Object.create(null);
|
|
8204
|
+
for (const op of this.operands) {
|
|
8205
|
+
const pinned = op.spec.pinnedGeneration !== void 0;
|
|
8206
|
+
const moved = op.endRow === void 0 ? void 0 : pinned ? !pinnedStillCurrent(op.spec, op.endRow) : liveMoved(op);
|
|
8207
|
+
operands[op.spec.name] = {
|
|
8208
|
+
read: op.read,
|
|
8209
|
+
pinned: op.spec.pinnedGeneration !== void 0,
|
|
8210
|
+
pinnedGeneration: op.spec.pinnedGeneration,
|
|
8211
|
+
startGeneration: op.startGeneration,
|
|
8212
|
+
endGeneration: op.endGeneration,
|
|
8213
|
+
moved,
|
|
8214
|
+
keys: op.indexKeys,
|
|
8215
|
+
chunkReads: op.chunkReads
|
|
8216
|
+
};
|
|
8217
|
+
}
|
|
8218
|
+
return {
|
|
8219
|
+
groups: this.groups,
|
|
8220
|
+
requests: {
|
|
8221
|
+
rangeReads: this.rangeReads,
|
|
8222
|
+
rangeBytes: this.rangeBytes,
|
|
8223
|
+
chunkReads: this.chunkReads,
|
|
8224
|
+
registryReads: this.registryReads,
|
|
8225
|
+
opens: this.opens,
|
|
8226
|
+
publishes: this.publishes,
|
|
8227
|
+
attributed: {
|
|
8228
|
+
get: this.rangeReads + this.registryReads + this.attributedGet,
|
|
8229
|
+
put: this.attributedPut
|
|
8230
|
+
}
|
|
8231
|
+
},
|
|
8232
|
+
budget: { maxRequests: this.limit, planned: this.planned, used: this.chunkReads },
|
|
8233
|
+
memory: {
|
|
8234
|
+
maxBufferedBytes: this.req.maxBufferedBytes,
|
|
8235
|
+
highWaterBytes: this.ledger.highWater,
|
|
8236
|
+
planBytes: this.planCharge
|
|
8237
|
+
},
|
|
8238
|
+
chunks: { pruned: this.pruned },
|
|
8239
|
+
maxRangesInFlight: this.gate.highWater,
|
|
8240
|
+
...this.feed === void 0 ? {} : { feed: this.feed.stats },
|
|
8241
|
+
operands,
|
|
8242
|
+
outputs: this.outputs.map((o) => ({
|
|
8243
|
+
operands: [
|
|
8244
|
+
...o.compiled.operands.map((i) => this.operands[i].spec.name),
|
|
8245
|
+
...o.compiled.fed.map((i) => this.checkedFeed.names[i - this.fedBase])
|
|
8246
|
+
],
|
|
8247
|
+
group: o.group,
|
|
8248
|
+
startedAt: o.startedAt,
|
|
8249
|
+
endedAt: o.endedAt,
|
|
8250
|
+
chunks: o.chunkCount,
|
|
8251
|
+
boundBytes: o.bound,
|
|
8252
|
+
peakBytes: o.peak
|
|
8253
|
+
}))
|
|
8254
|
+
};
|
|
8255
|
+
}
|
|
8256
|
+
};
|
|
8257
|
+
function tokenOfVersion(version) {
|
|
8258
|
+
if (version == null) return void 0;
|
|
8259
|
+
const colon = version.indexOf(":");
|
|
8260
|
+
return colon < 0 ? void 0 : version.slice(colon + 1);
|
|
8261
|
+
}
|
|
8262
|
+
function sameRow2(a, b) {
|
|
8263
|
+
const ia = incarnationOf(a);
|
|
8264
|
+
const ib = incarnationOf(b);
|
|
8265
|
+
return ia !== void 0 || ib !== void 0 ? ia === ib : a === b;
|
|
8266
|
+
}
|
|
8267
|
+
function pinnedStillCurrent(spec, now) {
|
|
8268
|
+
const pinned = spec.pinnedGeneration ?? null;
|
|
8269
|
+
if (pinned !== (now?.generation ?? null)) return false;
|
|
8270
|
+
if (pinned === null || now === null) return true;
|
|
8271
|
+
const was = tokenOfVersion(spec.pinnedVersion);
|
|
8272
|
+
if (was !== void 0 && !sameRow2(was, now.token)) return false;
|
|
8273
|
+
const held = spec.pinnedFingerprint;
|
|
8274
|
+
return held == null || now.fingerprint == null || held === now.fingerprint;
|
|
8275
|
+
}
|
|
8276
|
+
function liveMoved(op) {
|
|
8277
|
+
if (op.endRow === void 0 || !op.read) return void 0;
|
|
8278
|
+
const was = tokenOfVersion(op.startVersion);
|
|
8279
|
+
if (was !== void 0 && op.endRow !== null && !sameRow2(was, op.endRow.token)) return true;
|
|
8280
|
+
return op.startGeneration === void 0 ? void 0 : op.endGeneration !== op.startGeneration;
|
|
8281
|
+
}
|
|
8282
|
+
function hasKey(keys, key) {
|
|
8283
|
+
if (keys === void 0) return false;
|
|
8284
|
+
let lo = 0;
|
|
8285
|
+
let hi = keys.length;
|
|
8286
|
+
while (lo < hi) {
|
|
8287
|
+
const mid = lo + hi >>> 1;
|
|
8288
|
+
if (keys[mid] < key) lo = mid + 1;
|
|
8289
|
+
else hi = mid;
|
|
8290
|
+
}
|
|
8291
|
+
return lo < keys.length && keys[lo] === key;
|
|
8292
|
+
}
|
|
8293
|
+
function within2(keys, w) {
|
|
8294
|
+
return keys.filter((k) => k >= w.loKey && k <= w.hiKey);
|
|
8295
|
+
}
|
|
8296
|
+
var KeyContext = class {
|
|
8297
|
+
constructor(ledger, makeRoom, chunks) {
|
|
8298
|
+
this.ledger = ledger;
|
|
8299
|
+
this.makeRoom = makeRoom;
|
|
8300
|
+
this.chunks = chunks;
|
|
8301
|
+
}
|
|
8302
|
+
ledger;
|
|
8303
|
+
makeRoom;
|
|
8304
|
+
chunks;
|
|
8305
|
+
temp = 0;
|
|
8306
|
+
/** Charge `bytes` for a bitmap this evaluation made. */
|
|
8307
|
+
take(bytes) {
|
|
8308
|
+
if (!this.ledger.reserve(bytes, this.makeRoom)) {
|
|
8309
|
+
throw budgetError(this.ledger.limit, "a chunk being evaluated");
|
|
8310
|
+
}
|
|
8311
|
+
this.temp += bytes;
|
|
8312
|
+
}
|
|
8313
|
+
drop(held) {
|
|
8314
|
+
if (!held.owned) return;
|
|
8315
|
+
this.ledger.release(held.charge);
|
|
8316
|
+
this.temp -= held.charge;
|
|
8317
|
+
held.charge = 0;
|
|
8318
|
+
}
|
|
8319
|
+
/** `held` as a bitmap this evaluation owns and may change: a clone of one borrowed from the operand chunks. */
|
|
8320
|
+
owned(held) {
|
|
8321
|
+
if (held.owned) return held;
|
|
8322
|
+
const charge = residentBound(held.bitmap.size);
|
|
8323
|
+
this.take(charge);
|
|
8324
|
+
return { bitmap: held.bitmap.clone(), owned: true, charge };
|
|
8325
|
+
}
|
|
8326
|
+
/** Re-price an owned bitmap that grew. */
|
|
8327
|
+
regrow(held) {
|
|
8328
|
+
const want = residentBound(held.bitmap.size);
|
|
8329
|
+
if (want > held.charge) {
|
|
8330
|
+
this.take(want - held.charge);
|
|
8331
|
+
held.charge = want;
|
|
8332
|
+
}
|
|
8333
|
+
}
|
|
8334
|
+
/** Give back every charge a failed evaluation still holds. */
|
|
8335
|
+
reset() {
|
|
8336
|
+
this.ledger.release(this.temp);
|
|
8337
|
+
this.temp = 0;
|
|
8338
|
+
}
|
|
8339
|
+
};
|
|
8340
|
+
function evaluate(node, kc) {
|
|
8341
|
+
if (node.kind === "leaf") {
|
|
8342
|
+
const chunk = kc.chunks.get(node.operand);
|
|
8343
|
+
return chunk === void 0 || chunk.isEmpty ? null : { bitmap: chunk, owned: false, charge: 0 };
|
|
8344
|
+
}
|
|
8345
|
+
let acc = null;
|
|
8346
|
+
if (node.kind === "and") {
|
|
8347
|
+
for (const kid of node.kids) {
|
|
8348
|
+
const r = evaluate(kid, kc);
|
|
8349
|
+
if (r === null || r.bitmap.isEmpty) {
|
|
8350
|
+
if (r !== null) kc.drop(r);
|
|
8351
|
+
if (acc !== null) kc.drop(acc);
|
|
8352
|
+
return null;
|
|
8353
|
+
}
|
|
8354
|
+
if (acc === null) {
|
|
8355
|
+
acc = kc.owned(r);
|
|
8356
|
+
} else {
|
|
8357
|
+
acc.bitmap.andInPlace(r.bitmap);
|
|
8358
|
+
kc.drop(r);
|
|
8359
|
+
if (acc.bitmap.isEmpty) {
|
|
8360
|
+
kc.drop(acc);
|
|
8361
|
+
return null;
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
8365
|
+
return acc;
|
|
8366
|
+
}
|
|
8367
|
+
if (node.kind === "or") {
|
|
8368
|
+
for (const kid of node.kids) {
|
|
8369
|
+
const r = evaluate(kid, kc);
|
|
8370
|
+
if (r === null) continue;
|
|
8371
|
+
if (acc === null) {
|
|
8372
|
+
acc = kc.owned(r);
|
|
8373
|
+
} else {
|
|
8374
|
+
acc.bitmap.orInPlace(r.bitmap);
|
|
8375
|
+
kc.drop(r);
|
|
8376
|
+
kc.regrow(acc);
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
return acc === null || acc.bitmap.isEmpty ? null : acc;
|
|
8380
|
+
}
|
|
8381
|
+
const left = evaluate(node.kids[0], kc);
|
|
8382
|
+
if (left === null) return null;
|
|
8383
|
+
acc = kc.owned(left);
|
|
8384
|
+
for (let i = 1; i < node.kids.length; i++) {
|
|
8385
|
+
const r = evaluate(node.kids[i], kc);
|
|
8386
|
+
if (r === null) continue;
|
|
8387
|
+
acc.bitmap.andNotInPlace(r.bitmap);
|
|
8388
|
+
kc.drop(r);
|
|
8389
|
+
if (acc.bitmap.isEmpty) {
|
|
8390
|
+
kc.drop(acc);
|
|
8391
|
+
return null;
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
return acc;
|
|
8395
|
+
}
|
|
8396
|
+
|
|
8397
|
+
// src/core/combine-held.ts
|
|
8398
|
+
var PER_CHUNK_BYTES = 96;
|
|
8399
|
+
var HeldChunks = class {
|
|
8400
|
+
/** The chunk keys, ascending. */
|
|
8401
|
+
keys;
|
|
8402
|
+
/** How many ids each chunk holds. */
|
|
8403
|
+
cardinalities;
|
|
8404
|
+
/** What it holds in memory, for a call's resident-bytes ledger. */
|
|
8405
|
+
residentBytes;
|
|
8406
|
+
payloads;
|
|
8407
|
+
constructor(chunks) {
|
|
8408
|
+
const keys = [];
|
|
8409
|
+
const cards = [];
|
|
8410
|
+
const payloads = [];
|
|
8411
|
+
let bytes = 0;
|
|
8412
|
+
let last = -1;
|
|
8413
|
+
for (const chunk of chunks) {
|
|
8414
|
+
const { chunkKey, payload, cardinality } = chunk;
|
|
8415
|
+
if (!Number.isInteger(chunkKey) || chunkKey < 0 || chunkKey > MAX_REMAINDER || chunkKey <= last) {
|
|
8416
|
+
throw new ValidationError(
|
|
8417
|
+
`a held chunk's key must be an integer in [0, 65535] above the last; got ${String(chunkKey)}`
|
|
8418
|
+
);
|
|
8419
|
+
}
|
|
8420
|
+
assertChunkCardinalityInRange(cardinality);
|
|
8421
|
+
last = chunkKey;
|
|
8422
|
+
keys.push(chunkKey);
|
|
8423
|
+
cards.push(cardinality);
|
|
8424
|
+
payloads.push(payload);
|
|
8425
|
+
bytes += payload.byteLength + PER_CHUNK_BYTES;
|
|
8426
|
+
}
|
|
8427
|
+
this.keys = Uint16Array.from(keys);
|
|
8428
|
+
this.cardinalities = Uint32Array.from(cards);
|
|
8429
|
+
this.payloads = payloads;
|
|
8430
|
+
this.residentBytes = bytes;
|
|
8431
|
+
}
|
|
8432
|
+
/** Whether it holds no id. */
|
|
8433
|
+
get empty() {
|
|
8434
|
+
return this.keys.length === 0;
|
|
8435
|
+
}
|
|
8436
|
+
/** Whether {@link release} ran. */
|
|
8437
|
+
get released() {
|
|
8438
|
+
return this.payloads === void 0;
|
|
8439
|
+
}
|
|
8440
|
+
/**
|
|
8441
|
+
* The payload of the chunk at `key`, `null` when it holds none there. A view into what is held: not to be written to.
|
|
8442
|
+
* @throws {ValidationError} once released.
|
|
8443
|
+
*/
|
|
8444
|
+
payload(key) {
|
|
8445
|
+
const payloads = this.payloads;
|
|
8446
|
+
if (payloads === void 0) {
|
|
8447
|
+
throw new ValidationError(
|
|
8448
|
+
"this memory operand was released: make another with store.memory()"
|
|
8449
|
+
);
|
|
8450
|
+
}
|
|
8451
|
+
let lo = 0;
|
|
8452
|
+
let hi = this.keys.length - 1;
|
|
8453
|
+
while (lo <= hi) {
|
|
8454
|
+
const mid = lo + hi >>> 1;
|
|
8455
|
+
const at = this.keys[mid];
|
|
8456
|
+
if (at === key) return payloads[mid];
|
|
8457
|
+
if (at < key) lo = mid + 1;
|
|
8458
|
+
else hi = mid - 1;
|
|
8459
|
+
}
|
|
8460
|
+
return null;
|
|
8461
|
+
}
|
|
8462
|
+
/** Zero and drop the bytes. Every later read throws; a second call does nothing. */
|
|
8463
|
+
release() {
|
|
8464
|
+
const payloads = this.payloads;
|
|
8465
|
+
if (payloads === void 0) return;
|
|
8466
|
+
this.payloads = void 0;
|
|
8467
|
+
for (const payload of payloads) payload.fill(0);
|
|
8468
|
+
}
|
|
8469
|
+
};
|
|
8470
|
+
async function prepareHeld(input, codec, clock) {
|
|
8471
|
+
if (typeof input === "object" && input !== null && !(Symbol.iterator in input) && !(Symbol.asyncIterator in input) && Object.prototype.hasOwnProperty.call(input, "ascending")) {
|
|
8472
|
+
throw new ValidationError(
|
|
8473
|
+
"a memory operand takes ids, { serialized } or { bitmap }, with no `ascending` option: ids that arrive in chunk-key order, one key at a time, are a feed of materializeMany"
|
|
8474
|
+
);
|
|
8475
|
+
}
|
|
8476
|
+
const real = plainUint32(input);
|
|
8477
|
+
if (real === "detached") {
|
|
8478
|
+
throw new ValidationError("a memory operand's ids are a Uint32Array over a detached buffer");
|
|
8479
|
+
}
|
|
8480
|
+
if (real === "out of bounds") {
|
|
8481
|
+
throw new ValidationError(
|
|
8482
|
+
"a memory operand's ids are a resizable-buffer Uint32Array whose view is out of bounds"
|
|
8483
|
+
);
|
|
8484
|
+
}
|
|
8485
|
+
const prepared = real !== void 0 ? new DecodedLoadInput(codec.fromValues(real)) : prepareLoadInput(input, codec, "a memory operand");
|
|
8486
|
+
const encoded = await encodedChunksOfInput(prepared, codec, clock);
|
|
8487
|
+
const tick = yieldEvery(clock, 256);
|
|
8488
|
+
const chunks = [];
|
|
8489
|
+
for (const chunk of encoded) {
|
|
8490
|
+
chunks.push(chunk);
|
|
8491
|
+
const pause = tick();
|
|
8492
|
+
if (pause !== null) await pause;
|
|
8493
|
+
}
|
|
8494
|
+
return new HeldChunks(chunks);
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8497
|
+
// src/core/rollback.ts
|
|
8498
|
+
async function listGenerations(ref, deps) {
|
|
8499
|
+
validateUserRef(ref);
|
|
8500
|
+
const record = await deps.registry.get(ref);
|
|
8501
|
+
const current = record?.currentGen ?? null;
|
|
8502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8503
|
+
for await (const key of deps.storage.list(ref)) seen.add(key.generation);
|
|
8504
|
+
const described = record === null ? void 0 : await currentDescription(ref, record, deps);
|
|
8505
|
+
return [...seen].sort((a, b) => a - b).map(
|
|
8506
|
+
(generation) => generation === current ? { generation, current: true, ...described } : { generation, current: false }
|
|
8507
|
+
);
|
|
8508
|
+
}
|
|
8509
|
+
async function currentDescription(ref, record, deps) {
|
|
8510
|
+
if (record.summary === void 0) return void 0;
|
|
8511
|
+
let aead;
|
|
8512
|
+
if ("sealed" in record.summary && record.wrappedDeks !== void 0 && deps.keystore !== void 0) {
|
|
8513
|
+
try {
|
|
8514
|
+
aead = await deps.keystore.openDek(record.wrappedDeks);
|
|
8515
|
+
} catch {
|
|
8516
|
+
return void 0;
|
|
8517
|
+
}
|
|
8518
|
+
}
|
|
8519
|
+
const usable = usableSummary(ref, record, aead);
|
|
8520
|
+
if (usable === void 0) return void 0;
|
|
8521
|
+
const metadata = usable.metadata;
|
|
8522
|
+
return metadata === void 0 || Object.keys(metadata).length === 0 ? { cardinality: usable.cardinality } : { cardinality: usable.cardinality, metadata: Object.freeze({ ...metadata }) };
|
|
8523
|
+
}
|
|
8524
|
+
async function rollbackSegment(ref, toGeneration, deps, options = {}) {
|
|
8525
|
+
validateUserRef(ref);
|
|
8526
|
+
if (!Number.isInteger(toGeneration) || toGeneration < 0) {
|
|
8527
|
+
throw new ValidationError(
|
|
8528
|
+
`rollback: generation must be a non-negative integer; got ${String(toGeneration)}`
|
|
8529
|
+
);
|
|
8530
|
+
}
|
|
8531
|
+
const record = await deps.registry.get(ref);
|
|
8532
|
+
if (record === null) {
|
|
8533
|
+
throw new NotFoundError(`rollback: no registry row for segment "${ref.segment}"`);
|
|
8534
|
+
}
|
|
8535
|
+
if (record.status === "destroyed") {
|
|
8536
|
+
throw new ValidationError(
|
|
8537
|
+
`rollback: segment "${ref.segment}" is destroyed (crypto-shredded) \u2014 every generation of it is unreadable`
|
|
8538
|
+
);
|
|
8539
|
+
}
|
|
8540
|
+
if (record.currentGen === toGeneration) {
|
|
8541
|
+
return { fromGeneration: record.currentGen, generation: toGeneration };
|
|
8542
|
+
}
|
|
8543
|
+
const present = /* @__PURE__ */ new Set();
|
|
8544
|
+
for await (const key of deps.storage.list(ref)) present.add(key.generation);
|
|
8545
|
+
if (!present.has(toGeneration)) {
|
|
8546
|
+
const available = [...present].sort((a, b) => a - b);
|
|
8547
|
+
throw new NotFoundError(
|
|
8548
|
+
`rollback: generation ${toGeneration} of "${ref.segment}" is not in the bucket` + (available.length === 0 ? " (no generations remain)" : ` \u2014 present: ${available.join(", ")}`)
|
|
8549
|
+
);
|
|
8550
|
+
}
|
|
8551
|
+
if (record.currentGen !== null && toGeneration > record.currentGen && options.allowForward !== true) {
|
|
8552
|
+
throw new ValidationError(
|
|
8553
|
+
`rollback: generation ${toGeneration} of "${ref.segment}" is above the current pointer (${record.currentGen}) \u2014 it may never have been published. Pass { allowForward: true } if you are undoing an earlier rollback.`
|
|
8554
|
+
);
|
|
8555
|
+
}
|
|
8556
|
+
const keyed = record.wrappedDeks !== void 0 && record.wrappedDeks.length > 0;
|
|
8557
|
+
let aead;
|
|
8558
|
+
if (keyed && deps.keystore !== void 0) {
|
|
8559
|
+
try {
|
|
8560
|
+
aead = await deps.keystore.openDek(record.wrappedDeks);
|
|
8561
|
+
} catch {
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
const crypto = aead === void 0 ? void 0 : { aead, aadFor: (scope) => aadFor(ref, toGeneration, scope) };
|
|
8565
|
+
const { encrypted, reader } = await openRollbackTarget(
|
|
8566
|
+
deps.storage,
|
|
8567
|
+
{ ...ref, generation: toGeneration },
|
|
8568
|
+
crypto
|
|
8569
|
+
);
|
|
8570
|
+
if (encrypted !== keyed) {
|
|
8571
|
+
throw new IntegrityError(
|
|
8572
|
+
`rollback: generation ${toGeneration} of "${ref.segment}" is ${encrypted ? "encrypted" : "cleartext"}, but the segment is ${keyed ? "encrypted" : "cleartext"}, so no read of the segment could use it; it was never one of its generations. Roll back to another generation.`
|
|
8573
|
+
);
|
|
8574
|
+
}
|
|
8575
|
+
const summary = reader === void 0 ? void 0 : summaryOf(
|
|
8576
|
+
ref,
|
|
8577
|
+
toGeneration,
|
|
8578
|
+
{ cardinality: reader.count(), metadata: reader.metadata },
|
|
8579
|
+
aead
|
|
8580
|
+
);
|
|
8581
|
+
const churn = leaseChurn(deps);
|
|
8582
|
+
let against = record.token;
|
|
8583
|
+
let token;
|
|
8584
|
+
for (; ; ) {
|
|
8585
|
+
try {
|
|
8586
|
+
({ token } = await deps.registry.compareAndSwap(ref, against, {
|
|
8587
|
+
currentGen: toGeneration,
|
|
8588
|
+
summary
|
|
8589
|
+
}));
|
|
8590
|
+
break;
|
|
8591
|
+
} catch (err) {
|
|
8592
|
+
if (!isWriteConflictError(err)) throw err;
|
|
8593
|
+
const seen = await deps.registry.get(ref);
|
|
8594
|
+
const settled2 = await churn.settle(record, seen, () => deps.registry.get(ref));
|
|
8595
|
+
const now = settled2?.row ?? null;
|
|
8596
|
+
if (settled2 === void 0 || now === null || !onlyLeasesDiffer(record, now)) throw err;
|
|
8597
|
+
against = now.token;
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
let stillThere = false;
|
|
8601
|
+
for await (const key of deps.storage.list(ref)) {
|
|
8602
|
+
if (key.generation === toGeneration) {
|
|
8603
|
+
stillThere = true;
|
|
8604
|
+
break;
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
let replaced = false;
|
|
8608
|
+
if (stillThere && reader !== void 0 && toGeneration > (record.currentGen ?? -1)) {
|
|
8609
|
+
try {
|
|
8610
|
+
replaced = !await provesOwnObject(
|
|
8611
|
+
deps.storage,
|
|
8612
|
+
{ ...ref, generation: toGeneration },
|
|
8613
|
+
reader.fingerprint
|
|
8614
|
+
);
|
|
8615
|
+
} catch (err) {
|
|
8616
|
+
if (!isIntegrityError(err)) throw err;
|
|
8617
|
+
replaced = true;
|
|
8618
|
+
}
|
|
8619
|
+
}
|
|
8620
|
+
if (!stillThere || replaced) {
|
|
8621
|
+
let undone = false;
|
|
8622
|
+
let undoAgainst = token;
|
|
8623
|
+
for (; ; ) {
|
|
8624
|
+
try {
|
|
8625
|
+
await deps.registry.compareAndSwap(ref, undoAgainst, {
|
|
8626
|
+
currentGen: record.currentGen,
|
|
8627
|
+
summary: describingSummary(record)
|
|
8628
|
+
});
|
|
8629
|
+
undone = true;
|
|
8630
|
+
break;
|
|
8631
|
+
} catch (err) {
|
|
8632
|
+
if (!isWriteConflictError(err)) break;
|
|
8633
|
+
const moved = ["currentGen", "summary", "keptGens"];
|
|
8634
|
+
let now;
|
|
8635
|
+
try {
|
|
8636
|
+
const seen = await deps.registry.get(ref);
|
|
8637
|
+
if (seen === null || seen.currentGen !== toGeneration) break;
|
|
8638
|
+
const settled2 = await churn.settle(record, seen, () => deps.registry.get(ref), moved);
|
|
8639
|
+
now = settled2?.row ?? null;
|
|
8640
|
+
} catch {
|
|
8641
|
+
break;
|
|
8642
|
+
}
|
|
8643
|
+
if (now === null || now.currentGen !== toGeneration || !onlyLeasesDiffer(record, now, moved))
|
|
8644
|
+
break;
|
|
8645
|
+
undoAgainst = now.token;
|
|
8646
|
+
}
|
|
6057
8647
|
}
|
|
6058
8648
|
throw new NotFoundError(
|
|
6059
8649
|
`rollback: generation ${toGeneration} of "${ref.segment}" was ${replaced ? "replaced" : "collected"} while the pointer was moving` + (undone ? " \u2014 the pointer was put back" : `, and the move back failed, so the pointer may still name ${toGeneration} (a write that landed and lost its response reads as a failure here). Check which generation is current, then re-run a rollback to a generation that exists, or restore the object.`)
|
|
@@ -6117,7 +8707,7 @@ async function eraseIdFromSegment(ref, id, deps, options = {}) {
|
|
|
6117
8707
|
if (row === null) return "absent";
|
|
6118
8708
|
if (row.status === "destroyed") return "destroyed";
|
|
6119
8709
|
if (row.currentGen === null) return "no-generation";
|
|
6120
|
-
return row.currentGen === from && row.token === fromToken ? null : "superseded";
|
|
8710
|
+
return row.currentGen === from && (row.token === fromToken || onlyLeasesDiffer(record, row)) ? null : "superseded";
|
|
6121
8711
|
};
|
|
6122
8712
|
const holds = async (generation2) => {
|
|
6123
8713
|
const key2 = { ...base, generation: generation2 };
|
|
@@ -6288,7 +8878,13 @@ async function eraseIdFromSegment(ref, id, deps, options = {}) {
|
|
|
6288
8878
|
const published = await publishGeneration(deps.registry, key, {
|
|
6289
8879
|
expectFrom: from,
|
|
6290
8880
|
expectToken: fromToken,
|
|
8881
|
+
// A lease written while the rewrite streams moves the token and nothing it derived from: it does not refuse it.
|
|
8882
|
+
expectRow: record,
|
|
6291
8883
|
summary,
|
|
8884
|
+
// The row then records that no generation below the new pointer is kept, as the collection below leaves it.
|
|
8885
|
+
keep: 0,
|
|
8886
|
+
// Every generation below the new pointer is deleted whatever a lease says, so the row drops its leases.
|
|
8887
|
+
clearLeases: true,
|
|
6292
8888
|
// A write that ends without an answer is settled by reading the row, and a pointer at this number is this
|
|
6293
8889
|
// rewrite's only over the object it wrote: the footer proves it, so another incarnation's cannot pass for it.
|
|
6294
8890
|
holdsOwnObject: () => provesOwnObject(deps.storage, key, fingerprint),
|
|
@@ -6621,8 +9217,17 @@ async function listGenerations2(storage, ref) {
|
|
|
6621
9217
|
}
|
|
6622
9218
|
async function shredSegment(ref, deps, allowCleartext, op = "destroySegment") {
|
|
6623
9219
|
const base = { segment: ref.segment, namespace: ref.namespace };
|
|
9220
|
+
const churn = leaseChurn(deps);
|
|
9221
|
+
let lost;
|
|
6624
9222
|
for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
|
|
6625
|
-
|
|
9223
|
+
let record = await deps.registry.get(ref);
|
|
9224
|
+
if (lost !== void 0) {
|
|
9225
|
+
const settled2 = await churn.settle(lost, record, () => deps.registry.get(ref));
|
|
9226
|
+
if (settled2 !== void 0) {
|
|
9227
|
+
record = settled2.row;
|
|
9228
|
+
attempt -= 1;
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
6626
9231
|
if (record === null) {
|
|
6627
9232
|
return { ...base, destroyed: false, cryptoShredded: false, reason: "absent" };
|
|
6628
9233
|
}
|
|
@@ -6641,11 +9246,14 @@ async function shredSegment(ref, deps, allowCleartext, op = "destroySegment") {
|
|
|
6641
9246
|
keyId: void 0,
|
|
6642
9247
|
// The current generation's cached count and metadata go with it: sealed, they cannot be opened without
|
|
6643
9248
|
// the wrappings; clear, they would outlive the segment on its tombstone.
|
|
6644
|
-
summary: void 0
|
|
9249
|
+
summary: void 0,
|
|
9250
|
+
// A tombstone holds nothing: a lease names a generation of a segment that now resolves none.
|
|
9251
|
+
leases: void 0
|
|
6645
9252
|
});
|
|
6646
9253
|
return { ...base, destroyed: true, cryptoShredded: encrypted };
|
|
6647
9254
|
} catch (err) {
|
|
6648
9255
|
if (!isWriteConflictError(err)) throw err;
|
|
9256
|
+
lost = record;
|
|
6649
9257
|
}
|
|
6650
9258
|
}
|
|
6651
9259
|
throw new WriteConflictError(`${op}: contention shredding "${ref.segment}" \u2014 retry`);
|
|
@@ -6713,8 +9321,17 @@ async function setSegmentRetention(ref, deps, policy) {
|
|
|
6713
9321
|
validateRetentionPolicy(policy);
|
|
6714
9322
|
const base = { segment: ref.segment, namespace: ref.namespace };
|
|
6715
9323
|
let previousExpiresAt;
|
|
9324
|
+
const churn = leaseChurn(deps);
|
|
9325
|
+
let lost;
|
|
6716
9326
|
for (let attempt = 0; attempt < RETENTION_CAS_ATTEMPTS; attempt += 1) {
|
|
6717
|
-
|
|
9327
|
+
let record = await deps.registry.get(ref);
|
|
9328
|
+
if (lost !== void 0) {
|
|
9329
|
+
const settled2 = await churn.settle(lost, record, () => deps.registry.get(ref));
|
|
9330
|
+
if (settled2 !== void 0) {
|
|
9331
|
+
record = settled2.row;
|
|
9332
|
+
attempt -= 1;
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
6718
9335
|
try {
|
|
6719
9336
|
previousExpiresAt = readExpiresAt(record);
|
|
6720
9337
|
if (record === null) {
|
|
@@ -6741,6 +9358,7 @@ async function setSegmentRetention(ref, deps, policy) {
|
|
|
6741
9358
|
};
|
|
6742
9359
|
} catch (err) {
|
|
6743
9360
|
if (!isWriteConflictError(err)) throw err;
|
|
9361
|
+
lost = record ?? void 0;
|
|
6744
9362
|
}
|
|
6745
9363
|
}
|
|
6746
9364
|
throw new WriteConflictError(
|
|
@@ -6749,8 +9367,17 @@ async function setSegmentRetention(ref, deps, policy) {
|
|
|
6749
9367
|
}
|
|
6750
9368
|
async function clearSegmentRetention(ref, deps) {
|
|
6751
9369
|
validateUserRef(ref);
|
|
9370
|
+
const churn = leaseChurn(deps);
|
|
9371
|
+
let lost;
|
|
6752
9372
|
for (let attempt = 0; attempt < RETENTION_CAS_ATTEMPTS; attempt += 1) {
|
|
6753
|
-
|
|
9373
|
+
let record = await deps.registry.get(ref);
|
|
9374
|
+
if (lost !== void 0) {
|
|
9375
|
+
const settled2 = await churn.settle(lost, record, () => deps.registry.get(ref));
|
|
9376
|
+
if (settled2 !== void 0) {
|
|
9377
|
+
record = settled2.row;
|
|
9378
|
+
attempt -= 1;
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
6754
9381
|
if (record === null) return false;
|
|
6755
9382
|
if (record.status === "destroyed") return false;
|
|
6756
9383
|
if (record.retention === void 0 || !(EXPIRES_AT in record.retention)) return false;
|
|
@@ -6764,6 +9391,7 @@ async function clearSegmentRetention(ref, deps) {
|
|
|
6764
9391
|
return true;
|
|
6765
9392
|
} catch (err) {
|
|
6766
9393
|
if (!isWriteConflictError(err)) throw err;
|
|
9394
|
+
lost = record;
|
|
6767
9395
|
}
|
|
6768
9396
|
}
|
|
6769
9397
|
throw new WriteConflictError(
|
|
@@ -7135,6 +9763,31 @@ function failureReason(err) {
|
|
|
7135
9763
|
return `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
7136
9764
|
}
|
|
7137
9765
|
|
|
9766
|
+
// src/core/registry-reap.ts
|
|
9767
|
+
var DEFAULT_REAP_LIMIT = 1e3;
|
|
9768
|
+
async function reapRegistryTombstones(registry, options = {}) {
|
|
9769
|
+
const dryRun = options.dryRun === true;
|
|
9770
|
+
if (!dryRun && options.confirmNoLegacyWriters !== true) {
|
|
9771
|
+
throw new ValidationError(
|
|
9772
|
+
"reapRegistryTombstones: a real run needs `confirmNoLegacyWriters: true`, your statement that no process on a release before 0.12 writes this registry (one that did would issue a removed row\u2019s tokens again). Pass `dryRun: true` to see what would be removed."
|
|
9773
|
+
);
|
|
9774
|
+
}
|
|
9775
|
+
const limit = options.limit ?? DEFAULT_REAP_LIMIT;
|
|
9776
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
9777
|
+
throw new ValidationError(
|
|
9778
|
+
`reapRegistryTombstones: \`limit\` must be a positive integer; got ${String(limit)}`
|
|
9779
|
+
);
|
|
9780
|
+
}
|
|
9781
|
+
if (options.namespace !== void 0) validateUserNamespace(options.namespace);
|
|
9782
|
+
if (registry.reapLegacyTombstones === void 0) {
|
|
9783
|
+
throw new UnsupportedError(
|
|
9784
|
+
"reapRegistryTombstones: this registry keeps no deleted rows of a release before 0.12 to remove (only the object-store registries, S3, GCS and Azure Blob, do)"
|
|
9785
|
+
);
|
|
9786
|
+
}
|
|
9787
|
+
const request = options.namespace === void 0 ? { dryRun, limit } : { namespace: options.namespace, dryRun, limit };
|
|
9788
|
+
return registry.reapLegacyTombstones(request);
|
|
9789
|
+
}
|
|
9790
|
+
|
|
7138
9791
|
// src/core/consistency.ts
|
|
7139
9792
|
var DEFAULT_CHECK_CONCURRENCY = 8;
|
|
7140
9793
|
async function generationsPresent(storage, ref) {
|
|
@@ -7338,7 +9991,12 @@ export {
|
|
|
7338
9991
|
InProcessKeystore,
|
|
7339
9992
|
IntegrityError,
|
|
7340
9993
|
KeyUnavailableError,
|
|
9994
|
+
LEASE_SKEW_MS,
|
|
9995
|
+
LeaseExpiredError,
|
|
9996
|
+
LeaseLimitError,
|
|
7341
9997
|
LocalFsStorage,
|
|
9998
|
+
MAX_LEASES_PER_SEGMENT,
|
|
9999
|
+
MAX_LEASE_MS,
|
|
7342
10000
|
MIN_EXPIRES_AT_MS,
|
|
7343
10001
|
MemoryStorage,
|
|
7344
10002
|
NOOP_METRICS,
|
|
@@ -7349,6 +10007,7 @@ export {
|
|
|
7349
10007
|
RecordingAuditSink,
|
|
7350
10008
|
RetryingStorageChunkSource,
|
|
7351
10009
|
SegmentEngine,
|
|
10010
|
+
StaleOperandError,
|
|
7352
10011
|
TransientError,
|
|
7353
10012
|
UnsupportedError,
|
|
7354
10013
|
ValidationError,
|
|
@@ -7357,6 +10016,8 @@ export {
|
|
|
7357
10016
|
checkBudget,
|
|
7358
10017
|
clearSegmentRetention,
|
|
7359
10018
|
collectWithinBudget,
|
|
10019
|
+
compileCombineMany,
|
|
10020
|
+
decodeSerialized,
|
|
7360
10021
|
destroySegment,
|
|
7361
10022
|
dropSegment,
|
|
7362
10023
|
eraseIdFromSegment,
|
|
@@ -7367,7 +10028,10 @@ export {
|
|
|
7367
10028
|
groundedReport,
|
|
7368
10029
|
isCloudRoaringError,
|
|
7369
10030
|
isIntegrityError,
|
|
10031
|
+
isLeaseExpiredError,
|
|
10032
|
+
isLeaseLimitError,
|
|
7370
10033
|
isNotFoundError,
|
|
10034
|
+
isStaleOperandError,
|
|
7371
10035
|
isStorageBackend,
|
|
7372
10036
|
isTransientError,
|
|
7373
10037
|
isValidationError,
|
|
@@ -7377,11 +10041,16 @@ export {
|
|
|
7377
10041
|
loadSegment,
|
|
7378
10042
|
loadSegmentChunks,
|
|
7379
10043
|
mapWithConcurrency,
|
|
10044
|
+
prepareHeld,
|
|
7380
10045
|
readRetentionPolicy,
|
|
10046
|
+
reapRegistryTombstones,
|
|
10047
|
+
rebindCombineMany,
|
|
10048
|
+
releaseLease,
|
|
7381
10049
|
resolveBudget,
|
|
7382
10050
|
resolvePerOpBudget,
|
|
7383
10051
|
retireExpired,
|
|
7384
10052
|
rollbackSegment,
|
|
10053
|
+
runCombineMany,
|
|
7385
10054
|
runConsistencyCheck,
|
|
7386
10055
|
runExport,
|
|
7387
10056
|
safeMetrics,
|
|
@@ -7389,6 +10058,7 @@ export {
|
|
|
7389
10058
|
segmentKey,
|
|
7390
10059
|
setSegmentRetention,
|
|
7391
10060
|
splitId,
|
|
10061
|
+
takeLease,
|
|
7392
10062
|
withRetry
|
|
7393
10063
|
};
|
|
7394
10064
|
//# sourceMappingURL=index.js.map
|