@cloudbitmaps/roaring 0.21.0 → 0.23.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,16 +1,4 @@
1
- /**
2
- * SafeBitmap — the thin wrapper around the `roaring` (CRoaring) engine that owns the
3
- * untrusted-bytes boundary.
4
- *
5
- * All (de)serialization uses the **portable** format — the stable, bounds-checked one. The
6
- * `unsafe_frozen_*` formats are never used (they are explicitly documented as crash/attack
7
- * vectors). Every deserialize is preceded by a hard size cap and a structural check, because the native
8
- * portable deserializer bounds its reads and checks nothing else (see `portable/layout.ts`).
9
- */
10
- import roaring from 'roaring';
11
- import type { CodecBitmap, CodecInterface, EncodedChunk, LoadInput } from '@cloudbitmaps/core';
12
- declare const RoaringBitmap32: typeof roaring.RoaringBitmap32;
13
- type RoaringBitmap32 = InstanceType<typeof RoaringBitmap32>;
1
+ import type { CodecBitmap, CodecInterface, EncodedChunk, LoadInput, PortableBitmap } from '@cloudbitmaps/core';
14
2
  export declare class SafeBitmap implements CodecBitmap {
15
3
  private readonly bitmap;
16
4
  private constructor();
@@ -31,8 +19,6 @@ export declare class SafeBitmap implements CodecBitmap {
31
19
  static safeDeserialize(bytes: Uint8Array, maxBytes: number, options?: {
32
20
  readonly whole?: boolean;
33
21
  }): SafeBitmap;
34
- /** The `roaring` bitmap this wraps, for a caller that was handed it once and keeps no wrapper. */
35
- unwrap(): RoaringBitmap32;
36
22
  serialize(): Uint8Array;
37
23
  /**
38
24
  * Convert containers to run-encoding where that is smaller. Representation only — membership is untouched, and
@@ -96,9 +82,10 @@ export declare class SafeBitmap implements CodecBitmap {
96
82
  toUint32Array(): Uint32Array;
97
83
  }
98
84
  /**
99
- * A bare `RoaringBitmap32` of this package's own `roaring`, passed where ids go, loads as `{ bitmap }`: from its
100
- * containers, through the same check as any bytes, never id by id. Any other input passes through unchanged, and a
101
- * bitmap from another copy of `roaring` is loaded as the ids it iterates, which is correct and slower.
85
+ * A bare `RoaringBitmap32` of this package's own `roaring`, or a bitmap {@link deserializePortable} returned, passed
86
+ * where ids go, loads as `{ bitmap }`: from its containers, through the same check as any bytes, never id by id. Any
87
+ * other input passes through unchanged, and a bitmap from another copy of `roaring` is loaded as the ids it
88
+ * iterates, which is correct and slower.
102
89
  */
103
90
  export declare function bitmapAsLoadInput(input: LoadInput): LoadInput;
104
91
  /**
@@ -108,11 +95,25 @@ export declare function bitmapAsLoadInput(input: LoadInput): LoadInput;
108
95
  */
109
96
  export declare const roaringCodec: CodecInterface;
110
97
  /**
111
- * Decode portable Roaring bytes you hold into a `RoaringBitmap32`, through the check a `{ serialized }` load makes
112
- * first: a size cap, a structural check, one whole bitmap, and only then the native decoder.
98
+ * A decoded bitmap that can only be read: what {@link deserializePortable} returns. Its ids come out ascending when
99
+ * it is iterated, which is a promise. It is a `PortableBitmap`, so a load takes it as `{ bitmap }`, and the library
100
+ * keeps no reference to it. What {@link deserializePortable} returns has these members only: a call to any other
101
+ * throws `TypeError`, it has no `getSerializationSizeInBytes`, and `serialize` takes exactly `'portable'`. Its class
102
+ * is not part of the contract: do not cast it and do not test it with `instanceof`. Passed to a method of the `roaring`
103
+ * package from npm in place of a `RoaringBitmap32`, it answers wrongly: decode its bytes first. `JSON.stringify` of one gives `"{}"`; write
104
+ * `JSON.stringify([...bitmap])`. For the rest of a Roaring library's API, decode its portable bytes with that library.
105
+ */
106
+ export interface ReadonlyBitmap extends PortableBitmap, Iterable<number> {
107
+ /** How many ids it holds. */
108
+ readonly size: number;
109
+ /** Whether it holds `id`. */
110
+ has(id: number): boolean;
111
+ }
112
+ /**
113
+ * Decode portable Roaring bytes you hold into a {@link ReadonlyBitmap}, through the check a `{ serialized }` load
114
+ * makes first: a size cap, a structural check, one whole bitmap, and only then the native decoder.
113
115
  *
114
116
  * @param bytes a `Uint8Array`, which a Node `Buffer` is.
115
117
  * @throws {ValidationError} for bytes that are not exactly one well-formed portable bitmap, as a load refuses them.
116
118
  */
117
- export declare function deserializePortable(bytes: Uint8Array): RoaringBitmap32;
118
- export {};
119
+ export declare function deserializePortable(bytes: Uint8Array): ReadonlyBitmap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbitmaps/roaring",
3
- "version": "0.21.0",
3
+ "version": "0.23.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",
@@ -48,7 +48,8 @@
48
48
  ".": {
49
49
  "types": "./dist/index.d.ts",
50
50
  "default": "./dist/index.js"
51
- }
51
+ },
52
+ "./package.json": "./package.json"
52
53
  },
