@cloudbitmaps/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,6 +37,14 @@ function applyRemove(delta, remainder) {
37
37
  delta.removes.add(remainder);
38
38
  delta.adds.remove(remainder);
39
39
  }
40
+ function applyAddAll(delta, ids) {
41
+ delta.adds.orInPlace(ids);
42
+ delta.removes.andNotInPlace(ids);
43
+ }
44
+ function applyRemoveAll(delta, ids) {
45
+ delta.removes.orInPlace(ids);
46
+ delta.adds.andNotInPlace(ids);
47
+ }
40
48
  function effective(cold, delta) {
41
49
  const out = cold.clone();
42
50
  out.orInPlace(delta.adds);
@@ -130,6 +138,19 @@ async function collectWithinBudget(source, budget, op) {
130
138
  return out;
131
139
  }
132
140
 
141
+ // src/core/cooperative.ts
142
+ var YIELD_EVERY = 1024;
143
+ function yieldEvery(clock, everyN = YIELD_EVERY) {
144
+ if (clock === void 0) return () => null;
145
+ const pause = clock.yieldNow !== void 0 ? () => clock.yieldNow() : () => clock.sleep(1);
146
+ let n = 0;
147
+ return () => {
148
+ if (++n < everyN) return null;
149
+ n = 0;
150
+ return pause();
151
+ };
152
+ }
153
+
133
154
  // src/core/keys.ts
134
155
  var FIELD = " ";
135
156
  var NO_NAMESPACE = "/";
@@ -309,6 +330,8 @@ async function withRetry(op, policy, deps) {
309
330
  var DEFAULT_MAX_BITMAP_BYTES = 1 << 20;
310
331
  var DEFAULT_MAX_RETRIES = 16;
311
332
  var DEFAULT_INTERSECT_CONCURRENCY = 8;
333
+ var MUTATE_STAGE_IDS = 1 << 20;
334
+ var YIELD_EVERY_IDS = 1 << 14;
312
335
  var InstantClock = class {
313
336
  now() {
314
337
  return 0;
@@ -323,6 +346,7 @@ var ZeroRng = class {
323
346
  }
324
347
  };
325
348
  var DEFAULT_MAX_WARM_SCAN_BYTES = 64 * 1024 * 1024;
349
+ var DEFAULT_WRITE_CONCURRENCY = 4;
326
350
  var SegmentEngine = class {
327
351
  warm;
328
352
  cold;
@@ -352,7 +376,7 @@ var SegmentEngine = class {
352
376
  this.rng = deps.rng ?? new ZeroRng();
353
377
  this.occBackoff = deps.occBackoff ?? DEFAULT_OCC_BACKOFF;
354
378
  this.readOpts = { consistent: (deps.warmReadConsistency ?? "strong") !== "eventual" };
355
- this.writeConcurrency = deps.writeConcurrency ?? 1;
379
+ this.writeConcurrency = deps.writeConcurrency ?? DEFAULT_WRITE_CONCURRENCY;
356
380
  this.maxWarmScanBytes = deps.maxWarmScanBytes ?? DEFAULT_MAX_WARM_SCAN_BYTES;
357
381
  if (!Number.isFinite(this.maxWarmScanBytes) || this.maxWarmScanBytes < 1) {
358
382
  throw new ValidationError(
@@ -372,10 +396,10 @@ var SegmentEngine = class {
372
396
  return this.readModifyWrite(seg, chunkKey, (d) => applyRemove(d, remainder));
373
397
  }
374
398
  addMany(seg, ids) {
375
- return this.mutateMany(seg, ids, applyAdd);
399
+ return this.mutateMany(seg, ids, applyAddAll);
376
400
  }
377
401
  removeMany(seg, ids) {
378
- return this.mutateMany(seg, ids, applyRemove);
402
+ return this.mutateMany(seg, ids, applyRemoveAll);
379
403
  }
380
404
  async has(seg, id, opts) {
381
405
  const { chunkKey, remainder } = splitId(id);
@@ -456,9 +480,44 @@ var SegmentEngine = class {
456
480
  * re-resolves fresh (bypassing the TTL), which can hop generations even sub-TTL — still whole/immutable per
457
481
  * read, never torn. Full intra-op snapshot isolation is deferred (gap #4).
458
482
  */
459
- async *intersect(segs, options) {
483
+ /**
484
+ * `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
485
+ *
486
+ * The `exclude` operands are what make suppression **compose without materialising**. Applying suppression
487
+ * afterwards (intersect into a temp segment, then remove) writes an intermediate segment that nobody wants;
488
+ * applying it here folds it into the same chunk-aligned pass. It is also cheaper than it looks: an exclude
489
+ * operand is read **only at the keys that survived the intersection**, so a 61,000-chunk global opt-out list
490
+ * intersected against a narrow audience costs a handful of reads, not 61,000.
491
+ */
492
+ intersect(segs, options) {
493
+ return this.combine(segs, options?.exclude ?? [], "all", "intersect", options);
494
+ }
495
+ /**
496
+ * `segs[0] ∪ segs[1] ∪ …`, minus every id in `options.exclude` — streamed ascending.
497
+ *
498
+ * **Union is the one composite read with no chunk-skipping**, and that is a property of the operation rather
499
+ * than of this implementation: an id in *any* operand belongs to the result, so every chunk of every operand
500
+ * has to be read. `intersect` can prune a key absent from any operand; union cannot prune anything. It is
501
+ * budgeted exactly like `intersect` so a wide union is refused rather than quietly billed.
502
+ */
503
+ union(segs, options) {
504
+ return this.combine(segs, options?.exclude ?? [], "any", "union", options);
505
+ }
506
+ /**
507
+ * `seg \ (excludes[0] ∪ excludes[1] ∪ …)` — streamed ascending. Suppression on its own.
508
+ *
509
+ * Reads every chunk of `seg` (any of them may survive) but each exclude **only where it overlaps `seg`**, so
510
+ * the cost scales with the segment being filtered rather than with the size of the suppression list.
511
+ */
512
+ andNot(seg, excludes, options) {
513
+ return this.combine([seg], excludes, "all", "andNot", options);
514
+ }
515
+ async *combine(segs, excludeSegs, mode, op, options) {
460
516
  if (segs.length === 0) {
461
- throw new ValidationError("intersect requires at least one segment");
517
+ throw new ValidationError(`${op} requires at least one segment`);
518
+ }
519
+ if (op === "andNot" && excludeSegs.length === 0) {
520
+ throw new ValidationError("andNot requires at least one segment to subtract");
462
521
  }
463
522
  const limit = options?.concurrency ?? DEFAULT_INTERSECT_CONCURRENCY;
464
523
  if (!Number.isInteger(limit) || limit < 1) {
@@ -467,34 +526,52 @@ var SegmentEngine = class {
467
526
  );
468
527
  }
469
528
  const budget = resolvePerOpBudget(options?.budget, this.budget);
470
- const operands = await Promise.all(
471
- segs.map(async (seg) => {
472
- const warmRows = await this.collectWarm(seg, "intersect", null);
473
- const keys = await this.chunkKeys(seg, warmRows);
474
- const gen = await this.currentGen(seg);
475
- return { seg, warmRows, keys: new Set(keys), gen };
476
- })
477
- );
478
- const pivot = operands.reduce((a, b) => a.keys.size <= b.keys.size ? a : b);
479
- const common = [];
480
- for (const k of pivot.keys) {
481
- if (operands.every((op) => op.keys.has(k))) common.push(k);
482
- }
483
- common.sort((a, b) => a - b);
484
- checkBudget(budget, common.length * operands.length, "intersect");
529
+ const extract = async (seg) => {
530
+ const warmRows = await this.collectWarm(seg, op, null);
531
+ const keys = await this.chunkKeys(seg, warmRows);
532
+ const gen = await this.currentGen(seg);
533
+ return { seg, warmRows, keys: new Set(keys), gen };
534
+ };
535
+ const [operands, excludes] = await Promise.all([
536
+ Promise.all(segs.map(extract)),
537
+ Promise.all(excludeSegs.map(extract))
538
+ ]);
539
+ const candidates = [];
540
+ if (mode === "all") {
541
+ const pivot = operands.reduce((a, b) => a.keys.size <= b.keys.size ? a : b);
542
+ for (const k of pivot.keys) {
543
+ if (operands.every((o) => o.keys.has(k))) candidates.push(k);
544
+ }
545
+ } else {
546
+ const seen = /* @__PURE__ */ new Set();
547
+ for (const o of operands) for (const k of o.keys) seen.add(k);
548
+ for (const k of seen) candidates.push(k);
549
+ }
550
+ candidates.sort((a, b) => a - b);
551
+ const common = candidates;
552
+ let units = 0;
553
+ for (const key of common) {
554
+ for (const o of operands) if (mode === "all" || o.keys.has(key)) units += 1;
555
+ for (const e of excludes) if (e.keys.has(key)) units += 1;
556
+ }
557
+ checkBudget(budget, units, op);
485
558
  if (this.metricsOn) {
486
559
  const distinctKeys = /* @__PURE__ */ new Set();
487
- for (const op of operands) {
488
- for (const k of op.keys) distinctKeys.add(k);
560
+ for (const op2 of operands) {
561
+ for (const k of op2.keys) distinctKeys.add(k);
489
562
  }
563
+ for (const e of excludes) for (const k of e.keys) distinctKeys.add(k);
490
564
  this.metrics.onEvent({
491
565
  kind: "intersect",
492
- operands: segs.length,
566
+ op,
567
+ operands: segs.length + excludes.length,
493
568
  fetchedChunks: common.length,
569
+ // Never negative: under 'any' every candidate key came from some operand, so `distinctKeys` is a
570
+ // superset of `common`; excludes only ever add keys here.
494
571
  skippedChunks: distinctKeys.size - common.length
495
572
  });
496
573
  }
497
- const startAt = (key) => this.intersectChunk(operands, key).then(
574
+ const startAt = (key) => this.combineChunk(operands, excludes, mode, key).then(
498
575
  (result) => ({ key, result }),
499
576
  (error) => ({ key, result: null, error })
500
577
  );
@@ -516,16 +593,33 @@ var SegmentEngine = class {
516
593
  * first operand's effective chunk and mutates it in place — safe because `effectiveChunk` always returns
517
594
  * a FRESH bitmap (never a cached/shared Cold instance), so this can't poison the HOT cache.
518
595
  */
519
- async intersectChunk(operands, chunkKey) {
596
+ async combineChunk(operands, excludes, mode, chunkKey) {
597
+ const present = mode === "all" ? operands : operands.filter((o) => o.keys.has(chunkKey));
598
+ if (present.length === 0) return null;
520
599
  const chunks = await Promise.all(
521
- operands.map((op) => this.effectiveChunk(op.seg, chunkKey, op.warmRows, op.gen))
600
+ present.map((op) => this.effectiveChunk(op.seg, chunkKey, op.warmRows, op.gen))
522
601
  );
523
602
  const acc = chunks[0];
524
603
  for (let i = 1; i < chunks.length; i++) {
525
- acc.andInPlace(chunks[i]);
526
- if (acc.isEmpty) return null;
604
+ if (mode === "all") {
605
+ acc.andInPlace(chunks[i]);
606
+ if (acc.isEmpty) return null;
607
+ } else {
608
+ acc.orInPlace(chunks[i]);
609
+ }
527
610
  }
528
- return acc.isEmpty ? null : acc;
611
+ if (acc.isEmpty) return null;
612
+ const relevant = excludes.filter((e) => e.keys.has(chunkKey));
613
+ if (relevant.length > 0) {
614
+ const cuts = await Promise.all(
615
+ relevant.map((e) => this.effectiveChunk(e.seg, chunkKey, e.warmRows, e.gen))
616
+ );
617
+ for (const cut of cuts) {
618
+ acc.andNotInPlace(cut);
619
+ if (acc.isEmpty) return null;
620
+ }
621
+ }
622
+ return acc;
529
623
  }
530
624
  /**
531
625
  * Materialize `seg[0] ∩ seg[1] ∩ …` into `dest` via batched `addMany` (streaming, bounded memory).
@@ -533,9 +627,30 @@ var SegmentEngine = class {
533
627
  * `dest` is added to, not replaced.
534
628
  */
535
629
  async intersectInto(dest, segs, options) {
536
- const batchSize = Math.max(1, options?.batchSize ?? 4096);
630
+ await this.drainInto(dest, this.intersect(segs, options), options?.batchSize);
631
+ }
632
+ /**
633
+ * Materialize `segs[0] ∪ segs[1] ∪ …` (minus `options.exclude`) into `dest`. **Not atomic**, like
634
+ * {@link intersectInto}. See {@link union} for why this one cannot skip any chunk.
635
+ */
636
+ async unionInto(dest, segs, options) {
637
+ await this.drainInto(dest, this.union(segs, options), options?.batchSize);
638
+ }
639
+ /** Materialize `seg \ (excludes…)` into `dest`. **Not atomic**, like {@link intersectInto}. */
640
+ async andNotInto(dest, seg, excludes, options) {
641
+ await this.drainInto(dest, this.andNot(seg, excludes, options), options?.batchSize);
642
+ }
643
+ /**
644
+ * Stream a combine's results into `dest` in bounded batches.
645
+ *
646
+ * `dest` is **added to, not replaced**, and this is **not atomic** — ids land chunk-by-chunk, so a failure
647
+ * mid-stream leaves a partial result, exactly as `addMany` does. Batching keeps memory bounded no matter how
648
+ * large the result is.
649
+ */
650
+ async drainInto(dest, source, batchSizeOption) {
651
+ const batchSize = Math.max(1, batchSizeOption ?? 4096);
537
652
  let batch = [];
538
- for await (const id of this.intersect(segs, options)) {
653
+ for await (const id of source) {
539
654
  batch.push(id);
540
655
  if (batch.length >= batchSize) {
541
656
  await this.addMany(dest, batch);
@@ -544,19 +659,77 @@ var SegmentEngine = class {
544
659
  }
545
660
  if (batch.length > 0) await this.addMany(dest, batch);
546
661
  }
662
+ /**
663
+ * Group ids by chunk, then apply each chunk's whole batch in a single OCC read-modify-write.
664
+ *
665
+ * Accepts a **sync or async** iterable, so an Athena/BigQuery cursor streams straight in without the caller
666
+ * hand-batching `page → addMany(page)`.
667
+ *
668
+ * **The pending ids are held as BITMAPS, not as JS numbers, and that is the load-bearing choice.** Streaming
669
+ * input is unbounded by definition, so something has to give. The obvious answer — buffer N ids, flush them to
670
+ * the backend, repeat — bounds memory and is quietly catastrophic on cost: with ids arriving in arbitrary
671
+ * order, every flush touches nearly every chunk again, so an 11M-id stream at a 1M-id buffer would issue
672
+ * **11x** the OCC round-trips of a single pass. In a library whose headline claim is honest cost, silently
673
+ * multiplying someone's write bill by the size of their input is not a bounded-memory strategy; it is a
674
+ * different bug.
675
+ *
676
+ * So the staging buffer is capped, but it folds into per-chunk **compressed bitmaps** rather than being
677
+ * written out. Peak memory is the roaring representation of the ids seen so far — which is what the segment
678
+ * itself costs, and the floor for any implementation that writes each chunk exactly once — while the backend
679
+ * still sees exactly one read-modify-write per distinct chunk regardless of stream length. This is strictly
680
+ * less memory than the previous sync-only version, which held every remainder as an uncompressed JS number.
681
+ *
682
+ * There is no way to do better without a sorted stream: a chunk can only be written once it is known that no
683
+ * further ids for it will arrive, and unsorted input never gives that guarantee. Callers streaming *very*
684
+ * large sets want `bulkLoadCrbmGeneration` instead — it writes one immutable Cold object rather than tens of
685
+ * thousands of warm rows. The crossover is documented in the guide.
686
+ */
547
687
  async mutateMany(seg, ids, apply) {
548
688
  const byChunk = /* @__PURE__ */ new Map();
549
- for (const id of ids) {
689
+ const staged = /* @__PURE__ */ new Map();
690
+ let pending = 0;
691
+ const tickId = yieldEvery(this.clock, YIELD_EVERY_IDS);
692
+ const compact = () => {
693
+ for (const [chunkKey, rems] of staged) {
694
+ if (rems.length === 0) continue;
695
+ const existing = byChunk.get(chunkKey);
696
+ if (existing === void 0) byChunk.set(chunkKey, this.codec.fromValues(rems));
697
+ else existing.addMany(rems);
698
+ rems.length = 0;
699
+ }
700
+ pending = 0;
701
+ };
702
+ const stage = (id) => {
550
703
  const { chunkKey, remainder } = splitId(id);
551
- const rems = byChunk.get(chunkKey) ?? [];
704
+ let rems = staged.get(chunkKey);
705
+ if (rems === void 0) {
706
+ rems = [];
707
+ staged.set(chunkKey, rems);
708
+ }
552
709
  rems.push(remainder);
553
- byChunk.set(chunkKey, rems);
710
+ pending += 1;
711
+ };
712
+ if (Symbol.asyncIterator in ids) {
713
+ for await (const id of ids) {
714
+ stage(id);
715
+ if (pending >= MUTATE_STAGE_IDS) compact();
716
+ const pause = tickId();
717
+ if (pause !== null) await pause;
718
+ }
719
+ } else {
720
+ for (const id of ids) {
721
+ stage(id);
722
+ if (pending >= MUTATE_STAGE_IDS) compact();
723
+ const pause = tickId();
724
+ if (pause !== null) await pause;
725
+ }
554
726
  }
727
+ compact();
555
728
  await mapWithConcurrency(
556
729
  [...byChunk],
557
730
  this.writeConcurrency,
558
- ([chunkKey, rems]) => this.readModifyWrite(seg, chunkKey, (d) => {
559
- for (const r of rems) apply(d, r);
731
+ ([chunkKey, bitmap]) => this.readModifyWrite(seg, chunkKey, (d) => {
732
+ apply(d, bitmap);
560
733
  })
561
734
  );
562
735
  }
@@ -1662,6 +1835,7 @@ function coldBlobReader(driver, key) {
1662
1835
  }
1663
1836
  var DEFAULT_CURRENT_GEN_TTL_MS = 2e3;
1664
1837
  var BULK_FLUSH_IDS = 1 << 20;
1838
+ var YIELD_EVERY_IDS2 = 1 << 14;
1665
1839
  var DEFAULT_MAX_OPEN_SEGMENTS = 1024;
1666
1840
  var DEFAULT_MAX_OPEN_INDEX_BYTES = 64 * 1024 * 1024;
1667
1841
  var CrbmColdChunkSource = class {
@@ -1880,11 +2054,14 @@ var CrbmColdChunkSource = class {
1880
2054
  };
1881
2055
  function writeCrbmGeneration(driver, key, chunks, options = {}) {
1882
2056
  const sorted = [...chunks].sort((a, b) => a.chunkKey - b.chunkKey);
2057
+ const tick = yieldEvery(options.clock);
1883
2058
  return driver.putImmutable(key, async (sink) => {
1884
2059
  const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
1885
2060
  for (const { chunkKey, bitmap } of sorted) {
1886
2061
  if (bitmap.isEmpty) continue;
1887
2062
  await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
2063
+ const pause = tick();
2064
+ if (pause !== null) await pause;
1888
2065
  }
1889
2066
  await writer.finish();
1890
2067
  });
@@ -1892,6 +2069,7 @@ function writeCrbmGeneration(driver, key, chunks, options = {}) {
1892
2069
  async function writeCrbmGenerationStream(driver, key, chunks, options = {}) {
1893
2070
  const chunkKeys = [];
1894
2071
  let cardinality = 0;
2072
+ const tick = yieldEvery(options.clock);
1895
2073
  const { size, sha256 } = await driver.putImmutable(key, async (sink) => {
1896
2074
  const writer = new CrbmWriter(sink, { generation: key.generation, crypto: options.crypto });
1897
2075
  for await (const { chunkKey, bitmap } of chunks) {
@@ -1899,6 +2077,8 @@ async function writeCrbmGenerationStream(driver, key, chunks, options = {}) {
1899
2077
  await writer.addChunk(chunkKey, bitmap.serialize(), bitmap.size);
1900
2078
  chunkKeys.push(chunkKey);
1901
2079
  cardinality += bitmap.size;
2080
+ const pause = tick();
2081
+ if (pause !== null) await pause;
1902
2082
  }
1903
2083
  await writer.finish();
1904
2084
  });
@@ -1941,17 +2121,21 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
1941
2121
  const byChunk = /* @__PURE__ */ new Map();
1942
2122
  const pendingByChunk = /* @__PURE__ */ new Map();
1943
2123
  let pending = 0;
1944
- const flushPending = () => {
2124
+ const tickChunk = yieldEvery(options.clock);
2125
+ const tickId = yieldEvery(options.clock, YIELD_EVERY_IDS2);
2126
+ const flushPending = async () => {
1945
2127
  for (const [chunkKey, rems] of pendingByChunk) {
1946
2128
  if (rems.length === 0) continue;
1947
2129
  const existingBitmap = byChunk.get(chunkKey);
1948
2130
  if (existingBitmap === void 0) byChunk.set(chunkKey, codec.fromValues(rems));
1949
2131
  else existingBitmap.addMany(rems);
1950
2132
  rems.length = 0;
2133
+ const pause = tickChunk();
2134
+ if (pause !== null) await pause;
1951
2135
  }
1952
2136
  pending = 0;
1953
2137
  };
1954
- for await (const id of toAsyncIterable(ids)) {
2138
+ const ingest = (id) => {
1955
2139
  const { chunkKey, remainder } = splitId(id);
1956
2140
  let bucket = pendingByChunk.get(chunkKey);
1957
2141
  if (bucket === void 0) {
@@ -1959,14 +2143,29 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
1959
2143
  pendingByChunk.set(chunkKey, bucket);
1960
2144
  }
1961
2145
  bucket.push(remainder);
1962
- if (++pending >= BULK_FLUSH_IDS) flushPending();
2146
+ return ++pending >= BULK_FLUSH_IDS;
2147
+ };
2148
+ if (Symbol.asyncIterator in ids) {
2149
+ for await (const id of ids) {
2150
+ if (ingest(id)) await flushPending();
2151
+ const pause = tickId();
2152
+ if (pause !== null) await pause;
2153
+ }
2154
+ } else {
2155
+ for (const id of ids) {
2156
+ if (ingest(id)) await flushPending();
2157
+ const pause = tickId();
2158
+ if (pause !== null) await pause;
2159
+ }
1963
2160
  }
1964
- flushPending();
2161
+ await flushPending();
1965
2162
  let cardinality = 0;
1966
2163
  const chunks = [];
1967
2164
  for (const [chunkKey, bitmap] of byChunk) {
1968
2165
  cardinality += bitmap.size;
1969
2166
  chunks.push({ chunkKey, bitmap });
2167
+ const pause = tickChunk();
2168
+ if (pause !== null) await pause;
1970
2169
  }
1971
2170
  if (options.keystore !== void 0 && options.registry === void 0) {
1972
2171
  throw new ValidationError("bulk-load encryption requires a registry to store the wrapped DEK");
@@ -1989,7 +2188,10 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
1989
2188
  crypto = { aead: minted.aead, aadFor: (scope) => aadFor(key, key.generation, scope) };
1990
2189
  }
1991
2190
  }
1992
- const { size, sha256 } = await writeCrbmGeneration(driver, key, chunks, { crypto });
2191
+ const { size, sha256 } = await writeCrbmGeneration(driver, key, chunks, {
2192
+ crypto,
2193
+ clock: options.clock
2194
+ });
1993
2195
  if (options.registry !== void 0) {
1994
2196
  const becameCurrent = await publishGeneration(options.registry, key, {
1995
2197
  wrappedDeks: newWrapped
@@ -2005,13 +2207,6 @@ async function bulkLoadCrbmGeneration(driver, key, ids, options = {}) {
2005
2207
  }
2006
2208
  return { size, sha256, chunkCount: chunks.length, cardinality };
2007
2209
  }
2008
- async function* toAsyncIterable(src) {
2009
- if (Symbol.asyncIterator in src) {
2010
- yield* src;
2011
- } else {
2012
- yield* src;
2013
- }
2014
- }
2015
2210
 
2016
2211
  // src/core/compaction.ts
2017
2212
  var DEFAULT_MAX_BITMAP_BYTES2 = 1 << 20;
@@ -3757,6 +3952,6 @@ var RetryingRegistryDriver = class {
3757
3952
  }
3758
3953
  };
3759
3954
 
3760
- export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
3955
+ export { AWS_US_EAST_1_ONDEMAND, BoundedLru, BufferReader, BufferSink, CountingMetricsSink, CrbmColdChunkSource, CrbmReader, CrbmWriter, DEFAULT_BUDGET, DEFAULT_MAX_SCAN_SEGMENTS, DEFAULT_MAX_WARM_SCAN_BYTES, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, DEFAULT_WRITE_CONCURRENCY, InProcessKeystore, LocalFsColdDriver, LocalFsRegistryDriver, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, MemoryWarmDriver, NOOP_AUDIT, NOOP_METRICS, NodeAead, RecordingAuditSink, RetryingColdChunkSource, RetryingColdDriver, RetryingRegistryDriver, RetryingWarmDriver, SegmentEngine, aadFor, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, collectWithinBudget, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isTransient, joinId, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, withRetry, writeCrbmGeneration };
3761
3956
  //# sourceMappingURL=index.js.map
3762
3957
  //# sourceMappingURL=index.js.map