@cloudbitmaps/roaring 0.25.0 → 0.26.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/PRIVACY.md CHANGED
@@ -173,7 +173,7 @@ A call with a feed reads operands the library never stores, which no erasure can
173
173
  Erasure and crypto-shred are **immediate in storage and immediate in the store whose verb performed them**, for
174
174
  every read that starts after the verb returns. A read already in progress in that store can still yield an erased id
175
175
  from a chunk it had requested before the verb ran: up to 32 chunks for `iterate` and `count`, and up to `concurrency` + 1
176
- keys (33 by default) for a combine. That is the bound: the chunk or key a read is handing out and what it had already requested, which a `count` that reads chunks (on a source with no index of its own) reaches. A running `iterate` or combine on the library's own sources stops sooner: the verb invalidates the segment, so before the next key it takes the read lists its chunk keys again from the generation current then and drops what it had requested, and so yields the id only from the chunk or key it is handing out. A combine or `iterate` reads ranges of the object, and resolves the segment again before it serves each chunk, one held in its cache included, as a read of that chunk alone does, so the ranges it had already requested are dropped, not served. A pinned read in progress does not move to the rewritten generation: before its next chunk it opens its object again, and fails with `NotFoundError` once the erasure has deleted it, so it yields no more than that bound either. They are not immediate in any *other* store, in the same process or another, and this library ships nothing that could
176
+ keys (33 by default) for a combine or a count of a combine. That is the bound: the chunk or key a read is handing out and what it had already requested, which a `count` that reads chunks (on a source with no index of its own) reaches. A running `iterate` or combine on the library's own sources stops sooner: the verb invalidates the segment, so before the next key it takes the read lists its chunk keys again from the generation current then and drops what it had requested, and so yields the id only from the chunk or key it is handing out. A combine or `iterate` reads ranges of the object, and resolves the segment again before it serves each chunk, one held in its cache included, as a read of that chunk alone does, so the ranges it had already requested are dropped, not served. A pinned read in progress does not move to the rewritten generation: before its next chunk it opens its object again, and fails with `NotFoundError` once the erasure has deleted it, so it yields no more than that bound either. They are not immediate in any *other* store, in the same process or another, and this library ships nothing that could
177
177
  make them so — there is no daemon, no bus, and no connection between two stores that happen to point at the same
178
178
  bucket.
179
179
 
package/README.md CHANGED
@@ -105,8 +105,8 @@ name.
105
105
 
106
106
  ## What it costs
107
107
 
108
- A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks made 6 GETs and took 104.88 ms at the median, **$2.40 per
109
- million** at list prices: measured on S3 in-region, in the run of 2026-10-13, whose client had 128 sockets. On a spread layout it reads more
108
+ A cold `intersect` of two 500,000-id segments sharing 100 of their 1,999 chunks made 6 GETs and took 107.87 ms at the median, **$2.40 per
109
+ million** at list prices: measured on S3 in-region, in the run of 2026-10-30, whose client had 128 sockets. On a spread layout it reads more
110
110
  bytes than it needs, which cross-region can cost more than the requests it saves. A segment's first single-part `store.load()` made
111
111
  2 PUT + 3 GET, **$11.20 per million** at the default prices, pointer included: the requests are measured on S3 in-region, in the same run. A steady single-part `store.load()` at `keep: 12` that deletes a generation by name costs **$12.00 per million**, for 2 PUT + 5 GET and a free delete, the requests measured on S3 in-region, in the same run. A cold `count()` is one
112
112
  pointer read: it reads no payload and no object.
@@ -3572,6 +3572,29 @@ var CALL_KEYS = {
3572
3572
  "allowAbsentOperands",
3573
3573
  "exclude"
3574
3574
  ]),