53
54
  "files": [
54
55
  "dist",
@@ -65,7 +66,7 @@
65
66
  },
66
67
  "dependencies": {
67
68
  "roaring": "2.7.0",
68
- "@cloudbitmaps/core": "^0.21.0"
69
+ "@cloudbitmaps/core": "0.23.0"
69
70
  },
70
71
  "devDependencies": {
71
72
  "@types/node": "^22.0.0",
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
- import type { ExportFormat, ExportManifest, ExportSink } from '../index.js';
3
- export interface ExportConfig {
4
- readonly root: string;
5
- readonly out: string;
6
- readonly format: ExportFormat;
7
- readonly namespace?: string;
8
- }
9
- /** Parse + validate config from an environment map. Throws a {@link ValidationError} on misconfiguration. */
10
- export declare function parseConfig(env: Record<string, string | undefined>): ExportConfig;
11
- /**
12
- * A filesystem {@link ExportSink}. Writes each segment to a **unique** `<segment><ext>.<uuid>.part` temp
13
- * (`O_EXCL` create, mode `0o600`) and atomically renames it into `<out>/<namespace>/<segment><ext>` on `close()`,
14
- * so a half-written file never masquerades as complete and concurrent exports to the same dir can't clobber one
15
- * another. Uses a `FileHandle` (not `createWriteStream`) so an I/O fault **rejects the write** rather than
16
- * emitting an unhandled `'error'` event that would crash the process, and loops on short writes so a partial
17
- * `write()` never truncates the output. Artifacts are **owner-only** (dir `0o700`, files `0o600`) — cleartext.
18
- *
19
- * A namespace named `manifest.json`, in any letter case, is refused with a `ValidationError`, so each of its segments
20
- * lands in `failed`: its directory would take the manifest's path. Aborting a segment also removes its namespace
21
- * directory when that leaves it empty.
22
- */
23
- export declare function fsSink(out: string): ExportSink;
24
- /** CLI entry: build a LocalFs store, export every enumerated segment, then write the manifest. */
25
- export declare function main(env?: Record<string, string | undefined>, now?: () => number): Promise<ExportManifest>;
@@ -1,46 +0,0 @@
1
- /**
2
- * The checks a stream makes of a lease: before its first pull, and each time it reads a chunk. Kept apart from the store
3
- * so each can be held on its own, and so a stream of another shape (a rank read, a plain iterable of ids) is wrapped by
4
- * the same function.
5
- *
6
- * ```
7
- * caller ── next() ──> guarded stream ── next() ──> engine's stream
8
- * │ check before the first pull
9
- * │ check again at each chunk (or each item)
10
- * └─ a throwing check ends the engine's stream, then rejects
11
- * return() / throw() ─────────────────────────────> engine's stream (ended / thrown into)
12
- * ```
13
- *
14
- * A guarded stream is its own iterator and single-use, as the engine's generator it wraps is: it answers `next`,
15
- * `return` and `throw`, and iterating it gives it back.
16
- */
17
- /** An async iterator that is also its own iterable, with `return` and `throw` always present, as a generator's are. */
18
- interface GuardedStream<T> extends AsyncIterableIterator<T> {
19
- return(value?: unknown): Promise<IteratorResult<T, unknown>>;
20
- throw(err: unknown): Promise<IteratorResult<T, unknown>>;
21
- }
22
- /** What an id stream is, as far as a guard is concerned: ids, and the same ids a chunk at a time. */
23
- interface IdStream extends AsyncIterable<number> {
24
- batches(): AsyncIterable<Uint32Array>;
25
- }
26
- /** An id stream that is guarded: the per-id stream is an iterator of its own, and `batches()` is guarded too. */
27
- type GuardedIds = GuardedStream<number> & {
28
- batches(): GuardedStream<Uint32Array>;
29
- };
30
- /**
31
- * A stream of ascending ids that checks a lease before it first pulls, and each time it reads a chunk after that: once
32
- * the engine has the next chunk and before an id of it is yielded, so nothing of a chunk fetched past the lease is
33
- * served. Ids ascend, so a chunk is entered when `id >>> 16` changes. The check before the first pull is what stops a
34
- * stream built while the lease was live, and pulled after it ended, ending empty without ever checking. A throw ends the
35
- * engine's reads. Used only when a handle in the call holds a lease: a call with none returns the engine's stream
36
- * untouched. It takes any async iterable of ids, so a stream with no `.batches()` (a rank read) is wrapped the same way.
37
- */
38
- export declare const guardIdIterable: (inner: AsyncIterable<number>, check: () => void) => GuardedStream<number>;
39
- /** {@link guardIdIterable} for an `IdStream`, whose `.batches()` is guarded too. */
40
- export declare const guardIds: (inner: IdStream, check: () => void) => GuardedIds;
41
- /**
42
- * A stream of chunks that checks a lease before the first pull and before each item is handed on: an item is one
43
- * chunk (a batch of ids, or a chunk an `*Into` writes).
44
- */
45
- export declare const guardItems: <T>(inner: AsyncIterable<T>, check: () => void) => GuardedStream<T>;
46
- export {};
@@ -1,37 +0,0 @@
1
- /**
2
- * A storage driver that charges every **generation open** to a per-op budget, for `eraseSubject`.
3
- *
4
- * The budget counts fan-out in units of one backend read. `eraseSubject`'s scan charges one unit per registered
5
- * segment, but a segment whose current generation lacks the id is searched anyway: every other generation in its
6
- * bucket is opened, one tail read each, and with the keep-everything default of the `*Into` verbs that is one per
7
- * generation the segment ever had. Those opens are the work the scan's own count leaves out. They are counted here,
8
- * at the storage port where they happen, so an open added to the erasure later is charged without anyone
9
- * remembering to charge it, and no option of the public erasure call had to grow.
10
- *
11
- * Two opens are never charged, because the segment's unit already pays for them: the generation the row named when
12
- * the scan listed it, which every erasure must read to decide membership, and an object this call wrote itself,
13
- * which it reads back to verify. A refusal here comes from a read. Most of an erasure's reads that can be refused come
14
- * before its deletes; two kinds come after one: the look for a holder left once the call has deleted, and the zero-byte
15
- * read of a generation the call deleted that the row then names. A refusal of either is thrown in place of the call's
16
- * answer, so the call reports the budget, not what it deleted.
17
- */
18
- import type { GenKey, IStorageDriver, SegmentRef, StorageDeleteOptions } from '@cloudbitmaps/core';
19
- export declare class OpenChargingStorage implements IStorageDriver {
20
- private readonly inner;
21
- private readonly charge;
22
- private readonly free;
23
- /**
24
- * @param inner the driver the erasure would otherwise use
25
- * @param current the generation the segment's row named when it was listed, or `null` when it named none
26
- * @param charge called once per charged open; it throws to refuse
27
- */
28
- constructor(inner: IStorageDriver, current: number | null, charge: () => void);
29
- capabilities(): ReturnType<IStorageDriver['capabilities']>;
30
- putImmutable(key: GenKey, write: Parameters<IStorageDriver['putImmutable']>[1]): ReturnType<IStorageDriver['putImmutable']>;
31
- getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
32
- /** One `getTail` is one open: a reader takes the footer and the index from it, and reads chunks with `getRange`. */
33
- getTail(key: GenKey, maxBytes: number): ReturnType<IStorageDriver['getTail']>;
34
- /** Handed through whole: the erasure's delete of a holder is conditioned on the object it searched. */
35
- delete(key: GenKey, options?: StorageDeleteOptions): Promise<void>;
36
- list(ref: SegmentRef): AsyncIterable<GenKey>;
37
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * The keys `new CloudRoaring(options)` takes: at the top level, and inside each group whose value is an object. The
3
- * constructor refuses any other key by name, because an option it ignored would do nothing and look as if it had,
4
- * and the gates that read the docs' samples and the harnesses' option bags hold them to the same table. A test
5
- * holds the table to the option interfaces, in both directions.
6
- *
7
- * NOT part of the public API. It is its own module, rather than an `export` from the barrel, so the constructor
8
- * and those gates read one copy.
9
- */
10
- export declare const OPTION_KEYS: {
11
- readonly top: readonly ["storage", "cache", "encryption", "retry", "metrics", "budget", "seams"];
12
- readonly cache: readonly ["maxChunks", "ttlMs", "genTtlMs", "readerMax", "readerMaxBytes"];
13
- readonly encryption: readonly ["keystore", "required"];
14
- readonly retry: readonly ["maxAttempts", "baseDelayMs", "maxDelayMs", "backoffFactor", "jitter", "onRetry"];
15
- readonly budget: readonly ["maxRequests"];
16
- readonly seams: readonly ["clock", "rng"];
17
- };
18
- /** The groups: the top-level keys whose value, when it is an object, has keys of its own. */
19
- export type OptionGroup = Exclude<keyof typeof OPTION_KEYS, 'top'>;
package/dist/options.d.ts DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * The shape checks every call that takes an options bag makes before it reads or writes anything: the bag is an
3
- * object, it names only keys the call takes, and each switch is a boolean. The per-call key tables stay with the
4
- * facade, because each is held to the options type it describes.
5
- */
6
- import { ValidationError } from '@cloudbitmaps/core';
7
- /** A key of `T` that `K` does not list: a key table missing one fails to compile. */
8
- type Unlisted<T, K extends readonly PropertyKey[]> = Exclude<keyof T, K[number]>;
9
- /** The keys of `T`, every one of them, as a table the options check below reads. */
10
- export declare const keysOf: <T>() => <const K extends readonly (keyof T & string)[]>(keys: K & ([Unlisted<T, K>] extends [never] ? unknown : never)) => readonly string[];
11
- /** How a value of the wrong kind is named in a message: by its kind, never by its content. */
12
- export declare const kindOf: (v: unknown) => string;
13
- /**
14
- * Why `options` cannot be a call's options, or `undefined` when it can. `undefined` and `null` read as none; anything
15
- * but an object is refused, and so is a key `known` does not list, unless its value is `undefined`, so a spread of
16
- * options keeps working; and each of `booleans` present must be a boolean, `null` included. A bag of the wrong shape, a misspelt key or
17
- * a switch given as a string would otherwise read as no option at all, and silently widen what the call does: a scope
18
- * passed as a string swept every namespace, and `dryRun: 'true'` deleted.
19
- */
20
- export declare function optionsError(options: unknown, op: string, known: readonly string[], booleans?: readonly string[]): ValidationError | undefined;
21
- /**
22
- * The keys of `options` a call takes, each read by name. A spread copies only own enumerable properties, so an option
23
- * held in a getter or inherited from a prototype, which TypeScript accepts and {@link optionsError} reads, would pass
24
- * the checks and then be dropped: a `dryRun` so held ran a real sweep, and a `namespace` exported every tenant. The
25
- * call then reads the copy only, so it runs with exactly what was checked.
26
- */
27
- export declare function picked<T extends object>(options: T | null | undefined, keys: readonly string[]): Partial<T>;
28
- /** {@link optionsError}, thrown: for a call that rejects, rather than one that returns a stream. */
29
- export declare function checkOptions(options: unknown, op: string, known: readonly string[], booleans?: readonly string[]): void;
30
- export {};
@@ -1,54 +0,0 @@
1
- /**
2
- * A decoded portable-roaring bitmap that can be queried but not modified.
3
- *
4
- * Construction checks every container once, in {@link parsePortableLayout}, which is the same check the native
5
- * path runs; after that a membership test reads one container rather than materializing the set.
6
- */
7
- declare class PortableRoaringReader {
8
- private readonly view;
9
- private readonly containers;
10
- private constructor();
11
- /**
12
- * Parse and check a portable-format bitmap.
13
- *
14
- * @throws {IntegrityError} if the bytes are not a well-formed portable bitmap: see {@link parsePortableLayout}.
15
- */
16
- static decode(bytes: Uint8Array): PortableRoaringReader;
17
- /**
18
- * Total cardinality.
19
- *
20
- * Served entirely from the descriptive header, which {@link parsePortableLayout} has checked against every
21
- * container's payload, so this is O(containers) and independent of how many values the bitmap holds.
22
- */
23
- count(): number;
24
- /** Is `value` (a `u32`) in the set? */
25
- has(value: number): boolean;
26
- /** Binary search the descriptive header for the container owning `key`. */
27
- private findContainer;
28
- /**
29
- * 1,024 little-endian `u64` words. Read as `u32` at `(low >> 5) * 4`: a LE `u64` is two LE `u32`s with the
30
- * low word first, so the 32-bit view lands on the right half without any 64-bit arithmetic.
31
- */
32
- private bitmapHas;
33
- /**
34
- * Sorted `u16` values — binary search.
35
- *
36
- * The `at === low` early return means the `at < low` below is only ever evaluated when `at !== low`, so
37
- * writing it `at <= low` would behave identically. That is recorded because mutation testing surfaced it as
38
- * a surviving mutant: it is an *equivalent* one, unkillable by any test rather than a coverage gap — three
39
- * non-equivalent mutations of these same lines (inverting the branches, never reporting a hit, dropping the
40
- * last element) are all caught. Do not "fix" the survivor, and do not add a test chasing it.
41
- */
42
- private arrayHas;
43
- /**
44
- * `u16` run count, then that many `(start, length)` `u16` pairs sorted by start.
45
- *
46
- * `length` is the number of values **after** `start`, so a run covers the inclusive range
47
- * `[start, start + length]` and a single-value run is stored with length 0. Getting this off by one is the
48
- * easiest mistake in the format and is why the differential test drives runs specifically.
49
- */
50
- private runHas;
51
- }
52
- /** Convenience wrapper over {@link PortableRoaringReader.decode}. */
53
- export declare function decodePortableRoaring(bytes: Uint8Array): PortableRoaringReader;
54
- export {};
@@ -1,52 +0,0 @@
1
- import type { EncodedChunk } from '@cloudbitmaps/core';
2
- /** A container of sorted `u16` values. */
3
- export declare const ARRAY = 0;
4
- /** A container of 65,536 bits. */
5
- export declare const BITMAP = 1;
6
- /** A container of `(start, length)` runs. */
7
- declare const RUN = 2;
8
- type ContainerKind = typeof ARRAY | typeof BITMAP | typeof RUN;
9
- export interface PortableContainer {
10
- /** High 16 bits of every value in this container. */
11
- readonly key: number;
12
- /** Number of values. Stored as `cardinality - 1` on the wire, normalized here. */
13
- readonly cardinality: number;
14
- readonly kind: ContainerKind;
15
- /** Absolute byte offset of this container's payload within the source buffer; a run container's run count. */
16
- readonly offset: number;
17
- }
18
- /**
19
- * Parse the portable-format bitmap in `bytes`, and check its structure.
20
- *
21
- * An empty buffer is the empty bitmap, as it is to the native addon, which returns one without reading a byte.
22
- *
23
- * @throws {IntegrityError} if the cookie is unrecognized, any field runs past the buffer, or the structure is not
24
- * one a writer produces (see the module header for the list).
25
- */
26
- export declare function parsePortableLayout(bytes: Uint8Array): PortableContainer[];
27
- /**
28
- * {@link parsePortableLayout}'s check alone, for a caller that hands the bytes to another decoder afterwards and
29
- * so has no use for the layout: it allocates nothing per container.
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.
33
- * @throws {IntegrityError} exactly where {@link parsePortableLayout} does.
34
- */
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>;
52
- export {};
@@ -1,3 +0,0 @@
1
- export declare const RESERVED_NAMESPACE_PREFIX = "cbm.due.";
2
- /** Throw `ValidationError` for a namespace in the reserved prefix; `undefined` and every look-alike pass. */
3
- export declare function refuseReservedNamespace(namespace: string | undefined): void;
@@ -1,39 +0,0 @@
1
- /**
2
- * The default real-time {@link Clock} — the production half of the determinism seam.
3
- *
4
- * It lives outside `core/`, so `Date.now()`, `setTimeout` and `setImmediate` are allowed here; that separation is
5
- * the whole point of the seam, and it is lint-enforced on the other side.
6
- *
7
- * It sits in its own module rather than in `index.ts` so the facade, the tests and anything else that needs a real
8
- * clock can import it without importing the whole barrel.
9
- */
10
- import type { Clock } from '@cloudbitmaps/core';
11
- /** Default real-time clock — lives outside `core/`, so `Date.now()` + timers are allowed here. */
12
- export declare class SystemClock implements Clock {
13
- now(): number;
14
- sleep(ms: number): Promise<void>;
15
- /**
16
- * `setImmediate`, and it has to be — this is the one place where the choice of primitive is the whole feature.
17
- *
18
- * A long CPU-bound loop in `core/` (bulk-load is the one that matters) periodically hands the loop back so a
19
- * co-resident HTTP server keeps answering. Measured on the **synthetic yield-primitive benchmark** — a
20
- * 61,035-iteration loop yielding every 1,024, isolating the primitive rather than timing a real load (the
21
- * end-to-end figures are 519 ms → 24 ms; see `cooperative.ts`, which lists all three experiments):
22
- *
23
- * ```text
24
- * no yield 568 ms wall 568.0 ms worst event-loop gap
25
- * sleep(0) 555 ms wall 555.2 ms ← a microtask. Yields NOTHING.
26
- * setImmediate 569 ms wall 13.8 ms ← free, and 41x less starvation
27
- * setTimeout(1) 625 ms wall 15.0 ms ← +10% wall for no extra relief
28
- * ```
29
- *
30
- * `sleep(0)` resolving on a microtask is correct for its own contract and useless for this: microtasks drain
31
- * before the loop ever advances a phase, so `await sleep(0)` in a tight loop never lets a single pending I/O
32
- * callback run. `setImmediate` fires in the check phase *after* pending I/O, which is exactly the ordering a
33
- * server wants — inbound requests are serviced before the loop resumes chewing.
34
- *
35
- * A timer is not ref'd here (unlike {@link sleep}): a yield expresses "someone else may go first", never
36
- * "there is outstanding work" — and `setImmediate` cannot outlive the turn that scheduled it anyway.
37
- */
38
- yieldNow(): Promise<void>;
39
- }
@@ -1,118 +0,0 @@
1
- import { SafeBitmap } from '../roaring-codec.js';
2
- import type { StorageChunkSource, IRegistryDriver, IStorageDriver, SegmentRef } from '@cloudbitmaps/core';
3
- /**
4
- * The segment every conformance seeder must write. Exported so a driver's own test file derives the name
5
- * instead of restating it — a second copy that drifts makes the suite query a segment nobody seeded, which
6
- * fails as an empty result rather than as the mismatch it actually is.
7
- */
8
- export declare const CONFORMANCE_SEGMENT = "s:v1";
9
- /**
10
- * Contract tests for a {@link StorageChunkSource}. `makeSource` MUST build a fresh source seeded with the
11
- * given chunks (each an immutable Storage bitmap) and nothing else.
12
- */
13
- export declare function storageChunkSourceConformance(label: string, makeSource: (chunks: Array<{
14
- chunkKey: number;
15
- bitmap: SafeBitmap;
16
- }>) => Promise<StorageChunkSource>): void;
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' | 'delete of an absent key beside its neighbours' | 'list read-after-delete' | 'names and namespaces kept apart' | 'generations by number' | 'failed write' | 'missing location' | 'list across pages' | 'conditional delete';
19
- /**
20
- * Contract tests for an {@link IStorageDriver} — the contract its doc comment lists: write-once with
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` (of an absent key too, with neighbours present) after which a
23
- * read of the key is `NotFoundError` at once, a `list` that is read-after-delete, and a `delete` given `ifVersion` that does what `conditionalDelete` says. `makeDriver`
24
- * MUST return a driver over an empty, isolated keyspace on each call.
25
- *
26
- * `skip` names the cases a backend cannot exercise (a local emulator that does not model a contract), so each
27
- * skip is visible at the call site, with the reason beside it, rather than hidden in a weakened assertion.
28
- */
29
- export declare function storageDriverConformance(label: string, makeDriver: () => IStorageDriver, options?: {
30
- readonly skip?: readonly StorageDriverCase[];
31
- /**
32
- * Size of the object the `collision on a large upload` case writes. Pass one above the driver's single-request
33
- * upload threshold to exercise its multipart or resumable path, where the write-once precondition is
34
- * sent on a different request than a small object's. Omitted, the case does not run.
35
- */
36
- readonly largeBytes?: number;
37
- /**
38
- * A driver over a bucket or container that does not exist, built fresh on each call. The `missing location` case
39
- * holds it to failing a range read, a tail read, a delete and a listing with an error that is neither
40
- * `NotFoundError` nor `TransientError`: read as an absence, a misnamed or deleted bucket answers every read of the
41
- * store as an empty segment. Omitted, the case does not run.
42
- */
43
- readonly missingLocation?: () => IStorageDriver;
44
- /**
45
- * How many generations the `list across pages` case writes and lists back. Pass more than one listing page of the
46
- * driver under test (S3 and GCS list 1,000 a page, Azure Blob 5,000, a test double what it is built with), so a
47
- * driver that drops its continuation token fails. Omitted, the case does not run.
48
- */
49
- readonly pagedListSize?: number;
50
- /**
51
- * What the driver under test reports as `capabilities().conditionalDelete` once it has made a delete given
52
- * `ifVersion`, which is when a driver that resolves it lazily has resolved it. Stated at the call site, so a driver
53
- * that stops applying the condition fails here rather than being exempted by its own report. Omitted, the case
54
- * holds the driver to whatever it reports.
55
- */
56
- readonly conditionalDelete?: boolean;
57
- }): void;
58
- /**
59
- * Contract tests for an {@link IRegistryDriver}. `makeDriver` MUST return a fresh, empty, isolated driver on
60
- * each call. The OCC contract (create / token-fenced CAS / ABA) plus the registry's record semantics (forward
61
- * currentGen, status, clearable keyId, discovery).
62
- */
63
- export declare function registryConformance(label: string, makeDriver: () => IRegistryDriver, options?: {
64
- /**
65
- * A driver over a bucket or container that does not exist, built fresh on each call. The `missing location` case
66
- * holds it to failing a read, a listing and a create with an error that is neither `NotFoundError` nor
67
- * `TransientError`, and never answering a read with `null`: read as an absent row, a misnamed or deleted bucket
68
- * answers every segment as one with no generation. Omitted, the case does not run.
69
- */
70
- readonly missingLocation?: () => IRegistryDriver;
71
- /**
72
- * How many rows the `list across pages` case creates and lists back. Pass more than one listing page of the driver
73
- * under test (S3 and GCS list 1,000 a page, Azure Blob 5,000, a test double what it is built with), so a driver
74
- * that drops its continuation token fails. Omitted, the case does not run.
75
- */
76
- readonly pagedListSize?: number;
77
- }): void;
78
- /**
79
- * Contract tests for an {@link IRegistryDriver} under **concurrent writers** — `makeDrivers` MUST return two
80
- * independent driver instances over the **same** backing store, as two processes would see it.
81
- *
82
- * This exists because {@link registryConformance} drives one driver sequentially, and `ObjectStoreRegistry`
83
- * checks the OCC token in memory before it ever issues a conditional write. That in-process check answers
84
- * every sequential case, so the store-level precondition — the only thing that fences writers *across*
85
- * processes, and the entire reason these drivers are built on conditional writes — is never load-bearing
86
- * there. A backend that accepted `If-Match` and ignored it passed the full suite; so did a GCS driver whose
87
- * writes were not fenced at all. These cases are what make the fence testable.
88
- */
89
- export declare function registryConcurrency(label: string, makeDrivers: () => readonly [IRegistryDriver, IRegistryDriver]): void;
90
- /**
91
- * What {@link registryDeleteConformance} needs to see past the port: the driver, and the backend behind it.
92
- *
93
- * `stored` says whether the backend still holds anything for a row, a tombstone included. Through the port a deleted
94
- * row and a tombstoned one look the same (`get` answers `null`, `list` skips both); what tells them apart is whether
95
- * a later full listing still has an object to read, which is the cost a hard delete exists to remove.
96
- */
97
- export interface RegistryDeleteHarness {
98
- readonly driver: IRegistryDriver;
99
- /** Whether the backend holds anything for `ref`: a live row, or the tombstone a delete left in its place. */
100
- stored(ref: SegmentRef): Promise<boolean>;
101
- /**
102
- * Put `text` at `ref`'s row, as another writer left it: here, a row stamped with an earlier schema. Omitted by a
103
- * registry that persists nothing a test can plant (the in-memory one).
104
- */
105
- plantRow?(ref: SegmentRef, text: string): Promise<void>;
106
- }
107
- /**
108
- * Contract tests for what `delete` leaves behind, which differs by driver and is declared by
109
- * `capabilities().conditionalDelete`:
110
- *
111
- * - **`true`**: a delete removes the row from the backend for good, and does so only while the row is still the
112
- * version it read.
113
- * - **`false` or absent**: every delete leaves a tombstone, which a later full listing still reads.
114
- *
115
- * Either way `get` answers `null` afterwards, `list` skips the row, and a name created again is a new incarnation that
116
- * refuses every token an earlier one held.
117
- */
118
- export declare function registryDeleteConformance(label: string, make: () => RegistryDeleteHarness): void;
@@ -1,23 +0,0 @@
1
- /**
2
- * Internal surface for the coverage-guided fuzz harness, CODEC half.
3
- *
4
- * The concrete bitmap codec, and the one property the harness holds every decode it accepts to. The core half
5
- * (`CrbmReader`, the non-public `parseIndex`, `BufferReader`, `CloudRoaringError`, `DEFAULT_MAX_PAYLOAD_BYTES`, `DEFAULT_MAX_BITMAP_BYTES`) is
6
- * built from `@cloudbitmaps/core`'s own `src/testing/fuzz-core.ts` to `fuzz/build/fuzz-core.js`. Splitting it this
7
- * way keeps the published entries of both packages free of test-only exports: neither widened its public API to
8
- * feed the fuzzer.
9
- */
10
- import { SafeBitmap } from '../roaring-codec.js';
11
- export { SafeBitmap };
12
- /**
13
- * What an accepted decode must be: it iterates strictly ascending, `size` is the number of values it iterates, and
14
- * `has` confirms what it iterates. Throws a plain `Error` otherwise, which the fuzz targets and the corpus replay
15
- * report as a finding.
16
- *
17
- * Memory safety alone does not see a structurally broken bitmap. Bytes with containers or values out of order, or
18
- * a cardinality that disagrees with the bits, decode into memory that behaves until an operation trusts one of the
19
- * broken invariants, so a contract of "no crash" passes them. This fails them. It is bounded like the
20
- * materialization it replaces, because a valid run-heavy bitmap is tiny on disk and can hold billions of ids, and it
21
- * asks `has` of the first 4,096 values only, which is enough to reach every container a small input can hold.
22
- */
23
- export declare function assertConsistentDecode(bm: SafeBitmap): void;
@@ -1,38 +0,0 @@
1
- import type { Clock, IMetricsSink, IKeystore, IRegistryDriver, IStorageDriver, RetryPolicy, RetryingOptions, Rng, StorageChunkSource } from '@cloudbitmaps/core';
2
- /** The slice of a store's options the wiring reads; `CloudRoaringOptions` satisfies it. */
3
- interface StoreWiring {
4
- readonly storage: unknown;
5
- readonly encryption?: {
6
- readonly keystore?: IKeystore;
7
- readonly required?: boolean;
8
- };
9
- readonly cache?: {
10
- readonly genTtlMs?: number;
11
- readonly readerMax?: number;
12
- readonly readerMaxBytes?: number;
13
- };
14
- readonly retry?: (Partial<RetryPolicy> & {
15
- readonly onRetry?: (info: {
16
- attempt: number;
17
- delayMs: number;
18
- err: unknown;
19
- }) => void;
20
- }) | false;
21
- }
22
- /** What {@link wireStore} builds for the constructor. */
23
- interface WiredStore {
24
- /** What the engine reads through: the source, wrapped in the read retry unless it is off. */
25
- readonly storage: StorageChunkSource;
26
- /** The resolved source before the retry wrapper. */
27
- readonly source: StorageChunkSource;
28
- readonly driver: IStorageDriver | undefined;
29
- readonly registry: IRegistryDriver | undefined;
30
- /** The read retry the store shares with its lifecycle reads; `undefined` when retry is off. */
31
- readonly retryOptions: RetryingOptions | undefined;
32
- }
33
- /**
34
- * Resolve the storage seam, hand a backend the metrics sink, and wrap the source in the read retry. Refusals come in
35
- * that order: a bad `storage` first, then a bad `retry`.
36
- */
37
- export declare function wireStore(options: StoreWiring, clock: Clock, rng: Rng, metrics: IMetricsSink): WiredStore;
38
- export {};