@cloudbitmaps/roaring 0.15.0 → 0.16.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/README.md CHANGED
@@ -69,11 +69,10 @@ name.
69
69
 
70
70
  ## What it costs
71
71
 
72
- A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks is expected to make 6 GETs, **$2.40 per
73
- million**: counted by running the engine and priced at list, not yet measured on a cloud. On a spread layout it reads more
72
+ A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks made 6 GETs and took 95.03 ms at the median, **$2.40 per
73
+ million** at list prices: measured on S3 in-region, in the run of 2026-10-04, whose client had 128 sockets. On a spread layout it reads more
74
74
  bytes than it needs, which cross-region can cost more than the requests it saves. A segment's first `store.load()` is
75
- **$11.60 per million**, pointer included, from its measured requests at list prices (run of 2026-10-04, in-region, whose
76
- client had 50 sockets; the client this release builds allows 128, and its effect is not measured). A cold `count()` is one
75
+ expected at **$11.20 per million**, pointer included: counted by running the engine and priced at list, not yet measured on a cloud. A cold `count()` is one
77
76
  pointer read: it reads no payload and no object.
78
77
 
79
78
  The trade is stated plainly. A membership check that misses the cache costs a ranged GET against object storage,
@@ -33,6 +33,7 @@ import {
33
33
  estimateCost,
34
34
  groundedReport,
35
35
  loadSegment,
36
+ loadSegmentChunks,
36
37
  mapWithConcurrency,
37
38
  resolveBudget,
38
39
  resolvePerOpBudget,
@@ -460,7 +461,8 @@ function bitmapAsLoadInput(input) {
460
461
  var roaringCodec = {
461
462
  empty: () => SafeBitmap.empty(),
462
463
  fromValues: (values) => SafeBitmap.fromValues(values),
463
- safeDeserialize: (bytes, maxBytes, options) => SafeBitmap.safeDeserialize(bytes, maxBytes, options)
464
+ safeDeserialize: (bytes, maxBytes, options) => SafeBitmap.safeDeserialize(bytes, maxBytes, options),
465
+ owns: (bitmap) => bitmap instanceof SafeBitmap
464
466
  };
465
467
 
466
468
  // src/system-clock.ts
@@ -885,7 +887,7 @@ var CloudRoaring = class _CloudRoaring {
885
887
  const metadata = options?.metadata;
886
888
  let result;
887
889
  try {
888
- result = await loadSegment(dest, ids, deps, {
890
+ const loadOptions = {
889
891
  ...options?.allowEmpty === void 0 ? {} : { allowEmpty: options.allowEmpty },
890
892
  ...options?.guard === void 0 ? {} : { guard: options.guard },
891
893
  ...metadata === void 0 ? {} : { metadata },
@@ -898,7 +900,8 @@ var CloudRoaring = class _CloudRoaring {
898
900
  // as a side effect of adding a guard whose entire purpose is preventing data loss. Opt in with `keep`.
899
901
  keep: options?.keep ?? KEEP_EVERY_GENERATION,
900
902
  ...options?.audit === void 0 ? {} : { audit: options.audit }
901
- });
903
+ };
904
+ result = ids instanceof CombineChunks ? await loadSegmentChunks(dest, ids.chunks, deps, loadOptions) : await loadSegment(dest, ids, deps, loadOptions);
902
905
  } finally {
903
906
  this.engine.invalidate(dest);
904
907
  }
@@ -1702,6 +1705,32 @@ var EMPTY_IDS = {
1702
1705
  async *batches() {
1703
1706
  }
1704
1707
  };
1708
+ var AS_IDS = {
1709
+ none: EMPTY_IDS,
1710
+ failing,
1711
+ intersect: (engine, refs, opts) => withBatches(engine.intersect(refs, opts), () => engine.intersectBatches(refs, opts)),
1712
+ union: (engine, refs, opts) => withBatches(engine.union(refs, opts), () => engine.unionBatches(refs, opts)),
1713
+ andNot: (engine, base, refs, opts) => withBatches(engine.andNot(base, refs, opts), () => engine.andNotBatches(base, refs, opts))
1714
+ };
1715
+ var NO_CHUNKS = {
1716
+ async *[Symbol.asyncIterator]() {
1717
+ }
1718
+ };
1719
+ var CombineChunks = class {
1720
+ constructor(chunks) {
1721
+ this.chunks = chunks;
1722
+ }
1723
+ chunks;
1724
+ };
1725
+ var AS_CHUNKS = {
1726
+ none: NO_CHUNKS,
1727
+ failing: (err) => ({
1728
+ [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(err) })
1729
+ }),
1730
+ intersect: (engine, refs, opts) => engine.intersectChunks(refs, opts),
1731
+ union: (engine, refs, opts) => engine.unionChunks(refs, opts),
1732
+ andNot: (engine, base, refs, opts) => engine.andNotChunks(base, refs, opts)
1733
+ };
1705
1734
  var KEEP_EVERY_GENERATION = Number.MAX_SAFE_INTEGER;
1706
1735
  var makeSegment;
1707
1736
  var minting = false;
@@ -2000,17 +2029,20 @@ var Segment = class _Segment {
2000
2029
  * override the store's per-op denial-of-wallet budget for this call (or `false` to lift it).
2001
2030
  */
2002
2031
  intersect(others, options) {
2003
- if (this.expired() || others.some((o) => o.expired())) return EMPTY_IDS;
2032
+ return this.intersectAs(AS_IDS, others, options);
2033
+ }
2034
+ intersectAs(out, others, options) {
2035
+ if (this.expired() || others.some((o) => o.expired())) return out.none;
2004
2036
  const exclude = this.liveExcludes(options);
2005
2037
  let engine;
2006
2038
  try {
2007
2039
  engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
2008
2040
  } catch (err) {
2009
- return failing(err);
2041
+ return out.failing(err);
2010
2042
  }
2011
2043
  const refs = [this.ref, ...others.map((o) => o.ref)];
2012
2044
  const opts = this.refsIn(options, exclude);
2013
- return withBatches(engine.intersect(refs, opts), () => engine.intersectBatches(refs, opts));
2045
+ return out.intersect(engine, refs, opts);
2014
2046
  }
2015
2047
  /**
2016
2048
  * Materialize `this ∩ others…` (minus `exclude`) as a **new generation of `dest`** — `dest`'s previous contents
@@ -2037,7 +2069,12 @@ var Segment = class _Segment {
2037
2069
  this.combineEngine([this, ...others, ...options?.exclude ?? []]);
2038
2070
  return this.timed(
2039
2071
  "intersectInto",
2040
- () => this.materialize(dest.ref, this.intersect(others, options), "intersectInto", options)
2072
+ () => this.materialize(
2073
+ dest.ref,
2074
+ new CombineChunks(this.intersectAs(AS_CHUNKS, others, options)),
2075
+ "intersectInto",
2076
+ options
2077
+ )
2041
2078
  );
2042
2079
  }
2043
2080
  /**
@@ -2051,26 +2088,29 @@ var Segment = class _Segment {
2051
2088
  * segment once (`unionInto`, or a load) is the cheaper shape.
2052
2089
  */
2053
2090
  union(others, options) {
2091
+ return this.unionAs(AS_IDS, others, options);
2092
+ }
2093
+ unionAs(out, others, options) {
2054
2094
  const live = others.filter((o) => !o.expired());
2055
2095
  if (this.expired()) {
2056
- if (live.length === 0) return EMPTY_IDS;
2057
- return live[0].union(live.slice(1), options);
2096
+ if (live.length === 0) return out.none;
2097
+ return live[0].unionAs(out, live.slice(1), options);
2058
2098
  }
2059
2099
  if (live.length === 0 && others.length > 0) {
2060
2100
  const exclude2 = this.liveExcludes(options);
2061
- return exclude2.length > 0 ? this.andNot([...exclude2], options) : this.union([], options);
2101
+ return exclude2.length > 0 ? this.andNotAs(out, [...exclude2], options) : this.unionAs(out, [], options);
2062
2102
  }
2063
- if (live.length !== others.length) return this.union(live, options);
2103
+ if (live.length !== others.length) return this.unionAs(out, live, options);
2064
2104
  const exclude = this.liveExcludes(options);
2065
2105
  let engine;
2066
2106
  try {
2067
2107
  engine = this.combineEngine([this, ...others, ...exclude]) ?? this.engine;
2068
2108
  } catch (err) {
2069
- return failing(err);
2109
+ return out.failing(err);
2070
2110
  }
2071
2111
  const refs = [this.ref, ...others.map((o) => o.ref)];
2072
2112
  const opts = this.refsIn(options, exclude);
2073
- return withBatches(engine.union(refs, opts), () => engine.unionBatches(refs, opts));
2113
+ return out.union(engine, refs, opts);
2074
2114
  }
2075
2115
  /** Materialize `this ∪ others…` (minus `exclude`) as a **new generation of `dest`** — see {@link intersectInto}. */
2076
2116
  async unionInto(dest, others, options) {
@@ -2078,7 +2118,12 @@ var Segment = class _Segment {
2078
2118
  this.combineEngine([this, ...others, ...options?.exclude ?? []]);
2079
2119
  return this.timed(
2080
2120
  "unionInto",
2081
- () => this.materialize(dest.ref, this.union(others, options), "unionInto", options)
2121
+ () => this.materialize(
2122
+ dest.ref,
2123
+ new CombineChunks(this.unionAs(AS_CHUNKS, others, options)),
2124
+ "unionInto",
2125
+ options
2126
+ )
2082
2127
  );
2083
2128
  }
2084
2129
  /**
@@ -2097,25 +2142,25 @@ var Segment = class _Segment {
2097
2142
  * the suppression folds into the same pass rather than materializing an intermediate segment first.
2098
2143
  */
2099
2144
  andNot(excludes, options) {
2100
- if (this.expired()) return EMPTY_IDS;
2145
+ return this.andNotAs(AS_IDS, excludes, options);
2146
+ }
2147
+ andNotAs(out, excludes, options) {
2148
+ if (this.expired()) return out.none;
2101
2149
  const liveExcludes = excludes.filter((e) => !e.expired());
2102
2150
  if (liveExcludes.length === 0 && excludes.length > 0) {
2103
- return this.union([], options == null ? void 0 : readOptions(options));
2151
+ return this.unionAs(out, [], options == null ? void 0 : readOptions(options));
2104
2152
  }
2105
- if (liveExcludes.length !== excludes.length) return this.andNot(liveExcludes, options);
2153
+ if (liveExcludes.length !== excludes.length) return this.andNotAs(out, liveExcludes, options);
2106
2154
  let engine;
2107
2155
  try {
2108
2156
  engine = this.combineEngine([this, ...excludes]) ?? this.engine;
2109
2157
  } catch (err) {
2110
- return failing(err);
2158
+ return out.failing(err);
2111
2159
  }
2112
2160
  const base = this.ref;
2113
2161
  const refs = excludes.map((o) => o.ref);
2114
2162
  const opts = options == null ? void 0 : readOptions(options);
2115
- return withBatches(
2116
- engine.andNot(base, refs, opts),
2117
- () => engine.andNotBatches(base, refs, opts)
2118
- );
2163
+ return out.andNot(engine, base, refs, opts);
2119
2164
  }
2120
2165
  /** Materialize `this \ (excludes…)` as a **new generation of `dest`** — see {@link intersectInto}. */
2121
2166
  async andNotInto(dest, excludes, options) {
@@ -2123,7 +2168,12 @@ var Segment = class _Segment {
2123
2168
  this.combineEngine([this, ...excludes]);
2124
2169
  return this.timed(
2125
2170
  "andNotInto",
2126
- () => this.materialize(dest.ref, this.andNot(excludes, options), "andNotInto", options)
2171
+ () => this.materialize(
2172
+ dest.ref,
2173
+ new CombineChunks(this.andNotAs(AS_CHUNKS, excludes, options)),
2174
+ "andNotInto",
2175
+ options
2176
+ )
2127
2177
  );
2128
2178
  }
2129
2179
  /**