@cloudbitmaps/roaring 0.24.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
@@ -50,9 +50,37 @@ To persist, pass a different **backend** as `storage`. Nothing else changes:
50
50
  | `GcsStorage({ bucket, prefix })` | `@cloudbitmaps/gcs` | Google Cloud Storage; outside the [validated envelope](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/ROADMAP.md#the-validated-envelope--whats-proven-and-what-isnt) |
51
51
  | `AzureBlobStorage({ connectionString, container })` | `@cloudbitmaps/azure-blob` | Azure Blob Storage; outside the [validated envelope](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/ROADMAP.md#the-validated-envelope--whats-proven-and-what-isnt) |
52
52
 
53
- There is no `add` or `remove`: a segment changes only by loading a new generation. A load is a batch job, not a
53
+ `RoaringBitmap` is the codec's bitmap as a public class: a mutable set of unsigned 32-bit ids with the portable Roaring
54
+ bytes, in TypeScript with no native code. Import it from `@cloudbitmaps/roaring`, the same line as the store:
55
+ `import { CloudRoaring, MemoryStorage, RoaringBitmap } from '@cloudbitmaps/roaring'`. A store loads one as it is, and
56
+ `RoaringBitmap.deserialize(bytes)` is the one door for bytes you did not write.
57
+
58
+ Two calls are slower than the native `roaring` package by a ratio above 10 in the bench, and ship with their figures shown. The first `deserialize` in a fresh process has a measured ratio of about 14.6 (median 2.20 ms against 0.151 ms); it is a one-time cost of the first call (the expected reason is code not yet optimized plus compile time), and later decodes have measured ratios of 2.2 to 5.9 across the five data sets. The union of 64 dense bitmaps with `orMany` has a measured ratio of about 11. Every figure is in [the table](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/roaring-codec.md#the-public-class-against-native-in-memory).
59
+
60
+ <!-- typecheck -->
61
+ ```ts
62
+ import { CloudRoaring, MemoryStorage, RoaringBitmap } from '@cloudbitmaps/roaring';
63
+
64
+ const store = new CloudRoaring({ storage: new MemoryStorage() });
65
+
66
+ const vip = new RoaringBitmap([5, 99_999, 1_234_567_890]); // any iterable of ids
67
+ vip.add(7);
68
+ vip.removeMany([99_999]);
69
+ vip.addMany(new RoaringBitmap([8, 9])); // another bitmap, or a Uint32Array, works too
70
+
71
+ await store.load({ segment: 'vip' }, vip); // read, never changed or kept
72
+
73
+ // Bytes from another process go through the one safe door: capped, checked, one whole bitmap.
74
+ const received = RoaringBitmap.deserialize(vip.serialize());
75
+ console.log(received.size, received.minimum(), received.maximum()); // 5 5 1234567890
76
+ ```
77
+
78
+ [The API reference](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/api-reference.md#build-and-combine-bitmaps-by-themselves--roaringbitmap)
79
+ has every method and where it differs from the `roaring` package.
80
+
81
+ A stored segment has no `add` or `remove` (`RoaringBitmap` does, for a bitmap in memory): a segment changes only by loading a new generation. A load is a batch job, not a
54
82
  request handler. `store.exists(ref)` says whether a segment has been loaded. A load takes ids, or a bitmap you already
55
- hold: `store.load(ref, { bitmap })` for a `RoaringBitmap32`, or `{ serialized }` for portable Roaring bytes, checked
83
+ hold: `store.load(ref, bitmap)` for a `RoaringBitmap` (or a `RoaringBitmap32`), or `{ serialized }` for portable Roaring bytes, checked
56
84
  before anything is written and loaded with no per-id work, into a generation holding chunk for chunk what its ids would write (its footer carries its own write identity).
57
85
 
58
86
  To write many results over the same stored operands, `store.materializeMany` reads each operand once for a whole batch of
@@ -77,8 +105,8 @@ name.
77
105
 
78
106
  ## What it costs
79
107
 
80
- 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
81
- 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
82
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
83
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
84
112
  pointer read: it reads no payload and no object.