3575
+ intersectCount: keysOf()([
3576
+ "after",
3577
+ "through",
3578
+ "concurrency",
3579
+ "budget",
3580
+ "allowAbsentOperands",
3581
+ "exclude"
3582
+ ]),
3583
+ unionCount: keysOf()([
3584
+ "after",
3585
+ "through",
3586
+ "concurrency",
3587
+ "budget",
3588
+ "allowAbsentOperands",
3589
+ "exclude"
3590
+ ]),
3591
+ andNotCount: keysOf()([
3592
+ "after",
3593
+ "through",
3594
+ "concurrency",
3595
+ "budget",
3596
+ "allowAbsentOperands"
3597
+ ]),
3575
3598
  into: keysOf()([
3576
3599
  "after",
3577
3600
  "through",
@@ -3649,6 +3672,9 @@ var MISPLACED = {
3649
3672
  range: outside(CALL_KEYS.into, CALL_KEYS.range),
3650
3673
  andNot: outside(CALL_KEYS.into, CALL_KEYS.andNot),
3651
3674
  combine: outside(CALL_KEYS.into, CALL_KEYS.combine),
3675
+ intersectCount: outside(CALL_KEYS.into, CALL_KEYS.intersectCount),
3676
+ unionCount: outside(CALL_KEYS.into, CALL_KEYS.unionCount),
3677
+ andNotCount: outside(CALL_KEYS.into, CALL_KEYS.andNotCount),
3652
3678
  andNotInto: outside(CALL_KEYS.into, CALL_KEYS.andNotInto),
3653
3679
  exportSegments: ["codec"]
3654
3680
  };
@@ -5415,7 +5441,7 @@ function operandsLabel(verb) {
5415
5441
  }
5416
5442
  function refuseNoOperands(verb) {
5417
5443
  throw new ValidationError5(
5418
- verb === "intersect" ? "intersect requires at least one segment to intersect with; to read every id of one segment, use iterate()" : "intersectInto requires at least one segment to intersect with; to copy this segment into dest, call materializeMany({ operands: { src: thisSegment }, outputs: [{ dest, expr: { or: ['src'] } }], keep })"
5444
+ verb === "intersect" ? "intersect requires at least one segment to intersect with; to read every id of one segment, use iterate()" : verb === "intersectCount" ? "intersectCount requires at least one segment to intersect with; to count one segment, use count()" : "intersectInto requires at least one segment to intersect with; to copy this segment into dest, call materializeMany({ operands: { src: thisSegment }, outputs: [{ dest, expr: { or: ['src'] } }], keep })"
5419
5445
  );
5420
5446
  }
5421
5447
  var AS_IDS = {
@@ -6125,6 +6151,111 @@ var Segment = class _Segment {
6125
6151
  );
6126
6152
  return this.combineInto("andNotInto", dest, excludes, options);
6127
6153
  }
6154
+ /**
6155
+ * How many ids `this ∩ others…` holds (minus `exclude`): the length of what {@link intersect} streams, without
6156
+ * streaming it. Every key all the operands hold is decoded and counted as the stream reads it, and a key only some
6157
+ * operands hold is not read, so the call makes the requests {@link intersect} makes and returns a number instead of
6158
+ * the ids. It is exact: it never answers a key from an index, so it trusts nothing {@link count} trusts.
6159
+ *
6160
+ * The range, `concurrency`, `budget`, `allowAbsentOperands` and `exclude` are {@link intersect}'s; a write option fails
6161
+ * the call with {@link ValidationError} before any request, and so does an empty `others` (to count one segment, use
6162
+ * {@link count}).
6163
+ *
6164
+ * Like a stream, a count that runs while a segment is erased or reloaded can describe two instants, within the bound
6165
+ * the streams state. A union or a difference, and an intersection with an `exclude`, looks at its operands'
6166
+ * generations every `concurrency` keys; an intersection with no `exclude` makes no such look, since it decodes every
6167
+ * key and so shows a move in the chunks it decodes, as its stream does. Either way it plans again above the last key
6168
+ * it took when one moved. A pinned handle counts the generation it pinned, and each pinned operand's object is
6169
+ * looked up once before the first key (one footer read each, which `budget` does not charge, and which the stream
6170
+ * does not make), and again at each look where there is one, so an erasure of what it pinned fails the count with
6171
+ * {@link NotFoundError}; a
6172
+ * fault the look meets other than a transient one, which is retried, is raised as it is. A call that involves a
6173
+ * handle whose lease has ended, or has ended by the next look, throws {@link LeaseExpiredError}.
6174
+ */
6175
+ async intersectCount(others, options) {
6176
+ checkOptions(
6177
+ options,
6178
+ "intersectCount",
6179
+ CALL_KEYS.intersectCount,
6180
+ ["allowAbsentOperands"],
6181
+ MISPLACED.intersectCount
6182
+ );
6183
+ return this.countCombine("intersect", others, options ?? void 0);
6184
+ }
6185
+ /**
6186
+ * How many ids `this ∪ others…` holds (minus `exclude`): the length of what {@link union} streams, without streaming
6187
+ * it. A chunk key only one operand holds, that no `exclude` holds and the range does not cut, is answered from that
6188
+ * operand's index, so a union of segments that mostly do not overlap reads little of them; every other key is
6189
+ * decoded and counted. On handles that are not pinned it makes no more requests than {@link union} does, and reads
6190
+ * fewer bytes when the shared keys are few. An empty `others`, with no `exclude` and no range, is {@link count} of
6191
+ * `this`, answered from the registry row when it can be; when that figure is 0 it adds one registry read, to refuse
6192
+ * a segment that does not exist as the stream does.
6193
+ *
6194
+ * What it trusts: the index, for each key it answers without decoding, as {@link count} does, so a party who can write
6195
+ * an object's index can make that key's figure wrong; a decoded key is exact. A source with no per-chunk counts
6196
+ * decodes every key. It takes the options {@link intersectCount} takes; the erasure bound, the lease checks and the
6197
+ * pinned handles are as for {@link intersectCount}, a pinned operand adding a footer read before the first key and at
6198
+ * each look. An empty `others` on a pinned handle is {@link count} of the pin, with no look.
6199
+ */
6200
+ async unionCount(others, options) {
6201
+ checkOptions(
6202
+ options,
6203
+ "unionCount",
6204
+ CALL_KEYS.unionCount,
6205
+ ["allowAbsentOperands"],
6206
+ MISPLACED.unionCount
6207
+ );
6208
+ return this.countCombine("union", others, options ?? void 0);
6209
+ }
6210
+ /**
6211
+ * How many ids `this \ (excludes…)` holds: the length of what {@link andNot} streams, without streaming it. A chunk
6212
+ * key of `this` that no exclude holds and the range does not cut is answered from its index and not read; a key an
6213
+ * exclude shares is decoded and counted. On handles that are not pinned it makes no more requests than {@link andNot}
6214
+ * does, and reads fewer bytes when the excludes overlap little of `this`. An empty `excludes`, with no range, is
6215
+ * {@link count} of `this` (the stream of it is the whole of `this`), answered from the registry row when it can be,
6216
+ * with one registry read added when that figure is 0.
6217
+ *
6218
+ * It takes the options {@link andNot} takes (every one of {@link intersectCount}'s but `exclude`, which a count of a
6219
+ * difference has no use for, since `excludes` is its subtraction) and trusts what {@link unionCount} trusts. The
6220
+ * erasure bound, the lease checks and the pinned handles are as for {@link intersectCount}.
6221
+ */
6222
+ async andNotCount(excludes, options) {
6223
+ checkOptions(
6224
+ options,
6225
+ "andNotCount",
6226
+ CALL_KEYS.andNotCount,
6227
+ ["allowAbsentOperands"],
6228
+ MISPLACED.andNotCount
6229
+ );
6230
+ return this.countCombine("andNot", excludes, options ?? void 0);
6231
+ }
6232
+ /** The one body of the three count methods, after each has checked its own options. */
6233
+ async countCombine(verb, operands, options) {
6234
+ const name = `${verb}Count`;
6235
+ const exclude = verb === "andNot" ? NO_SEGMENTS : this.excludesOf(options);
6236
+ const others = segmentList(
6237
+ operands,
6238
+ verb === "andNot" ? `${name}: \`excludes\`` : `${name}: \`others\``
6239
+ );
6240
+ if (verb === "intersect" && others.length === 0) refuseNoOperands("intersectCount");
6241
+ const handles = [this, ...others, ...exclude];
6242
+ this.assertSegments(name, verb === "andNot" ? "an exclude" : "an operand", others);
6243
+ this.assertSegments(name, "an exclude", exclude);
6244
+ this.assertLeases(handles);
6245
+ const engine = this.combineEngine(handles) ?? this.engine;
6246
+ const read = options == null ? void 0 : readOptions(options);
6247
+ const guard2 = handles.some((h) => h.leaseHold !== void 0) ? () => this.assertLeases(handles) : void 0;
6248
+ const opts = guard2 === void 0 ? read : { ...read, guard: guard2 };
6249
+ const refs = others.map((o) => o.ref);
6250
+ if (verb === "andNot") return engine.combineCount([this.ref], refs, "all", verb, opts);
6251
+ return engine.combineCount(
6252
+ [this.ref, ...refs],
6253
+ exclude.map((o) => o.ref),
6254
+ verb === "union" ? "any" : "all",
6255
+ verb,
6256
+ opts
6257
+ );
6258
+ }
6128
6259
  /**
6129
6260
  * The one body of the three combines, read as ids or as chunks. `operands` is `others` for `intersect` and
6130
6261
  * `union`, and `excludes` for `andNot`, which takes no `exclude` option.