@cloudbitmaps/roaring 0.19.1 → 0.21.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
@@ -3,28 +3,25 @@ import {
3
3
  BoundedLru,
4
4
  PinnedStorageChunkSource,
5
5
  segmentKey,
6
- CrbmStorageChunkSource,
6
+ CrbmStorageChunkSource as CrbmStorageChunkSource2,
7
7
  DEFAULT_BUDGET,
8
- DEFAULT_RETRY_POLICY,
8
+ DEFAULT_RETRY_POLICY as DEFAULT_RETRY_POLICY2,
9
9
  withRetry,
10
- isStorageBackend,
11
- NOOP_METRICS,
12
- RetryingStorageChunkSource,
10
+ NOOP_METRICS as NOOP_METRICS2,
11
+ RetryingStorageChunkSource as RetryingStorageChunkSource2,
13
12
  SegmentEngine,
14
13
  LEASE_SKEW_MS,
15
14
  LeaseExpiredError,
16
15
  MAX_LEASE_MS,
17
16
  NotFoundError,
18
17
  UnsupportedError,
19
- ValidationError as ValidationError2,
18
+ ValidationError as ValidationError4,
20
19
  WriteConflictError,
21
20
  checkBudget,
22
21
  collectWithinBudget,
23
22
  excludingReservedRows,
24
23
  dropSegment,
25
24
  eraseIdFromSegment,
26
- estimateCost,
27
- groundedReport,
28
25
  judgeLoad,
29
26
  loadSegment,
30
27
  loadSegmentChunks,
@@ -45,7 +42,7 @@ import {
45
42
  splitId,
46
43
  takeLease
47
44
  } from "@cloudbitmaps/core";
48
- import { validateSegmentRef } from "@cloudbitmaps/core/driver-kit";
45
+ import { pointerIdOf, requirePositiveInt, validateSegmentRef } from "@cloudbitmaps/core/driver-kit";
49
46
 
50
47
  // src/open-charging-storage.ts
51
48
  var OpenChargingStorage = class {
@@ -78,8 +75,9 @@ var OpenChargingStorage = class {
78
75
  if (!this.free.has(key.generation)) this.charge();
79
76
  return this.inner.getTail(key, maxBytes);
80
77
  }
81
- delete(key) {
82
- return this.inner.delete(key);
78
+ /** Handed through whole: the erasure's delete of a holder is conditioned on the object it searched. */
79
+ delete(key, options) {
80
+ return this.inner.delete(key, options);
83
81
  }
84
82
  list(ref) {
85
83
  return this.inner.list(ref);
@@ -523,90 +521,265 @@ var OPTION_KEYS = {
523
521
  };
524
522
 
525
523
  // src/lease-guards.ts
526
- var guardIdIterable = (inner, check) => ({
527
- [Symbol.asyncIterator]: () => {
528
- const it = inner[Symbol.asyncIterator]();
529
- let chunk = -1;
530
- let first = true;
531
- const stop = async (err) => {
532
- await it.return?.();
533
- throw err;
534
- };
535
- return {
536
- async next() {
537
- if (first) {
538
- first = false;
539
- try {
540
- check();
541
- } catch (err) {
542
- return stop(err);
543
- }
544
- }
545
- const r = await it.next();
546
- if (r.done === true) return r;
547
- const key = r.value >>> 16;
548
- if (key !== chunk) {
549
- try {
550
- check();
551
- } catch (err) {
552
- return stop(err);
553
- }
554
- chunk = key;
555
- }
556
- return r;
557
- },
558
- async return() {
559
- await it.return?.();
560
- return { done: true, value: void 0 };
524
+ var done = (value) => ({ done: true, value });
525
+ var guard = (inner, check, keyOf) => {
526
+ const it = inner[Symbol.asyncIterator]();
527
+ let first = true;
528
+ let over = false;
529
+ let last = -1;
530
+ const end = async (value) => {
531
+ over = true;
532
+ await it.return?.(value);
533
+ return value;
534
+ };
535
+ const stop = async (err) => {
536
+ await end();
537
+ throw err;
538
+ };
539
+ const deliver = (r) => {
540
+ if (over) return done();
541
+ if (r.done === true) return r;
542
+ const key = keyOf?.(r.value);
543
+ if (key === void 0 || key !== last) {
544
+ try {
545
+ check();
546
+ } catch (err) {
547
+ return stop(err);
561
548
  }
562
- };
563
- }
564
- });
565
- var guardIds = (inner, check) => ({
566
- [Symbol.asyncIterator]: () => guardIdIterable(inner, check)[Symbol.asyncIterator](),
567
- batches: () => guardItems(inner.batches(), check)
568
- });
569
- var guardItems = (inner, check) => ({
570
- [Symbol.asyncIterator]: () => {
571
- const it = inner[Symbol.asyncIterator]();
572
- let first = true;
573
- const stop = async (err) => {
574
- await it.return?.();
575
- throw err;
576
- };
577
- return {
578
- async next() {
579
- if (first) {
580
- first = false;
581
- try {
582
- check();
583
- } catch (err) {
584
- return stop(err);
585
- }
586
- }
587
- const r = await it.next();
588
- if (r.done === true) return r;
549
+ if (key !== void 0) last = key;
550
+ }
551
+ return r;
552
+ };
553
+ return {
554
+ [Symbol.asyncIterator]() {
555
+ return this;
556
+ },
557
+ async next() {
558
+ if (over) return done();
559
+ if (first) {
560
+ first = false;
589
561
  try {
590
562
  check();
591
563
  } catch (err) {
592
564
  return stop(err);
593
565
  }
594
- return r;
595
- },
596
- async return() {
597
- await it.return?.();
598
- return { done: true, value: void 0 };
566
+ }
567
+ return deliver(await it.next());
568
+ },
569
+ async return(value) {
570
+ return done(await end(value));
571
+ },
572
+ async throw(err) {
573
+ first = false;
574
+ if (it.throw === void 0) return stop(err);
575
+ let r;
576
+ try {
577
+ r = await it.throw(err);
578
+ } catch (thrown) {
579
+ over = true;
580
+ throw thrown;
581
+ }
582
+ return deliver(r);
583
+ }
584
+ };
585
+ };
586
+ var guardIdIterable = (inner, check) => guard(inner, check, (id) => id >>> 16);
587
+ var guardIds = (inner, check) => Object.assign(guardIdIterable(inner, check), {
588
+ batches: () => guardItems(inner.batches(), check)
589
+ });
590
+ var guardItems = (inner, check) => guard(inner, check);
591
+
592
+ // src/wire-store.ts
593
+ import {
594
+ CrbmStorageChunkSource,
595
+ DEFAULT_RETRY_POLICY,
596
+ NOOP_METRICS,
597
+ RetryingStorageChunkSource,
598
+ ValidationError as ValidationError2,
599
+ isStorageBackend
600
+ } from "@cloudbitmaps/core";
601
+ function resolveRetryPolicy(partial) {
602
+ const baseDelayMs = partial.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs;
603
+ return {
604
+ maxAttempts: partial.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts,
605
+ baseDelayMs,
606
+ maxDelayMs: partial.maxDelayMs ?? (typeof baseDelayMs === "number" && baseDelayMs > DEFAULT_RETRY_POLICY.maxDelayMs ? baseDelayMs : DEFAULT_RETRY_POLICY.maxDelayMs),
607
+ backoffFactor: partial.backoffFactor ?? DEFAULT_RETRY_POLICY.backoffFactor,
608
+ jitter: partial.jitter ?? DEFAULT_RETRY_POLICY.jitter
609
+ };
610
+ }
611
+ function wireStore(options, clock, rng, metrics) {
612
+ const resolved = resolveStorageSource(options, clock);
613
+ if (metrics !== NOOP_METRICS && isStorageBackend(options.storage)) {
614
+ options.storage.attachMetrics?.(metrics);
615
+ }
616
+ let storage = resolved.source;
617
+ let retryOptions;
618
+ if (options.retry !== false) {
619
+ const { onRetry: userOnRetry, ...ov } = options.retry ?? {};
620
+ if (userOnRetry !== void 0 && typeof userOnRetry !== "function") {
621
+ throw new ValidationError2("retry.onRetry must be a function");
622
+ }
623
+ retryOptions = {
624
+ clock,
625
+ rng,
626
+ policy: resolveRetryPolicy(ov),
627
+ // Bridge transient-fault retries into the metrics stream, then call the user's own hook.
628
+ onRetry: (info) => {
629
+ metrics.onEvent({
630
+ kind: "retry",
631
+ reason: "transient",
632
+ attempt: info.attempt,
633
+ delayMs: info.delayMs
634
+ });
635
+ userOnRetry?.(info);
599
636
  }
600
637
  };
638
+ storage = new RetryingStorageChunkSource(storage, retryOptions);
601
639
  }
602
- });
603
- var guardChunks = (inner, check) => guardItems(inner, check);
640
+ return {
641
+ storage,
642
+ source: resolved.source,
643
+ driver: resolved.driver,
644
+ registry: resolved.registry,
645
+ retryOptions
646
+ };
647
+ }
648
+ function resolveStorageSource(options, clock) {
649
+ const storage = options.storage;
650
+ if (storage === null || typeof storage !== "object") {
651
+ throw new ValidationError2(
652
+ "`storage` must be a StorageBackend, an IStorageDriver, or a StorageChunkSource"
653
+ );
654
+ }
655
+ const asBackend = storage;
656
+ const isObject = (v) => v !== null && typeof v === "object";
657
+ const hasGetChunk = typeof storage.getChunk === "function";
658
+ const hasPutImmutable = typeof storage.putImmutable === "function";
659
+ const isBackend = isStorageBackend(storage);
660
+ if (isBackend && (hasGetChunk || hasPutImmutable)) {
661
+ throw new ValidationError2(
662
+ `\`storage\` is a StorageBackend that also exposes ${hasGetChunk ? "`getChunk`" : "`putImmutable`"} \u2014 ambiguous. A backend must not also be a driver or a source; pass whichever one you mean.`
663
+ );
664
+ }
665
+ const hasStorageHalf = isObject(asBackend.storage);
666
+ const hasRegistryHalf = isObject(asBackend.registry);
667
+ if (!isBackend && hasPutImmutable && hasRegistryHalf) {
668
+ throw new ValidationError2(
669
+ "`storage` looks like a driver that also carries a `registry`. Passed as a bare driver it would have no pointer at all \u2014 generations would resolve by list-scan, so reads could serve a generation that was written but never published. If you meant a backend, use a backend class \u2014 S3Storage, GcsStorage, AzureBlobStorage, LocalFsStorage or MemoryStorage \u2014 or, to pair a driver of your own with a registry, brand the pair with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`."
670
+ );
671
+ }
672
+ if (!isBackend && !hasGetChunk && !hasPutImmutable && hasStorageHalf && hasRegistryHalf) {
673
+ const storageOk = typeof asBackend.storage.putImmutable === "function";
674
+ const registryOk = typeof asBackend.registry.compareAndSwap === "function";
675
+ if (!storageOk || !registryOk) {
676
+ const bad = !storageOk ? "`storage` half is not an IStorageDriver (no `putImmutable`)" : "`registry` half is not an IRegistryDriver (no `compareAndSwap`)";
677
+ throw new ValidationError2(
678
+ "`storage` looks like a backend but its " + bad + " \u2014 use a backend class, or brand a pair of drivers with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`."
679
+ );
680
+ }
681
+ throw new ValidationError2(
682
+ "`storage` must be a backend \u2014 S3Storage, GcsStorage, AzureBlobStorage, LocalFsStorage or MemoryStorage. An object with `.storage` and `.registry` is not one: a backend builds both halves from a single bucket and prefix, so they cannot disagree, and hand-assembling them re-opens exactly that mismatch \u2014 a store whose pointer and generations live in different places reads as empty rather than failing. A driver author who genuinely wants halves of their own \u2014 an instrumented driver, a registry in a database you already run \u2014 brands the pair with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`, which is taking on that they agree."
683
+ );
684
+ }
685
+ if (isBackend) {
686
+ const backend = storage;
687
+ return {
688
+ source: new CrbmStorageChunkSource(backend.storage, {
689
+ registry: backend.registry,
690
+ keystore: options.encryption?.keystore,
691
+ requireEncryption: options.encryption?.required,
692
+ clock,
693
+ currentGenTtlMs: options.cache?.genTtlMs,
694
+ maxOpenSegments: options.cache?.readerMax,
695
+ maxOpenIndexBytes: options.cache?.readerMaxBytes
696
+ }),
697
+ driver: backend.storage,
698
+ registry: backend.registry
699
+ };
700
+ }
701
+ if (hasGetChunk && hasPutImmutable) {
702
+ throw new ValidationError2(
703
+ "`storage` exposes both `getChunk` and `putImmutable` \u2014 ambiguous; pass a StorageBackend, an IStorageDriver, or a StorageChunkSource, not a hybrid"
704
+ );
705
+ }
706
+ if (!hasGetChunk && !hasPutImmutable) {
707
+ throw new ValidationError2(
708
+ "`storage` must be a StorageBackend, an IStorageDriver, or a StorageChunkSource"
709
+ );
710
+ }
711
+ if (hasGetChunk) {
712
+ if (options.encryption?.keystore !== void 0 || options.encryption?.required === true) {
713
+ throw new ValidationError2(
714
+ "keystore/requireEncryption apply only when the store builds its own read path; configure them on the StorageChunkSource you passed instead"
715
+ );
716
+ }
717
+ return { source: storage, driver: void 0, registry: void 0 };
718
+ }
719
+ const driver = storage;
720
+ return {
721
+ source: new CrbmStorageChunkSource(driver, {
722
+ keystore: options.encryption?.keystore,
723
+ requireEncryption: options.encryption?.required,
724
+ clock,
725
+ currentGenTtlMs: options.cache?.genTtlMs,
726
+ maxOpenSegments: options.cache?.readerMax,
727
+ maxOpenIndexBytes: options.cache?.readerMaxBytes
728
+ }),
729
+ driver,
730
+ registry: void 0
731
+ };
732
+ }
733
+
734
+ // src/options.ts
735
+ import { ValidationError as ValidationError3 } from "@cloudbitmaps/core";
736
+ var keysOf = () => (keys) => keys;
737
+ var kindOf = (v) => v === null ? "null" : Array.isArray(v) ? "an array" : `a ${typeof v}`;
738
+ function optionsError(options, op, known, booleans = []) {
739
+ if (options === void 0 || options === null) return void 0;
740
+ if (typeof options !== "object" || Array.isArray(options)) {
741
+ return new ValidationError3(`${op}: options must be an object; got ${kindOf(options)}`);
742
+ }
743
+ const bag = options;
744
+ const unknown = Object.keys(bag).filter((k) => !known.includes(k) && bag[k] !== void 0);
745
+ if (unknown.length > 0) {
746
+ return new ValidationError3(
747
+ `${op}: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}; it takes { ${known.join(", ")} }`
748
+ );
749
+ }
750
+ const audit = bag.audit;
751
+ if (audit !== void 0 && audit !== null && typeof audit.onEvent !== "function") {
752
+ return new ValidationError3(
753
+ `${op}: audit must be a sink with an onEvent(event) method, such as a RecordingAuditSink`
754
+ );
755
+ }
756
+ for (const name of booleans) {
757
+ const v = bag[name];
758
+ if (v !== void 0 && typeof v !== "boolean") {
759
+ return new ValidationError3(`${op}: \`${name}\` must be a boolean; got ${kindOf(v)}`);
760
+ }
761
+ }
762
+ return void 0;
763
+ }
764
+ function picked(options, keys) {
765
+ const out = {};
766
+ if (options === void 0 || options === null) return out;
767
+ for (const key of keys) {
768
+ const value = options[key];
769
+ if (value !== void 0) out[key] = value;
770
+ }
771
+ return out;
772
+ }
773
+ function checkOptions(options, op, known, booleans) {
774
+ const err = optionsError(options, op, known, booleans);
775
+ if (err !== void 0) throw err;
776
+ }
604
777
 
605
778
  // src/index.ts
606
779
  import {
607
780
  MemoryStorage,
608
781
  LocalFsStorage,
609
- CrbmStorageChunkSource as CrbmStorageChunkSource2,
782
+ CrbmStorageChunkSource as CrbmStorageChunkSource3,
610
783
  destroySegment,
611
784
  eraseNamespace,
612
785
  excludingReservedRows as excludingReservedRows2,
@@ -619,7 +792,7 @@ import {
619
792
  NodeAead,
620
793
  aadFor,
621
794
  CloudRoaringError,
622
- ValidationError as ValidationError3,
795
+ ValidationError as ValidationError5,
623
796
  WriteConflictError as WriteConflictError2,
624
797
  IntegrityError as IntegrityError3,
625
798
  NotFoundError as NotFoundError2,
@@ -648,10 +821,7 @@ import {
648
821
  BufferReader,
649
822
  CountingMetricsSink,
650
823
  RecordingAuditSink,
651
- DEFAULT_RETRY_POLICY as DEFAULT_RETRY_POLICY2,
652
- AWS_US_EAST_1_ONDEMAND,
653
- ELASTICACHE_REDIS_US_EAST_1_ONDEMAND,
654
- ONE_REDIS_HA_CLUSTER
824
+ DEFAULT_RETRY_POLICY as DEFAULT_RETRY_POLICY3
655
825
  } from "@cloudbitmaps/core";
656
826
  var SystemRng = class {
657
827
  /** A float in `[0, 1)`. Jitter only — never key material, never anything a caller can observe. */
@@ -667,14 +837,12 @@ function rowVersionOfLive(version) {
667
837
  return cut < 0 ? version : version.slice(0, cut);
668
838
  }
669
839
  function validateConcurrency(concurrency) {
670
- if (concurrency !== void 0 && (!Number.isInteger(concurrency) || concurrency < 1)) {
671
- throw new ValidationError2(`concurrency must be a positive integer; got ${concurrency}`);
672
- }
840
+ requirePositiveInt("concurrency", concurrency);
673
841
  }
674
842
  function requireScope(options, op) {
675
843
  if (options.namespace !== void 0) {
676
844
  if (typeof options.namespace !== "string") {
677
- throw new ValidationError2(
845
+ throw new ValidationError4(
678
846
  `${op}: namespace must be a string; got ${kindOf(options.namespace)}`
679
847
  );
680
848
  }
@@ -682,12 +850,11 @@ function requireScope(options, op) {
682
850
  }
683
851
  refuseReservedNamespace(options.namespace);
684
852
  if (options.namespace === void 0 && options.allNamespaces !== true) {
685
- throw new ValidationError2(
853
+ throw new ValidationError4(
686
854
  `${op} scans the global id space across all namespaces \u2014 pass an explicit \`namespace\`, or \`{ allNamespaces: true }\` to intentionally sweep the whole fleet`
687
855
  );
688
856
  }
689
857
  }
690
- var keysOf = () => (keys) => keys;
691
858
  var CALL_KEYS = {
692
859
  range: keysOf()(["after", "through"]),
693
860
  andNot: keysOf()([
@@ -747,7 +914,7 @@ var CALL_KEYS = {
747
914
  ]),
748
915
  rollback: keysOf()([
749
916
  "audit",
750
- "allowForward"
917
+ "allowUnknown"
751
918
  ]),
752
919
  dropSegment: keysOf()([
753
920
  "confirmSegment",
@@ -771,66 +938,12 @@ var CALL_KEYS = {
771
938
  checkConsistency: keysOf()([
772
939
  "namespace",
773
940
  "concurrency",
774
- "summaries"
941
+ "summaries",
942
+ "generations"
775
943
  ]),
776
- exportSegments: keysOf()(["format", "codec", "namespace", "ndjsonBatchBytes"]),
777
- costReport: keysOf()(["pricing", "workload"]),
778
- workload: keysOf()([
779
- "readsPerSec",
780
- "intersectsPerSec",
781
- "cacheHitRate",
782
- "chunksPerIntersect",
783
- "operandsPerIntersect",
784
- "loadsPerMonth",
785
- "requestsPerLoad",
786
- "hotSegments",
787
- "readerProcesses",
788
- "genTtlMs",
789
- "retirementsPerMonth",
790
- "purgesPerMonth",
791
- "conditionalDelete"
792
- ])
944
+ exportSegments: keysOf()(["format", "codec", "namespace", "ndjsonBatchBytes"])
793
945
  };
794
- var kindOf = (v) => v === null ? "null" : Array.isArray(v) ? "an array" : `a ${typeof v}`;
795
- function optionsError(options, op, known, booleans = []) {
796
- if (options === void 0 || options === null) return void 0;
797
- if (typeof options !== "object" || Array.isArray(options)) {
798
- return new ValidationError2(`${op}: options must be an object; got ${kindOf(options)}`);
799
- }
800
- const bag = options;
801
- const unknown = Object.keys(bag).filter((k) => !known.includes(k) && bag[k] !== void 0);
802
- if (unknown.length > 0) {
803
- return new ValidationError2(
804
- `${op}: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}; it takes { ${known.join(", ")} }`
805
- );
806
- }
807
- const audit = bag.audit;
808
- if (audit !== void 0 && audit !== null && typeof audit.onEvent !== "function") {
809
- return new ValidationError2(
810
- `${op}: audit must be a sink with an onEvent(event) method, such as a RecordingAuditSink`
811
- );
812
- }
813
- for (const name of booleans) {
814
- const v = bag[name];
815
- if (v !== void 0 && typeof v !== "boolean") {
816
- return new ValidationError2(`${op}: \`${name}\` must be a boolean; got ${kindOf(v)}`);
817
- }
818
- }
819
- return void 0;
820
- }
821
- function picked(options, keys) {
822
- const out = {};
823
- if (options === void 0 || options === null) return out;
824
- for (const key of keys) {
825
- const value = options[key];
826
- if (value !== void 0) out[key] = value;
827
- }
828
- return out;
829
- }
830
- function checkOptions(options, op, known, booleans) {
831
- const err = optionsError(options, op, known, booleans);
832
- if (err !== void 0) throw err;
833
- }
946
+ var SEGMENT_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.roaring.segment/1");
834
947
  var viewOf;
835
948
  var MATERIALIZE_MANY_KEYS = [
836
949
  "operands",
@@ -862,91 +975,6 @@ var DEFAULT_MAX_BUFFERED_BYTES = 256 * 1024 * 1024;
862
975
  var DEFAULT_PUBLISH_CONCURRENCY = 8;
863
976
  var DEFAULT_BATCH_CONCURRENCY = 1;
864
977
  var PIN_PARALLELISM = 16;
865
- function resolveStorageSource(options, clock) {
866
- const storage = options.storage;
867
- if (storage === null || typeof storage !== "object") {
868
- throw new ValidationError2(
869
- "`storage` must be a StorageBackend, an IStorageDriver, or a StorageChunkSource"
870
- );
871
- }
872
- const asBackend = storage;
873
- const isObject = (v) => v !== null && typeof v === "object";
874
- const hasGetChunk = typeof storage.getChunk === "function";
875
- const hasPutImmutable = typeof storage.putImmutable === "function";
876
- const isBackend = isStorageBackend(storage);
877
- if (isBackend && (hasGetChunk || hasPutImmutable)) {
878
- throw new ValidationError2(
879
- `\`storage\` is a StorageBackend that also exposes ${hasGetChunk ? "`getChunk`" : "`putImmutable`"} \u2014 ambiguous. A backend must not also be a driver or a source; pass whichever one you mean.`
880
- );
881
- }
882
- const hasStorageHalf = isObject(asBackend.storage);
883
- const hasRegistryHalf = isObject(asBackend.registry);
884
- if (!isBackend && hasPutImmutable && hasRegistryHalf) {
885
- throw new ValidationError2(
886
- "`storage` looks like a driver that also carries a `registry`. Passed as a bare driver it would have no pointer at all \u2014 generations would resolve by list-scan, so reads could serve a generation that was written but never published. If you meant a backend, use a backend class \u2014 S3Storage, GcsStorage, AzureBlobStorage, LocalFsStorage or MemoryStorage \u2014 or, to pair a driver of your own with a registry, brand the pair with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`."
887
- );
888
- }
889
- if (!isBackend && !hasGetChunk && !hasPutImmutable && hasStorageHalf && hasRegistryHalf) {
890
- const storageOk = typeof asBackend.storage.putImmutable === "function";
891
- const registryOk = typeof asBackend.registry.compareAndSwap === "function";
892
- if (!storageOk || !registryOk) {
893
- const bad = !storageOk ? "`storage` half is not an IStorageDriver (no `putImmutable`)" : "`registry` half is not an IRegistryDriver (no `compareAndSwap`)";
894
- throw new ValidationError2(
895
- "`storage` looks like a backend but its " + bad + " \u2014 use a backend class, or brand a pair of drivers with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`."
896
- );
897
- }
898
- throw new ValidationError2(
899
- "`storage` must be a backend \u2014 S3Storage, GcsStorage, AzureBlobStorage, LocalFsStorage or MemoryStorage. An object with `.storage` and `.registry` is not one: a backend builds both halves from a single bucket and prefix, so they cannot disagree, and hand-assembling them re-opens exactly that mismatch \u2014 a store whose pointer and generations live in different places reads as empty rather than failing. A driver author who genuinely wants halves of their own \u2014 an instrumented driver, a registry in a database you already run \u2014 brands the pair with `brandAsBackend` from `@cloudbitmaps/core/driver-kit`, which is taking on that they agree."
900
- );
901
- }
902
- if (isBackend) {
903
- const backend = storage;
904
- return {
905
- source: new CrbmStorageChunkSource(backend.storage, {
906
- registry: backend.registry,
907
- keystore: options.encryption?.keystore,
908
- requireEncryption: options.encryption?.required,
909
- clock,
910
- currentGenTtlMs: options.cache?.genTtlMs,
911
- maxOpenSegments: options.cache?.readerMax,
912
- maxOpenIndexBytes: options.cache?.readerMaxBytes
913
- }),
914
- driver: backend.storage,
915
- registry: backend.registry
916
- };
917
- }
918
- if (hasGetChunk && hasPutImmutable) {
919
- throw new ValidationError2(
920
- "`storage` exposes both `getChunk` and `putImmutable` \u2014 ambiguous; pass a StorageBackend, an IStorageDriver, or a StorageChunkSource, not a hybrid"
921
- );
922
- }
923
- if (!hasGetChunk && !hasPutImmutable) {
924
- throw new ValidationError2(
925
- "`storage` must be a StorageBackend, an IStorageDriver, or a StorageChunkSource"
926
- );
927
- }
928
- if (hasGetChunk) {
929
- if (options.encryption?.keystore !== void 0 || options.encryption?.required === true) {
930
- throw new ValidationError2(
931
- "keystore/requireEncryption apply only when the store builds its own read path; configure them on the StorageChunkSource you passed instead"
932
- );
933
- }
934
- return { source: storage, driver: void 0, registry: void 0 };
935
- }
936
- const driver = storage;
937
- return {
938
- source: new CrbmStorageChunkSource(driver, {
939
- keystore: options.encryption?.keystore,
940
- requireEncryption: options.encryption?.required,
941
- clock,
942
- currentGenTtlMs: options.cache?.genTtlMs,
943
- maxOpenSegments: options.cache?.readerMax,
944
- maxOpenIndexBytes: options.cache?.readerMaxBytes
945
- }),
946
- driver,
947
- registry: void 0
948
- };
949
- }
950
978
  var NOT_A_GROUP = {
951
979
  Null: "null",
952
980
  Array: "an array",
@@ -989,7 +1017,7 @@ var CloudRoaring = class _CloudRoaring {
989
1017
  */
990
1018
  static rejectUnknownOptions(options) {
991
1019
  if (options === null || options === void 0 || typeof options !== "object") {
992
- throw new ValidationError2(
1020
+ throw new ValidationError4(
993
1021
  "CloudRoaring needs an options object with a `storage` key \u2014 got " + (options === null ? "null" : typeof options)
994
1022
  );
995
1023
  }
@@ -1007,7 +1035,7 @@ var CloudRoaring = class _CloudRoaring {
1007
1035
  const tag = Object.prototype.toString.call(value).slice(8, -1);
1008
1036
  if (typeof value !== "object" || value === null || NOT_A_GROUP[tag] !== void 0) {
1009
1037
  const got = typeof value !== "object" ? typeof value : NOT_A_GROUP[tag];
1010
- throw new ValidationError2(
1038
+ throw new ValidationError4(
1011
1039
  `CloudRoaring's \`${group}\` must be an object${offOk ? " or `false`" : ""} \u2014 got ${got}`
1012
1040
  );
1013
1041
  }
@@ -1017,7 +1045,7 @@ var CloudRoaring = class _CloudRoaring {
1017
1045
  if (unknown.length === 0) return;
1018
1046
  const takes = (keys) => keys.map((k) => `\`${k}\``).join(", ");
1019
1047
  const named = [...new Set(unknown.map((u) => u.group))];
1020
- throw new ValidationError2(
1048
+ throw new ValidationError4(
1021
1049
  `CloudRoaring does not take ${takes(unknown.map((u) => u.group === null ? u.key : `${u.group}.${u.key}`))}. ` + named.map(
1022
1050
  (g) => g === null ? `The store takes ${takes(OPTION_KEYS.top)}.` : `\`${g}\` takes ${takes(OPTION_KEYS[g])}.`
1023
1051
  ).join(" ") + " https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/api-reference.md#build-a-store--new-cloudroaringoptions"
@@ -1037,7 +1065,7 @@ var CloudRoaring = class _CloudRoaring {
1037
1065
  if (seam === void 0 || seam === null) continue;
1038
1066
  const has = (m) => typeof seam[m] === "function";
1039
1067
  if (typeof seam !== "object" || !needs.every(has)) {
1040
- throw new ValidationError2(
1068
+ throw new ValidationError4(
1041
1069
  `CloudRoaring's \`${name}\` must be an object with ${needs.map((m) => `\`${m}()\``).join(" and ")}`
1042
1070
  );
1043
1071
  }
@@ -1048,50 +1076,17 @@ var CloudRoaring = class _CloudRoaring {
1048
1076
  _CloudRoaring.rejectHollowSeams(options);
1049
1077
  const clock = options.seams?.clock ?? new SystemClock();
1050
1078
  const rng = options.seams?.rng ?? new SystemRng();
1051
- const metrics = safeMetrics(options.metrics ?? NOOP_METRICS);
1079
+ const metrics = safeMetrics(options.metrics ?? NOOP_METRICS2);
1052
1080
  const cache = new BoundedLru({
1053
1081
  maxEntries: options.cache?.maxChunks ?? DEFAULT_CACHE_MAX_CHUNKS,
1054
1082
  ttlMs: options.cache?.ttlMs,
1055
1083
  clock
1056
1084
  });
1057
- const resolved = resolveStorageSource(options, clock);
1058
- if (metrics !== NOOP_METRICS && isStorageBackend(options.storage)) {
1059
- options.storage.attachMetrics?.(metrics);
1060
- }
1061
- let storage = resolved.source;
1062
- if (options.retry !== false) {
1063
- const { onRetry: userOnRetry, ...ov } = options.retry ?? {};
1064
- if (userOnRetry !== void 0 && typeof userOnRetry !== "function") {
1065
- throw new ValidationError2("retry.onRetry must be a function");
1066
- }
1067
- const policy = {
1068
- maxAttempts: ov.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts,
1069
- baseDelayMs: ov.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
1070
- maxDelayMs: ov.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
1071
- backoffFactor: ov.backoffFactor ?? DEFAULT_RETRY_POLICY.backoffFactor,
1072
- jitter: ov.jitter ?? DEFAULT_RETRY_POLICY.jitter
1073
- };
1074
- const retryOpts = {
1075
- clock,
1076
- rng,
1077
- policy,
1078
- // Bridge transient-fault retries into the metrics stream, then call the user's own hook.
1079
- onRetry: (info) => {
1080
- metrics.onEvent({
1081
- kind: "retry",
1082
- reason: "transient",
1083
- attempt: info.attempt,
1084
- delayMs: info.delayMs
1085
- });
1086
- userOnRetry?.(info);
1087
- }
1088
- };
1089
- storage = new RetryingStorageChunkSource(storage, retryOpts);
1090
- this.retryOptions = retryOpts;
1091
- }
1085
+ const wired = wireStore(options, clock, rng, metrics);
1086
+ this.retryOptions = wired.retryOptions;
1092
1087
  this.budget = resolveBudget(options.budget, DEFAULT_BUDGET);
1093
1088
  const deps = {
1094
- storage,
1089
+ storage: wired.storage,
1095
1090
  cache,
1096
1091
  codec: roaringCodec,
1097
1092
  // the facade injects the flagship codec; core stays codec-agnostic
@@ -1101,12 +1096,12 @@ var CloudRoaring = class _CloudRoaring {
1101
1096
  };
1102
1097
  this.engine = new SegmentEngine(deps);
1103
1098
  this.cache = cache;
1104
- this.crbmSource = resolved.source instanceof CrbmStorageChunkSource ? resolved.source : void 0;
1099
+ this.crbmSource = wired.source instanceof CrbmStorageChunkSource2 ? wired.source : void 0;
1105
1100
  this.clock = clock;
1106
1101
  this.rng = rng;
1107
1102
  this.metrics = metrics;
1108
- this.storageDriver = resolved.driver;
1109
- this.registry = resolved.registry;
1103
+ this.storageDriver = wired.driver;
1104
+ this.registry = wired.registry;
1110
1105
  this.keystore = options.encryption?.keystore;
1111
1106
  this.requireEncryption = options.encryption?.required ?? false;
1112
1107
  }
@@ -1141,26 +1136,28 @@ var CloudRoaring = class _CloudRoaring {
1141
1136
  }
1142
1137
  /**
1143
1138
  * Get a handle to a segment. Validates the name (non-empty, well-formed, within the encoded-length cap). The options
1144
- * are `{ namespace }` and nothing else: anything else is refused with {@link ValidationError}, by name, since a
1145
- * misspelt `namespace` would otherwise address the default namespace. An `expiresAt` among them is refused with its own
1146
- * message: a handle carries no deadline, and one left unread would serve the data past it. A key whose value is
1147
- * `undefined` is read as absent, so a spread of options keeps working.
1139
+ * are a plain object holding `{ namespace }` and nothing else: any other own key, enumerable or not, is refused with
1140
+ * {@link ValidationError}, by name, since a misspelt `namespace` would otherwise address the default namespace, and a
1141
+ * deadline such as `expiresAt` would be left unread. A class instance, or an object built on another, is refused too,
1142
+ * since it can carry an option no scan of its own keys sees. A key whose value is `undefined` is read as absent, so a
1143
+ * spread of options keeps working.
1148
1144
  */
1149
1145
  segment(name, options) {
1150
1146
  if (options !== void 0 && options !== null) {
1151
1147
  if (typeof options !== "object" || Array.isArray(options)) {
1152
- throw new ValidationError2("segment: options must be an object such as { namespace }");
1148
+ throw new ValidationError4("segment: options must be an object such as { namespace }");
1153
1149
  }
1154
- if (options.expiresAt !== void 0) {
1155
- throw new ValidationError2(
1156
- "segment: `expiresAt` is not an option of a handle; check a deadline where you read, or record it with `store.setRetention(ref, { expiresAt })` and run `store.retireExpired()`"
1150
+ const proto = Object.getPrototypeOf(options);
1151
+ if (proto !== null && Object.getPrototypeOf(proto) !== null) {
1152
+ throw new ValidationError4(
1153
+ "segment: options must be a plain object such as { namespace }, not a class instance or an object built on another"
1157
1154
  );
1158
1155
  }
1159
- const unknown = Object.keys(options).filter(
1156
+ const unknown = Object.getOwnPropertyNames(options).filter(
1160
1157
  (k) => k !== "namespace" && options[k] !== void 0
1161
1158
  );
1162
1159
  if (unknown.length > 0) {
1163
- throw new ValidationError2(
1160
+ throw new ValidationError4(
1164
1161
  `segment: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}; a handle takes { namespace } only`
1165
1162
  );
1166
1163
  }
@@ -1190,13 +1187,20 @@ var CloudRoaring = class _CloudRoaring {
1190
1187
  * of it. A materialisation is a load whose ids happen to come from a combine instead of from upstream, so
1191
1188
  * everything `load()` learned the hard way applies unchanged: the generation is written UNPUBLISHED, the
1192
1189
  * guard runs while the old generation is still authoritative, the publish is fenced as above, and a refused
1193
- * object is reclaimed only when a re-read finds the row unchanged (the same token) or gone (hard invariant 1:
1194
- * deleting it after a purge-and-recreate would put a live row over a missing generation).
1195
- *
1196
- * That last check narrows the window rather than closing it: the row read and the delete are two round
1197
- * trips, and `IStorageDriver` has no conditional delete to make them one. the collection a load runs carries
1198
- * the same residual. The failure it leaves is an orphan object, which costs storage until
1199
- * something collects it — deliberately the cheaper side of the trade.
1190
+ * object is reclaimed only when a re-read finds the row unchanged (the same token, or one that differs only in
1191
+ * its leases), `destroyed`, or gone, or finds key material on it when the load wrote cleartext; under any other
1192
+ * row it is kept (hard invariant 1: deleting it after a purge-and-recreate would put a live row over a missing
1193
+ * generation).
1194
+ *
1195
+ * Where the row is gone, or keyed over a cleartext write, the object's footer must match what the load wrote, and
1196
+ * the delete passes the version that read reported, so on a storage driver that reports `conditionalDelete` an
1197
+ * object put under the number since is kept. Where the row is unchanged or `destroyed`, the check narrows the
1198
+ * window rather than closing it: the row read and the delete are two round trips, and the delete is by number,
1199
+ * since the write reports no version of the object it made. The other deletes a load leads to are by number too: its
1200
+ * collection deletes what a listing, or the row's list of kept generations, names below the pointer, and a drop's
1201
+ * sweep and a tombstoned segment's collection delete what a listing names under a `destroyed` row. The failure the
1202
+ * checks leave when they refuse is an orphan object, which costs storage until something collects it —
1203
+ * deliberately the cheaper side of the trade.
1200
1204
  *
1201
1205
  * Written and published in one step, with no guard, an empty combine — a typo'd operand, an `exclude` that
1202
1206
  * swallowed everything, an operand that had not loaded yet — would silently replace `dest` with an empty
@@ -1243,9 +1247,9 @@ var CloudRoaring = class _CloudRoaring {
1243
1247
  }
1244
1248
  const reason = result.reason;
1245
1249
  if (reason === "superseded") {
1246
- const wrote = result.size > 0 ? `generation ${result.generation} was written and is not current` : `nothing was written \u2014 another writer took generation ${result.generation} first`;
1250
+ const wrote = result.size > 0 ? `generation ${result.generation} was written and may not be current` : `nothing was written \u2014 another writer took generation ${result.generation} first`;
1247
1251
  throw new WriteConflictError(
1248
- `${op}: the destination "${dest.segment}" changed while this materialisation was in flight, so its result is not the destination's current generation: ${wrote}. The pointer may have moved, the row may have been created or rewritten (a retention policy does either), dropped or purged. Re-read the destination and re-run.`
1252
+ `${op}: the destination "${dest.segment}" changed while this materialisation was in flight, so its result may not be the destination's current generation: ${wrote}. The pointer may have moved, the row may have been created or rewritten (a retention policy does either), dropped or purged. Re-read the destination and re-run.`
1249
1253
  );
1250
1254
  }
1251
1255
  return {
@@ -1262,7 +1266,7 @@ var CloudRoaring = class _CloudRoaring {
1262
1266
  /** The one body of the forms above. */
1263
1267
  async materializeMany(options) {
1264
1268
  if (typeof options !== "object" || options === null) {
1265
- throw new ValidationError2(
1269
+ throw new ValidationError4(
1266
1270
  "materializeMany needs an options object with `operands`, `outputs` and `keep`"
1267
1271
  );
1268
1272
  }
@@ -1271,13 +1275,13 @@ var CloudRoaring = class _CloudRoaring {
1271
1275
  (k) => !MATERIALIZE_MANY_KEYS.includes(k)
1272
1276
  );
1273
1277
  if (unknown.length > 0) {
1274
- throw new ValidationError2(
1278
+ throw new ValidationError4(
1275
1279
  `materializeMany: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}`
1276
1280
  );
1277
1281
  }
1278
1282
  const dryRunIn = options.dryRun;
1279
1283
  if (dryRunIn !== void 0 && typeof dryRunIn !== "boolean") {
1280
- throw new ValidationError2("materializeMany: dryRun must be a boolean");
1284
+ throw new ValidationError4("materializeMany: dryRun must be a boolean");
1281
1285
  }
1282
1286
  const dryRun = dryRunIn === true;
1283
1287
  const operandsIn = options.operands;
@@ -1289,7 +1293,7 @@ var CloudRoaring = class _CloudRoaring {
1289
1293
  const feedIn = options.feed;
1290
1294
  const mayBeEmptyIn = options.mayBeEmpty;
1291
1295
  if (feedIn !== void 0 && options.maxBufferedBytes === void 0) {
1292
- throw new ValidationError2(
1296
+ throw new ValidationError4(
1293
1297
  "materializeMany: maxBufferedBytes is required with a feed: the feed is read once, so the call runs as one group and the budget is what bounds it"
1294
1298
  );
1295
1299
  }
@@ -1299,22 +1303,22 @@ var CloudRoaring = class _CloudRoaring {
1299
1303
  const budgetOption = options.budget;
1300
1304
  const allowAbsentOperands = options.allowAbsentOperands;
1301
1305
  if (typeof pinOption !== "boolean")
1302
- throw new ValidationError2("materializeMany: pin must be a boolean");
1306
+ throw new ValidationError4("materializeMany: pin must be a boolean");
1303
1307
  if (allowAbsentOperands !== void 0 && typeof allowAbsentOperands !== "boolean") {
1304
- throw new ValidationError2("materializeMany: allowAbsentOperands must be a boolean");
1308
+ throw new ValidationError4("materializeMany: allowAbsentOperands must be a boolean");
1305
1309
  }
1306
1310
  if (typeof operandsIn !== "object" || operandsIn === null || Array.isArray(operandsIn)) {
1307
- throw new ValidationError2("materializeMany: operands must be an object of name to segment");
1311
+ throw new ValidationError4("materializeMany: operands must be an object of name to segment");
1308
1312
  }
1309
1313
  if (!Array.isArray(outputsIn) || outputsIn.length === 0) {
1310
- throw new ValidationError2("materializeMany: outputs must be a non-empty array");
1314
+ throw new ValidationError4("materializeMany: outputs must be a non-empty array");
1311
1315
  }
1312
1316
  const names = Object.keys(operandsIn);
1313
1317
  const handles = /* @__PURE__ */ new Map();
1314
1318
  for (const name of names) {
1315
1319
  const handle = operandsIn[name];
1316
1320
  if (!(handle instanceof Segment)) {
1317
- throw new ValidationError2(
1321
+ throw new ValidationError4(
1318
1322
  `materializeMany: operands["${name}"] must be a segment from store.segment()`
1319
1323
  );
1320
1324
  }
@@ -1322,14 +1326,14 @@ var CloudRoaring = class _CloudRoaring {
1322
1326
  }
1323
1327
  if (mayBeEmptyIn !== void 0 && feedIn === void 0) {
1324
1328
  if (!Array.isArray(mayBeEmptyIn)) {
1325
- throw new ValidationError2(
1329
+ throw new ValidationError4(
1326
1330
  "materializeMany: mayBeEmpty must be an array of fed operand names"
1327
1331
  );
1328
1332
  }
1329
1333
  if (mayBeEmptyIn.length > 0) {
1330
1334
  const first = mayBeEmptyIn[0];
1331
1335
  const what = typeof first !== "string" ? "something that is not a name" : handles.has(first) ? `"${first}", a stored operand` : `"${first}", which is not an operand of this call`;
1332
- throw new ValidationError2(
1336
+ throw new ValidationError4(
1333
1337
  `materializeMany: mayBeEmpty names ${what}; it names fed operands, and this call has no feed`
1334
1338
  );
1335
1339
  }
@@ -1339,28 +1343,28 @@ var CloudRoaring = class _CloudRoaring {
1339
1343
  const destKeys = /* @__PURE__ */ new Set();
1340
1344
  outputsIn.forEach((raw, i) => {
1341
1345
  if (typeof raw !== "object" || raw === null) {
1342
- throw new ValidationError2(`outputs[${i}] must be an object`);
1346
+ throw new ValidationError4(`outputs[${i}] must be an object`);
1343
1347
  }
1344
1348
  const bad = Object.keys(raw).filter(
1345
1349
  (k) => !MATERIALIZE_MANY_OUTPUT_KEYS.includes(k)
1346
1350
  );
1347
1351
  if (bad.length > 0) {
1348
- throw new ValidationError2(
1352
+ throw new ValidationError4(
1349
1353
  `outputs[${i}]: unknown option${bad.length > 1 ? "s" : ""} ${bad.map((k) => `"${k}"`).join(", ")}`
1350
1354
  );
1351
1355
  }
1352
1356
  const spec = raw;
1353
1357
  const dest = spec.dest;
1354
1358
  if (!(dest instanceof Segment)) {
1355
- throw new ValidationError2(`outputs[${i}].dest must be a segment from store.segment()`);
1359
+ throw new ValidationError4(`outputs[${i}].dest must be a segment from store.segment()`);
1356
1360
  }
1357
1361
  if (operandKeys.has(dest.key())) {
1358
- throw new ValidationError2(
1362
+ throw new ValidationError4(
1359
1363
  `outputs[${i}].dest is also an operand of the call: one call is one pass over operands that do not change under it, so chain two calls`
1360
1364
  );
1361
1365
  }
1362
1366
  if (destKeys.has(dest.key())) {
1363
- throw new ValidationError2(
1367
+ throw new ValidationError4(
1364
1368
  `outputs[${i}].dest names a segment another output of the call writes`
1365
1369
  );
1366
1370
  }
@@ -1380,7 +1384,7 @@ var CloudRoaring = class _CloudRoaring {
1380
1384
  ...pinnedAt === void 0 ? {} : {
1381
1385
  pinnedGeneration: pinnedAt.generation,
1382
1386
  pinnedVersion: pinnedAt.version,
1383
- pinnedFingerprint: pinnedAt.fingerprint ?? null
1387
+ pinnedFingerprint: pinnedAt.fingerprint
1384
1388
  },
1385
1389
  current: (read) => this.currentRowOf(seg.ref, read?.fingerprint === true)
1386
1390
  };
@@ -1433,13 +1437,13 @@ var CloudRoaring = class _CloudRoaring {
1433
1437
  held.set(key, { at, handle });
1434
1438
  } else if (prior.at !== void 0 && at !== void 0) {
1435
1439
  if (!samePin(prior.at, at)) {
1436
- throw new ValidationError2(
1440
+ throw new ValidationError4(
1437
1441
  `materializeMany: operand "${name}" holds a segment another operand holds, ${twoPins(prior.at, at)}: one call reads a segment at one generation`
1438
1442
  );
1439
1443
  }
1440
1444
  } else if (prior.at !== void 0 || at !== void 0) {
1441
1445
  if (!pinOption) {
1442
- throw new ValidationError2(
1446
+ throw new ValidationError4(
1443
1447
  `materializeMany: operand "${name}" holds a segment another operand holds, ${twoPins(prior.at, at)}: one call reads a segment at one generation`
1444
1448
  );
1445
1449
  }
@@ -1453,7 +1457,7 @@ var CloudRoaring = class _CloudRoaring {
1453
1457
  else if (pinOption) toPin.push([key, handle]);
1454
1458
  else pins.set(key, void 0);
1455
1459
  }
1456
- const metricsOn = this.metrics !== NOOP_METRICS;
1460
+ const metricsOn = this.metrics !== NOOP_METRICS2;
1457
1461
  const work = async () => {
1458
1462
  const taken = await mapWithConcurrency(
1459
1463
  toPin,
@@ -1463,7 +1467,7 @@ var CloudRoaring = class _CloudRoaring {
1463
1467
  for (const [key, at] of taken) pins.set(key, at);
1464
1468
  const live = new Map([...pins].filter((e) => e[1] !== void 0));
1465
1469
  const pinnedSource = new PinnedStorageChunkSource(crbm, live);
1466
- const source = this.retryOptions === void 0 ? pinnedSource : new RetryingStorageChunkSource(pinnedSource, this.retryOptions);
1470
+ const source = this.retryOptions === void 0 ? pinnedSource : new RetryingStorageChunkSource2(pinnedSource, this.retryOptions);
1467
1471
  const final = rebindCombineMany(compiled, operandSpecs(pins));
1468
1472
  return runCombineMany(final, {
1469
1473
  source,
@@ -1527,7 +1531,7 @@ var CloudRoaring = class _CloudRoaring {
1527
1531
  /** A call's feed with the store's erasure counter: read once here, checked by the combine before any record is read. */
1528
1532
  feedOf(feed, mayBeEmpty, epochAt) {
1529
1533
  if (typeof feed !== "object" || feed === null || Array.isArray(feed)) {
1530
- throw new ValidationError2(
1534
+ throw new ValidationError4(
1531
1535
  "materializeMany: feed must be an object with names, records and counts"
1532
1536
  );
1533
1537
  }
@@ -1561,7 +1565,7 @@ var CloudRoaring = class _CloudRoaring {
1561
1565
  }
1562
1566
  }
1563
1567
  const row = await this.withRetries(() => registry.get(ref));
1564
- return row === null ? null : { generation: row.currentGen, token: row.token };
1568
+ return row === null ? null : { generation: row.currentGen, token: pointerIdOf(row) };
1565
1569
  }
1566
1570
  /** Throw what a read of, or a publish to, `handle` must throw now: its lease error, if it has one. */
1567
1571
  assertLease(handle) {
@@ -1606,7 +1610,7 @@ var CloudRoaring = class _CloudRoaring {
1606
1610
  const ref = { segment: rec.segment, namespace: rec.namespace };
1607
1611
  if (this.crbmSource !== void 0) {
1608
1612
  const held = await this.crbmSource.currentVersion(ref);
1609
- const listed = rec.currentGen === null ? null : `${rec.currentGen}:${String(rec.token)}`;
1613
+ const listed = rec.currentGen === null ? null : `${rec.currentGen}:${pointerIdOf(rec)}`;
1610
1614
  if (rowVersionOfLive(held) !== listed) this.engine.invalidate(ref);
1611
1615
  }
1612
1616
  return await this.engine.has(ref, id) ? { segment: rec.segment, namespace: rec.namespace } : null;
@@ -1626,10 +1630,14 @@ var CloudRoaring = class _CloudRoaring {
1626
1630
  * can still hold it, and so can one above the pointer after a {@link CloudRoaring.rollback}, which the rollback
1627
1631
  * could make current again. Each holder is deleted — below the pointer by a `keep: 0` collection, above it one
1628
1632
  * by one, re-proved against the row first — and the generations above the pointer that never held the id stay
1629
- * as rollback targets. An entry says `erased: true` only once the call has listed the bucket and read what is
1630
- * left: **no generation of the segment holds the id**. The returned per-segment record is your **erasure ledger** —
1631
- * persist it / route it to your audit sink as the proof of deletion (a `segment.rewrite` audit event is also
1632
- * emitted per rewrite when you pass `audit`).
1633
+ * as rollback targets, but for any object no read of the segment can open, which goes whatever the id. An entry
1634
+ * says `erased: true` only once the call has listed the bucket and read what is left: **no generation of the
1635
+ * segment holds the id**. The returned per-segment record is your **erasure ledger** —
1636
+ * persist it / route it to your audit sink as the proof of deletion (when you pass `audit`, an audit event is also
1637
+ * emitted per segment erased: `segment.rewrite` for a rewrite, `segment.collect` where only other generations held
1638
+ * the id and nothing was rewritten). A segment where the erasure deleted only objects no read of it can open (a first
1639
+ * load's sealed under a key no row holds) found the id in none, so the ledger does not list it; the audit sink gets
1640
+ * `segment.collect` for it with no `fromGeneration`, which is the only record of that deletion.
1633
1641
  *
1634
1642
  * Uses the backend's **own** two halves, so the membership check and the rewrite provably run
1635
1643
  * over the same generation. Requires the store built with a **backend** (throws
@@ -1651,13 +1659,21 @@ var CloudRoaring = class _CloudRoaring {
1651
1659
  * an error, read off the row: a moved pointer is a `'superseded'` entry, which a re-run settles, and a segment
1652
1660
  * that a concurrent `dropSegment` tombstoned or a retention sweep purged is left out of the ledger. A tombstoned
1653
1661
  * segment is still searched: a cleartext object left under it that holds the id is deleted, with everything under
1654
- * the tombstone, and the entry reads `erased: true`.
1662
+ * the tombstone, and the entry reads `erased: true`. So is a segment whose row names no generation yet (a retention
1663
+ * policy set before its first load): a first load's object that holds the id is deleted, once the row's `pointerId`
1664
+ * is renewed so that the load, if still running, is refused at its publish, and the entry reads `erased: true`. An
1665
+ * encrypted first load's object there cannot be searched, so it is deleted whatever the id: on an encrypted store,
1666
+ * an erasure of any id refuses an encrypted first load in flight that has written its object onto a segment made
1667
+ * ahead of its data. Each such delete, and each of a holder above the pointer, names the object the erasure read:
1668
+ * on a storage driver that reports `conditionalDelete`, another erasure of the id that deletes it first, followed by
1669
+ * a load that takes the number freed and publishes, does not lose that load's generation to this erasure's delete,
1670
+ * which is refused, and the entry says what the row says; on one that does not, it can.
1655
1671
  *
1656
1672
  * **Read `note` on any `erased: false` entry — the two reasons mean different things.** `'superseded'` means
1657
- * another writer (a load, another erasure, or a rollback) moved the pointer mid-call, so **this call** did not
1658
- * erase the id. Re-run: it erases the id if it is still there, and lists nothing for the segment if a racing
1659
- * erasure of the same id already removed it. Do not read `'superseded'` as "the id is still present" —
1660
- * read it as "not done by this call, and the re-run settles it".
1673
+ * another writer (a load, another erasure, or a rollback) moved the pointer, or replaced an object the call meant to
1674
+ * delete, mid-call, so **this call** did not erase the id. Re-run: it erases the id if it is still there, and lists
1675
+ * nothing for the segment if a racing erasure of the same id already removed it. Do not read `'superseded'` as
1676
+ * "the id is still present" — read it as "not done by this call, and the re-run settles it".
1661
1677
  * `` `error: …` `` is a per-segment fault (caught so one segment can't discard the whole ledger) and it can land
1662
1678
  * on either side of the publish: if the rewrite had not published, the id is still there and a re-run erases
1663
1679
  * it; if the publish succeeded and only the **collection** of the old generation failed, the id is already
@@ -1753,6 +1769,8 @@ var CloudRoaring = class _CloudRoaring {
1753
1769
  * What the bucket holds, not what the segment has ever been — collection deletes superseded objects, so this is
1754
1770
  * the grace window plus whatever has not been collected yet. It is the set {@link CloudRoaring.rollback} can
1755
1771
  * choose from, which is the reason to look at it. One registry read and one listing; it does not open the objects.
1772
+ * Each entry says whether the row knows the generation (`known`): a known generation needs no `allowUnknown`; an unknown
1773
+ * one does, except below a row's pointer when the row has no usable record or the target is older than a full one reaches.
1756
1774
  *
1757
1775
  * Needs a backend.
1758
1776
  */
@@ -1837,35 +1855,58 @@ var CloudRoaring = class _CloudRoaring {
1837
1855
  * (`segment.rollback`), because every other pointer move can be reconstructed from "a load happened" and this
1838
1856
  * one cannot.
1839
1857
  *
1840
- * It refuses rather than guesses: a generation not in the bucket (collected, or never written) throws
1841
- * `NotFoundError` naming what *is* available, a crypto-shredded segment throws
1842
- * {@link ValidationError} because every generation of it is unreadable, and a target that is cleartext under an
1843
- * encrypted segment, or encrypted under a cleartext one, throws {@link IntegrityError} from one tail read of it,
1844
- * because every read would refuse it. Rolling to the generation already current is a no-op that reports itself.
1858
+ * It refuses rather than guesses. The row records, in `knownGens`, the generations its loads and rollbacks made
1859
+ * current, each with the footer checksum of its object, and a rollback moves only onto one of those: a generation the
1860
+ * row does not know (a load that wrote its object and died before the publish, a refused load's object, an object
1861
+ * sealed under a key no row holds, a load published after the point a registry was restored to) throws
1862
+ * {@link ValidationError} before any request to storage, and a known one whose object carries another checksum than
1863
+ * the recorded one throws {@link IntegrityError}, with or without a keystore, since the footer is in the clear. A
1864
+ * generation not in the bucket (collected, or never written) throws `NotFoundError` naming what *is* available, a
1865
+ * crypto-shredded segment throws {@link ValidationError} because every generation of it is unreadable, and a target
1866
+ * that is cleartext under an encrypted segment, or encrypted under a cleartext one, throws {@link IntegrityError} from
1867
+ * one tail read of it, because every read would refuse it. Rolling to the generation already current is a no-op that
1868
+ * reports itself, once one footer read finds its object in the bucket (and the one the row recorded); when the object is
1869
+ * gone it throws `NotFoundError` naming what is.
1845
1870
  *
1846
1871
  * The same tail read (and a range read when the target's index is longer than it) gives the target's id count and
1847
1872
  * metadata, which the rollback writes into the row with the pointer, so a reader that sees the target as current sees
1848
1873
  * what describes it. The store's keystore opens an encrypted target for this; without it, or when it cannot open the
1849
- * key for any reason, the segment still rolls back and the row carries no summary of the target.
1874
+ * key (it holds none of the key's KEKs, one it holds does not unwrap it, or it throws `TransientError` or times out
1875
+ * as `AbortSignal.timeout()` and the AWS SDK report a timeout), a rollback onto a known generation still happens,
1876
+ * opening nothing, and the row carries no summary of the target. Any other failure of the keystore is
1877
+ * thrown. A swap of the pointer that gets no answer is settled by reading the row: one that landed, the row being the
1878
+ * one the swap leaves, is checked as any is, and otherwise its `TransientError` is thrown. After the swap the target's
1879
+ * footer is read once more, and the pointer put back if the object is gone or is another.
1850
1880
  *
1851
1881
  * It deletes nothing. The generations above the new pointer stay put — which is what makes this reversible —
1852
- * and are then *above* `currentGen`, where collection never looks. They remain until loads pass them (the first
1853
- * load whose number one of them holds numbers above them all, and collection then keeps the newest `keep` of what is
1854
- * below its pointer), {@link CloudRoaring.dropSegment}
1855
- * deletes them, or {@link CloudRoaring.eraseSubject} does: all of those present when it rewrites, and only those holding
1856
- * the id when the current generation does not. An operator who has just undone a bad load should not have the
1882
+ * and are then *above* `currentGen`, where collection never looks, and still known to the row, so rolling forward onto
1883
+ * one needs no flag. They remain until loads pass them (the first load whose
1884
+ * number one of them holds numbers above them all, and collection then keeps `keep` of what is below its pointer, the
1885
+ * generation that load superseded first), {@link CloudRoaring.dropSegment}
1886
+ * deletes them, or {@link CloudRoaring.eraseSubject} does: all of those present when it rewrites, and, when the
1887
+ * current generation does not hold the id, only those holding it and any no read of the segment can open, whatever
1888
+ * the id. An operator who has just undone a bad load should not have the
1857
1889
  * evidence collected out from under them, while a rollback target that still holds erased data would make the
1858
1890
  * erasure undoable.
1859
1891
  *
1860
- * A target **above** the pointer needs `{ allowForward: true }`, and is refused with {@link ValidationError}
1861
- * without it: that is also where objects live that were never published, such as a load that wrote its object
1862
- * and died before the publish. Undoing an earlier rollback is what the opt-in is for.
1892
+ * `{ allowUnknown: true }` moves onto a present object the row does not vouch for, or one whose checksum differs from
1893
+ * the recorded one, as long as it is the segment's kind of object, opens, and names the generation, and records it as
1894
+ * known (over a usable record; a row with none stays without one). On an encrypted segment, outside the two cases the pointer alone decides (below), it also needs the key: with no keystore, or one that holds none of the key's KEKs or
1895
+ * one that does not unwrap it, it is refused with `KeyUnavailableError` before the target is read, since an object
1896
+ * sealed under a key no row holds passes every check that needs no key, and a key service that did not answer in
1897
+ * time is thrown as `TransientError`, to retry. Every move the row did not vouch for is audited with `unknown: true`.
1898
+ * Two cases are decided by the pointer alone: a target below it needs no flag, no key and no checksum check, and the
1899
+ * move is audited the same way. They are a row with no usable `knownGens` (a registry that drops the field, or a pointer
1900
+ * moved by hand), and a target numbered below the bound of a full record (128 entries or more, leased ones included),
1901
+ * which is its least recently current entry that no lease names (an `*Into` destination keeps every generation in the
1902
+ * bucket and names only the newest 128). A generation a publish with a smaller `keep` no longer names but that stays in
1903
+ * the bucket (a lease spared it, or its delete failed) needs `allowUnknown` until a later listing collects it.
1863
1904
  *
1864
1905
  * Needs a backend.
1865
1906
  */
1866
1907
  async rollback(ref, toGeneration, options = {}) {
1867
1908
  validateSegmentRef(ref);
1868
- checkOptions(options, "rollback", CALL_KEYS.rollback, ["allowForward"]);
1909
+ checkOptions(options, "rollback", CALL_KEYS.rollback, ["allowUnknown"]);
1869
1910
  options ??= {};
1870
1911
  const deps = this.lifecycleDeps("rollback");
1871
1912
  try {
@@ -1922,10 +1963,16 @@ var CloudRoaring = class _CloudRoaring {
1922
1963
  * published, a retention change, a rollback or an erasure wrote the row, or the row was deleted.
1923
1964
  *
1924
1965
  * A refused load deletes the object it wrote before returning — it sits above `currentGen`, where generation
1925
- * collection deliberately never looks — but only while the segment's registry row is unchanged or gone. Once
1926
- * another write has changed the row, the generation number it holds may name another incarnation's live object,
1927
- * so it leaves the orphan rather than risk deleting live data. No row's list names it, so it takes no place in a
1928
- * window, and a listing deletes it once a later generation is current above it.
1966
+ * collection deliberately never looks — but only while the segment's registry row is unchanged (or differs only in
1967
+ * its leases), `destroyed` or gone, or has key material when the load wrote cleartext, which has no place in an
1968
+ * encrypted segment's bucket. Under a row that is gone or keyed, one read of the object's footer must match what the
1969
+ * load wrote first. Once another write has otherwise changed the row, the generation number it holds may name another
1970
+ * writer's object, and a footer names bytes, not the load that wrote them, so it leaves the orphan rather than risk
1971
+ * deleting live data. A publish never adds the orphan to the row's list, and a load's listing deletes it once a
1972
+ * generation above it is current, unless the row's list names it; where the row records none (after a rollback, or
1973
+ * with a `keep` above 64), that listing's window can keep it and, at a `keep` up to 64, record it, and the publishes
1974
+ * after carry it until the window pushes it out. Until it goes, a rollback onto it is refused unless `allowUnknown` is passed, since the list never names it (a list seeded
1975
+ * over a row with none can); with the override it publishes what no load published.
1929
1976
  *
1930
1977
  * **Collection is by name at any `keep` up to 64.** The segment's row records the generations a load keeps, so a load
1931
1978
  * that found nothing above the pointer deletes the generations its publish pushed out of the window and lists
@@ -2016,7 +2063,7 @@ var CloudRoaring = class _CloudRoaring {
2016
2063
  async dropSegment(ref, options) {
2017
2064
  validateSegmentRef(ref);
2018
2065
  if (options === void 0 || options === null) {
2019
- throw new ValidationError2("dropSegment: options { confirmSegment } are required");
2066
+ throw new ValidationError4("dropSegment: options { confirmSegment } are required");
2020
2067
  }
2021
2068
  checkOptions(options, "dropSegment", CALL_KEYS.dropSegment, ["dryRun"]);
2022
2069
  const deps = this.lifecycleDeps("dropSegment");
@@ -2138,8 +2185,8 @@ var CloudRoaring = class _CloudRoaring {
2138
2185
  * Pass `purgeTombstones: false` to keep every tombstone, that row included. On a backend whose registry reports
2139
2186
  * `conditionalDelete` (AWS S3, Azure Blob, the local filesystem and memory, by default; GCS when you set `conditionalDelete: true`)
2140
2187
  * the purge removes the row from the bucket for good, by a delete the store applies only to the version it judged,
2141
- * so a full sweep reads what is live or inside its grace rather than every name a namespace ever held; elsewhere,
2142
- * and for a row a release before 0.12 wrote, it leaves a tombstone. Each retirement files a pointer in the due index
2188
+ * so a full sweep reads what is live or inside its grace rather than every name a namespace ever held; elsewhere it
2189
+ * leaves a tombstone. Each retirement files a pointer in the due index
2143
2190
  * under the day its tombstone's grace ends, so `scan: 'index'` purges as well as retires.
2144
2191
  *
2145
2192
  * Needs the store built with a **backend** (throws {@link UnsupportedError} otherwise),
@@ -2184,7 +2231,10 @@ var CloudRoaring = class _CloudRoaring {
2184
2231
  * reader converging, you need to signal them — this is the call to make when your own fan-out delivers.
2185
2232
  *
2186
2233
  * It also forgets any replacement this store found of a pin's object, so once it is called after a restore puts that
2187
- * object back, the pin reads it again.
2234
+ * object back, the pin reads it again. A read of the segment already running on this store, a pin's and a combine's
2235
+ * with a pinned operand included, caches nothing more of what it began reading before the call, and a pinned one
2236
+ * opens its object again before its next chunk, failing with `NotFoundError` if the object is gone or replaced, or
2237
+ * its row is gone or destroyed.
2188
2238
  *
2189
2239
  * Synchronous, best-effort, and safe to call for a segment this store has never read.
2190
2240
  *
@@ -2204,7 +2254,8 @@ var CloudRoaring = class _CloudRoaring {
2204
2254
  *
2205
2255
  * The pinned handle gets its own engine but **shares the store's chunk cache**, under keys of its own: the
2206
2256
  * pinned view reports the version captured at pin time, marked as a pin's, so its decoded chunks are never
2207
- * those of a live read that fetched across a publish (see {@link PinnedStorageChunkSource.currentVersion}).
2257
+ * those of a live read that fetched across a publish (see {@link PinnedStorageChunkSource.currentVersion}). It
2258
+ * shares the store engine's invalidations too, so the store's writes reach a pinned read already running.
2208
2259
  */
2209
2260
  async pinSegment(ref, leaseUntil, named) {
2210
2261
  const crbm = this.crbmSource;
@@ -2283,7 +2334,8 @@ var CloudRoaring = class _CloudRoaring {
2283
2334
  * An engine reading every segment in `pins` at its pinned generation and everything else live, with the store's
2284
2335
  * retries, so a transient fault on a pinned read is retried as it would be on a live one.
2285
2336
  *
2286
- * It **shares the store's chunk cache and its memory bound**, under keys of its own for each pinned segment.
2337
+ * It **shares the store's chunk cache and its memory bound**, under keys of its own for each pinned segment, and the
2338
+ * store engine's invalidations, so an invalidation of a pinned segment reaches a read of the pin already running.
2287
2339
  */
2288
2340
  engineWithPins(pins) {
2289
2341
  const crbm = this.crbmSource;
@@ -2294,12 +2346,13 @@ var CloudRoaring = class _CloudRoaring {
2294
2346
  }
2295
2347
  const pinned = new PinnedStorageChunkSource(crbm, pins);
2296
2348
  return new SegmentEngine({
2297
- storage: this.retryOptions === void 0 ? pinned : new RetryingStorageChunkSource(pinned, this.retryOptions),
2349
+ storage: this.retryOptions === void 0 ? pinned : new RetryingStorageChunkSource2(pinned, this.retryOptions),
2298
2350
  cache: this.cache,
2299
2351
  codec: roaringCodec,
2300
2352
  clock: this.clock,
2301
2353
  metrics: this.metrics,
2302
- budget: this.budget
2354
+ budget: this.budget,
2355
+ sharesInvalidationsWith: this.engine
2303
2356
  });
2304
2357
  }
2305
2358
  /**
@@ -2325,7 +2378,7 @@ var CloudRoaring = class _CloudRoaring {
2325
2378
  if (held.has(key)) {
2326
2379
  const before = held.get(key);
2327
2380
  if (!samePin(before, at)) {
2328
- throw new ValidationError2(
2381
+ throw new ValidationError4(
2329
2382
  `the same segment is in this combine twice, ${twoPins(before, at)}: one call reads a segment at one generation, so materialise one of them into a segment of its own first, with \`intersectInto(dest, [])\``
2330
2383
  );
2331
2384
  }
@@ -2339,7 +2392,7 @@ var CloudRoaring = class _CloudRoaring {
2339
2392
  /** `op` under the store's retries, as the store's own reads run; bare when the store has retries off. */
2340
2393
  withRetries(op) {
2341
2394
  const r = this.retryOptions;
2342
- return r === void 0 ? op() : withRetry(op, r.policy ?? DEFAULT_RETRY_POLICY, r);
2395
+ return r === void 0 ? op() : withRetry(op, r.policy ?? DEFAULT_RETRY_POLICY2, r);
2343
2396
  }
2344
2397
  /** The store's registry, or a typed error naming the operation that needs one. */
2345
2398
  requireRegistry(op) {
@@ -2363,17 +2416,27 @@ var CloudRoaring = class _CloudRoaring {
2363
2416
  * A segment whose pointer names a missing object is the torn restore this reports; a cold `count()` of it still
2364
2417
  * answers the number its row records, while a read of the object throws, so the count alone never shows it. With
2365
2418
  * `summaries: true` it also opens each segment's current object (one tail read each) and reports a segment whose
2366
- * row's summary says another id count or metadata than the object holds as `summary-mismatch`, which is what a
2419
+ * row's summary names another object by its fingerprint, or says another id count or metadata than the object holds,
2420
+ * as `summary-mismatch`, which is what a
2367
2421
  * restore of the registry from another point than the bucket leaves a count to answer. A sealed summary is held
2368
2422
  * against its object when this store has the key, and counted in `summariesUnchecked` when it does not.
2369
2423
  *
2424
+ * With `generations: true` it also holds every generation a row's ledger of known generations names, the current one
2425
+ * included, against its object: one footer read each (104 bytes, no key, not retried), reporting
2426
+ * `known-generation-mismatch` with the `generation` where the object under that number is not the one the row
2427
+ * recorded. A named generation that is not in the bucket is not reported. A row with a pointer whose ledger cannot
2428
+ * be used is counted in `generationsUnchecked`, and none of its objects is read.
2429
+ *
2370
2430
  * It holds the registry rows it enumerates resident, at most 250,000 of them, and throws
2371
2431
  * `BudgetExceededError` past that rather than report a partial scan as a whole one. This method takes no
2372
2432
  * ceiling of its own: narrow the scan with `namespace`, or call `runConsistencyCheck` over the backend's
2373
2433
  * `storage` and `registry` with a higher `maxScanSegments`.
2374
2434
  */
2375
2435
  async checkConsistency(options = {}) {
2376
- checkOptions(options, "checkConsistency", CALL_KEYS.checkConsistency, ["summaries"]);
2436
+ checkOptions(options, "checkConsistency", CALL_KEYS.checkConsistency, [
2437
+ "summaries",
2438
+ "generations"
2439
+ ]);
2377
2440
  options ??= {};
2378
2441
  const deps = this.lifecycleDeps("checkConsistency");
2379
2442
  return runConsistencyCheck(
@@ -2399,24 +2462,15 @@ var CloudRoaring = class _CloudRoaring {
2399
2462
  const registry = this.requireRegistry("exportSegments");
2400
2463
  return runExport(this, registry, sink, { ...opts, codec: opts.codec ?? roaringCodec });
2401
2464
  }
2402
- /**
2403
- * Planning cost estimate — pure, no instance/data needed: sizing, sales, what-if. For a real, grounded report
2404
- * from live segment sizes, use `store.segment(name).costReport()`. See {@link CostReport}.
2405
- * @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
2406
- * needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
2407
- */
2408
- static estimateCost(input) {
2409
- return estimateCost(input);
2410
- }
2411
2465
  };
2412
2466
  var withBatches = (gen, batches) => Object.assign(gen, { batches });
2413
- var failing = (err) => {
2414
- const rejecting = () => ({ next: () => Promise.reject(err) });
2415
- return {
2416
- [Symbol.asyncIterator]: rejecting,
2417
- batches: () => ({ [Symbol.asyncIterator]: rejecting })
2418
- };
2419
- };
2467
+ var refusing = (err) => (
2468
+ // eslint-disable-next-line require-yield
2469
+ async function* () {
2470
+ throw err;
2471
+ }
2472
+ );
2473
+ var failing = (err) => withBatches(refusing(err)(), refusing(err));
2420
2474
  var samePin = (a, b) => a === void 0 || b === void 0 ? a === b : a.generation === b.generation && a.fingerprint === b.fingerprint;
2421
2475
  var twoPins = (a, b) => {
2422
2476
  const one = (p) => p === void 0 ? "live" : `pinned at generation ${p.generation ?? "none"}`;
@@ -2434,9 +2488,12 @@ var readOptions = (options) => ({
2434
2488
  });
2435
2489
  var NO_SEGMENTS = [];
2436
2490
  function segmentList(list, what) {
2437
- if (!Array.isArray(list)) throw new ValidationError2(`${what} must be an array of segments`);
2491
+ if (!Array.isArray(list)) throw new ValidationError4(`${what} must be an array of segments`);
2438
2492
  return list;
2439
2493
  }
2494
+ function operandsLabel(verb) {
2495
+ return verb === "andNot" ? "andNot: `excludes`" : `${verb}: \`others\``;
2496
+ }
2440
2497
  var AS_IDS = {
2441
2498
  failing,
2442
2499
  guard: guardIds,
@@ -2451,10 +2508,8 @@ var CombineChunks = class {
2451
2508
  chunks;
2452
2509
  };
2453
2510
  var AS_CHUNKS = {
2454
- guard: guardChunks,
2455
- failing: (err) => ({
2456
- [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(err) })
2457
- }),
2511
+ guard: guardItems,
2512
+ failing: (err) => refusing(err)(),
2458
2513
  intersect: (engine, refs, opts) => engine.intersectChunks(refs, opts),
2459
2514
  union: (engine, refs, opts) => engine.unionChunks(refs, opts),
2460
2515
  andNot: (engine, base, refs, opts) => engine.andNotChunks(base, refs, opts)
@@ -2492,6 +2547,7 @@ var Segment = class _Segment {
2492
2547
  lease;
2493
2548
  leaseHold;
2494
2549
  static {
2550
+ Object.defineProperty(_Segment.prototype, SEGMENT_BRAND, { value: true });
2495
2551
  makeSegment = (parts) => {
2496
2552
  minting = true;
2497
2553
  try {
@@ -2512,7 +2568,7 @@ var Segment = class _Segment {
2512
2568
  */
2513
2569
  constructor(parts) {
2514
2570
  if (!minting) {
2515
- throw new ValidationError2(
2571
+ throw new ValidationError4(
2516
2572
  "a Segment is not constructed directly; call `store.segment(name)`"
2517
2573
  );
2518
2574
  }
@@ -2528,7 +2584,7 @@ var Segment = class _Segment {
2528
2584
  if (parts.lease !== void 0) {
2529
2585
  this.lease = Object.freeze({ holder: parts.lease.holder, until: parts.lease.until });
2530
2586
  }
2531
- this.metricsOn = parts.metrics !== NOOP_METRICS;
2587
+ this.metricsOn = parts.metrics !== NOOP_METRICS2;
2532
2588
  }
2533
2589
  /**
2534
2590
  * **Hold this segment at the generation that is current right now**, for as long as you keep the handle.
@@ -2583,11 +2639,16 @@ var Segment = class _Segment {
2583
2639
  * - `eraseSubject` invalidates a pin of each segment it scans that is not already destroyed.
2584
2640
  *
2585
2641
  * An invalidated pin opens its object again, and fails if that object is gone or replaced, or its row is gone or
2586
- * destroyed.
2642
+ * destroyed. A read of the pin already running when the invalidation comes, a combine on this store with the pin as
2643
+ * an operand included, looks the pin up again before it hands out its next chunk: it carries on with what it holds
2644
+ * where the object is still there, and fails with `NotFoundError` where it is gone. It caches nothing more of what it
2645
+ * began reading before the invalidation, so the pin's next read opens the object again too.
2587
2646
  * Anything else leaves the pin as it is: after a `destroySegment` beside this store, or an erasure, a drop or a
2588
2647
  * retirement through another store, in this process or another, the pin answers from what it holds until this
2589
2648
  * store's reader cache evicts the pin's reader and its chunk cache evicts the chunks the pin decoded, or
2590
- * {@link CloudRoaring.invalidate} is called on this store. Where the
2649
+ * {@link CloudRoaring.invalidate} is called on this store. A combine on another store that takes the pin as an
2650
+ * operand reads it through that store's reader and chunk cache, which only that store's own writes and its
2651
+ * `invalidate` reach. Where the
2591
2652
  * object the pin reads has been deleted, by an erasure, a drop or a sweep, a chunk the pin has not cached fails at
2592
2653
  * once. No timed refresh bounds any of that, as none bounds anything else a pin holds.
2593
2654
  *
@@ -2610,7 +2671,7 @@ var Segment = class _Segment {
2610
2671
  * {@link UnsupportedError} on a store wired with a pre-built source that cannot pin.
2611
2672
  */
2612
2673
  async pin(options) {
2613
- this.assertLeases([this]);
2674
+ this.assertLease();
2614
2675
  const leaseUntil = this.leaseUntilOf("pin", options);
2615
2676
  return this.pinned(this.ref, leaseUntil);
2616
2677
  }
@@ -2622,11 +2683,11 @@ var Segment = class _Segment {
2622
2683
  leaseUntilOf(op, options) {
2623
2684
  if (options === void 0 || options === null) return void 0;
2624
2685
  if (typeof options !== "object" || Array.isArray(options)) {
2625
- throw new ValidationError2(`${op}: options must be an object such as { leaseUntil }`);
2686
+ throw new ValidationError4(`${op}: options must be an object such as { leaseUntil }`);
2626
2687
  }
2627
2688
  const unknown = Object.keys(options).filter((k) => k !== "leaseUntil");
2628
2689
  if (unknown.length > 0) {
2629
- throw new ValidationError2(
2690
+ throw new ValidationError4(
2630
2691
  `${op}: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}; this version takes { leaseUntil } only`
2631
2692
  );
2632
2693
  }
@@ -2634,17 +2695,17 @@ var Segment = class _Segment {
2634
2695
  if (until === void 0) return void 0;
2635
2696
  const now = this.clock.now();
2636
2697
  if (typeof until !== "number" || !Number.isSafeInteger(until)) {
2637
- throw new ValidationError2(
2698
+ throw new ValidationError4(
2638
2699
  `${op}: leaseUntil must be an integer epoch-MILLISECONDS; got ${String(until)}`
2639
2700
  );
2640
2701
  }
2641
2702
  if (until <= now) {
2642
- throw new ValidationError2(
2703
+ throw new ValidationError4(
2643
2704
  `${op}: leaseUntil (${until}) is not after now (${now}); it is epoch MILLISECONDS, so a value in seconds reads as long past. Use \`Date.now() + hours * 3_600_000\``
2644
2705
  );
2645
2706
  }
2646
2707
  if (until > now + MAX_LEASE_MS) {
2647
- throw new ValidationError2(
2708
+ throw new ValidationError4(
2648
2709
  `${op}: leaseUntil is more than 14 days away; the longest lease is ${MAX_LEASE_MS} ms. Take a new lease when a job outlasts it`
2649
2710
  );
2650
2711
  }
@@ -2700,10 +2761,12 @@ var Segment = class _Segment {
2700
2761
  */
2701
2762
  assertSegments(handles) {
2702
2763
  for (const h of handles) {
2703
- if (h instanceof _Segment || typeof h === "object" && h !== null && typeof h.leaseError === "function") {
2764
+ if (h instanceof _Segment || typeof h === "object" && h !== null && h[SEGMENT_BRAND] === true) {
2704
2765
  continue;
2705
2766
  }
2706
- throw new ValidationError2("an operand must be a segment from store.segment()");
2767
+ throw new ValidationError4(
2768
+ "an operand must be a segment from `store.segment()` of a compatible copy of @cloudbitmaps/roaring; install one version of it"
2769
+ );
2707
2770
  }
2708
2771
  }
2709
2772
  /**
@@ -2716,6 +2779,11 @@ var Segment = class _Segment {
2716
2779
  if (err !== void 0) throw err;
2717
2780
  }
2718
2781
  }
2782
+ /** Throw this handle's lease error, if it has one. The calls that return a value instead of throwing use {@link leaseError}. */
2783
+ assertLease() {
2784
+ const err = this.leaseError();
2785
+ if (err !== void 0) throw err;
2786
+ }
2719
2787
  /**
2720
2788
  * Reopen a generation a pin named earlier, as a pinned handle like the one {@link Segment.pin} returns, held at
2721
2789
  * `at.generation` instead of the current one. Pass what the earlier pin recorded, `{ generation, fingerprint }`
@@ -2739,16 +2807,16 @@ var Segment = class _Segment {
2739
2807
  * is in `options`, the second.
2740
2808
  */
2741
2809
  async pinAt(at, options) {
2742
- this.assertLeases([this]);
2810
+ this.assertLease();
2743
2811
  const { generation, fingerprint } = at ?? {};
2744
2812
  const unknown = typeof at === "object" && at !== null ? Object.keys(at).filter((k) => k !== "generation" && k !== "fingerprint") : [];
2745
2813
  if (unknown.length > 0) {
2746
- throw new ValidationError2(
2814
+ throw new ValidationError4(
2747
2815
  `pinAt: unknown option${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}; this version takes { generation, fingerprint } only`
2748
2816
  );
2749
2817
  }
2750
2818
  if (typeof generation !== "number" || !Number.isSafeInteger(generation) || generation < 0 || typeof fingerprint !== "string" || !/^\d+:\d+$/.test(fingerprint)) {
2751
- throw new ValidationError2(
2819
+ throw new ValidationError4(
2752
2820
  "pinAt: pass { generation, fingerprint } as recorded by an earlier pin (its pinnedAt); a generation number without the fingerprint of its object does not identify it"
2753
2821
  );
2754
2822
  }
@@ -2782,8 +2850,9 @@ var Segment = class _Segment {
2782
2850
  return handles.some((h) => h.leaseHold !== void 0) ? out.guard(result, () => this.assertLeases(handles)) : result;
2783
2851
  }
2784
2852
  /**
2785
- * Time an op with the injected clock and emit an `op` metric on completion (success or throw). Skipped
2786
- * entirely when no sink is wired, so the default path pays nothing.
2853
+ * Time an op with the injected clock and emit an `op` metric on completion (success or throw). With no sink wired it
2854
+ * reads no clock and emits nothing, but a call still passes through this async frame; `has()` and `count()` return the
2855
+ * engine's promise directly instead.
2787
2856
  */
2788
2857
  async timed(name, fn) {
2789
2858
  if (!this.metricsOn) return fn();
@@ -2794,18 +2863,23 @@ var Segment = class _Segment {
2794
2863
  this.metrics.onEvent({ kind: "op", name, ms: Math.max(0, this.clock.now() - startedAt) });
2795
2864
  }
2796
2865
  }
2797
- /** Membership: one chunk — the cache, else one ranged GET. Throws {@link ValidationError} on a bad id. */
2866
+ /**
2867
+ * Membership: one chunk. A cold resolution of the segment is one registry read and a warm one none; then a cached
2868
+ * chunk answers with no request, and an uncached one is one range read, after a tail read when no reader of the
2869
+ * generation is open, unless that tail carried the chunk. Throws {@link ValidationError} on a bad id.
2870
+ */
2798
2871
  has(id) {
2799
2872
  const lease = this.leaseError();
2800
2873
  if (lease !== void 0) return Promise.reject(lease);
2874
+ if (!this.metricsOn) return this.engine.has(this.ref, id);
2801
2875
  return this.timed("has", () => this.engine.has(this.ref, id));
2802
2876
  }
2803
2877
  /**
2804
2878
  * Cardinality of the generation this handle reads: **one registry read when cold, none when warm, and no read of the
2805
2879
  * object**, with **zero payload reads**. The registry row records the id count of the generation it names, written
2806
2880
  * by the write that made it current, and a count answers from that. A row with no summary it can use (one written
2807
- * before rows carried it, one that names another generation, a sealed one that does not open) sends the count to the
2808
- * `.crbm` index, summed with a tail read of the object. A segment whose pointer names a missing object (a torn
2881
+ * without it, as a registry of your own may, one that names another generation, a sealed one that does not open)
2882
+ * sends the count to the `.crbm` index, summed with a tail read of the object. A segment whose pointer names a missing object (a torn
2809
2883
  * restore) still counts the row's number, while a read of the object throws; `checkConsistency` is what finds it.
2810
2884
  *
2811
2885
  * What this trusts: the row's summary, or the index's sum, and no payload is decoded to confirm it. The summary is
@@ -2821,24 +2895,38 @@ var Segment = class _Segment {
2821
2895
  count() {
2822
2896
  const lease = this.leaseError();
2823
2897
  if (lease !== void 0) return Promise.reject(lease);
2898
+ if (!this.metricsOn) return this.engine.count(this.ref);
2824
2899
  return this.timed("count", () => this.engine.count(this.ref));
2825
2900
  }
2826
2901
  /**
2827
- * What the generation this handle reads is, from one resolution: its number, its id count and the metadata it
2828
- * was loaded with (absent when it has none). It is what answers {@link Segment.count}, so the three describe one
2829
- * generation and cannot straddle a publish. One registry read when cold, none while warm, and none on a pinned
2830
- * handle, which answers for the generation it pinned. A segment with no generation answers
2831
- * `{ generation: null, cardinality: 0 }`.
2832
- *
2833
- * Trust is as for `count()`: the registry row's word, not confirmed against the object until the object is
2834
- * opened, when a disagreement makes this process stop using that row's summary.
2902
+ * What the generation this handle reads is, from one resolution: its number, its id count, the metadata it was
2903
+ * loaded with (absent when it has none), and `sizeBytes`, its object's bytes in storage. All four come from the
2904
+ * registry row when it has a summary this store can use, as `count()`'s do: the summary records the object's
2905
+ * fingerprint, its size with its checksum. That is **one registry read when cold, none when warm, and no read of the
2906
+ * object**. With no summary it can use, they come from the generation's object, opened once: its footer and index,
2907
+ * with no payload read, which is a tail read of the object more when cold (and a range read for an index longer than
2908
+ * the tail) and keeps the generation in the reader cache as a read does. On a pinned handle it answers for the
2909
+ * generation it pinned, from the pinned object. A segment with no generation answers
2910
+ * `{ generation: null, cardinality: 0, sizeBytes: null }`, and a store whose source cannot report a size answers
2911
+ * `sizeBytes: null`. A pointer that names a missing object (a torn restore) still answers from the row's summary, as
2912
+ * `count()` does, while a read of the object throws `NotFoundError`; with no summary to use, `stat()` throws it too.
2913
+ * `checkConsistency` is what finds such a row.
2914
+ *
2915
+ * Trust is as for `count()`: what the row's summary says is believed until the object is next opened. Every open of
2916
+ * the generation holds the object to the fingerprint the summary records, so another object under the number is
2917
+ * refused when it is opened, and holds its count against the summary's; a disagreement in count makes this process
2918
+ * stop using that summary, so `stat()` then answers from the object, whose index is checked for internal consistency
2919
+ * and whose count is the index's sum. A reader already open is not checked again until the row changes, it is
2920
+ * evicted or it is invalidated.
2921
+ *
2922
+ * `sizeBytes` is what `groundedReport` in `@cloudbitmaps/tools` prices storage from.
2835
2923
  *
2836
2924
  * ```ts
2837
- * const { generation, cardinality, metadata } = await store.segment('active-30d').stat();
2925
+ * const { generation, cardinality, metadata, sizeBytes } = await store.segment('active-30d').stat();
2838
2926
  * ```
2839
2927
  */
2840
2928
  async stat() {
2841
- this.assertLeases([this]);
2929
+ this.assertLease();
2842
2930
  return this.engine.stat(this.ref);
2843
2931
  }
2844
2932
  /**
@@ -2868,7 +2956,7 @@ var Segment = class _Segment {
2868
2956
  engine.iterate(ref, bounds),
2869
2957
  () => engine.iterateBatches(ref, bounds)
2870
2958
  );
2871
- return this.leaseHold === void 0 ? stream : guardIds(stream, () => this.assertLeases([this]));
2959
+ return this.leaseHold === void 0 ? stream : guardIds(stream, () => this.assertLease());
2872
2960
  }
2873
2961
  /**
2874
2962
  * **Pinned handles only.** The ids at 1-based ranks `n`, `2n`, `3n` …, counted over the ids in `(after, through]`
@@ -2902,22 +2990,22 @@ var Segment = class _Segment {
2902
2990
  */
2903
2991
  everyNth(n, options) {
2904
2992
  const lease = this.leaseError();
2905
- if (lease !== void 0) return failing(lease);
2993
+ if (lease !== void 0) return refusing(lease)();
2906
2994
  const bad = optionsError(options, "everyNth", CALL_KEYS.range);
2907
- if (bad !== void 0) return failing(bad);
2995
+ if (bad !== void 0) return refusing(bad)();
2908
2996
  if (this.pinnedAt === void 0) {
2909
- return failing(
2997
+ return refusing(
2910
2998
  new UnsupportedError(
2911
2999
  "everyNth needs a pinned handle: a live handle can place a rank by one generation and read it from another. Call `pin()` first."
2912
3000
  )
2913
- );
3001
+ )();
2914
3002
  }
2915
3003
  const stream = this.engine.everyNth(
2916
3004
  this.ref,
2917
3005
  n,
2918
3006
  options == null ? void 0 : rangeOf(options)
2919
3007
  );
2920
- return this.leaseHold === void 0 ? stream : guardIdIterable(stream, () => this.assertLeases([this]));
3008
+ return this.leaseHold === void 0 ? stream : guardIdIterable(stream, () => this.assertLease());
2921
3009
  }
2922
3010
  /**
2923
3011
  * The options a combine hands the engine, read once, when it is called ({@link readOptions}), with the `exclude`
@@ -2957,28 +3045,7 @@ var Segment = class _Segment {
2957
3045
  intersect(others, options) {
2958
3046
  const bad = optionsError(options, "intersect", CALL_KEYS.combine, ["allowAbsentOperands"]);
2959
3047
  if (bad !== void 0) return failing(bad);
2960
- return this.intersectAs(AS_IDS, others, options ?? void 0);
2961
- }
2962
- intersectAs(out, others, options) {
2963
- let exclude;
2964
- let handles;
2965
- try {
2966
- exclude = this.excludesOf(options);
2967
- handles = [this, ...segmentList(others, "intersect: `others`"), ...exclude];
2968
- this.assertSegments(handles);
2969
- this.assertLeases(handles);
2970
- } catch (err) {
2971
- return out.failing(err);
2972
- }
2973
- let engine;
2974
- try {
2975
- engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
2976
- } catch (err) {
2977
- return out.failing(err);
2978
- }
2979
- const refs = [this.ref, ...others.map((o) => o.ref)];
2980
- const opts = this.refsIn(options, exclude);
2981
- return this.guarded(out, handles, out.intersect(engine, refs, opts));
3048
+ return this.combineAs(AS_IDS, "intersect", others, options ?? void 0);
2982
3049
  }
2983
3050
  /**
2984
3051
  * Materialize `this ∩ others…` (minus `exclude`) as a **new generation of `dest`** — `dest`'s previous contents
@@ -3002,21 +3069,7 @@ var Segment = class _Segment {
3002
3069
  */
3003
3070
  async intersectInto(dest, others, options) {
3004
3071
  checkOptions(options, "intersectInto", CALL_KEYS.into, ["allowEmpty", "allowAbsentOperands"]);
3005
- const operands = [
3006
- ...segmentList(others, "intersectInto: `others`"),
3007
- ...this.excludesOf(options)
3008
- ];
3009
- this.assertIntoHandles(dest, operands);
3010
- this.combineEngine([this, ...operands]);
3011
- return this.timed(
3012
- "intersectInto",
3013
- () => this.materialize(
3014
- dest.ref,
3015
- new CombineChunks(this.intersectAs(AS_CHUNKS, others, options)),
3016
- "intersectInto",
3017
- options
3018
- )
3019
- );
3072
+ return this.combineInto("intersectInto", dest, others, options);
3020
3073
  }
3021
3074
  /**
3022
3075
  * `this ∪ others…`, minus `options.exclude` — streamed ascending.
@@ -3031,44 +3084,12 @@ var Segment = class _Segment {
3031
3084
  union(others, options) {
3032
3085
  const bad = optionsError(options, "union", CALL_KEYS.combine, ["allowAbsentOperands"]);
3033
3086
  if (bad !== void 0) return failing(bad);
3034
- return this.unionAs(AS_IDS, others, options ?? void 0);
3035
- }
3036
- unionAs(out, others, options) {
3037
- let exclude;
3038
- let handles;
3039
- try {
3040
- exclude = this.excludesOf(options);
3041
- handles = [this, ...segmentList(others, "union: `others`"), ...exclude];
3042
- this.assertSegments(handles);
3043
- this.assertLeases(handles);
3044
- } catch (err) {
3045
- return out.failing(err);
3046
- }
3047
- let engine;
3048
- try {
3049
- engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
3050
- } catch (err) {
3051
- return out.failing(err);
3052
- }
3053
- const refs = [this.ref, ...others.map((o) => o.ref)];
3054
- const opts = this.refsIn(options, exclude);
3055
- return this.guarded(out, handles, out.union(engine, refs, opts));
3087
+ return this.combineAs(AS_IDS, "union", others, options ?? void 0);
3056
3088
  }
3057
3089
  /** Materialize `this ∪ others…` (minus `exclude`) as a **new generation of `dest`** — see {@link intersectInto}. */
3058
3090
  async unionInto(dest, others, options) {
3059
3091
  checkOptions(options, "unionInto", CALL_KEYS.into, ["allowEmpty", "allowAbsentOperands"]);
3060
- const operands = [...segmentList(others, "unionInto: `others`"), ...this.excludesOf(options)];
3061
- this.assertIntoHandles(dest, operands);
3062
- this.combineEngine([this, ...operands]);
3063
- return this.timed(
3064
- "unionInto",
3065
- () => this.materialize(
3066
- dest.ref,
3067
- new CombineChunks(this.unionAs(AS_CHUNKS, others, options)),
3068
- "unionInto",
3069
- options
3070
- )
3071
- );
3092
+ return this.combineInto("unionInto", dest, others, options);
3072
3093
  }
3073
3094
  /**
3074
3095
  * `this \ (excludes…)` — streamed ascending. Suppression on its own.
@@ -3084,12 +3105,26 @@ var Segment = class _Segment {
3084
3105
  andNot(excludes, options) {
3085
3106
  const bad = optionsError(options, "andNot", CALL_KEYS.andNot, ["allowAbsentOperands"]);
3086
3107
  if (bad !== void 0) return failing(bad);
3087
- return this.andNotAs(AS_IDS, excludes, options ?? void 0);
3108
+ return this.combineAs(AS_IDS, "andNot", excludes, options ?? void 0);
3088
3109
  }
3089
- andNotAs(out, excludes, options) {
3110
+ /** Materialize `this \ (excludes…)` as a **new generation of `dest`** — see {@link intersectInto}. */
3111
+ async andNotInto(dest, excludes, options) {
3112
+ checkOptions(options, "andNotInto", CALL_KEYS.andNotInto, [
3113
+ "allowEmpty",
3114
+ "allowAbsentOperands"
3115
+ ]);
3116
+ return this.combineInto("andNotInto", dest, excludes, options);
3117
+ }
3118
+ /**
3119
+ * The one body of the three combines, read as ids or as chunks. `operands` is `others` for `intersect` and
3120
+ * `union`, and `excludes` for `andNot`, which takes no `exclude` option.
3121
+ */
3122
+ combineAs(out, verb, operands, options) {
3123
+ let exclude;
3090
3124
  let handles;
3091
3125
  try {
3092
- handles = [this, ...segmentList(excludes, "andNot: `excludes`")];
3126
+ exclude = verb === "andNot" ? NO_SEGMENTS : this.excludesOf(options);
3127
+ handles = [this, ...segmentList(operands, operandsLabel(verb)), ...exclude];
3093
3128
  this.assertSegments(handles);
3094
3129
  this.assertLeases(handles);
3095
3130
  } catch (err) {
@@ -3097,65 +3132,41 @@ var Segment = class _Segment {
3097
3132
  }
3098
3133
  let engine;
3099
3134
  try {
3100
- engine = this.combineEngine([this, ...excludes]) ?? this.engine;
3135
+ engine = this.combineEngine([this, ...operands, ...exclude]) ?? this.engine;
3101
3136
  } catch (err) {
3102
3137
  return out.failing(err);
3103
3138
  }
3104
- const base = this.ref;
3105
- const refs = excludes.map((o) => o.ref);
3106
- const opts = options == null ? void 0 : readOptions(options);
3107
- return this.guarded(out, handles, out.andNot(engine, base, refs, opts));
3139
+ if (verb === "andNot") {
3140
+ const refs2 = operands.map((o) => o.ref);
3141
+ const opts2 = options == null ? void 0 : readOptions(options);
3142
+ return this.guarded(out, handles, out.andNot(engine, this.ref, refs2, opts2));
3143
+ }
3144
+ const refs = [this.ref, ...operands.map((o) => o.ref)];
3145
+ const opts = this.refsIn(options, exclude);
3146
+ return this.guarded(
3147
+ out,
3148
+ handles,
3149
+ verb === "union" ? out.union(engine, refs, opts) : out.intersect(engine, refs, opts)
3150
+ );
3108
3151
  }
3109
- /** Materialize `this \ (excludes…)` as a **new generation of `dest`** — see {@link intersectInto}. */
3110
- async andNotInto(dest, excludes, options) {
3111
- checkOptions(options, "andNotInto", CALL_KEYS.andNotInto, [
3112
- "allowEmpty",
3113
- "allowAbsentOperands"
3114
- ]);
3115
- this.assertIntoHandles(dest, segmentList(excludes, "andNotInto: `excludes`"));
3116
- this.combineEngine([this, ...excludes]);
3152
+ /** The one body of the three `*Into` verbs, after each has checked its own options. */
3153
+ combineInto(name, dest, operands, options) {
3154
+ const verb = name.slice(0, -"Into".length);
3155
+ const all = verb === "andNot" ? segmentList(operands, `${name}: \`excludes\``) : [...segmentList(operands, `${name}: \`others\``), ...this.excludesOf(options)];
3156
+ this.assertIntoHandles(dest, all);
3157
+ this.combineEngine([this, ...all]);
3117
3158
  return this.timed(
3118
- "andNotInto",
3159
+ name,
3119
3160
  () => this.materialize(
3120
3161
  dest.ref,
3121
- new CombineChunks(this.andNotAs(AS_CHUNKS, excludes, options)),
3122
- "andNotInto",
3162
+ new CombineChunks(this.combineAs(AS_CHUNKS, verb, operands, options)),
3163
+ name,
3123
3164
  options
3124
3165
  )
3125
3166
  );
3126
3167
  }
3127
- /**
3128
- * Grounded cost report for this segment: storage cost from its **real** `.crbm` size (exact, no payload
3129
- * reads); request cost from the supplied `workload` rates. A segment with no Storage generation reports zero
3130
- * storage. The pointer refresh is priced at the store's own `cache.genTtlMs`, or at none when the store never
3131
- * refreshes, unless the workload sets `genTtlMs`. See {@link CostReport} — it always includes a verdict (incl.
3132
- * the lose-zone).
3133
- * @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
3134
- * needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
3135
- */
3136
- async costReport(options) {
3137
- checkOptions(options, "costReport", CALL_KEYS.costReport);
3138
- const shape = options?.workload;
3139
- if (shape !== void 0 && shape !== null && (typeof shape !== "object" || Array.isArray(shape))) {
3140
- throw new ValidationError2("costReport: workload must be an object");
3141
- }
3142
- this.assertLeases([this]);
3143
- const canMeasure = this.engine.supportsStorageSize;
3144
- const size = canMeasure ? await this.engine.segmentSize(this.ref) : null;
3145
- const refreshMs = this.engine.pointerRefreshMs;
3146
- const given = options?.workload === void 0 || options.workload === null ? void 0 : picked(options.workload, CALL_KEYS.workload);
3147
- const workload = refreshMs === void 0 || given?.genTtlMs !== void 0 ? given : { ...given, genTtlMs: refreshMs };
3148
- return groundedReport({
3149
- storageBytes: size?.sizeBytes ?? 0,
3150
- grounded: canMeasure,
3151
- workload,
3152
- pricing: options?.pricing,
3153
- extraNotes: canMeasure ? void 0 : ["storage source has no sizeOf() \u2014 storage not measured, reported as $0."]
3154
- });
3155
- }
3156
3168
  };
3157
3169
  export {
3158
- AWS_US_EAST_1_ONDEMAND,
3159
3170
  BudgetExceededError,
3160
3171
  BufferReader,
3161
3172
  CapabilityError,
@@ -3163,9 +3174,8 @@ export {
3163
3174
  CloudRoaringError,
3164
3175
  CountingMetricsSink,
3165
3176
  CrbmReader,
3166
- CrbmStorageChunkSource2 as CrbmStorageChunkSource,
3167
- DEFAULT_RETRY_POLICY2 as DEFAULT_RETRY_POLICY,
3168
- ELASTICACHE_REDIS_US_EAST_1_ONDEMAND,
3177
+ CrbmStorageChunkSource3 as CrbmStorageChunkSource,
3178
+ DEFAULT_RETRY_POLICY3 as DEFAULT_RETRY_POLICY,
3169
3179
  InProcessKeystore,
3170
3180
  IntegrityError3 as IntegrityError,
3171
3181
  KeyUnavailableError,
@@ -3179,13 +3189,12 @@ export {
3179
3189
  MemoryStorage,
3180
3190
  NodeAead,
3181
3191
  NotFoundError2 as NotFoundError,
3182
- ONE_REDIS_HA_CLUSTER,
3183
3192
  RecordingAuditSink,
3184
3193
  Segment,
3185
3194
  StaleOperandError,
3186
3195
  TransientError,
3187
3196
  UnsupportedError2 as UnsupportedError,
3188
- ValidationError3 as ValidationError,
3197
+ ValidationError5 as ValidationError,
3189
3198
  WriteConflictError2 as WriteConflictError,
3190
3199
  aadFor,
3191
3200
  deserializePortable,