@cloudbitmaps/core 0.17.0 → 0.18.1

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/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  MAX_NAME_LENGTH,
16
16
  NotFoundError,
17
17
  RESERVED_NAMESPACE_PREFIX,
18
+ StaleOperandError,
18
19
  TransientError,
19
20
  UnsupportedError,
20
21
  ValidationError,
@@ -40,6 +41,7 @@ import {
40
41
  isLive,
41
42
  isNotFoundError,
42
43
  isReservedNamespace,
44
+ isStaleOperandError,
43
45
  isStorageBackend,
44
46
  isTransientError,
45
47
  isValidationError,
@@ -68,7 +70,7 @@ import {
68
70
  validateUserNamespace,
69
71
  validateUserRef,
70
72
  webCryptoEntropy
71
- } from "./chunk-CV7GJO3B.js";
73
+ } from "./chunk-K4EXZDMU.js";
72
74
 
73
75
  // src/core/bit-route.ts
74
76
  var U32_MAX = 4294967295;
@@ -119,6 +121,42 @@ async function collectWithinBudget(source, budget, op) {
119
121
  return out;
120
122
  }
121
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
+
122
160
  // src/core/chunk-stream.ts
123
161
  var ChunkStream = class {
124
162
  iterator;
@@ -548,7 +586,7 @@ var SegmentEngine = class {
548
586
  if (cardinalities) {
549
587
  let total2 = 0;
550
588
  for (const [k, n] of cardinalities) {
551
- this.assertChunkKeyInRange(k);
589
+ assertChunkKeyInRange(k);
552
590
  total2 += n;
553
591
  }
554
592
  return total2;
@@ -731,11 +769,9 @@ var SegmentEngine = class {
731
769
  { loKey, loRem, hiKey, hiRem }
732
770
  );
733
771
  for (const k of keys) {
734
- this.assertChunkKeyInRange(k);
772
+ assertChunkKeyInRange(k);
735
773
  const c = counts.get(k);
736
- if (!Number.isInteger(c) || c < 1 || c > CHUNK_COUNT) {
737
- throw new IntegrityError(`chunk cardinality from a tier is out of range: ${c}`);
738
- }
774
+ assertChunkCardinalityInRange(c);
739
775
  }
740
776
  const gen = await this.cacheVersion(seg);
741
777
  const cutsFirst = loRem > 0 && keys[0] === loKey;
@@ -1157,7 +1193,7 @@ var SegmentEngine = class {
1157
1193
  }
1158
1194
  return streamed.stream.take(chunkKey).then((read) => {
1159
1195
  if (read.bytes === null) return null;
1160
- const bitmap = this.decodeChunk(read.bytes, chunkKey);
1196
+ const bitmap = decodeChunkBytes(this.codec, read.bytes, chunkKey, this.maxBitmapBytes);
1161
1197
  if (this.cache && !streamed.invalidated) {
1162
1198
  const version = this.streamedVersion(streamed.gen, read.version);
1163
1199
  if (version !== null) {
@@ -1179,52 +1215,9 @@ var SegmentEngine = class {
1179
1215
  chunkCacheKey(ref, version) {
1180
1216
  return version === void 0 ? chunkRefKey(ref) : chunkGenKey(ref, version);
1181
1217
  }
1182
- /** The segment's chunk keys, ascending — a shape read off the index, no payload. Keys are untrusted (invariant 5). */
1218
+ /** The segment's chunk keys, ascending, checked as untrusted tier data. */
1183
1219
  async chunkKeys(seg) {
1184
- const keys = [...await this.storage.listChunkKeys(seg)];
1185
- for (const k of keys) this.assertChunkKeyInRange(k);
1186
- keys.sort((a, b) => a - b);
1187
- for (let i = 1; i < keys.length; i++) {
1188
- if (keys[i] === keys[i - 1]) {
1189
- throw new IntegrityError(`chunk key from a tier is listed twice: ${keys[i]}`);
1190
- }
1191
- }
1192
- return keys;
1193
- }
1194
- /**
1195
- * Tier-derived keys are untrusted (invariant 5) — fail fast on a corrupt/out-of-range key rather than
1196
- * letting it flow into the id-routing step and produce a bogus id.
1197
- */
1198
- assertChunkKeyInRange(k) {
1199
- if (!Number.isInteger(k) || k < 0 || k >= CHUNK_COUNT) {
1200
- throw new IntegrityError(`chunk key from a tier is out of range: ${k}`);
1201
- }
1202
- }
1203
- /**
1204
- * The other half of invariant 5: a chunk payload holds **remainders** — 16-bit offsets within one chunk — so
1205
- * every value must be `<= 0xffff`.
1206
- *
1207
- * Nothing upstream establishes that. The byte cap bounds *size*, and CRC/AEAD prove the bytes are the bytes
1208
- * that were written — which anyone able to write the bucket satisfies trivially. A value `>= 65536` would then
1209
- * reach the id-routing step, which masks it (`remainder & 0xffff`) and emits a **fabricated id belonging to a different
1210
- * chunk's id space**: indistinguishable from real data, inflating `count()` and creating spurious `intersect`
1211
- * matches.
1212
- *
1213
- * Costs one `maximum()` per chunk, not per id — `maximum` is optional on the codec seam precisely so a codec
1214
- * that cannot answer in O(1) opts out instead of making the read path walk every value.
1215
- *
1216
- * One value stands for all of them only because the decode is structurally checked. Roaring answers `maximum()`
1217
- * from its last container, so a payload listing its containers out of order would report the wrong one's
1218
- * largest value and pass here while holding values above 65,535. The codec's `safeDeserialize` refuses that
1219
- * payload, and every other shape that would make this answer wrong, before it reaches this check.
1220
- */
1221
- assertChunkPayloadInRange(bitmap, chunkKey) {
1222
- const max = bitmap.maximum?.();
1223
- if (max !== void 0 && max > MAX_REMAINDER) {
1224
- throw new IntegrityError(
1225
- `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`
1226
- );
1227
- }
1220
+ return checkedChunkKeys(await this.storage.listChunkKeys(seg));
1228
1221
  }
1229
1222
  /**
1230
1223
  * Refuse a combine whose operand names a segment that was never created.
@@ -1338,15 +1331,6 @@ var SegmentEngine = class {
1338
1331
  entry.read.then(settled2, settled2);
1339
1332
  return entry.read;
1340
1333
  }
1341
- /**
1342
- * A chunk's stored bytes decoded under the size cap (invariant 5) and range-checked: the one place a chunk becomes a
1343
- * bitmap, whether its bytes came from a request of its own or from a stream of coalesced ranges.
1344
- */
1345
- decodeChunk(bytes, chunkKey) {
1346
- const bitmap = this.codec.safeDeserialize(bytes, this.maxBitmapBytes);
1347
- this.assertChunkPayloadInRange(bitmap, chunkKey);
1348
- return bitmap;
1349
- }
1350
1334
  /**
1351
1335
  * One chunk, decoded, range-checked and cached. A source with `getChunks` is read through it, as a one-key stream, so
1352
1336
  * a `storage.get` is reported only for a request that was sent (a chunk the source holds in memory sends none);
@@ -1393,7 +1377,7 @@ var SegmentEngine = class {
1393
1377
  }
1394
1378
  }
1395
1379
  if (!bytes) return null;
1396
- const bitmap = this.decodeChunk(bytes, ref.chunkKey);
1380
+ const bitmap = decodeChunkBytes(this.codec, bytes, ref.chunkKey, this.maxBitmapBytes);
1397
1381
  if (key !== "" && this.openReads.get(cacheKey)?.token === token) this.cache?.set(key, bitmap);
1398
1382
  return bitmap;
1399
1383
  }
@@ -4042,16 +4026,16 @@ function overCap(what, length) {
4042
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.`
4043
4027
  );
4044
4028
  }
4045
- function prepareLoadInput(input, codec) {
4029
+ function prepareLoadInput(input, codec, subject = "a load") {
4046
4030
  if (typeof input !== "object" || input === null) {
4047
4031
  throw new ValidationError(
4048
- `a load takes ids, { serialized } or { bitmap }; got ${String(input)}`
4032
+ `${subject} takes ids, { serialized } or { bitmap }; got ${String(input)}`
4049
4033
  );
4050
4034
  }
4051
4035
  const tag = tagOf(input);
4052
4036
  if (tag === "Uint8Array" || tag === "Uint8ClampedArray") {
4053
4037
  throw new ValidationError(
4054
- `a load'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.`
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.`
4055
4039
  );
4056
4040
  }
4057
4041
  if (Symbol.iterator in input || Symbol.asyncIterator in input) return input;
@@ -4074,7 +4058,7 @@ function prepareLoadInput(input, codec) {
4074
4058
  return new DecodedLoadInput(decodeSerialized(bitmap.serialize("portable"), codec, what));
4075
4059
  }
4076
4060
  throw new ValidationError(
4077
- `a load takes ids (an iterable of integers), { serialized } or { bitmap }, as the only key; got an object with keys [${keys.join(", ")}]. Load options go in the next argument.`
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." : ""}`
4078
4062
  );
4079
4063
  }
4080
4064
  function decodeSerialized(bytes, codec, what = "the buffer") {
@@ -5300,6 +5284,18 @@ var CrbmStorageChunkSource = class {
5300
5284
  }
5301
5285
  }
5302
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
+ }
5303
5299
  function* encodeEach(chunks) {
5304
5300
  for (const { chunkKey, bitmap } of chunks) {
5305
5301
  if (bitmap.isEmpty) continue;
@@ -5643,17 +5639,10 @@ async function bulkLoadAhead(driver, ref, ahead, ids, options = {}) {
5643
5639
  }
5644
5640
  const codec = requireCodec(options.codec, "bulkLoadCrbmGeneration");
5645
5641
  let chunks;
5646
- if (ids instanceof DecodedLoadInput && ids.bitmap.encodeChunks !== void 0) {
5647
- const pause = yieldEvery(options.clock, 1);
5648
- await pause();
5649
- chunks = ids.bitmap.encodeChunks();
5650
- await pause();
5651
- } else if (ids instanceof ChunkLoadInput) {
5642
+ if (ids instanceof ChunkLoadInput) {
5652
5643
  chunks = encodeEach(rangeCheckedAtWrite(await collectChunks(ids, codec, options.clock)));
5653
5644
  } else {
5654
- chunks = encodeEach(
5655
- await bucketIds(ids instanceof DecodedLoadInput ? ids.bitmap : ids, codec, options.clock)
5656
- );
5645
+ chunks = await encodedChunksOfInput(ids, codec, options.clock);
5657
5646
  }
5658
5647
  if (options.keystore !== void 0 && options.registry === void 0) {
5659
5648
  throw new ValidationError("an encrypted load requires a registry to store the wrapped DEK");
@@ -6271,6 +6260,2240 @@ async function runLoad(ref, prepare, deps, options) {
6271
6260
  };
6272
6261
  }
6273
6262
 
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) {
6269
+ throw new ValidationError(
6270
+ `${path}: more than ${MAX_EXPR_NODES} nodes in one output's expression`
6271
+ );
6272
+ }
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 };
6279
+ }
6280
+ if (typeof expr !== "object" || expr === null || Array.isArray(expr)) {
6281
+ throw new ValidationError(
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}`
6283
+ );
6284
+ }
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(", ")}`
6290
+ );
6291
+ }
6292
+ if (depth >= MAX_EXPR_DEPTH) {
6293
+ throw new ValidationError(
6294
+ `${path}: expression nests more than ${MAX_EXPR_DEPTH} operators deep (an object that contains itself is refused here too)`
6295
+ );
6296
+ }
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`);
6300
+ }
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`
6304
+ );
6305
+ }
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++]);
6366
+ }
6367
+ }
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]);
6410
+ }
6411
+ }
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
+
6274
8497
  // src/core/rollback.ts
6275
8498
  async function listGenerations(ref, deps) {
6276
8499
  validateUserRef(ref);
@@ -7784,6 +10007,7 @@ export {
7784
10007
  RecordingAuditSink,
7785
10008
  RetryingStorageChunkSource,
7786
10009
  SegmentEngine,
10010
+ StaleOperandError,
7787
10011
  TransientError,
7788
10012
  UnsupportedError,
7789
10013
  ValidationError,
@@ -7792,6 +10016,7 @@ export {
7792
10016
  checkBudget,
7793
10017
  clearSegmentRetention,
7794
10018
  collectWithinBudget,
10019
+ compileCombineMany,
7795
10020
  decodeSerialized,
7796
10021
  destroySegment,
7797
10022
  dropSegment,
@@ -7806,6 +10031,7 @@ export {
7806
10031
  isLeaseExpiredError,
7807
10032
  isLeaseLimitError,
7808
10033
  isNotFoundError,
10034
+ isStaleOperandError,
7809
10035
  isStorageBackend,
7810
10036
  isTransientError,
7811
10037
  isValidationError,
@@ -7815,13 +10041,16 @@ export {
7815
10041
  loadSegment,
7816
10042
  loadSegmentChunks,
7817
10043
  mapWithConcurrency,
10044
+ prepareHeld,
7818
10045
  readRetentionPolicy,
7819
10046
  reapRegistryTombstones,
10047
+ rebindCombineMany,
7820
10048
  releaseLease,
7821
10049
  resolveBudget,
7822
10050
  resolvePerOpBudget,
7823
10051
  retireExpired,
7824
10052
  rollbackSegment,
10053
+ runCombineMany,
7825
10054
  runConsistencyCheck,
7826
10055
  runExport,
7827
10056
  safeMetrics,