@cloudbitmaps/roaring 0.11.1 → 0.12.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.
@@ -1,3 +1,4 @@
1
+ import type { EncodedChunk } from '@cloudbitmaps/core';
1
2
  /** A container of sorted `u16` values. */
2
3
  export declare const ARRAY = 0;
3
4
  /** A container of 65,536 bits. */
@@ -27,6 +28,24 @@ export declare function parsePortableLayout(bytes: Uint8Array): PortableContaine
27
28
  * {@link parsePortableLayout}'s check alone, for a caller that hands the bytes to another decoder afterwards and
28
29
  * so has no use for the layout: it allocates nothing per container.
29
30
  *
31
+ * @returns where the bitmap ends: the offset one past its last container's last byte (0 for an empty buffer). A
32
+ * caller that needs the buffer to be exactly one bitmap compares it with the buffer's length.
30
33
  * @throws {IntegrityError} exactly where {@link parsePortableLayout} does.
31
34
  */
32
- export declare function checkPortableLayout(bytes: Uint8Array): void;
35
+ export declare function checkPortableLayout(bytes: Uint8Array): number;
36
+ /**
37
+ * Each container of the portable bitmap in `bytes`, as the one-container portable bitmap a `.crbm` chunk stores:
38
+ * its key moved to 0, its cardinality and kind kept, its body copied unchanged. These are exactly the bytes the
39
+ * native serializer writes for a bitmap holding only that container's low 16 bits, so a chunk cut from a whole
40
+ * bitmap is the chunk built from its ids, provided the whole bitmap was encoded canonically first.
41
+ *
42
+ * Lazy, one container per step, and each container checked as it is copied, by the same rules as
43
+ * {@link checkPortableLayout} (keys in order, and each body holding what its header says), so a stored chunk is
44
+ * never one a reader refuses. The bytes are the native serializer's, of a bitmap decoded from bytes that passed the
45
+ * check, so on every honest input this finds nothing. It is what stands between a store and a bitmap decoded from a
46
+ * buffer another thread was still writing during the load's call: the check saw the bytes as they were, the decode
47
+ * as they became.
48
+ *
49
+ * @throws {IntegrityError} at the first container that runs past the buffer or does not hold what its header says.
50
+ */
51
+ export declare function containerPayloads(bytes: Uint8Array): Generator<EncodedChunk>;
@@ -1,4 +1,4 @@
1
- import type { CodecBitmap, CodecInterface } from '@cloudbitmaps/core';
1
+ import type { CodecBitmap, CodecInterface, EncodedChunk, LoadInput } from '@cloudbitmaps/core';
2
2
  export declare class SafeBitmap implements CodecBitmap {
3
3
  private readonly bitmap;
4
4
  private constructor();
@@ -7,14 +7,18 @@ export declare class SafeBitmap implements CodecBitmap {
7
7
  /**
8
8
  * Cap the size, check the structure, then deserialize with the **portable** format.
9
9
  * Throws `IntegrityError` if the input exceeds `maxBytes`, is not a well-formed portable bitmap, or fails to
10
- * decode — the native addon is never handed unbounded, malformed or unsafe-format input.
10
+ * decode — the native addon is never handed unbounded, malformed or unsafe-format input. With `whole`, bytes
11
+ * after the bitmap's last container are refused too; without it they are ignored, as the native decoder ignores
12
+ * them.
11
13
  *
12
14
  * The structural check is what makes the decoded bitmap's answers mean anything. The native deserializer
13
15
  * bounds its reads and checks nothing else, so bytes of the right length and the wrong shape (containers or
14
16
  * values out of order, overlapping runs, a cardinality that disagrees with the bits) decode into a bitmap
15
17
  * whose `maximum()`, `has()` and `size` are wrong, and some of those shapes crash the process when used.
16
18
  */
17
- static safeDeserialize(bytes: Uint8Array, maxBytes: number): SafeBitmap;
19
+ static safeDeserialize(bytes: Uint8Array, maxBytes: number, options?: {
20
+ readonly whole?: boolean;
21
+ }): SafeBitmap;
18
22
  serialize(): Uint8Array;
19
23
  /**
20
24
  * Convert containers to run-encoding where that is smaller. Representation only — membership is untouched, and
@@ -33,8 +37,23 @@ export declare class SafeBitmap implements CodecBitmap {
33
37
  *
34
38
  * Called only when writing an immutable objects generation — see {@link CodecBitmap.optimize} for why the read
35
39
  * path deliberately does not.
40
+ *
41
+ * CANONICAL, which `runOptimize()` alone is not. Where a container's run and array encodings are the same size
42
+ * (cardinality = 2 × runs + 1: three values in one run, five in two), CRoaring keeps whichever kind the
43
+ * container already has. A chunk built from ids starts as an array and stays one; the same set reached through
44
+ * `addRange`, an earlier `runOptimize()` or an operation over run containers is a run, and serializes to other
45
+ * bytes. Undoing run compression first starts every container where one built from ids starts, so the bytes
46
+ * depend on membership alone. On a bitmap built from ids there is nothing to undo.
36
47
  */
37
48
  optimize(): void;
49
+ /**
50
+ * This set as storage chunks, cut from its own containers: {@link optimize} and one `serialize` when called, then
51
+ * a lazy header walk that copies one container out per step (`containerPayloads`). Per container and per byte,
52
+ * never per id, and the payloads are the ones {@link SafeBitmap.fromValues} of each chunk's low 16 bits would
53
+ * serialize to. The walk trusts the bytes it cuts because the native serializer wrote them, from a bitmap that
54
+ * passed the structural check when it was decoded.
55
+ */
56
+ encodeChunks(): Iterable<EncodedChunk>;
38
57
  /**
39
58
  * Largest value, or `undefined` when empty. O(1) — roaring reads it off the last container, so the engine's
40
59
  * per-chunk range assertion costs one call per chunk rather than a walk per id. The last container holds the
@@ -60,6 +79,12 @@ export declare class SafeBitmap implements CodecBitmap {
60
79
  [Symbol.iterator](): IterableIterator<number>;
61
80
  toArray(): number[];
62
81
  }
82
+ /**
83
+ * A bare `RoaringBitmap32` of this package's own `roaring`, passed where ids go, loads as `{ bitmap }`: from its
84
+ * containers, through the same check as any bytes, never id by id. Any other input passes through unchanged, and a
85
+ * bitmap from another copy of `roaring` is loaded as the ids it iterates, which is correct and slower.
86
+ */
87
+ export declare function bitmapAsLoadInput(input: LoadInput): LoadInput;
63
88
  /**
64
89
  * The roaring {@link CodecInterface} — the flagship codec, delegating to {@link SafeBitmap}'s statics. This is
65
90
  * the default the `CloudRoaring` facade injects into the engine; `core/` itself never hard-references it once a
@@ -1,5 +1,5 @@
1
1
  import { SafeBitmap } from '../roaring-codec.js';
2
- import type { StorageChunkSource, IRegistryDriver, IStorageDriver } from '@cloudbitmaps/core';
2
+ import type { StorageChunkSource, IRegistryDriver, IStorageDriver, SegmentRef } from '@cloudbitmaps/core';
3
3
  /**
4
4
  * The segment every conformance seeder must write. Exported so a driver's own test file derives the name
5
5
  * instead of restating it — a second copy that drifts makes the suite query a segment nobody seeded, which
@@ -15,11 +15,12 @@ export declare function storageChunkSourceConformance(label: string, makeSource:
15
15
  bitmap: SafeBitmap;
16
16
  }>) => Promise<StorageChunkSource>): void;
17
17
  /** The storage-driver cases {@link storageDriverConformance} runs; an emulator that cannot model one skips it by name. */
18
- export type StorageDriverCase = 'write-once round trip' | 'collision' | 'collision on a large upload' | 'missing object' | 'out-of-range read' | 'tail size' | 'idempotent delete' | 'list read-after-delete';
18
+ export type StorageDriverCase = 'write-once round trip' | 'collision' | 'collision on a large upload' | 'missing object' | 'out-of-range read' | 'tail size' | 'idempotent delete' | 'delete of an absent key beside its neighbours' | 'list read-after-delete';
19
19
  /**
20
20
  * Contract tests for an {@link IStorageDriver} — the contract its doc comment lists: write-once with
21
21
  * `WriteConflictError` on a collision, `NotFoundError` for a missing object, `ValidationError` for an out-of-range
22
- * read, `getTail`'s true total size, idempotent `delete`, and a `list` that is read-after-delete. `makeDriver` MUST
22
+ * read, `getTail`'s true total size, idempotent `delete` (of an absent key too, with neighbours present), and a `list`
23
+ * that is read-after-delete. `makeDriver` MUST
23
24
  * return a driver over an empty, isolated keyspace on each call.
24
25
  *
25
26
  * `skip` names the cases a backend cannot exercise (a local emulator that does not model a contract), so each
@@ -52,3 +53,34 @@ export declare function registryConformance(label: string, makeDriver: () => IRe
52
53
  * writes were not fenced at all. These cases are what make the fence testable.
53
54
  */
54
55
  export declare function registryConcurrency(label: string, makeDrivers: () => readonly [IRegistryDriver, IRegistryDriver]): void;
56
+ /**
57
+ * What {@link registryDeleteConformance} needs to see past the port: the driver, and the backend behind it.
58
+ *
59
+ * `stored` says whether the backend still holds anything for a row, a tombstone included. Through the port a deleted
60
+ * row and a tombstoned one look the same (`get` answers `null`, `list` skips both); what tells them apart is whether
61
+ * a later full listing still has an object to read, which is the cost a hard delete exists to remove.
62
+ */
63
+ export interface RegistryDeleteHarness {
64
+ readonly driver: IRegistryDriver;
65
+ /** Whether the backend holds anything for `ref`: a live row, or the tombstone a delete left in its place. */
66
+ stored(ref: SegmentRef): Promise<boolean>;
67
+ /**
68
+ * Put `text` at `ref`'s row, as another writer left it: here, a row as a release before 0.12 wrote it. Omitted by
69
+ * a registry that persists nothing a test can plant (the in-memory one).
70
+ */
71
+ plantRow?(ref: SegmentRef, text: string): Promise<void>;
72
+ }
73
+ /**
74
+ * Contract tests for what `delete` leaves behind, which differs by driver and is declared by
75
+ * `capabilities().conditionalDelete`:
76
+ *
77
+ * - **`true`**: a delete removes a row whose token carries an incarnation id from the backend for good, and does so
78
+ * only while the row is still the version it read. A row a release before 0.12 wrote has a bare decimal token and
79
+ * is tombstoned, never removed: a process still on that release, re-creating the name over nothing, would issue
80
+ * those same counters again from 0.
81
+ * - **`false` or absent**: every delete leaves a tombstone, which a later full listing still reads.
82
+ *
83
+ * Either way `get` answers `null` afterwards, `list` skips the row, and a name created again is a new incarnation that
84
+ * refuses every token an earlier one held.
85
+ */
86
+ export declare function registryDeleteConformance(label: string, make: () => RegistryDeleteHarness): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbitmaps/roaring",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Distributed, cloud-native Roaring Bitmaps — query and intersect billion-scale integer sets straight out of object storage, from a bounded RAM cache over immutable objects in your own bucket, at a fraction of an always-on cache.",
5
5
  "keywords": [
6
6
  "roaring",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "roaring": "2.7.0",
68
- "@cloudbitmaps/core": "^0.11.1"
68
+ "@cloudbitmaps/core": "^0.12.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^22.0.0",