@cloudbitmaps/core 0.21.0 → 0.22.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.
Files changed (72) hide show
  1. package/README.md +16 -5
  2. package/dist/{chunk-IGLIZBR7.js → chunk-OSU7W2Y3.js} +74 -30
  3. package/dist/chunk-OSU7W2Y3.js.map +7 -0
  4. package/dist/core/bit-route.d.ts +7 -0
  5. package/dist/core/codec.d.ts +1 -1
  6. package/dist/core/combine-expr.d.ts +9 -5
  7. package/dist/core/combine-feed.d.ts +11 -3
  8. package/dist/core/combine-many.d.ts +17 -13
  9. package/dist/core/determinism.d.ts +2 -1
  10. package/dist/core/erase-id.d.ts +18 -8
  11. package/dist/core/erasure.d.ts +1 -1
  12. package/dist/core/errors.d.ts +53 -9
  13. package/dist/core/load.d.ts +48 -14
  14. package/dist/core/metrics.d.ts +1 -1
  15. package/dist/core/ports.d.ts +7 -6
  16. package/dist/core/registry-scan.d.ts +0 -5
  17. package/dist/core/retention-sweep.d.ts +1 -1
  18. package/dist/core/retention.d.ts +1 -1
  19. package/dist/core/retry.d.ts +4 -0
  20. package/dist/core/rollback.d.ts +1 -0
  21. package/dist/driver-kit.d.ts +7 -2
  22. package/dist/driver-kit.js +5 -10
  23. package/dist/driver-kit.js.map +1 -1
  24. package/dist/drivers/backends.d.ts +2 -0
  25. package/dist/drivers/crypto.d.ts +1 -1
  26. package/dist/export/index.d.ts +9 -8
  27. package/dist/index.d.ts +11 -10
  28. package/dist/index.js +766 -634
  29. package/dist/index.js.map +4 -4
  30. package/package.json +7 -2
  31. package/dist/chunk-IGLIZBR7.js.map +0 -7
  32. package/dist/core/absent-operands.d.ts +0 -8
  33. package/dist/core/bulk-load.d.ts +0 -148
  34. package/dist/core/cas-retry.d.ts +0 -27
  35. package/dist/core/check.d.ts +0 -22
  36. package/dist/core/chunk-checks.d.ts +0 -39
  37. package/dist/core/chunk-stream.d.ts +0 -22
  38. package/dist/core/chunk-window.d.ts +0 -37
  39. package/dist/core/combine-ledger.d.ts +0 -43
  40. package/dist/core/crbm/crc32c.d.ts +0 -10
  41. package/dist/core/crbm/fingerprint.d.ts +0 -23
  42. package/dist/core/crbm/format.d.ts +0 -150
  43. package/dist/core/crbm/frame.d.ts +0 -10
  44. package/dist/core/crbm/plan-reads.d.ts +0 -50
  45. package/dist/core/crbm/varint.d.ts +0 -14
  46. package/dist/core/crbm/writer.d.ts +0 -72
  47. package/dist/core/crbm-open.d.ts +0 -154
  48. package/dist/core/crbm-write.d.ts +0 -75
  49. package/dist/core/due-index.d.ts +0 -53
  50. package/dist/core/generation-gc.d.ts +0 -293
  51. package/dist/core/generation-list.d.ts +0 -11
  52. package/dist/core/id-window.d.ts +0 -46
  53. package/dist/core/invalidation-scope.d.ts +0 -77
  54. package/dist/core/item-pull.d.ts +0 -29
  55. package/dist/core/known-generations-shape.d.ts +0 -13
  56. package/dist/core/known-generations.d.ts +0 -147
  57. package/dist/core/ledger-seed.d.ts +0 -17
  58. package/dist/core/metadata.d.ts +0 -42
  59. package/dist/core/order.d.ts +0 -2
  60. package/dist/core/publish.d.ts +0 -207
  61. package/dist/core/reader-defaults.d.ts +0 -29
  62. package/dist/core/summary.d.ts +0 -110
  63. package/dist/core/token.d.ts +0 -41
  64. package/dist/drivers/_shared/entropy.d.ts +0 -5
  65. package/dist/drivers/_shared/registry.d.ts +0 -107
  66. package/dist/drivers/localfs/exact-case.d.ts +0 -34
  67. package/dist/drivers/localfs/fs-util.d.ts +0 -55
  68. package/dist/drivers/localfs/paths.d.ts +0 -40
  69. package/dist/drivers/localfs/registry.d.ts +0 -50
  70. package/dist/drivers/localfs/storage.d.ts +0 -28
  71. package/dist/drivers/memory.d.ts +0 -64
  72. package/dist/testing/fuzz-core.d.ts +0 -15
@@ -1,3 +1,8 @@
1
+ /**
2
+ * BitRouteSplitter — maps a 32-bit id to its chunk (top 16 bits) and remainder
3
+ * (bottom 16 bits), per the data model.
4
+ */
5
+ import { ValidationError } from './errors.js';
1
6
  export declare const U32_MAX = 4294967295;
2
7
  export declare const CHUNK_COUNT = 65536;
