@cloudbitmaps/roaring 0.22.0 → 0.24.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/NOTICE +2 -2
- package/PRIVACY.md +24 -18
- package/README.md +28 -11
- package/dist/bin/export-segments.js +1980 -319
- package/dist/bin/export-segments.js.map +4 -4
- package/dist/index.d.ts +263 -61
- package/dist/index.js +1996 -315
- package/dist/index.js.map +4 -4
- package/dist/roaring-codec.d.ts +36 -105
- package/package.json +4 -5
package/NOTICE
CHANGED
|
@@ -7,6 +7,6 @@ file. You may obtain a copy of the License at:
|
|
|
7
7
|
|
|
8
8
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
9
|
|
|
10
|
-
@cloudbitmaps/roaring builds on the Roaring Bitmap data structure and
|
|
11
|
-
|
|
10
|
+
@cloudbitmaps/roaring builds on the Roaring Bitmap data structure and implements the Roaring format and
|
|
11
|
+
algorithms of Lemire et al.; CRoaring is used in its tests as the reference.
|
|
12
12
|
Those projects are independent works under their own licenses.
|
package/PRIVACY.md
CHANGED
|
@@ -60,16 +60,18 @@ CloudBitmaps gives you three levers with different guarantees. Use them delibera
|
|
|
60
60
|
|
|
61
61
|
| Lever | API | Guarantee | Use for |
|
|
62
62
|
|---|---|---|---|
|
|
63
|
-
| **Subject erasure** | `store.eraseSubject(id, { namespace })` | *Physical on return* — the segment's current generation is rewritten without the id (every chunk streamed through, one bit cleared), published **fenced on the generation it streamed**, and **the generation that held the bit is deleted before the call returns**. So is every other generation still holding the id: a retained *superseded* one (an ex-member dropped by a re-seed), and each one above the pointer after a `rollback` — generations a rollback can move the pointer onto again — while, when the current generation does not hold the id, the ones there that never held it stay as rollback targets, but for any object no read of the segment can open, which goes whatever the id. (When the current generation holds the id, the rewrite is numbered above everything, so its `keep: 0` collection takes every older generation, above the pointer or below.) Before it says `erased: true` the call lists the bucket and reads what is left, so **`erased: true` means no generation of the segment holds the id**, above the pointer or below it. `eraseSubject` reports a per-segment fault as an `error: …` ledger entry rather than throwing it, and a call that a racing writer overtook (a load, another erasure, or a rollback) as `erased: false, note: 'superseded'`, so "on return" is a claim about every segment whose entry says **`erased: true`** (a fault is an `error: …` entry, never `erased: true`). The store that performs the call stops serving the id immediately; **other processes converge on their own read TTL** — see [One process, and the rest of your fleet](#one-process-and-the-rest-of-your-fleet). Does **not** reach backups, replicas or noncurrent object versions — those hold the old generation until their own lifecycle removes it. It carries a generation's metadata over unchanged and does **not** scan it, so **never put a subject's id in metadata**. | "forget this person" — GDPR Art. 17 |
|
|
63
|
+
| **Subject erasure** | `store.eraseSubject(id, { namespace })` | *Physical on return* — the segment's current generation is rewritten without the id (every chunk streamed through, one bit cleared), published **fenced on the generation it streamed**, and **the generation that held the bit is deleted before the call returns**. So is every other generation still holding the id: a retained *superseded* one (an ex-member dropped by a re-seed), and each one above the pointer after a `rollback` — generations a rollback can move the pointer onto again — while, when the current generation does not hold the id, the ones there that never held it stay as rollback targets, but for any object no read of the segment can open, which goes whatever the id. (When the current generation holds the id, the rewrite is numbered above everything, so its `keep: 0` collection takes every older generation, above the pointer or below. A holder found only above the pointer likewise takes every generation below the pointer with it, clean ones included ([every holder goes](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#every-holder-goes)); above the pointer, a generation with no copy of the id stays, and so does the current one.) Before it says `erased: true` the call lists the bucket and reads what is left, so **`erased: true` means no generation of the segment holds the id**, above the pointer or below it. `eraseSubject` reports a per-segment fault as an `error: …` ledger entry rather than throwing it, and a call that a racing writer overtook (a load, another erasure, or a rollback) as `erased: false, note: 'superseded'`, so "on return" is a claim about every segment whose entry says **`erased: true`** (a fault is an `error: …` entry, never `erased: true`). The store that performs the call stops serving the id immediately; **other processes converge on their own read TTL** — see [One process, and the rest of your fleet](#one-process-and-the-rest-of-your-fleet). Does **not** reach backups, replicas or noncurrent object versions — those hold the old generation until their own lifecycle removes it. It carries a generation's metadata over unchanged and does **not** scan it, so **never put a subject's id in metadata**. The scope is one `namespace` or `{ allNamespaces: true }`, never both (both throws `ValidationError`); a segment loaded with no `namespace` is reached only by `{ allNamespaces: true }`, and `'_default'` is an ordinary namespace name, not those segments. | "forget this person" — GDPR Art. 17 |
|
|
64
64
|
| **Dispose** | `store.dropSegment(ref, { confirmSegment })` | *Immediate* — the segment is tombstoned and its Storage generations deleted, reclaiming the space. **Check `generationsRemaining`:** if it is non-empty the space was *not* fully reclaimed and the drop should be re-run (a load that had read the segment before the tombstone landed, even one still consuming its ids, or an `eraseSubject` rewrite already under way, can write its object after the drop's sweep; it deletes that object itself once its publish is refused, and only one whose process stops in between, or whose publish fails without a definite answer (a lost response, a timeout), leaves it, for a re-run of the drop). Works on cleartext; on an encrypted segment it *also* crypto-shreds, on the terms in the next row. Does **not** reach noncurrent versions / replicas / PITR snapshots — deleting an object is weaker than removing a key. | retiring a dated bucket; rolling-window retention |
|
|
65
65
|
| **Crypto-shred** | `destroySegment` / `eraseNamespace` | *At rest, complete once no copy of the wrapped key survives* — removes the segment's wrapped DEK(s) from its registry row in one compare-and-swap, without touching the bytes or any KEK. **Every** copy of the objects (current, prior generations, backups, WORM-locked objects) is then unreadable once no retained copy of that row still holds the wrapped key — noncurrent object versions, backups and PITR copies of the row included — or once every KEK that wrapped it is destroyed. Until then, a retained copy of the row and a KEK that wrapped it decrypt the segment, and a registry restore to a point before the shred makes it readable again; see *Erasure vs. backups / WORM* below, and [an optional registry expiry rule](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/disaster-recovery.md#optional-make-a-shred-durable-with-a-registry-expiry-rule) that makes a shred durable after a set number of days. Requires the segment to be encrypted. A process that had already opened the segment holds the **unwrapped** key in memory and keeps reading until it is told — call `store.invalidate(ref)` there; see [One process, and the rest of your fleet](#one-process-and-the-rest-of-your-fleet). | whole-segment / tenant offboarding; erasure under immutable backups (see below) |
|
|
66
66
|
|
|
67
67
|
**Subject-wide erasure** (GDPR Art. 17 — "forget this person everywhere") is
|
|
68
|
-
`store.eraseSubject(id, { namespace })` — or `{ allNamespaces: true }` to sweep every tenant deliberately. For
|
|
69
|
-
every **registered** segment the id is in, it rewrites the current generation without the
|
|
70
|
-
rewrite, and deletes every generation that held
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
`store.eraseSubject(id, { namespace })` — or `{ allNamespaces: true }` to sweep every tenant deliberately; giving both throws `ValidationError`. A segment loaded with no `namespace` is in no namespace you can name: only `{ allNamespaces: true }` reaches it, and `'_default'`, the label the export tool and the disaster-recovery samples print, and the bucket's object keys use, for those segments, is an ordinary namespace name here, so passing it scans a namespace of that name, not them. For
|
|
69
|
+
every **registered** segment the id is in, it [rewrites the current generation without the
|
|
70
|
+
id](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#erasure-is-a-rewrite), publishes the rewrite, and deletes [every generation that held
|
|
71
|
+
the bit](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#every-holder-goes) — the one it replaced, a retained superseded one, and any
|
|
72
|
+
above the pointer that a rollback left — so the bit is physically gone from the bucket on return, for idle
|
|
73
|
+
and archival segments as much as busy ones (a noncurrent version, a replica or a backup keeps the old object until
|
|
74
|
+
its own lifecycle removes it; see *Honest limits* below). It checks rather than assumes: before an entry says `erased: true`,
|
|
73
75
|
the call lists the segment's generations and reads each one it has not already seen without the id. There is no logical-then-physical gap and no
|
|
74
76
|
scheduled step to wait for: an erasure *is* a new generation, the same shape as every other write in the library.
|
|
75
77
|
It reuses the store's own drivers (so build the store with a backend). It returns an
|
|
@@ -86,7 +88,7 @@ not in are not listed. A segment where the erasure found the id in nothing it co
|
|
|
86
88
|
no read of it can open, is not listed either: the audit sink gets `segment.collect { collected }` for it, with no
|
|
87
89
|
`fromGeneration`, and that event is the only record of the deletion. `store.subjectReport(id, { namespace })` answers
|
|
88
90
|
the read side (Art. 15 — which segments
|
|
89
|
-
an id is in).
|
|
91
|
+
an id is in, and which destroyed segments still hold it in objects left in the bucket).
|
|
90
92
|
|
|
91
93
|
An erasure that finds the id in no generation but an older one also writes the segment's registry row once, before it
|
|
92
94
|
deletes anything, when the row's ledger of known generations names a generation the call is about to delete: it takes
|
|
@@ -102,7 +104,7 @@ again, the row changes a second time.
|
|
|
102
104
|
|
|
103
105
|
Two of the rules while you erase. **Do not load a segment while erasing from it**: a load that lands after the rewrite carries whatever
|
|
104
106
|
its source held, and the library cannot know that source was meant to exclude the id — fix the source first, or
|
|
105
|
-
quiesce loads of the affected segments for the duration. A writer that lands *during* the rewrite is caught and
|
|
107
|
+
quiesce loads of the affected segments for the duration, and let loads already running finish before you start. Every load that read the row before an erasure that found the id began is refused (`superseded`) and never publishes what its source held, whether by this erasure's own [write to the row](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#ledger-loses-what-it-deletes) or by an earlier write that changed the row's `pointerId` or its ledger since the erasure read it (a retention or residency write does not count); a load that starts during the erasure is not covered. A writer that lands *during* the rewrite is caught and
|
|
106
108
|
the entry says `erased: false, note: 'superseded'` — because the publish was refused **by that fence**, because
|
|
107
109
|
a racing **erasure** (which collects with `keep: 0`) deleted the generation the rewrite was still streaming, or
|
|
108
110
|
because an operator's `rollback` moved the pointer while the call was deleting generations above it. When two
|
|
@@ -131,7 +133,7 @@ same one, a generation still holding the id — can also come from the collect-o
|
|
|
131
133
|
only outside the current generation and nothing was published; there the pointer has not moved, and a re-run
|
|
132
134
|
simply repeats the attempt.) **Read what the re-run says.** Usually it reports `erased: true` against the
|
|
133
135
|
generation it found the id in, which is the receipt the failed call could not give you. If a racing collector took
|
|
134
|
-
that generation first it reports nothing for the segment: the bit is gone, but no run holds a receipt for it, so
|
|
136
|
+
that generation first, or this call's own delete of it landed after a fault the call could not settle, it reports nothing for the segment: the bit is gone, but no run holds a receipt for it, so
|
|
135
137
|
keep the failed call's error alongside your ledger. And if the segment's registry row has since been purged, it is
|
|
136
138
|
not a segment any more and `eraseSubject` does not scan it at all — anything left in its bucket is an **orphan**,
|
|
137
139
|
which neither `store.checkConsistency()` (it visits registry rows only) nor the collection a load and the retention
|
|
@@ -229,7 +231,7 @@ registry row is a random holder id, a generation number and an instant, with no
|
|
|
229
231
|
|
|
230
232
|
**Your exit path** (and a building block for a **data-portability / Art. 20** response): `store.exportSegments(sink,
|
|
231
233
|
{ format })` (and the `export-segments` CLI) dumps the current generation of every registered segment that is not
|
|
232
|
-
destroyed (crypto-shredded or dropped) to a portable
|
|
234
|
+
destroyed (crypto-shredded, destroyed in the clear or dropped) to a portable
|
|
233
235
|
file — `roaring` (loadable by any roaring library) or `ndjson` (newline ids) — readable without CloudBitmaps. A destroyed segment resolves no generation, so it is not exported: the manifest's `skipped` lists it, and `segments`, `failed` and `skipped` together account for every segment row the registry listed.
|
|
234
236
|
It's a **controller-side bulk dump** (all segments, opaque ids), not a per-subject deliverable — the per-subject
|
|
235
237
|
rights are `subjectReport` (Art. 15) / `eraseSubject` (Art. 17). Encrypted segments are decrypted transparently
|
|
@@ -360,7 +362,9 @@ So the two are complements, not alternatives: **`dropSegment` for "stop paying f
|
|
|
360
362
|
them included), its timestamps and its token. It holds no id, and on an encrypted segment no wrapped key, since the
|
|
361
363
|
drop shreds it. The due index holds a pointer to it whose name spells out the same namespace and name. Both stay
|
|
362
364
|
for `tombstoneGraceMs` (24 h by default) after the retirement, and for as long as the segment's storage cannot be
|
|
363
|
-
proven gone
|
|
365
|
+
proven gone, except a segment that held no generation, whose row the same pass deletes (with `purgeTombstones` on, the default; from the bucket where the
|
|
366
|
+
registry reports `conditionalDelete`; elsewhere as a deleted marker that keeps the same fields), so it never waits out
|
|
367
|
+
the grace. Then the sweep's purge removes the row, and the pointers it read to it, from the bucket, where the registry
|
|
364
368
|
reports `conditionalDelete`: the default for S3 when its client sends to an AWS S3 host,
|
|
365
369
|
for Azure Blob, the local filesystem and memory. **A pointer can outlive its row**, and with it the name in its key: a
|
|
366
370
|
purge that ran with another `tombstoneGraceMs` than the sweep that filed the pointer, a delete that landed and lost its
|
|
@@ -369,13 +373,15 @@ response, a removal the registry refused (`purgeFaults` counts it), or a pointer
|
|
|
369
373
|
it reads, and an unscoped fleet scan, one given no `namespace`, from every day. A deployment that scopes every sweep to a
|
|
370
374
|
namespace, or runs only index scans, keeps such a pointer until an unscoped fleet scan runs. Where the registry does not
|
|
371
375
|
report `conditionalDelete` (an S3 client that sends to an S3-compatible store or an emulator, or a GCS client, by default, or
|
|
372
|
-
`conditionalDelete: false`), the purge leaves a tombstone instead: every one of those fields, in the bucket, indefinitely, read by every full listing.
|
|
373
|
-
|
|
376
|
+
`conditionalDelete: false`), the purge leaves a tombstone instead: every one of those fields, in the bucket, indefinitely, read by every full listing. Such a
|
|
377
|
+
registry files no pointer for a retirement's purge, but `setRetention` files its expiry-day pointer on every registry,
|
|
378
|
+
and removing it there (at the retirement, a `clearRetention`, or a moved expiry) leaves a deleted marker under the
|
|
379
|
+
reserved `cbm.due.` namespace whose key spells out the segment's namespace and name, also indefinitely. A removed
|
|
374
380
|
row stays recoverable wherever the storage keeps a copy of it: with object versioning on, its earlier versions stay until
|
|
375
381
|
a noncurrent-version rule expires them, as an overwritten row's do; with soft delete on, it stays for the retention
|
|
376
382
|
window (GCS: on by default for a new bucket, 7 days; Azure Blob: where enabled, for the days set), whatever the registry
|
|
377
|
-
did. A tombstone a hand-run `dropSegment
|
|
378
|
-
crypto-shred left is never purged by the library.
|
|
383
|
+
did. A [tombstone a hand-run `dropSegment`, a
|
|
384
|
+
crypto-shred or a cleartext destroy left is never purged by the library](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#tombstones-purged-narrowly).
|
|
379
385
|
|
|
380
386
|
Full detail, including the dated-bucket pattern and the pitfalls, is in the retention section of the
|
|
381
387
|
[retention guide](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/retention.md).
|
|
@@ -422,13 +428,13 @@ rewrite: `fromGeneration` → `generation`), `segment.collect` (a subject erasur
|
|
|
422
428
|
no reader resolves: the generations holding the id, when it found the id only outside the current generation, or, with
|
|
423
429
|
no `fromGeneration`, only objects no read of the segment can open, which it deletes whatever the id), `segment.erase`
|
|
424
430
|
(a genuine
|
|
425
|
-
crypto-shred), `segment.dispose` (a `dropSegment`, including every retirement the sweep performs) and
|
|
431
|
+
crypto-shred), `segment.tombstone` (a cleartext tombstone from `allowCleartext`, which deletes nothing), `segment.dispose` (a `dropSegment`, including every retirement the sweep performs) and
|
|
426
432
|
`namespace.erase` — for your audit log / SIEM. Each `segment.*` event names the segment's `incarnation` when its
|
|
427
433
|
registry token carries one, so a segment created again under the same name is told apart from the one before.
|
|
428
434
|
It is off by default and exception-safe. See the [dashboards guide](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/dashboards.md), which also says
|
|
429
435
|
which event is the receipt for which claim. An erasure *ledger* — per-subject-request proof of physical deletion
|
|
430
|
-
— is returned by **`eraseSubject`**. `subjectReport` is the read side and returns
|
|
431
|
-
in; it performs no deletion and issues no ledger.
|
|
436
|
+
— is returned by **`eraseSubject`**. `subjectReport` is the read side and returns which segments an id is
|
|
437
|
+
in, and which destroyed segments' leftover objects hold it; it performs no deletion and issues no ledger.
|
|
432
438
|
|
|
433
439
|
**Log hygiene:** `segment` / `namespace` are *your* strings and may encode sensitive purpose; IDs are personal
|
|
434
440
|
data. The library never logs bitmap contents or raw IDs, but **you** should treat segment names and IDs as PII
|
package/README.md
CHANGED
|
@@ -17,14 +17,9 @@ pnpm add @cloudbitmaps/s3 # the storage you have: or @cloudbit
|
|
|
17
17
|
pnpm add @cloudbitmaps/tools # optional: price a store, offline
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Install the same version of every `@cloudbitmaps/*` package: they release together, and each pins the exact version of `core` it was built with.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
- **pnpm 11 and later:** put `allowBuilds: { roaring: true }` in a `pnpm-workspace.yaml` beside your `package.json`. Those versions no longer read the `package.json` `pnpm` key. Without it the install exits 1 with `ERR_PNPM_IGNORED_BUILDS`.
|
|
24
|
-
- **pnpm 10:** add `{ "pnpm": { "onlyBuiltDependencies": ["roaring"] } }` to your `package.json`. Without it the install exits 0 and the package throws at `import`.
|
|
25
|
-
- **pnpm 9:** nothing extra.
|
|
26
|
-
|
|
27
|
-
[Details](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#cannot-find-module-buildreleaseroaringnode-after-a-successful-install).
|
|
22
|
+
The store's Roaring is written in TypeScript, with no native code: [what that means](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/roaring-codec.md).
|
|
28
23
|
|
|
29
24
|
## Use
|
|
30
25
|
|
|
@@ -84,8 +79,8 @@ name.
|
|
|
84
79
|
|
|
85
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
|
|
86
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
|
|
87
|
-
bytes than it needs, which cross-region can cost more than the requests it saves. A segment's first `store.load()` made
|
|
88
|
-
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 `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
|
|
82
|
+
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
|
+
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
|
|
89
84
|
pointer read: it reads no payload and no object.
|
|
90
85
|
|
|
91
86
|
The trade is stated plainly. A membership check that misses the cache costs a ranged GET against object storage,
|
|
@@ -96,9 +91,16 @@ give the method and what the numbers do not establish. To price your own store,
|
|
|
96
91
|
|
|
97
92
|
## Before production
|
|
98
93
|
|
|
99
|
-
-
|
|
94
|
+
- Run `await store.preflight()` once before the first load. It reads the registry, writes, tries to write again, reads, lists and
|
|
95
|
+
deletes one probe object and checks the backend's lifecycle rules, and answers `{ ok, checks }`; see [`store.preflight()`](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#check-the-setup-with-storepreflight).
|
|
96
|
+
It does not check `s3:AbortMultipartUpload`, the registry's `If-Match`, or a read after a delete, among others: [the full list](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#what-preflight-does-not-check).
|
|
97
|
+
- Grant the permissions the library issues and add a lifecycle rule to abort incomplete uploads. The S3 and GCS
|
|
98
|
+
backends' `checkLifecycle()` read the bucket's rules and say whether one would delete or archive what the store
|
|
99
|
+
keeps; Azure Blob's answers `unchecked` and says how to check by hand; `MemoryStorage` and `LocalFsStorage` have no
|
|
100
|
+
rules and answer `ok`.
|
|
100
101
|
- Use a bucket that honors conditional writes and is strongly consistent (a read or a listing made after a write or a
|
|
101
|
-
delete sees it), and `@aws-sdk/client-s3` 3.700.0 or later if you pass your own S3 client.
|
|
102
|
+
delete sees it), and `@aws-sdk/client-s3` 3.700.0 or later if you pass your own S3 client. Preflight reports a second write that succeeds (which is how an SDK of 3.640.0 or older shows) and a read or listing
|
|
103
|
+
that lags a write, but not an SDK from 3.641.0 to 3.699.0, which keeps `If-None-Match` but drops `If-Match`: the SDK floor stays yours to check.
|
|
102
104
|
- Set a request timeout on your storage client. Reads retry by themselves, and a read can be timed on every backend (`readTimeoutMs`); a write that fails transiently is yours to re-run.
|
|
103
105
|
- Back up the registry with the data, and the keystore too if you encrypt. If you lose the key, the data is gone.
|
|
104
106
|
- Schedule your loads, and `retireExpired` if you use retention. Run loads off the request path.
|
|
@@ -124,6 +126,21 @@ says what else differs.
|
|
|
124
126
|
- [Changelog](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/CHANGELOG.md)
|
|
125
127
|
- [Privacy and shared responsibility](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/PRIVACY.md)
|
|
126
128
|
|
|
129
|
+
## Credits
|
|
130
|
+
|
|
131
|
+
The Roaring Bitmap format and its algorithms are the work of Daniel Lemire and colleagues. See
|
|
132
|
+
[roaringbitmap.org](https://roaringbitmap.org/) and the [Roaring format specification](https://github.com/RoaringBitmap/RoaringFormatSpec),
|
|
133
|
+
which defines the portable bytes every chunk of a `.crbm` object holds. The store's codec implements that format and those
|
|
134
|
+
algorithms in TypeScript ([how it is held to CRoaring](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/roaring-codec.md#the-guarantee-the-same-bytes)); CRoaring, through the `roaring` binding, is the reference its tests compare it against. The papers:
|
|
135
|
+
|
|
136
|
+
- S. Chambi, D. Lemire, O. Kaser, R. Godin, "Better bitmap performance with Roaring bitmaps", *Software: Practice and
|
|
137
|
+
Experience* 46(5), 2016. [arXiv:1402.6407](https://arxiv.org/abs/1402.6407)
|
|
138
|
+
- D. Lemire, G. Ssi-Yan-Kai, O. Kaser, "Consistently faster and smaller compressed bitmaps with Roaring", *Software:
|
|
139
|
+
Practice and Experience* 46(11), 2016. [arXiv:1603.06549](https://arxiv.org/abs/1603.06549)
|
|
140
|
+
- D. Lemire, O. Kaser, N. Kurz, L. Deri, C. O'Hara, F. Saint-Jacques, G. Ssi-Yan-Kai, "Roaring Bitmaps: Implementation
|
|
141
|
+
of an Optimized Software Library", *Software: Practice and Experience* 48(4), 2018.
|
|
142
|
+
[arXiv:1709.07821](https://arxiv.org/abs/1709.07821)
|
|
143
|
+
|
|
127
144
|
## License
|
|
128
145
|
|
|
129
146
|
Apache-2.0
|