3
8
  interface BitRoute {
@@ -6,6 +11,8 @@ interface BitRoute {
6
11
  /** Bottom 16 bits — the value within the chunk (0..65535). */
7
12
  readonly remainder: number;
8
13
  }
14
+ /** Was `err` thrown by {@link splitId} for an id outside the u32 range? */
15
+ export declare function isBadIdError(err: unknown): err is ValidationError;
9
16
  /** Split a u32 id into `{ chunkKey, remainder }`. Throws `ValidationError` for non-u32 input. */
10
17
  export declare function splitId(id: number): BitRoute;
11
18
  /**
@@ -103,7 +103,7 @@ export interface CodecBitmap {
103
103
  /**
104
104
  * This set as storage chunks: ascending by chunk key, empty chunks left out, each payload exactly the bytes that
105
105
  * {@link CodecInterface.fromValues} of that chunk's low 16 bits, then {@link optimize}, then {@link serialize}
106
- * would give. So a generation written from these is byte for byte the one the same ids write. It may re-encode
106
+ * would give. So a generation written from these holds chunk for chunk what the same ids write. It may re-encode
107
107
  * this bitmap in place first, as `optimize()` does; membership is untouched.
108
108
  *
109
109
  * **Flavor-author surface, and optional.** A load from a bitmap or from serialized bytes writes these, never
@@ -1,10 +1,14 @@
1
- /** A string names an operand; a node holds exactly one operator over a non-empty list of expressions. */
2
- export type CombineExpr = string | {
3
- and: CombineExpr[];
1
+ /**
2
+ * An expression over the operands of a batch combine (a store's `materializeMany`): a string names an operand; a node
3
+ * holds exactly one of `and`, `or` or `andNot` over a non-empty list of expressions. `andNot` subtracts every entry after
4
+ * the first from the first, so it takes at least two. Plain JSON, so it can be logged and sent between tasks.
5
+ */
6
+ export type Expr = string | {
7
+ and: Expr[];
4
8
  } | {
5
- or: CombineExpr[];
9
+ or: Expr[];
6
10
  } | {
7
- andNot: CombineExpr[];
11
+ andNot: Expr[];
8
12
  };
9
13
  /** The most operator nodes one expression may nest. A literal that contains itself is refused by it. */
10
14
  export declare const MAX_EXPR_DEPTH = 64;
@@ -1,6 +1,14 @@
1
1
  import type { CodecBitmap, CodecInterface } from './codec.js';
2
- /** One record of a feed: the ids each named operand holds at one chunk key. */
3
- export interface CombineManyFeedRecord {
2
+ /**
3
+ * One record of the feed of a batch combine (a store's `materializeMany`): the ids some of the fed operands hold at one
4
+ * chunk key. `key` is `id >>> 16`, an integer from 0 to 65,535, never below the previous record's; a key may arrive as
5
+ * several consecutive records, each carrying some of the operands, and one record per key is the usual shape. `operands`
6
+ * maps a declared fed name to its ids at the key: a real `Uint32Array` (not an `Int32Array`, `Buffer`, array or
7
+ * look-alike), strictly ascending, every id inside the key. A name is given at most once per key, and an empty array is
8
+ * the same as leaving the name out. The record is copied as it arrives, so the caller may drop or reuse its arrays once
9
+ * it has yielded it.
10
+ */
11
+ export interface MaterializeManyFeedRecord {
4
12
  /** The chunk key, `id >>> 16`, from 0 to 65,535. Non-decreasing over the feed; a key may arrive as several records. */
5
13
  readonly key: number;
6
14
  /** Ids by operand name: ascending, unique, every one inside `key`. An operand is named at most once per key. */
@@ -11,7 +19,7 @@ export interface CombineManyFeed {
11
19
  /** The fed operand names, declared up front. */
12
20
  readonly names: readonly string[];
13
21
  /** The records, read once, in key order. */
14
- readonly records: AsyncIterable<CombineManyFeedRecord>;
22
+ readonly records: AsyncIterable<MaterializeManyFeedRecord>;
15
23
  /**
16
24
  * The ids each fed operand holds over the whole feed, keyed by exactly `names`: an object, or a function called once
17
25
  * after the last record. A count that differs from what the feed held refuses every fed output.
@@ -1,14 +1,14 @@
1
1
  import type { CodecBitmap, CodecInterface } from './codec.js';
2
2
  import type { IMetricsSink } from './metrics.js';
3
3
  import type { CombineManyFeed } from './combine-feed.js';
4
- import type { CombineExpr } from './combine-expr.js';
4
+ import type { Expr } from './combine-expr.js';
5
5
  import type { Clock } from './determinism.js';
6
6
  import type { Budget } from './budget.js';
7
7
  import type { LoadGuard } from './load.js';
8
8
  import type { GenerationMetadata, SegmentRef, StorageChunkSource } from './ports.js';
9
- export type { CombineExpr } from './combine-expr.js';
9
+ export type { Expr } from './combine-expr.js';
10
10
  export { MAX_EXPR_DEPTH, MAX_EXPR_NODES } from './combine-expr.js';
11
- export type { CombineManyFeed, CombineManyFeedRecord } from './combine-feed.js';
11
+ export type { CombineManyFeed, MaterializeManyFeedRecord } from './combine-feed.js';
12
12
  /** The range requests a pass keeps in flight at once across all its operands, inside a client's socket pool. */
13
13
  export declare const MAX_RANGES_IN_FLIGHT = 64;
14
14
  /** One named stored operand, as the pass reads it. */
@@ -48,9 +48,9 @@ export interface CombineManyOperand {
48
48
  }
49
49
  /** What one output asks for. */
50
50
  export interface CombineManyOutput<R> {
51
- readonly expr: CombineExpr;
51
+ readonly expr: Expr;
52
52
  /** Subtracted from the result: a root `andNot`. */
53
- readonly exclude?: readonly CombineExpr[];
53
+ readonly exclude?: readonly Expr[];
54
54
  readonly allowEmpty?: boolean;
55
55
  readonly guard?: LoadGuard;
56
56
  readonly metadata?: GenerationMetadata;
@@ -126,8 +126,8 @@ export type CombineManyOutcome<R> = {
126
126
  readonly ok: false;
127
127
  readonly error: unknown;
128
128
  };
129
- /** What the call did to one operand. */
130
- export interface CombineManyOperandStats {
129
+ /** What a batch combine (a store's `materializeMany`) did to one operand: see {@link MaterializeManyStats.operands}. */
130
+ export interface MaterializeManyOperandStats {
131
131
  /** Whether an output of the call read it. An operand no output names is not read. */
132
132
  readonly read: boolean;
133
133
  /** Whether the operand was read at a pinned generation. */
@@ -155,8 +155,8 @@ export interface CombineManyOperandStats {
155
155
  /** Chunk reads made of it, over all groups. */
156
156
  readonly chunkReads: number;
157
157
  }
158
- /** What the call did to one output. */
159
- export interface CombineManyOutputStats {
158
+ /** What a batch combine (a store's `materializeMany`) did to one output: see {@link MaterializeManyStats.outputs}. */
159
+ export interface MaterializeManyOutputStats {
160
160
  /** The names of the operands its expression reads: the stored ones in call order, then the fed ones in `feed.names` order. */
161
161
  readonly operands: readonly string[];
162
162
  /** The group that computed it (from 0), or `null` when it never ran. */
@@ -171,7 +171,11 @@ export interface CombineManyOutputStats {
171
171
  /** The most its buffer held resident, in the ledger's own measure; compare with `boundBytes` to see how tight a plan was. */
172
172
  readonly peakBytes: number;
173
173
  }
174
- export interface CombineManyStats {
174
+ /**
175
+ * What a batch combine (a store's `materializeMany`) did: the groups it ran, its requests by class, the chunk-read budget
176
+ * it used, the most it held resident, and, for each operand, the generation it was pinned at, began at and ended at.
177
+ */
178
+ export interface MaterializeManyStats {
175
179
  /** Groups run, the re-runs of outputs a lying index made too big included. */
176
180
  readonly groups: number;
177
181
  readonly requests: {
@@ -233,12 +237,12 @@ export interface CombineManyStats {
233
237
  readonly ids: number;
234
238
  };
235
239
  /** By operand name, an object with no prototype, so any name is a key. */
236
- readonly operands: Readonly<Record<string, CombineManyOperandStats>>;
237
- readonly outputs: readonly CombineManyOutputStats[];
240
+ readonly operands: Readonly<Record<string, MaterializeManyOperandStats>>;
241
+ readonly outputs: readonly MaterializeManyOutputStats[];
238
242
  }
239
243
  export interface CombineManyRun<R> {
240
244
  readonly outputs: readonly CombineManyOutcome<R>[];
241
- readonly stats: CombineManyStats;
245
+ readonly stats: MaterializeManyStats;
242
246
  }
243
247
  /** A request that passed every check that needs no I/O: what {@link runCombineMany} runs. */
244
248
  export interface CompiledCombineMany<R> {
@@ -42,7 +42,8 @@ export interface Rng {
42
42
  }
43
43
  /**
44
44
  * Injected entropy: `length` cryptographically strong random bytes, for an identity that must never repeat (a
45
- * registry row's incarnation id, and the random part every registry write adds to its token). Deliberately not an
45
+ * registry row's incarnation id, the random part every registry write adds to its token, and a generation's write
46
+ * identity, the 12 bytes a cleartext object's footer carries). Deliberately not an
46
47
  * {@link Rng}: an `Rng` is seedable for simulation, and a seeded source hands every process the same "random"
47
48
  * identity. Production wiring uses the platform's Web Crypto; a test injects a deterministic source to replay a run.
48
49
  */
@@ -48,8 +48,8 @@
48
48
  * where the truth was to re-run.
49
49
  *
50
50
  * Which reason comes from **re-reading the row**, not from assuming a supersession: a moved pointer is
51
- * `'superseded'`, a tombstoned row `'destroyed'`, a purged row `'absent'`, a row with no pointer
52
- * `'no-generation'`. A pointer still on `from` means the object it names is genuinely gone — the forbidden
51
+ * `'superseded'`, a tombstoned row `'destroyed'`, a purged row `'absent'`, a row that lost its pointer
52
+ * `'superseded'` too (its bucket was not searched, so no claim about it is made; a re-run searches it). A pointer still on `from` means the object it names is genuinely gone — the forbidden
53
53
  * `missing-storage-generation` state — or is another object than the row's summary names by its fingerprint, and that
54
54
  * throws, because no re-run fixes either. That `NotFoundError` is the only signal of those states, so a faulting
55
55
  * re-read rethrows it rather than replacing it with a transient-looking registry error.
@@ -102,7 +102,7 @@
102
102
  import { type IAuditSink } from './audit.js';
103
103
  import type { CodecInterface } from './codec.js';
104
104
  import type { Yielder } from './cooperative.js';
105
- import type { Rng } from './determinism.js';
105
+ import type { Entropy, Rng } from './determinism.js';
106
106
  import type { IKeystore } from './crypto.js';
107
107
  import type { IStorageDriver, IRegistryDriver, SegmentRef } from './ports.js';
108
108
  import { type ReadRetry } from './retry.js';
@@ -148,6 +148,13 @@ export interface EraseIdDeps {
148
148
  * renewal. Absent, the read retry's source is used if it has one, and otherwise each wait is its bound.
149
149
  */
150
150
  readonly rng?: Rng;
151
+ /**
152
+ * The source of the rewrite's identity: 12 random bytes its footer carries when the segment is cleartext (an encrypted
153
+ * rewrite carries its index's AEAD nonce), so the rewrite is not the same object as a load of the same ids at its
154
+ * number. Required: an erasure without one (a JavaScript caller can leave it out) is refused with
155
+ * {@link ValidationError} before any read, keystore call, delete or write, even when it would find nothing to rewrite.
156
+ */
157
+ readonly entropy: Entropy;
151
158
  /** Per-chunk decode ceiling (invariant 5); defaults to 1 MiB. The `.crbm` reader refuses an entry above its own `maxPayloadBytes` (1 MiB, plus 28 bytes when encrypted) at open, so raise that on the chunk source too. */
152
159
  readonly maxBitmapBytes?: number;
153
160
  /**
@@ -170,8 +177,9 @@ export interface EraseIdResult {
170
177
  readonly erased: boolean;
171
178
  /**
172
179
  * Why the id was not erased, when `erased` is false. `'absent'` (no registry row), `'destroyed'` (a crypto-shred
173
- * tombstone — already unreadable), `'no-generation'` (a row with no generation yet: its bucket can hold first loads'
174
- * objects, none of them found to hold the id, and `collected` names any this call deleted unsearched, see below),
180
+ * tombstone — already unreadable), `'no-generation'` (a row with no generation yet when the call read it, its first
181
+ * read: its bucket can hold first loads' objects, none of them found to hold the id, and `collected` names any this
182
+ * call deleted unsearched, see below),
175
183
  * `'not-member'` (no
176
184
  * generation in the bucket holds the id — the common case across a fleet scan — with the same note on `collected`),
177
185
  * or `'superseded'`.
@@ -188,10 +196,12 @@ export interface EraseIdResult {
188
196
  * another erasure of the *same* id, the re-run reports `'not-member'` because it is already gone. Either way the
189
197
  * re-run settles it, which is why it is the documented action for this reason and for no other.
190
198
  *
191
- * The first three reasons are also returned when the row changes *underneath* a call already in flight, not
199
+ * `'absent'` and `'destroyed'` are also returned when the row changes *underneath* a call already in flight, not
192
200
  * only when it is read up front — a concurrent `dropSegment` gives `'destroyed'`, once the tombstone's objects have
193
- * been searched as a fresh call searches them, and a retention sweep that purges the row gives `'absent'`. A caller
194
- * branching on `reason` never has to care which it was.
201
+ * been searched as a fresh call searches them, and a retention sweep that purges the row gives `'absent'`.
202
+ * `'no-generation'` is only the answer to a row read with no generation: a row that loses its pointer *after* the
203
+ * call found one (a purge and re-creation, or a hand-made write) is `'superseded'`, because the call found a holder
204
+ * in that bucket and stopped before deleting it, and a re-run searches it again.
195
205
  */
196
206
  readonly reason?: 'absent' | 'destroyed' | 'no-generation' | 'not-member' | 'superseded';
197
207
  /**
@@ -57,7 +57,7 @@ export declare function destroySegment(ref: SegmentRef, deps: EraseDeps, options
57
57
  * Returns a per-segment result (skips cleartext segments unless `allowCleartext`).
58
58
  *
59
59
  * **Bounded.** It lists the whole namespace before it destroys anything, and holds the listing resident, so the
60
- * listing is capped at `maxScanSegments` (default {@link DEFAULT_MAX_SCAN_SEGMENTS}, 250,000), the ceiling every
60
+ * listing is capped at `maxScanSegments` (default 250,000), the ceiling every
61
61
  * other fleet scan keeps. A namespace over it throws `BudgetExceededError` **before any segment is destroyed**,
62
62
  * never part-way through; raise `maxScanSegments` when the namespace really is that large and the memory is there.
63
63
  * A `maxScanSegments` that is not a safe whole number of at least 1 throws `ValidationError`, again before anything is destroyed.
@@ -15,14 +15,14 @@
15
15
  * does not match — and silently, because a `catch` that stops matching just falls through, defeating
16
16
  * transient retry and the publish path's conflict handling with no error of its own.
17
17
  *
18
- * `Symbol.for` is identity-stable across copies, bundles and realms, so classify errors with the exported
19
- * predicates below (never `instanceof`) anywhere an error may cross that boundary.
18
+ * `Symbol.for` is identity-stable across copies, bundles and realms, so classify errors with `errorKind`
19
+ * (never `instanceof`) anywhere an error may cross that boundary.
20
20
  */
21
21
  declare const ERROR_BRAND: unique symbol;
22
22
  declare const TRANSIENT_BRAND: unique symbol;
23
23
  /** Base class for every error CloudRoaring throws. */
24
24
  export declare class CloudRoaringError extends Error {
25
- /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
25
+ /** Cross-bundle brand — see {@link errorKind}. Non-enumerable-ish (symbol key ⇒ not in JSON). */
26
26
  readonly [ERROR_BRAND]: true;
27
27
  constructor(message: string);
28
28
  }
@@ -33,6 +33,8 @@ export declare class CloudRoaringError extends Error {
33
33
  */
34
34
  export declare class ValidationError extends CloudRoaringError {
35
35
  }
36
+ /** What a contention {@link WriteConflictError} ends with: the call is safe to run again. */
37
+ export declare const CONTENTION_NEXT_STEP = "; another writer changed the segment's row while this call ran. Re-run the call: it reads the row afresh";
36
38
  /** An OCC conditional write/delete lost the race — the row changed since it was read. */
37
39
  export declare class WriteConflictError extends CloudRoaringError {
38
40
  }
@@ -97,14 +99,46 @@ export declare class UnsupportedError extends CloudRoaringError {
97
99
  export declare class CapabilityError extends CloudRoaringError {
98
100
  }
99
101
  /**
100
- * An operation would exceed its per-op **denial-of-wallet budget** — too many backend requests for a single
101
- * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call — so it is refused **before** fanning out
102
- * (hard invariant 6). Default-on but generous (normal ops never hit it); tune it
103
- * per store (`budget`) or per op, or disable with `budget: false`. Deterministic (never retried): the op is too
104
- * big by policy, not by luck. Each request's bytes are separately capped (the safe-deserialize ceiling), so
105
- * bounding the request count transitively bounds bytes. Carries the projected count + the limit, never data.
102
+ * An operation would exceed one of its per-op **denial-of-wallet bounds** — too many backend requests
103
+ * (`budget.maxRequests`), too many bytes held at once by a combine (`maxBufferedBytes`), or too many registry rows
104
+ * in a scan (`maxScanSegments`) — so it is refused, before the fan-out where it can be known beforehand (hard
105
+ * invariant 6). Default-on but generous (normal ops never hit it); tune each per store or per op, or disable the
106
+ * request budget with `budget: false`. Deterministic (never retried): the op is too big by policy, not by luck.
107
+ * Each request's bytes are separately capped (the safe-deserialize ceiling), so bounding the request count
108
+ * transitively bounds bytes.
109
+ *
110
+ * Carries an option name and two numbers, and no data: `option` names the option to raise, `limit` is the bound in
111
+ * force, and `spent` is what the call had reached when it was refused. What `spent` measures depends on `option`:
112
+ *
113
+ * - `'budget.maxRequests'`: the requests the call needs, which raising the option to it lets through. For a plan, it
114
+ * is the planned reads (with the reads already taken, when a run plans again); an enumeration that
115
+ * stops at the bound reports `limit + 1`, a lower bound.
116
+ * - `'maxBufferedBytes'`: a **lower bound** on the bytes the call needs resident: what it held plus what it asked
117
+ * for at the moment it was refused. Raising the limit to `spent` can be refused again, further on, because the
118
+ * call had not finished growing. The one figure that lets an output through is the per-output refusal's, which
119
+ * the message gives as "raise maxBufferedBytes to at least N". Otherwise raise the limit with room to spare, or
120
+ * split the outputs across calls.
121
+ * - `'maxScanSegments'`: `limit + 1`, a lower bound: the scan stops at the first row past the bound (for the retention
122
+ * sweep's due-index scan, the first due-index pointer, unread, which may be stale), so it never learns the fleet's
123
+ * size.
124
+ *
125
+ * The message states the same limit.
106
126
  */
107
127
  export declare class BudgetExceededError extends CloudRoaringError {
128
+ /** The option to raise to let the call through. */
129
+ readonly option: 'budget.maxRequests' | 'maxBufferedBytes' | 'maxScanSegments';
130
+ /** The bound in force: requests, bytes or rows, by `option`. */
131
+ readonly limit: number;
132
+ /**
133
+ * What the call had reached when refused. By `option`: the requests the call needs (`'budget.maxRequests'`), a
134
+ * lower bound on the resident bytes it needs (`'maxBufferedBytes'`), or `limit + 1` (`'maxScanSegments'`).
135
+ */
136
+ readonly spent: number;
137
+ constructor(message: string, details: {
138
+ readonly option: 'budget.maxRequests' | 'maxBufferedBytes' | 'maxScanSegments';
139
+ readonly limit: number;
140
+ readonly spent: number;
141
+ });
108
142
  }
109
143
  /**
110
144
  * An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the
@@ -164,4 +198,14 @@ export declare function isCapabilityError(err: unknown): err is CapabilityError;
164
198
  export declare function isBudgetExceededError(err: unknown): err is BudgetExceededError;
165
199
  /** No key the keystore holds can unwrap an encrypted segment's data key. */
166
200
  export declare function isKeyUnavailableError(err: unknown): err is KeyUnavailableError;
201
+ /** The name of one of the library's error classes, as `errorKind` returns it. */
202
+ export type ErrorKind = 'CloudRoaringError' | 'ValidationError' | 'WriteConflictError' | 'IntegrityError' | 'NotFoundError' | 'UnsupportedError' | 'CapabilityError' | 'TransientError' | 'KeyUnavailableError' | 'BudgetExceededError' | 'LeaseExpiredError' | 'LeaseLimitError' | 'StaleOperandError';
203
+ /**
204
+ * Which of the library's error classes `err` is, or `undefined` when it is none of them. Works on every copy of
205
+ * the package, every bundle and every realm, where `instanceof` can silently stop matching: it reads the
206
+ * `Symbol.for` brands and the error's `name`. An application's own subclass of one class returns
207
+ * `'CloudRoaringError'`, except a subclass of `TransientError`, which returns `'TransientError'`; an error
208
+ * cloned out of a worker (the brand is gone) returns `undefined`.
209
+ */
210
+ export declare function errorKind(err: unknown): ErrorKind | undefined;
167
211
  export {};
@@ -1,6 +1,6 @@
1
1
  import { type IAuditSink } from './audit.js';
2
2
  import { type CodecBitmap, type CodecInterface } from './codec.js';
3
- import type { Clock, Rng } from './determinism.js';
3
+ import type { Clock, Entropy, Rng } from './determinism.js';
4
4
  import type { IKeystore } from './crypto.js';
5
5
  import { type LoadInput } from './load-input.js';
6
6
  import { type ReadRetry } from './retry.js';
@@ -18,6 +18,14 @@ export interface LoadDeps {
18
18
  * bound). Absent, the read retry's source is used if it has one, and otherwise each wait is its bound.
19
19
  */
20
20
  readonly rng?: Rng;
21
+ /**
22
+ * The source of the write's identity: 12 random bytes the new generation's footer carries (for a cleartext generation;
23
+ * an encrypted one carries its index's AEAD nonce), so two loads of the same ids at the same number are two objects
24
+ * and every check that a footer is "mine" names this write. Required: a load without one (a JavaScript caller can
25
+ * leave it out) is refused with {@link ValidationError} before any read, keystore call or write.
26
+ * `@cloudbitmaps/roaring` supplies Web Crypto's by default.
27
+ */
28
+ readonly entropy: Entropy;
21
29
  /**
22
30
  * The store's read retry, for the read the guard makes of the current generation: a transient fault there is run
23
31
  * again under it rather than failing the load. Absent, the read is made once. It does not govern the write: a
@@ -75,12 +83,20 @@ export interface LoadGuard {
75
83
  * `'outputs[3].'`).
76
84
  */
77
85
  export declare function checkedGuard(guard: LoadGuard | undefined, where: string): LoadGuard | undefined;
86
+ /**
87
+ * Options of `store.load` and of core's `loadSegment`: what a load may publish (`allowEmpty`, `guard`), what it keeps
88
+ * (`keep`) and what it records (`metadata`, `audit`).
89
+ */
78
90
  export interface LoadOptions {
79
91
  /**
80
92
  * Publish an empty generation over a non-empty one. Off by default: an empty result is far more often an
81
93
  * upstream failure than an intent, and the failure is indistinguishable from success once it lands.
82
94
  */
83
95
  readonly allowEmpty?: boolean;
96
+ /**
97
+ * Bounds a plausible result must meet, judged before the pointer moves; a load outside them is refused
98
+ * (`published: false`), not thrown. See {@link LoadGuard}.
99
+ */
84
100
  readonly guard?: LoadGuard;
85
101
  /**
86
102
  * Generations to keep below the new pointer as a grace window (default 1). A non-negative integer: anything
@@ -97,14 +113,17 @@ export interface LoadOptions {
97
113
  * load superseded comes first. After a rollback that is the one the rollback restored, and it stays in the window of
98
114
  * every load after it, ahead of the generations the rollback moved away from, for as many loads as `keep` says.
99
115
  *
100
- * The segment's row records the generations a load keeps, up to 64, so a load whose number one existence check found
101
- * free collects without listing: it deletes the generations its publish pushed out of the window, and lists the
102
- * segment's objects on every sixteenth generation to take whatever that pass leaves, such as an object a crashed load
103
- * left below the pointer. A `keep` above 64 collects by listing on every load, and so does the first load of a row with
104
- * no usable ledger, which starts one from the bucket; so does a load whose check found the current generation's object
105
- * gone: the guard's read of it, or, when the guard took the size from the row's summary, one zero-byte read made
106
- * before a `keep` of 1 or more takes a name. A `keep` at least the generation published collects nothing and asks
107
- * for nothing.
116
+ * The segment's row records the generations a load keeps, up to 127.
117
+ *
118
+ * With a `keep` of at most 64, a load whose number one existence check found free deletes by name the generations
119
+ * its publish pushed out of the window, and lists the segment's objects on every sixteenth generation to take
120
+ * whatever that pass leaves, such as an object a crashed load left below the pointer.
121
+ *
122
+ * A load lists instead on every load when `keep` is above 64, when the row has a pointer and no usable record (the
123
+ * load starts one from the bucket), when the row's record names more than 64 generations below the pointer that no
124
+ * lease holds (as after a `keep` above 64, or `materializeMany`), or when the guard's read, or the zero-byte read
125
+ * before a delete by name, finds the current generation's object missing. A publish that a read of the row settles
126
+ * after its last attempt lists too. A `keep` at least the generation published collects nothing and asks for nothing.
108
127
  */
109
128
  readonly keep?: number;
110
129
  /**
@@ -119,6 +138,7 @@ export interface LoadOptions {
119
138
  * over as it is. Sealed under the segment's key when the segment is encrypted.
120
139
  */
121
140
  readonly metadata?: GenerationMetadata;
141
+ /** Audit sink for this load: it receives `segment.publish`, or `segment.load-refused` when the load is refused. */
122
142
  readonly audit?: IAuditSink;
123
143
  }
124
144
  /** The bounds a guard can refuse a generation for: every {@link LoadRefusal} but a lost race. */
@@ -141,7 +161,7 @@ export interface LoadJudgement {
141
161
  * would, and an encrypted one with no keystore the `KeyUnavailableError`. What it says holds for now only: a load
142
162
  * made later is judged against the segment as it is then.
143
163
  */
144
- export declare function judgeLoad(ref: SegmentRef, cardinality: number, deps: LoadDeps, options?: Pick<LoadOptions, 'allowEmpty' | 'guard'>): Promise<LoadJudgement>;
164
+ export declare function judgeLoad(ref: SegmentRef, cardinality: number, deps: Omit<LoadDeps, 'entropy'>, options?: Pick<LoadOptions, 'allowEmpty' | 'guard'>): Promise<LoadJudgement>;
145
165
  /** Why a load did not become current. */
146
166
  export type LoadRefusal = 'empty' | 'min-cardinality' | 'min-retained' | 'max-growth'
147
167
  /**
@@ -152,10 +172,14 @@ export type LoadRefusal = 'empty' | 'min-cardinality' | 'min-retained' | 'max-gr
152
172
  * the bucket, still billed, until a later load's collection deletes it or the segment is dropped, because its
153
173
  * generation number may by then name another writer's object; until it goes, a rollback onto it needs `allowUnknown`, as onto any generation the row does not know. Once the row is
154
174
  * `destroyed` (dropped or crypto-shredded), the object is deleted; once the row is gone, so is an object whose footer
155
- * matches what this load wrote (a re-created segment's load of the same cleartext ids writes a match), and so is a
156
- * cleartext one under a row with key material.
175
+ * matches what this load wrote (another load's object of the same ids is not a match: the footer carries each write's
176
+ * own identity), and so is a cleartext one under a row with key material.
157
177
  */
158
178
  | 'superseded';
179
+ /**
180
+ * What a load did: the generation it wrote, whether that is now current (`published`, and `reason` when not), the
181
+ * object it wrote and what it collected.
182
+ */
159
183
  export interface LoadResult {
160
184
  /**
161
185
  * The generation written. Present even when refused: a refusal deletes that object while the segment's row is
@@ -169,10 +193,19 @@ export interface LoadResult {
169
193
  readonly published: boolean;
170
194
  /** Set only when `published` is false. */
171
195
  readonly reason?: LoadRefusal;
196
+ /**
197
+ * Bytes of the generation's object as stored, or 0 when another load took the generation number first and
198
+ * nothing was written.
199
+ */
172
200
  readonly size: number;
201
+ /**
202
+ * SHA-256 of the object as stored, or `''` when another load took the generation number first. The library neither
203
+ * keeps nor checks it; reads are checked with CRC32C.
204
+ */
173
205
  readonly sha256: string;
206
+ /** Non-empty chunks in the generation written (at most 65,536), or 0 when nothing was written. */
174
207
  readonly chunkCount: number;
175
- /** Distinct ids in the generation that was written, post-dedup. */
208
+ /** Distinct ids in the generation that was written, post-dedup, or 0 when nothing was written. */
176
209
  readonly cardinality: number;
177
210
  /**
178
211
  * What the segment held when the guard judged it — `null` when there was no current generation, or when no
@@ -197,7 +230,8 @@ export interface LoadResult {
197
230
  *
198
231
  * `input` is ids, or a whole bitmap as `{ serialized }` portable Roaring bytes or a `{ bitmap }` with
199
232
  * `serialize('portable')` ({@link LoadInput}). A bitmap input is checked, decoded and serialized before the first
200
- * request, and is written from its own chunks; the generation is byte for byte the one its ids would write.
233
+ * request, and is written from its own chunks; the generation holds chunk for chunk what its ids would write, and carries its own write
234
+ * identity in the footer like any other.
201
235
  *
202
236
  * Returns what was written and whether it became current. A refusal is **not** an exception: `published: false`
203
237
  * with a `reason` is a normal outcome a caller branches on, in the same shape as a successful load, because the
@@ -133,7 +133,7 @@ export type MetricEvent = {
133
133
  };
134
134
  /**
135
135
  * The sink you plug in. One method, synchronous, fire-and-forget. It must never throw back into the
136
- * caller — `CloudRoaring` wraps a user sink with {@link safeMetrics} so a buggy sink can't break I/O.
136
+ * caller — `CloudRoaring` wraps a user sink so that a buggy sink can't break I/O.
137
137
  */
138
138
  export interface IMetricsSink {
139
139
  onEvent(event: MetricEvent): void;
@@ -334,7 +334,9 @@ export interface IStorageDriver {
334
334
  /**
335
335
  * Stream a new immutable generation. The driver opens a destination, hands `write` a {@link BlobSink},
336
336
  * then atomically commits (and computes the content hash). Throws {@link WriteConflictError} if the key
337
- * already exists (write-once).
337
+ * already exists (write-once). Call `write` once: the object committed is the bytes that one call wrote, because
338
+ * the caller records the write identity of that call's object (its footer) for every later proof; a driver that
339
+ * runs `write` again after a failed attempt must commit only the last run's bytes.
338
340
  */
339
341
  putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
340
342
  size: number;
@@ -414,9 +416,8 @@ export interface ClearRegistrySummary {
414
416
  /** How many ids the generation holds: an integer from 0 to 2^32. */
415
417
  readonly cardinality: number;
416
418
  /**
417
- * The fingerprint of the generation's object, `<size>:<footer checksum>` in decimal, as `CrbmReader.fingerprint` and
418
- * a pin's `pinnedAt.fingerprint` spell it: the object's size in bytes, which `stat()` reports, and the CRC32C its
419
- * footer stores.
419
+ * The fingerprint of the generation's object, `<size>:<footer checksum>` in decimal, as a pin's `pinnedAt.fingerprint`
420
+ * spells it: the object's size in bytes, which `stat()` reports, and the CRC32C its footer stores.
420
421
  */
421
422
  readonly fingerprint: string;
422
423
  /**
@@ -670,8 +671,8 @@ declare const STORAGE_BACKEND_BRAND: unique symbol;
670
671
  * Where a store keeps everything: the generations, and the pointer saying which one is current.
671
672
  *
672
673
  * **Branded on purpose.** The two fields alone are not enough to qualify, and that is the whole point. This
673
- * shape is also {@link LoadDeps}, so before the brand any `{ storage, registry }`
674
- * object literal satisfied it — including one assembling halves from two *unrelated* stores, which the store
674
+ * same two fields are the heart of the deps core's standalone functions take, so without the brand any `{ storage, registry }`
675
+ * object literal would satisfy it — including one assembling halves from two *unrelated* stores, which the store
675
676
  * accepted and then answered **empty** for a segment that holds data, because it read a pointer from a place
676
677
  * nothing had ever been written. That is the exact silent-empty failure one-class-per-backend exists to
677
678
  * remove, and it was reachable in five lines.
@@ -15,11 +15,6 @@ export declare function drainRegistry(registry: IRegistryDriver, options: {
15
15
  namespace?: string;
16
16
  maxScanSegments: number;
17
17
  op: string;
18
- /**
19
- * How a caller of `op` raises the ceiling, completing "Narrow it with `namespace`, or …" in the refusal.
20
- * Defaults to raising `maxScanSegments`, which is right wherever `op` takes that option itself.
21
- */
22
- raise?: string;
23
18
  /**
24
19
  * Whether the caller could scope the scan down to a `namespace`. Defaults to true; `eraseNamespace` is already
25
20
  * one namespace, so it passes false and the refusal does not tell it to narrow what it cannot.
@@ -163,7 +163,7 @@ export interface RetireExpiredResult {
163
163
  *
164
164
  * A refused purge is not charged to `limit`, so retirements go on: a tombstone that cannot be purged does not hold
165
165
  * the segments behind it past their expiry. **Purging stops for the rest of the call after
166
- * {@link MAX_CONSECUTIVE_PURGE_FAULTS} (3) refused purges in a row**, and a purge that succeeds starts the count
166
+ * 3 refused purges in a row**, and a purge that succeeds starts the count
167
167
  * again. A blanket refusal therefore costs three attempts a call, and a refusal particular to one row (a blob with a
168
168
  * snapshot, an object under a legal hold) holds nothing behind it. The next call tries again. Check this field; a
169
169
  * ledger entry for each is `skipped`, but a caller that reads only `retired` sees none of it.
@@ -59,7 +59,7 @@ export declare function withoutSweepMark(meta: GovernanceMeta | undefined): Gove
59
59
  * The three-way answer exists so a fleet sweep can *report* a malformed policy instead of choosing between two
60
60
  * bad options: treating it as "no policy" hides an operator error on a segment someone believes is expiring, and
61
61
  * throwing aborts a ledger over one bad row. Nothing writes an invalid policy through
62
- * {@link setSegmentRetention} — hand-edited rows, an older/newer writer, or a restore are how it happens.
62
+ * `store.setRetention` — hand-edited rows, an older/newer writer, or a restore are how it happens.
63
63
  */
64
64
  export declare function readRetentionPolicy(meta: GovernanceMeta | undefined): RetentionPolicy | null | 'invalid';
65
65
  /** The stored policy for one segment, or `null` if it has none (and `'invalid'` for a malformed one). */
@@ -1,4 +1,8 @@
1
1
  import type { Clock, Rng } from './determinism.js';
2
+ /**
3
+ * How the store retries a transient read: attempts, exponential backoff with a cap, and jitter. By default 4 attempts,
4
+ * a 50 ms base delay, ×2, a 2 s cap and full jitter; the store's `retry` option overrides them a field at a time.
5
+ */
2
6
  export interface RetryPolicy {
3
7
  /** Total attempts including the first (so `maxAttempts: 4` = 1 try + 3 retries). Must be ≥ 1. */
4
8
  readonly maxAttempts: number;
@@ -51,6 +51,7 @@ export interface GenerationEntry {
51
51
  * carries the id count and metadata its row records, from the same read.
52
52
  */
53
53
  export declare function listGenerations(ref: SegmentRef, deps: GenerationListDeps): Promise<GenerationEntry[]>;
54
+ /** What a rollback moved: the generation the pointer named before, and the one it names now. */
54
55
  export interface RollbackResult {
55
56
  /** The generation the pointer named before the rollback. `null` when the segment had no current generation. */
56
57
  readonly fromGeneration: number | null;
@@ -1,7 +1,12 @@
1
1
  export type { GenKey, IRegistryDriver, IStorageDriver, SegmentRef, StorageBackend, StorageCaps, StorageDeleteOptions, TailRead, } from './core/ports.js';
2
2
  export { brandAsBackend, STORAGE_BACKEND } from './core/ports.js';
3
- export type { IMetricsSink, MetricEvent } from './core/metrics.js';
4
- export { IntegrityError, isIntegrityError, isNotFoundError, isTransientError, isValidationError, isWriteConflictError, NotFoundError, TransientError, ValidationError, WriteConflictError, } from './core/errors.js';
3
+ export type { ClearRegistrySummary, GenerationMetadata, GovernanceMeta, KnownGeneration, LeaseEntry, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, RegistryStatus, RegistrySummary, RegistryWriteOptions, SealedRegistrySummary, Token, } from './core/ports.js';
4
+ export type { ChunkRead, ChunkRef, GenerationSummary, ReadChunksOptions, SegmentSize, StorageChunkSource, } from './core/ports.js';
5
+ export type { Aead, AeadSealed, IKeystore, WrappedDek } from './core/crypto.js';
6
+ export type { Entropy } from './core/determinism.js';
7
+ export type { IMetricsSink, MetricEvent, MetricOpName } from './core/metrics.js';
8
+ export { CloudRoaringError, errorKind, IntegrityError, NotFoundError, TransientError, ValidationError, WriteConflictError, } from './core/errors.js';
9
+ export type { ErrorKind } from './core/errors.js';
5
10
  export { RESOLVED_FIELDS, pointerIdOf, renewPointer, renewsPointer } from './core/pointer-id.js';
6
11
  export { validateSegmentRef } from './core/validate.js';
7
12
  export type { BlobSink } from './core/blob.js';