@cloudbitmaps/roaring 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRIVACY.md +139 -73
- package/README.md +80 -154
- package/dist/bin/export-segments.js +784 -320
- package/dist/bin/export-segments.js.map +4 -4
- package/dist/index.d.ts +357 -204
- package/dist/index.js +815 -294
- package/dist/index.js.map +4 -4
- package/dist/open-charging-storage.d.ts +34 -0
- package/dist/option-keys.d.ts +19 -0
- package/dist/portable/decode.d.ts +6 -7
- package/dist/portable/layout.d.ts +32 -0
- package/dist/reserved-namespace.d.ts +3 -0
- package/dist/roaring-codec.d.ts +13 -6
- package/dist/system-clock.d.ts +3 -4
- package/dist/testing/conformance.d.ts +21 -1
- package/dist/testing/fuzz-codec.d.ts +19 -5
- package/package.json +2 -3
- package/dist/codec-bound.d.ts +0 -38
- package/dist/moved-options.d.ts +0 -14
package/dist/index.d.ts
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* object store, behind one registry pointer per segment. You wire storage **once**, as a single config object:
|
|
6
6
|
* pass a **backend** as `storage` — `S3Storage`, `GcsStorage`, `AzureBlobStorage`, `LocalFsStorage` or
|
|
7
7
|
* `MemoryStorage` — and it carries both halves, the generations and the pointer, from one bucket and one
|
|
8
|
-
* prefix. Add
|
|
8
|
+
* prefix. Add `encryption: { keystore }` for encryption-at-rest / crypto-shred.
|
|
9
9
|
*
|
|
10
10
|
* Two narrower shapes are also accepted for `storage`: a bare {@link IStorageDriver}, which has no pointer and
|
|
11
11
|
* so resolves generations by list-scanning storage (**cleartext and read-only**), and an already-built
|
|
12
12
|
* {@link StorageChunkSource} for advanced reader options you configure yourself.
|
|
13
13
|
*
|
|
14
|
-
* **Data gets in by loading a generation**, never by mutating one: `
|
|
15
|
-
*
|
|
14
|
+
* **Data gets in by loading a generation**, never by mutating one: `store.load()` streams a set of ids into one
|
|
15
|
+
* immutable object and advances the segment's pointer to it. The pointer only moves forward, so a load out-raced by
|
|
16
|
+
* a newer generation reports `superseded` instead of landing, and so does one that read the row when the row has
|
|
17
|
+
* changed since. Every other write in the library is a load in
|
|
16
18
|
* disguise — `intersectInto`/`unionInto`/`andNotInto` write a new generation of their destination, and
|
|
17
19
|
* `eraseSubject` rewrites a generation without one id. Reads (`has`/`count`/`iterate`/`intersect`/`union`/`andNot`)
|
|
18
20
|
* see whole, checksum-verified generations and nothing else.
|
|
@@ -21,8 +23,7 @@
|
|
|
21
23
|
* `checkConsistency`, `exportSegments` — reuse the store's own drivers, so you never re-pass them (they need the
|
|
22
24
|
* store built with a backend). See the README and the getting-started guide.
|
|
23
25
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import type { BudgetOption, GenerationEntry, LoadGuard, LoadOptions, LoadRefusal, LoadResult, RollbackResult, Clock, StorageChunkSource, ConsistencyReport, CostReport, DropResult, EstimateInput, ExportManifest, ExportOptions, ExportSink, IAuditSink, IStorageDriver, IKeystore, IMetricsSink, StorageBackend, PricingProfile, RetentionPolicy, RetireExpiredOptions, RetireExpiredResult, RetryPolicy, Rng, SetRetentionResult, PinnedAt, SegmentRef, Workload } from '@cloudbitmaps/core';
|
|
26
|
+
import type { BudgetOption, GenerationEntry, LoadGuard, LoadOptions, LoadRefusal, LoadResult, RollbackResult, Clock, StorageChunkSource, ConsistencyReport, CostReport, DropResult, EstimateInput, ExportManifest, ExportOptions, ExportSink, IAuditSink, IStorageDriver, IKeystore, IMetricsSink, StorageBackend, PricingProfile, RetentionPolicy, RetireExpiredOptions, RetireExpiredResult, RetryPolicy, Rng, SetRetentionResult, PinnedAt, IdRange, SegmentRef, Workload } from '@cloudbitmaps/core';
|
|
26
27
|
import type { SegmentInfo } from '@cloudbitmaps/core';
|
|
27
28
|
/**
|
|
28
29
|
* Wiring for a {@link CloudRoaring} store. **Only `storage` is required** — the minimal call is
|
|
@@ -43,7 +44,7 @@ export interface CloudRoaringOptions {
|
|
|
43
44
|
* **Pass the backend itself, not `backend.storage`.** They differ by one property access and the mistake is
|
|
44
45
|
* silent: `storage: backend` is the store, while `storage: backend.storage` hands over the *driver half*
|
|
45
46
|
* alone, which constructs without complaint and gives you a cleartext, read-only store that fails later on
|
|
46
|
-
* the first write or lifecycle call.
|
|
47
|
+
* the first write or lifecycle call. `destroySegment` and `eraseNamespace` take `backend.registry`; the store takes `backend`.
|
|
47
48
|
*
|
|
48
49
|
* Two lower-level shapes stay accepted for wiring the facade does not cover:
|
|
49
50
|
*
|
|
@@ -64,11 +65,16 @@ export interface CloudRoaringOptions {
|
|
|
64
65
|
*/
|
|
65
66
|
readonly encryption?: EncryptionOptions;
|
|
66
67
|
/**
|
|
67
|
-
* Resilience: by default every
|
|
68
|
-
* connections) with bounded, jittered exponential backoff (see {@link DEFAULT_RETRY_POLICY})
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* (`
|
|
68
|
+
* Resilience: by default every read that answers a query retries **transient** faults (throttling, 5xx, dropped
|
|
69
|
+
* connections) with bounded, jittered exponential backoff (see {@link DEFAULT_RETRY_POLICY}): `has`, `count`,
|
|
70
|
+
* `iterate` and the combines, the `*Into` verbs' reads of their operands included, a pinned handle's reads and
|
|
71
|
+
* `pin()` itself. Writes are not retried, and nor are an erasure's reads, a load's guard read, or the calls that
|
|
72
|
+
* read the registry or list the bucket directly (`exists`, `segments`, `generations`, `getRetention`, and the
|
|
73
|
+
* registry scan `subjectReport`, `exportSegments` and `checkConsistency` start from): they report a transient fault to their caller, because a conditional write that lands and then loses its response would, replayed,
|
|
74
|
+
* report its own write as a conflict. Pass a partial policy to tune it — anything you leave out keeps its
|
|
75
|
+
* default — or `false` to turn the read retry off (e.g. if your injected client already retries).
|
|
76
|
+
* Deterministic errors (`ValidationError`/`IntegrityError`/`WriteConflictError`/…) are never retried by this
|
|
77
|
+
* layer.
|
|
72
78
|
*/
|
|
73
79
|
readonly retry?: RetryOptions | false;
|
|
74
80
|
/**
|
|
@@ -80,7 +86,8 @@ export interface CloudRoaringOptions {
|
|
|
80
86
|
/**
|
|
81
87
|
* Per-op **denial-of-wallet** budget: the max backend requests a single
|
|
82
88
|
* `count`/`iterate`/`intersect`/`union`/`andNot`/`subjectReport`/`eraseSubject` may fan out into before it's
|
|
83
|
-
* refused with {@link BudgetExceededError}
|
|
89
|
+
* refused with {@link BudgetExceededError} (`eraseSubject` reports an over-budget segment in its ledger instead,
|
|
90
|
+
* see {@link CloudRoaring.eraseSubject}) — so one runaway op can't drive unbounded GET cost on a shared
|
|
84
91
|
* backend. **On by default, generous** ({@link DEFAULT_BUDGET}: 1,000,000 requests — a normal op never hits it).
|
|
85
92
|
* Tune with `{ maxRequests }`, override per op (on the combines / `subjectReport` / `eraseSubject`), or set
|
|
86
93
|
* `false` to disable. The check is O(1) (before fan-out), so the hot path is untouched; per-request bytes are
|
|
@@ -102,10 +109,23 @@ export interface CacheOptions {
|
|
|
102
109
|
/**
|
|
103
110
|
* How long (ms) the store trusts a segment's resolved `currentGen` before re-resolving it on the next read
|
|
104
111
|
* (default 2000) — the bound on read staleness after a load publishes a new generation. Applies when
|
|
105
|
-
* `storage` is a **backend**, whose registry supplies the cheap `currentGen` read the refresh needs.
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
112
|
+
* `storage` is a **backend**, whose registry supplies the cheap `currentGen` read the refresh needs. Lazy — no
|
|
113
|
+
* timer; ≤ one registry read per segment per window, opening a new reader only when the generation actually
|
|
114
|
+
* advanced.
|
|
115
|
+
*
|
|
116
|
+
* **While the registry cannot be read, the bound stretches.** A refresh that fails with a
|
|
117
|
+
* {@link TransientError} keeps serving the generation the reader holds, and the key it unwrapped, and is tried
|
|
118
|
+
* again 500 ms later (or after this TTL, if shorter), so the store converges within one retry of the registry
|
|
119
|
+
* answering. A refresh that fails with anything else, an access denial or a row that will not parse, is not
|
|
120
|
+
* ridden out: the read that meets it throws that error, and the reader is dropped.
|
|
121
|
+
*
|
|
122
|
+
* `0` turns this timed refresh off, and so does wiring a bare `IStorageDriver`, which has no registry. That is
|
|
123
|
+
* all it does. The store still moves a segment on to whatever generation is current when its reader cache
|
|
124
|
+
* evicts the segment, when a read has to fetch from a generation a sweep deleted, and when it is invalidated,
|
|
125
|
+
* as its own `load`, `rollback`, `eraseSubject` and `*Into` writes do and {@link CloudRoaring.invalidate} does.
|
|
126
|
+
* A read its caches can answer touches no storage, so a sweep alone moves nothing. What `0` gives up is the bound:
|
|
127
|
+
* the store notices another process's publish, erasure or drop only through one of those, whenever that happens. To read
|
|
128
|
+
* one generation for as long as you need it, take a {@link Segment.pin}.
|
|
109
129
|
*/
|
|
110
130
|
readonly genTtlMs?: number;
|
|
111
131
|
/**
|
|
@@ -149,11 +169,14 @@ export interface EncryptionOptions {
|
|
|
149
169
|
/**
|
|
150
170
|
* {@link CloudRoaringOptions.retry} — a partial {@link RetryPolicy} plus the retry callback.
|
|
151
171
|
*
|
|
152
|
-
* Partial on purpose
|
|
153
|
-
*
|
|
172
|
+
* Partial on purpose, so tuning one field does not mean restating all five. Anything omitted here keeps its
|
|
173
|
+
* {@link DEFAULT_RETRY_POLICY} value.
|
|
154
174
|
*/
|
|
155
175
|
export interface RetryOptions extends Partial<RetryPolicy> {
|
|
156
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* Observability: called before each transient-retry backoff wait. Best-effort: an error it throws is
|
|
178
|
+
* swallowed, so it can neither stop the retry nor change the error a read fails with.
|
|
179
|
+
*/
|
|
157
180
|
readonly onRetry?: (info: {
|
|
158
181
|
attempt: number;
|
|
159
182
|
delayMs: number;
|
|
@@ -211,27 +234,36 @@ export interface SubjectErasureEntry {
|
|
|
211
234
|
readonly segment: string;
|
|
212
235
|
readonly namespace?: string;
|
|
213
236
|
/**
|
|
214
|
-
* True iff the id **was**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
237
|
+
* True iff the id **was** in the segment and **no generation of it holds the id now** — not the current one, not
|
|
238
|
+
* one below the pointer, and not one above it, where a rollback leaves the generations it rolled back from. The
|
|
239
|
+
* current generation is one without it, every generation that held it has been deleted from the bucket, and the
|
|
240
|
+
* call listed the bucket and read what was left before this entry was returned (see
|
|
241
|
+
* {@link CloudRoaring.eraseSubject}).
|
|
217
242
|
*/
|
|
218
243
|
readonly erased: boolean;
|
|
219
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* The generation the id was found in (present whenever the segment was read) — the newest of them, when the
|
|
246
|
+
* current generation did not hold it and several others did.
|
|
247
|
+
*/
|
|
220
248
|
readonly fromGeneration?: number;
|
|
221
249
|
/** The generation written without the id (present whenever one was written). */
|
|
222
250
|
readonly generation?: number;
|
|
223
251
|
/**
|
|
224
|
-
* Why the id was NOT erased from this segment, when `erased` is false. `'superseded'` —
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
252
|
+
* Why the id was NOT erased from this segment, when `erased` is false. `'superseded'` — the pointer moved while
|
|
253
|
+
* the call was in flight, by a load, another erasure or a rollback, so **this call** did not erase the id;
|
|
254
|
+
* re-run against the new generation, which erases it if it is still there and reports nothing for the segment
|
|
255
|
+
* if the racing writer already removed it. `` `error: <message>` `` — an isolated per-segment fault
|
|
256
|
+
* (per-segment faults are recorded so one segment can't discard the whole ledger); re-run after fixing the
|
|
257
|
+
* fault. A budget refusal is one of these: the segment's generations did not fit `budget`, so it was not finished;
|
|
258
|
+
* re-run with a higher `budget`. A fault that landed once part of the work was already done — a Storage `delete` fault, a collect that
|
|
259
|
+
* could not prove the segment was still the same one, or a generation still holding the id when the bucket was
|
|
260
|
+
* listed at the end, whether or not a rewrite was published first — also re-runs, but **read what the re-run
|
|
261
|
+
* says**: it usually reports `erased: true` against the generation it found the id in, it reports
|
|
232
262
|
* nothing at all if a racing collector took that generation first (the bit is gone, but no run holds a
|
|
233
263
|
* receipt for it), and if the segment's row has since been purged it is no longer scanned at all — anything
|
|
234
|
-
* left in its bucket is an orphan
|
|
264
|
+
* left in its bucket is an orphan, which `store.generations(ref)` lists and `store.dropSegment(ref, {
|
|
265
|
+
* confirmSegment })` deletes; `checkConsistency` and the collection a load runs read rows, so neither reaches it.
|
|
266
|
+
* **Segments the id is not in
|
|
235
267
|
* are not listed, and neither are segments that no longer have a registry row** — an empty ledger is not by
|
|
236
268
|
* itself proof the id is gone.
|
|
237
269
|
*/
|
|
@@ -258,14 +290,17 @@ export interface EraseSubjectResult {
|
|
|
258
290
|
export type MaterializeRefusal = Exclude<LoadRefusal, 'superseded'>;
|
|
259
291
|
/** What an `*Into` verb wrote: the new generation of the destination, and whether it became current. */
|
|
260
292
|
export interface MaterializeResult {
|
|
261
|
-
/**
|
|
293
|
+
/**
|
|
294
|
+
* The generation written. Present even when refused: the refusal deleted that object, unless the destination's
|
|
295
|
+
* row changed while it was writing, which leaves it in the bucket.
|
|
296
|
+
*/
|
|
262
297
|
readonly generation: number;
|
|
263
298
|
/**
|
|
264
299
|
* Whether this generation is now the destination's current one.
|
|
265
300
|
*
|
|
266
|
-
*
|
|
267
|
-
* a refusal is reported, not thrown, so a caller that ignores it sees a
|
|
268
|
-
* write that deliberately did not happen.
|
|
301
|
+
* `false` when the guard refused the result: an empty one over a non-empty destination, or one outside
|
|
302
|
+
* `guard`'s bounds. Branch on it: a refusal is reported, not thrown, so a caller that ignores it sees a
|
|
303
|
+
* successful-looking result for a write that deliberately did not happen.
|
|
269
304
|
*/
|
|
270
305
|
readonly published: boolean;
|
|
271
306
|
/** Set only when `published` is false. A lost race throws {@link WriteConflictError} rather than appearing here. */
|
|
@@ -290,29 +325,11 @@ export interface MaterializeResult {
|
|
|
290
325
|
*/
|
|
291
326
|
readonly collected: readonly number[];
|
|
292
327
|
}
|
|
293
|
-
/**
|
|
294
|
-
* Options that moved into a group, and where each one went.
|
|
295
|
-
*
|
|
296
|
-
* TypeScript rejects these at the call site, which covers most callers. It does not cover a plain-JS caller, a
|
|
297
|
-
* config object that arrived as JSON, or anything that reached the constructor through an `as` cast — and for
|
|
298
|
-
* this particular set, being ignored is worse than being rejected, because **every one of them is a knob whose
|
|
299
|
-
* absence is silent and wrong**: a dropped `requireEncryption` reads cleartext when the caller demanded
|
|
300
|
-
* encryption, a dropped `clock` makes a "deterministic" job non-deterministic, and a dropped
|
|
301
|
-
* `coldReaderCacheMaxBytes` restores a 64 MiB ceiling someone had deliberately lowered for a small heap.
|
|
302
|
-
* None of those announces itself; each looks like the store simply working.
|
|
303
|
-
*/
|
|
304
|
-
/**
|
|
305
|
-
* How a `0.9.x` option is answered: it moved into a group, it was renamed, or it is gone.
|
|
306
|
-
*
|
|
307
|
-
* The category is DATA, not inferred from how the guidance happens to be punctuated. The first version
|
|
308
|
-
* decided by testing whether the replacement text looked like an identifier, which got two entries wrong in
|
|
309
|
-
* opposite directions: `registry` HAS a successor and was announced as "removed", and `cold` → `storage` was
|
|
310
|
-
* announced as "moved into a group" when `storage` is the one required flat option, not a group. A reader
|
|
311
|
-
* told to look in a group that does not exist is the failure this whole guard is about.
|
|
312
|
-
*/
|
|
313
328
|
export declare class CloudRoaring {
|
|
314
329
|
private readonly engine;
|
|
315
330
|
private readonly cache;
|
|
331
|
+
/** The store's retry options, for a pinned engine to read through as the store's own does; undefined when off. */
|
|
332
|
+
private readonly retryOptions;
|
|
316
333
|
private readonly crbmSource;
|
|
317
334
|
private readonly clock;
|
|
318
335
|
private readonly metrics;
|
|
@@ -323,70 +340,77 @@ export declare class CloudRoaring {
|
|
|
323
340
|
/** Resolved store-level per-op budget (null = disabled); the admin scans use it, with a per-op override. */
|
|
324
341
|
private readonly budget;
|
|
325
342
|
/**
|
|
326
|
-
* Refuse
|
|
343
|
+
* Refuse any option key the store does not take, at the top level or inside a group, naming each one.
|
|
327
344
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
345
|
+
* An option the store ignored would do nothing and look as if it had — a typo'd `cache.maxChunk`, a key from
|
|
346
|
+
* another library's config spread into this one — so every key is checked against {@link OPTION_KEYS}.
|
|
330
347
|
*/
|
|
331
|
-
private static
|
|
348
|
+
private static rejectUnknownOptions;
|
|
332
349
|
constructor(options: CloudRoaringOptions);
|
|
333
350
|
/**
|
|
334
351
|
* The write-side deps, from the store's own drivers, for the lifecycle helpers and the `*Into` verbs. Requires
|
|
335
352
|
* the store to have been constructed with a **backend**, which supplies both halves: an `IStorageDriver` to
|
|
336
353
|
* write generations through (a pre-built `StorageChunkSource` has none) and the registry holding the pointer
|
|
337
354
|
* every write publishes.
|
|
338
|
-
* Out-of-process callers use the free functions with explicit deps.
|
|
339
355
|
*/
|
|
340
356
|
private lifecycleDeps;
|
|
341
357
|
/** Get a handle to a segment. Validates the name (non-empty, well-formed, within the encoded-length cap). */
|
|
342
358
|
segment(name: string, options?: SegmentOptions): Segment;
|
|
343
359
|
/**
|
|
344
|
-
* Write `ids` as a **new generation of `dest`** and publish it
|
|
345
|
-
*
|
|
360
|
+
* Write `ids` as a **new generation of `dest`** and publish it — the shared body of the `*Into` verbs. An `*Into`
|
|
361
|
+
* materialisation is a load, and publishes the same way (hard invariant 1): every load that finds a row fences its
|
|
362
|
+
* publish on the row's token; a guarded load (the default, since the empty refusal reads the current generation)
|
|
363
|
+
* also fences on the pointer it judged (`expectFrom`), and one that found no row fences on that absence instead.
|
|
364
|
+
* Only an unguarded load (`allowEmpty: true` and no `guard.minRetained`) onto a segment with no row publishes bare
|
|
365
|
+
* forward-only. The destination's previous generation stays readable until the publish lands (readers re-resolve
|
|
346
366
|
* within `cache.genTtlMs`).
|
|
347
367
|
*
|
|
348
368
|
* This routes through `loadSegment` rather than writing the generation itself, and that is the whole point
|
|
349
369
|
* of it. A materialisation is a load whose ids happen to come from a combine instead of from upstream, so
|
|
350
370
|
* everything `load()` learned the hard way applies unchanged: the generation is written UNPUBLISHED, the
|
|
351
|
-
* guard runs while the old generation is still authoritative, the publish is fenced
|
|
352
|
-
*
|
|
353
|
-
* object is reclaimed only after re-reading the row and finding the same incarnation (hard invariant 1:
|
|
371
|
+
* guard runs while the old generation is still authoritative, the publish is fenced as above, and a refused
|
|
372
|
+
* object is reclaimed only when a re-read finds the row unchanged (the same token) or gone (hard invariant 1:
|
|
354
373
|
* deleting it after a purge-and-recreate would put a live row over a missing generation).
|
|
355
374
|
*
|
|
356
375
|
* That last check narrows the window rather than closing it: the row read and the delete are two round
|
|
357
|
-
* trips, and `IStorageDriver` has no conditional delete to make them one.
|
|
358
|
-
* the same residual
|
|
376
|
+
* trips, and `IStorageDriver` has no conditional delete to make them one. the collection a load runs carries
|
|
377
|
+
* the same residual. The failure it leaves is an orphan object, which costs storage until
|
|
359
378
|
* something collects it — deliberately the cheaper side of the trade.
|
|
360
379
|
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
380
|
+
* Written and published in one step, with no guard, an empty combine — a typo'd operand, an `exclude` that
|
|
381
|
+
* swallowed everything, an operand that had not loaded yet — would silently replace `dest` with an empty
|
|
382
|
+
* generation: the same failure `load()`'s guard exists to prevent, on the same data, reachable without passing
|
|
383
|
+
* any option at all.
|
|
365
384
|
*
|
|
366
|
-
* **A lost race
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* carries it.
|
|
385
|
+
* **A lost race throws.** `loadSegment` reports one as `reason: 'superseded'`; the `*Into` verbs throw
|
|
386
|
+
* {@link WriteConflictError} for it, so a caller can `catch (WriteConflictError)`. That one refusal is
|
|
387
|
+
* translated into the throw, and `MaterializeResult.reason` never carries it.
|
|
370
388
|
*
|
|
371
389
|
* **A `WriteConflictError` does not by itself mean nothing was published**, and that is worth knowing
|
|
372
|
-
* before you write the retry. `'superseded'` covers
|
|
373
|
-
* the pointer moved, the row's token changed, the row was purged —
|
|
374
|
-
* "somebody beat us" the name suggests. A token can also change on a write
|
|
375
|
-
* all, such as a `setRetention` on the destination. On top of that, the collection pass that runs AFTER a
|
|
390
|
+
* before you write the retry. `'superseded'` covers five different causes — the write-once PUT collided,
|
|
391
|
+
* the pointer moved, a row appeared where the load found none, the row's token changed, the row was purged —
|
|
392
|
+
* and only the first three are the "somebody beat us" the name suggests. A token can also change on a write
|
|
393
|
+
* that is not a supersession at all, such as a `setRetention` on the destination. On top of that, the collection pass that runs AFTER a
|
|
376
394
|
* successful publish can raise the same error. So: treat it as "re-read the destination and decide",
|
|
377
395
|
* never as "the write did not happen".
|
|
378
396
|
*
|
|
379
|
-
* **And it
|
|
397
|
+
* **And by default it collects nothing**, unlike `load()`. See the `keep` default below.
|
|
380
398
|
*/
|
|
381
399
|
private materialize;
|
|
382
400
|
/**
|
|
383
401
|
* **Subject access (GDPR Art. 15 / CCPA right-to-know): which segments is this id a member of?**
|
|
384
402
|
*
|
|
385
403
|
* Enumerates the **registered** segments (via the store's own `registry`) and does a `has(id)` on each — no
|
|
386
|
-
* drivers to re-pass. Complete only over registered segments (every loaded segment has a row, so
|
|
387
|
-
*
|
|
388
|
-
* load for a rare request; this admin scan is `O(registered segments)` and touches no hot path.
|
|
389
|
-
*
|
|
404
|
+
* drivers to re-pass. Complete only over registered segments (every loaded segment has a row, so build the
|
|
405
|
+
* store on the backend the loads used). There is deliberately **no `id → segments` reverse index** — that would
|
|
406
|
+
* tax every load for a rare request; this admin scan is `O(registered segments)` and touches no hot path. Needs
|
|
407
|
+
* a storage backend (throws {@link UnsupportedError} otherwise).
|
|
408
|
+
*
|
|
409
|
+
* **Current as of the registry row, not as of the reader's cache.** Each segment's resolved generation is
|
|
410
|
+
* compared with the row the scan listed, and a segment whose cached generation differs is re-resolved before the
|
|
411
|
+
* read, so a load or an erasure from another process shows at once — whatever `cache.genTtlMs` is, `0` included.
|
|
412
|
+
* The comparison is on the generation **and** the row's token, so a segment retired, purged and loaded again from
|
|
413
|
+
* generation 0 while this store held its old generation 0 is told apart too.
|
|
390
414
|
*/
|
|
391
415
|
subjectReport(id: number, options?: {
|
|
392
416
|
namespace?: string;
|
|
@@ -400,44 +424,59 @@ export declare class CloudRoaring {
|
|
|
400
424
|
*
|
|
401
425
|
* For each **registered** segment the id is a member of, `eraseIdFromSegment` rewrites the current generation
|
|
402
426
|
* without the id — every chunk streamed through, one bit cleared — publishes the rewrite **fenced on the
|
|
403
|
-
* generation it was derived from**, and
|
|
404
|
-
*
|
|
427
|
+
* generation it was derived from**, and collects the generation that held the bit. A segment whose current
|
|
428
|
+
* generation does not hold the id is searched anyway, every generation in its bucket: a retained superseded one
|
|
429
|
+
* can still hold it, and so can one above the pointer after a {@link CloudRoaring.rollback}, which the rollback
|
|
430
|
+
* could make current again. Each holder is deleted — below the pointer by a `keep: 0` collection, above it one
|
|
431
|
+
* by one, re-proved against the row first — and the generations above the pointer that never held the id stay
|
|
432
|
+
* as rollback targets. An entry says `erased: true` only once the call has listed the bucket and read what is
|
|
433
|
+
* left: **no generation of the segment holds the id**. The returned per-segment record is your **erasure ledger** —
|
|
405
434
|
* persist it / route it to your audit sink as the proof of deletion (a `segment.rewrite` audit event is also
|
|
406
435
|
* emitted per rewrite when you pass `audit`).
|
|
407
436
|
*
|
|
408
437
|
* Uses the backend's **own** two halves, so the membership check and the rewrite provably run
|
|
409
438
|
* over the same generation. Requires the store built with a **backend** (throws
|
|
410
439
|
* {@link UnsupportedError} otherwise; a pre-built `StorageChunkSource` store has no `IStorageDriver` to write
|
|
411
|
-
* through
|
|
440
|
+
* through; build the store on a backend instead).
|
|
412
441
|
*
|
|
413
442
|
* **One contract remains** (an integrator obligation the library cannot check): **do not load the segment
|
|
414
443
|
* while erasing from it.** A load that lands after the rewrite carries whatever its source held, and the
|
|
415
444
|
* library cannot know that source was meant to exclude the id. Quiesce loads of the affected segments for the
|
|
416
445
|
* duration, or fix the source first and load after. A writer that lands *during* the rewrite is caught: the
|
|
417
|
-
* rewrite's publish is refused **by the fence** —
|
|
446
|
+
* rewrite's publish is refused **by the fence** — the publish lands its compare-and-swap only
|
|
418
447
|
* while the pointer is still on the generation the rewrite streamed — and the entry says `note: 'superseded'`,
|
|
419
|
-
* so re-run. Forward-only alone would NOT refuse it:
|
|
448
|
+
* so re-run. Forward-only alone would NOT refuse it: the rewrite's number goes above everything in the bucket,
|
|
420
449
|
* so the rewrite would out-rank the newer generation and then collect it.
|
|
421
450
|
*
|
|
422
451
|
* A racing **erasure** is caught before that, and reported the same way. It collects with `keep: 0`, taking
|
|
423
452
|
* every generation below its new pointer — the one this rewrite is streaming, and the object this rewrite
|
|
424
|
-
* just wrote — so the loser can find its own inputs deleted mid-flight. That surfaces as
|
|
425
|
-
* an error, read off the row: a moved pointer is `'superseded'
|
|
426
|
-
* a retention sweep
|
|
453
|
+
* just wrote — so the loser can find its own inputs deleted mid-flight. That surfaces as an outcome rather than
|
|
454
|
+
* an error, read off the row: a moved pointer is a `'superseded'` entry, which a re-run settles, and a segment
|
|
455
|
+
* that a concurrent `dropSegment` tombstoned or a retention sweep purged is left out of the ledger, as a fresh
|
|
456
|
+
* call would leave it out.
|
|
427
457
|
*
|
|
428
458
|
* **Read `note` on any `erased: false` entry — the two reasons mean different things.** `'superseded'` means
|
|
429
|
-
* another writer (a load, or
|
|
430
|
-
* the id. Re-run: it erases the id if it is still there, and lists nothing for the segment if a racing
|
|
459
|
+
* another writer (a load, another erasure, or a rollback) moved the pointer mid-call, so **this call** did not
|
|
460
|
+
* erase the id. Re-run: it erases the id if it is still there, and lists nothing for the segment if a racing
|
|
431
461
|
* erasure of the same id already removed it. Do not read `'superseded'` as "the id is still present" —
|
|
432
462
|
* read it as "not done by this call, and the re-run settles it".
|
|
433
463
|
* `` `error: …` `` is a per-segment fault (caught so one segment can't discard the whole ledger) and it can land
|
|
434
464
|
* on either side of the publish: if the rewrite had not published, the id is still there and a re-run erases
|
|
435
465
|
* it; if the publish succeeded and only the **collection** of the old generation failed, the id is already
|
|
436
|
-
* absent from every read and what remains is an object in the bucket that still contains the bit.
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* `
|
|
466
|
+
* absent from every read and what remains is an object in the bucket that still contains the bit. The same
|
|
467
|
+
* note, after a publish, can mean a rollback moved the pointer back onto a generation that still holds the id
|
|
468
|
+
* while the collection ran — then the id is served again, and the entry says so rather than `erased: true`. A
|
|
469
|
+
* re-run searches every generation in the bucket, so it rewrites or collects what is left and usually reports
|
|
470
|
+
* `erased: true`; {@link SubjectErasureEntry.note} says what else a re-run can report. Re-running is otherwise safe
|
|
471
|
+
* and idempotent: a segment the id is no longer in is not listed. Admin-only path;
|
|
472
|
+
* `O(registered segments + superseded generations)`, no hot-path cost: a segment whose current generation lacks
|
|
473
|
+
* the id is searched generation by generation, one open for each other generation in its bucket, and with the
|
|
474
|
+
* keep-everything default of the `*Into` verbs that is one per generation the segment ever had. The per-op
|
|
475
|
+
* `budget` is charged one unit for each segment and one for each generation opened beyond the one its row names,
|
|
476
|
+
* so a fleet with long histories can exhaust it where the segment count alone would not. A segment that does is
|
|
477
|
+
* reported `erased: false` with an `error:` note, before it deletes anything (the one refusal that can come after a
|
|
478
|
+
* rewrite is the last check for a generation a concurrent writer left behind), and the
|
|
479
|
+
* rest of the scan continues: the call itself does not throw for it. Per-subject crypto-shred is infeasible (a subject's bit is
|
|
441
480
|
* co-mingled in a shared container), so this is the single-subject erasure route; whole-segment/tenant erasure
|
|
442
481
|
* is `dropSegment` / the `destroySegment`/`eraseNamespace` free functions.
|
|
443
482
|
*/
|
|
@@ -453,7 +492,7 @@ export declare class CloudRoaring {
|
|
|
453
492
|
*
|
|
454
493
|
* What the bucket holds, not what the segment has ever been — collection deletes superseded objects, so this is
|
|
455
494
|
* the grace window plus whatever has not been collected yet. It is the set {@link CloudRoaring.rollback} can
|
|
456
|
-
* choose from, which is the reason to look at it. One
|
|
495
|
+
* choose from, which is the reason to look at it. One registry read and one listing; it does not open the objects.
|
|
457
496
|
*
|
|
458
497
|
* Needs a backend.
|
|
459
498
|
*/
|
|
@@ -476,7 +515,7 @@ export declare class CloudRoaring {
|
|
|
476
515
|
* a handle carrying an expired `expiresAt` reads empty by a rule that lives on the handle, not the row.
|
|
477
516
|
*
|
|
478
517
|
* Not a lock: the answer can change the moment it returns. If it has to hold, use the fence built for that —
|
|
479
|
-
* `load`'s `guard`, or `expectFrom`/`expectToken` on a publish. Needs a
|
|
518
|
+
* `load`'s `guard`, or `expectFrom`/`expectToken` on a publish. Needs a storage backend.
|
|
480
519
|
*
|
|
481
520
|
* ```ts
|
|
482
521
|
* if (!(await store.exists({ segment: 'users' }))) {
|
|
@@ -498,13 +537,13 @@ export declare class CloudRoaring {
|
|
|
498
537
|
* Scoping to a namespace narrows the LIST prefix, so it really is the difference between reading one tenant
|
|
499
538
|
* and reading all of them.
|
|
500
539
|
*
|
|
501
|
-
* It streams, and stopping the iteration stops the scan
|
|
502
|
-
*
|
|
503
|
-
*
|
|
540
|
+
* It streams, and stopping the iteration stops the scan. It reads the registry directly and is not retried: a
|
|
541
|
+
* transient fault part-way through ends the loop with that error, and calling `segments()` again scans from the
|
|
542
|
+
* start.
|
|
504
543
|
*
|
|
505
544
|
* Yields `destroyed` tombstones and rows with `currentGen: null`, because a filtered enumeration that looks
|
|
506
545
|
* complete is worse than an honest one — filter on `status`/`currentGen` yourself, or ask
|
|
507
|
-
* {@link CloudRoaring.exists} the narrower question. Needs a
|
|
546
|
+
* {@link CloudRoaring.exists} the narrower question. Needs a storage backend.
|
|
508
547
|
*
|
|
509
548
|
* ```ts
|
|
510
549
|
* for await (const s of store.segments({ namespace: 'active-daily' })) {
|
|
@@ -532,14 +571,22 @@ export declare class CloudRoaring {
|
|
|
532
571
|
* current is a no-op that reports itself.
|
|
533
572
|
*
|
|
534
573
|
* It deletes nothing. The generations above the new pointer stay put — which is what makes this reversible —
|
|
535
|
-
* and are then *above* `currentGen`, where collection never looks
|
|
536
|
-
*
|
|
537
|
-
*
|
|
574
|
+
* and are then *above* `currentGen`, where collection never looks. They remain until a load numbers above them
|
|
575
|
+
* (collection then keeps the newest `keep` of what is below its pointer), {@link CloudRoaring.dropSegment}
|
|
576
|
+
* deletes them, or {@link CloudRoaring.eraseSubject} does: all of those present when it rewrites, and only those holding
|
|
577
|
+
* the id when the current generation does not. An operator who has just undone a bad load should not have the
|
|
578
|
+
* evidence collected out from under them, while a rollback target that still holds erased data would make the
|
|
579
|
+
* erasure undoable.
|
|
580
|
+
*
|
|
581
|
+
* A target **above** the pointer needs `{ allowForward: true }`, and is refused with {@link ValidationError}
|
|
582
|
+
* without it: that is also where objects live that were never published, such as a load that wrote its object
|
|
583
|
+
* and died before the publish. Undoing an earlier rollback is what the opt-in is for.
|
|
538
584
|
*
|
|
539
585
|
* Needs a backend.
|
|
540
586
|
*/
|
|
541
587
|
rollback(ref: SegmentRef, toGeneration: number, options?: {
|
|
542
588
|
audit?: IAuditSink;
|
|
589
|
+
allowForward?: boolean;
|
|
543
590
|
}): Promise<RollbackResult>;
|
|
544
591
|
/**
|
|
545
592
|
* **Replace this segment's contents** with `ids`, as one new immutable generation, and make it current.
|
|
@@ -550,8 +597,8 @@ export declare class CloudRoaring {
|
|
|
550
597
|
* attention to and everybody pays for.
|
|
551
598
|
*
|
|
552
599
|
* ```ts
|
|
553
|
-
* const r = await store.load('audience:active', idsFromWarehouse, {
|
|
554
|
-
* guard: {
|
|
600
|
+
* const r = await store.load({ segment: 'audience:active' }, idsFromWarehouse, {
|
|
601
|
+
* guard: { minRetained: 0.5 }, // refuse a load that would drop more than half the segment
|
|
555
602
|
* });
|
|
556
603
|
* if (!r.published) console.warn(`load refused: ${r.reason}`);
|
|
557
604
|
* ```
|
|
@@ -569,15 +616,22 @@ export declare class CloudRoaring {
|
|
|
569
616
|
* `eraseIdFromSegment` instead, because three of its four refusals are expected guard outcomes rather than
|
|
570
617
|
* faults.
|
|
571
618
|
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
619
|
+
* `'superseded'` means another writer got there first: another load took the same generation number, so this
|
|
620
|
+
* one wrote nothing (`size: 0`), or the segment's registry row changed while the load was writing — another load
|
|
621
|
+
* published, a retention change, a rollback or an erasure wrote the row, or the row was deleted.
|
|
622
|
+
*
|
|
623
|
+
* A refused load deletes the object it wrote before returning — it sits above `currentGen`, where generation
|
|
624
|
+
* collection deliberately never looks — but only while the segment's registry row is unchanged or gone. Once
|
|
625
|
+
* another write has changed the row, the generation number it holds may name another incarnation's live object,
|
|
626
|
+
* so it leaves the orphan rather than risk deleting live data. The orphan is an ordinary generation once a later
|
|
627
|
+
* one is current above it, and collection counts it within `keep`.
|
|
576
628
|
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
629
|
+
* What it **throws** for is a fault rather than an outcome: invalid options or ids, and a crypto-shredded
|
|
630
|
+
* segment (`ValidationError`); a key the keystore cannot provide (`KeyUnavailableError`); a current generation
|
|
631
|
+
* that will not open when a guard reads its size (`IntegrityError`); a driver failure; and a collection pass
|
|
632
|
+
* that could not prove the segment was still the same one (`WriteConflictError`). The last can be raised
|
|
633
|
+
* **after** the publish already landed, so a throw does not by itself mean the load did not take effect —
|
|
634
|
+
* re-read the pointer rather than assuming.
|
|
581
635
|
*
|
|
582
636
|
* Needs a backend (throws {@link UnsupportedError} otherwise).
|
|
583
637
|
*/
|
|
@@ -612,10 +666,14 @@ export declare class CloudRoaring {
|
|
|
612
666
|
* could not remove rather than returning a result that looks like a clean drop.
|
|
613
667
|
*
|
|
614
668
|
* Reads become empty within `cache.genTtlMs` (default 2 s), not instantly: a store that had already read this
|
|
615
|
-
* segment may answer from its cached generation + cached chunks until that window lapses
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
669
|
+
* segment may answer from its cached generation + cached chunks until that window lapses, or, while the registry
|
|
670
|
+
* cannot be read because of a transient fault, until a retry 500 ms apart reaches it again. A reader that never
|
|
671
|
+
* touched it sees empty at once. **That bound needs a registry and `cache.genTtlMs > 0`.** A store with no registry
|
|
672
|
+
* (a bare `IStorageDriver`), with `cache.genTtlMs: 0`, or on a storage source built with no clock, has no timed
|
|
673
|
+
* refresh. It notices the drop only when a read has to fetch
|
|
674
|
+
* from a deleted generation or its reader cache evicts the segment, and until then a chunk it has cached
|
|
675
|
+
* answers without reaching storage, so it can keep answering `true` for a dropped segment indefinitely. Tell
|
|
676
|
+
* it with {@link invalidate}, or restart it.
|
|
619
677
|
*
|
|
620
678
|
* Needs the store built with a **backend** (throws {@link UnsupportedError} otherwise),
|
|
621
679
|
* because it has to enumerate and delete generations — a pre-built `StorageChunkSource` only reads.
|
|
@@ -648,8 +706,8 @@ export declare class CloudRoaring {
|
|
|
648
706
|
*
|
|
649
707
|
* A value in the past is legal and means "eligible on the next sweep" — backfilling a policy onto existing
|
|
650
708
|
* buckets is normal. A value below `MIN_EXPIRES_AT_MS` (2001-09-09) is rejected: it is almost certainly epoch
|
|
651
|
-
* **seconds**, which would read as long-expired and retire the segment on the next pass. Needs a
|
|
652
|
-
*
|
|
709
|
+
* **seconds**, which would read as long-expired and retire the segment on the next pass. Needs a storage
|
|
710
|
+
* backend (throws {@link UnsupportedError} otherwise), and refuses a crypto-shredded segment.
|
|
653
711
|
*/
|
|
654
712
|
setRetention(ref: SegmentRef, policy: RetentionPolicy): Promise<SetRetentionResult>;
|
|
655
713
|
/**
|
|
@@ -679,7 +737,7 @@ export declare class CloudRoaring {
|
|
|
679
737
|
* ```ts
|
|
680
738
|
* // In your scheduled handler. Start with a preview in a new deployment.
|
|
681
739
|
* const preview = await store.retireExpired({ namespace: 'active-daily', dryRun: true });
|
|
682
|
-
* console.log(`would retire ${preview.
|
|
740
|
+
* console.log(`would retire ${preview.wouldRetire} of ${preview.scanned} (limited: ${preview.limited})`);
|
|
683
741
|
*
|
|
684
742
|
* const swept = await store.retireExpired({ namespace: 'active-daily' });
|
|
685
743
|
* for (const e of swept.entries) {
|
|
@@ -705,8 +763,9 @@ export declare class CloudRoaring {
|
|
|
705
763
|
* first, since nothing else ever would for a tombstoned segment. Attribution is a **positive marker the sweep
|
|
706
764
|
* stamps on its own retirements**, not an inference from "destroyed + an expired policy": a crypto-shred leaves
|
|
707
765
|
* `retention` untouched, so setting a policy and then honouring a right-to-erasure request mid-window produces
|
|
708
|
-
* exactly that row, and deleting it would destroy the Art. 17 attestation and un-fence the name.
|
|
709
|
-
*
|
|
766
|
+
* exactly that row, and deleting it would destroy the Art. 17 attestation and un-fence the name. By default a
|
|
767
|
+
* segment that held nothing has its row deleted in the pass that retires it, since that row would only fence the name.
|
|
768
|
+
* Pass `purgeTombstones: false` to keep every tombstone, that row included.
|
|
710
769
|
*
|
|
711
770
|
* Needs the store built with a **backend** (throws {@link UnsupportedError} otherwise),
|
|
712
771
|
* because retiring a segment deletes its storage objects. `now` defaults to the store's clock.
|
|
@@ -723,21 +782,27 @@ export declare class CloudRoaring {
|
|
|
723
782
|
* that advances `currentGen`**, which the snapshot TTL notices and the generation-keyed cache misses on.
|
|
724
783
|
* Neither notices an event that *destroys* what they were derived from.
|
|
725
784
|
*
|
|
726
|
-
* The
|
|
727
|
-
* what they touch. This method is for the cases they cannot see:
|
|
785
|
+
* The store's own writes handle themselves: `load`, `rollback`, `eraseSubject`, `dropSegment` and `retireExpired`
|
|
786
|
+
* here, and a segment's `*Into` verbs, invalidate what they touch. This method is for the cases they cannot see:
|
|
728
787
|
*
|
|
729
788
|
* - **`destroySegment` / `eraseNamespace`**, which are free functions over raw drivers rather than methods
|
|
730
|
-
* here, so a crypto-shred performed beside this store
|
|
731
|
-
*
|
|
789
|
+
* here, so a crypto-shred performed beside this store invalidates nothing in it. It keeps its open reader and
|
|
790
|
+
* the unwrapped DEK, and decrypts with them, chunks it had never fetched before the shred included, until its
|
|
791
|
+
* snapshot re-resolves: within `cache.genTtlMs` when the store has a registry and a `genTtlMs` above 0, and
|
|
792
|
+
* otherwise only when its caches let the segment go or a read has to fetch from a generation a sweep collected.
|
|
793
|
+
* A pin answers with no timed bound, until it is told or both caches let it go.
|
|
732
794
|
* - **Another process.** Erasing on one box invalidates nothing on the others; each store bounds its own
|
|
733
|
-
* staleness by `cache.genTtlMs`, and a store
|
|
734
|
-
*
|
|
735
|
-
* this is the call to make when your own fan-out delivers.
|
|
795
|
+
* staleness by `cache.genTtlMs`, and a store with no registry (a bare `IStorageDriver`), with
|
|
796
|
+
* `cache.genTtlMs: 0`, or on a storage source built with no clock has no bound at all: it converges only when a read happens to miss its caches. If a compliance deadline depends on every
|
|
797
|
+
* reader converging, you need to signal them — this is the call to make when your own fan-out delivers.
|
|
798
|
+
*
|
|
799
|
+
* It also forgets any replacement this store found of a pin's object, so once it is called after a restore puts that
|
|
800
|
+
* object back, the pin reads it again.
|
|
736
801
|
*
|
|
737
802
|
* Synchronous, best-effort, and safe to call for a segment this store has never read.
|
|
738
803
|
*
|
|
739
804
|
* ```ts
|
|
740
|
-
* await destroySegment(ref, {
|
|
805
|
+
* await destroySegment(ref, { registry: backend.registry }, { confirmSegment: ref.segment });
|
|
741
806
|
* store.invalidate(ref); // this process
|
|
742
807
|
* await bus.publish('cloudbitmaps.invalidate', ref); // and every other one
|
|
743
808
|
* ```
|
|
@@ -746,19 +811,16 @@ export declare class CloudRoaring {
|
|
|
746
811
|
/**
|
|
747
812
|
* Build a handle held at the generation `ref` resolves to right now. See {@link Segment.pin}.
|
|
748
813
|
*
|
|
749
|
-
* The pinned handle gets its own engine but **shares the store's chunk cache**,
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* Sharing it on a generation-only key was how a pinned read could resurrect an id already reported
|
|
753
|
-
* physically gone.
|
|
814
|
+
* The pinned handle gets its own engine but **shares the store's chunk cache**, under keys of its own: the
|
|
815
|
+
* pinned view reports the version captured at pin time, marked as a pin's, so its decoded chunks are never
|
|
816
|
+
* those of a live read that fetched across a publish (see {@link PinnedStorageChunkSource.currentVersion}).
|
|
754
817
|
*/
|
|
755
818
|
private pinSegment;
|
|
756
819
|
/**
|
|
757
|
-
* An engine reading every segment in `pins` at its pinned generation and everything else live
|
|
820
|
+
* An engine reading every segment in `pins` at its pinned generation and everything else live, with the store's
|
|
821
|
+
* retries, so a transient fault on a pinned read is retried as it would be on a live one.
|
|
758
822
|
*
|
|
759
|
-
* It **shares the store's chunk cache
|
|
760
|
-
* version rather than the generation number: a pinned view reports the version captured at pin time, so its
|
|
761
|
-
* decoded chunks cannot collide with the live generation's.
|
|
823
|
+
* It **shares the store's chunk cache and its memory bound**, under keys of its own for each pinned segment.
|
|
762
824
|
*/
|
|
763
825
|
private engineWithPins;
|
|
764
826
|
/**
|
|
@@ -769,19 +831,30 @@ export declare class CloudRoaring {
|
|
|
769
831
|
* `other.intersect([snap])` silently does not, and the two are the same question. So the combine collects
|
|
770
832
|
* every pin in play and runs on an engine that honours all of them. With no pins anywhere this is the
|
|
771
833
|
* store's own engine and costs nothing.
|
|
834
|
+
*
|
|
835
|
+
* One engine reads a segment at one generation, so a combine that holds the same segment at two — two pins of
|
|
836
|
+
* it, or a pin and a live handle — is refused. Keeping one of them would answer the question for the wrong
|
|
837
|
+
* instant, and say nothing: diffing two snapshots of a segment returned no ids at all.
|
|
772
838
|
*/
|
|
773
839
|
private engineForCombine;
|
|
840
|
+
/** `op` under the store's retries, as the store's own reads run; bare when the store has retries off. */
|
|
841
|
+
private withRetries;
|
|
774
842
|
/** The store's registry, or a typed error naming the operation that needs one. */
|
|
775
843
|
private requireRegistry;
|
|
776
844
|
/**
|
|
777
845
|
* **Cross-tier DR consistency check.** After a restore/failover, verify every registered segment's `currentGen`
|
|
778
846
|
* actually has its `.crbm` present in Storage — catching a **torn restore** where the registry (`currentGen`) came
|
|
779
847
|
* back ahead of the object store, so a pointer references a generation that isn't there (reads would then
|
|
780
|
-
* throw). Read-only, bounded fan-out; run it at startup after a restore. Returns `{ checked, inconsistent
|
|
781
|
-
* `inconsistent` empty ⇒ coherent; otherwise it names the segments to recover (restore the object
|
|
782
|
-
* roll the registry back to a coherent point). Needs the store built with a **backend**
|
|
848
|
+
* throw). Read-only, bounded fan-out; run it at startup after a restore. Returns `{ checked, inconsistent,
|
|
849
|
+
* errored }` — `inconsistent` empty ⇒ coherent; otherwise it names the segments to recover (restore the object
|
|
850
|
+
* store, or roll the registry back to a coherent point). Needs the store built with a **backend**
|
|
783
851
|
* (throws {@link UnsupportedError} otherwise). `destroyed` (crypto-shredded) segments are skipped. Pair it with
|
|
784
852
|
* the DR runbook (docs/guide/disaster-recovery.md).
|
|
853
|
+
*
|
|
854
|
+
* It holds the registry rows it enumerates resident, at most 250,000 of them, and throws
|
|
855
|
+
* `BudgetExceededError` past that rather than report a partial scan as a whole one. This method takes no
|
|
856
|
+
* ceiling of its own: narrow the scan with `namespace`, or call `runConsistencyCheck` over the backend's
|
|
857
|
+
* `storage` and `registry` with a higher `maxScanSegments`.
|
|
785
858
|
*/
|
|
786
859
|
checkConsistency(options?: {
|
|
787
860
|
namespace?: string;
|
|
@@ -806,8 +879,13 @@ export declare class CloudRoaring {
|
|
|
806
879
|
*/
|
|
807
880
|
static estimateCost(input: EstimateInput): CostReport;
|
|
808
881
|
}
|
|
809
|
-
/**
|
|
810
|
-
|
|
882
|
+
/**
|
|
883
|
+
* Options common to every chunk-aligned combine (`intersect` / `union` / `andNot`), and the `*Into` verbs.
|
|
884
|
+
*
|
|
885
|
+
* The range, `after` and `through`, applies to every operand and every `exclude`: the combine yields only the ids
|
|
886
|
+
* in `(after, through]` and fetches only the chunks the range overlaps, as {@link Segment.iterate} does.
|
|
887
|
+
*/
|
|
888
|
+
export interface BaseCombineOptions extends IdRange {
|
|
811
889
|
/** Max chunk keys resolved concurrently — bounds the Storage footprint. A positive integer. */
|
|
812
890
|
readonly concurrency?: number;
|
|
813
891
|
/** Override the store's per-op denial-of-wallet budget for this call (`false` lifts it). */
|
|
@@ -840,7 +918,10 @@ export interface BaseCombineOptions {
|
|
|
840
918
|
* list costs reads proportional to the audience, not to itself.
|
|
841
919
|
*/
|
|
842
920
|
export interface CombineOptions extends BaseCombineOptions {
|
|
843
|
-
/**
|
|
921
|
+
/**
|
|
922
|
+
* Segments whose ids are subtracted from the result. An **expired** handle here excludes nothing, in every
|
|
923
|
+
* combine — it is skipped without being read, exactly as in {@link Segment.andNot}.
|
|
924
|
+
*/
|
|
844
925
|
readonly exclude?: Segment[];
|
|
845
926
|
}
|
|
846
927
|
/**
|
|
@@ -857,12 +938,11 @@ export interface MaterializeOptions extends CombineOptions {
|
|
|
857
938
|
*
|
|
858
939
|
* It is on the call rather than on the store because that is where every other auditable operation takes it
|
|
859
940
|
* (`eraseSubject`, `dropSegment`, `retireExpired`): the caller who performs the act decides where the record
|
|
860
|
-
* goes. Without it a `*Into`
|
|
861
|
-
* leave no trace in the compliance trail.
|
|
941
|
+
* goes. Without it a `*Into` would be the one write path in the library that could make a generation current
|
|
942
|
+
* and leave no trace in the compliance trail.
|
|
862
943
|
*
|
|
863
|
-
* It sits HERE rather than on {@link BaseCombineOptions}
|
|
864
|
-
*
|
|
865
|
-
* anything. Same rule, now applied to itself.
|
|
944
|
+
* It sits HERE rather than on {@link BaseCombineOptions} for the reason this type exists: the streaming verbs
|
|
945
|
+
* write nothing, so an audit sink on `intersect()` would be a parameter that could not do anything.
|
|
866
946
|
*/
|
|
867
947
|
readonly audit?: IAuditSink;
|
|
868
948
|
/**
|
|
@@ -875,11 +955,12 @@ export interface MaterializeOptions extends CombineOptions {
|
|
|
875
955
|
/** Refuse an implausible result rather than publish it. Same bounds, and same meaning, as on `load()`. */
|
|
876
956
|
readonly guard?: LoadGuard;
|
|
877
957
|
/**
|
|
878
|
-
* Generations to keep below the new pointer — see {@link LoadOptions.keep}.
|
|
958
|
+
* Generations to keep below the new pointer — see {@link LoadOptions.keep}. A value that is not a non-negative
|
|
959
|
+
* integer throws `ValidationError`.
|
|
879
960
|
*
|
|
880
|
-
* **Defaults to keeping everything**, unlike `load()`, which keeps 1 and collects the rest.
|
|
881
|
-
*
|
|
882
|
-
*
|
|
961
|
+
* **Defaults to keeping everything**, unlike `load()`, which keeps 1 and collects the rest. An operator's
|
|
962
|
+
* recovery story can depend on a materialisation collecting nothing: `rollbackSegment` refuses a target that
|
|
963
|
+
* has been collected. Pass a number to collect on the way through; `0` keeps only the
|
|
883
964
|
* generation this call publishes.
|
|
884
965
|
*/
|
|
885
966
|
readonly keep?: number;
|
|
@@ -892,15 +973,6 @@ export interface MaterializeOptions extends CombineOptions {
|
|
|
892
973
|
* solves the same problem the same way, by taking {@link BaseCombineOptions}.
|
|
893
974
|
*/
|
|
894
975
|
export type AndNotIntoOptions = Omit<MaterializeOptions, 'exclude'>;
|
|
895
|
-
/** How a `Segment` hands a result stream back to its store to become a new generation of `dest`. */
|
|
896
|
-
/** Build a pinned twin of a handle — injected into `Segment` so it stays free of store wiring. */
|
|
897
|
-
type Pin = (ref: SegmentRef, expiresAt?: number) => Promise<Segment>;
|
|
898
|
-
/**
|
|
899
|
-
* The engine a combine should run on, given every handle involved — `undefined` when none is pinned and the
|
|
900
|
-
* store's own engine will do. Injected so `Segment` stays free of store wiring.
|
|
901
|
-
*/
|
|
902
|
-
type CombineEngine = (handles: readonly Segment[]) => SegmentEngine | undefined;
|
|
903
|
-
type Materialize = (dest: SegmentRef, ids: AsyncIterable<number>, op: string, options?: MaterializeOptions) => Promise<MaterializeResult>;
|
|
904
976
|
/**
|
|
905
977
|
* A handle bound to one segment — the read verbs, plus the three `*Into` verbs that write a **new generation**
|
|
906
978
|
* of another segment.
|
|
@@ -908,36 +980,30 @@ type Materialize = (dest: SegmentRef, ids: AsyncIterable<number>, op: string, op
|
|
|
908
980
|
* **IDs must be integers in `[0, 2^32)`** (dense 32-bit). A non-integer / negative / out-of-range id
|
|
909
981
|
* throws {@link ValidationError}.
|
|
910
982
|
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
983
|
+
* A handle has no per-id write: data enters a segment as a whole generation (`store.load()`, or one of the
|
|
984
|
+
* `*Into` verbs), and leaves it the same way (`eraseSubject`, `dropSegment`).
|
|
913
985
|
*/
|
|
914
986
|
export declare class Segment {
|
|
987
|
+
private readonly metricsOn;
|
|
915
988
|
private readonly engine;
|
|
916
989
|
private readonly ref;
|
|
917
990
|
private readonly clock;
|
|
918
991
|
private readonly metrics;
|
|
919
992
|
private readonly materialize;
|
|
920
|
-
/** Build a pinned twin of this handle — injected so `Segment` stays free of store wiring. */
|
|
921
993
|
private readonly pinned;
|
|
922
994
|
private readonly combineEngine;
|
|
923
995
|
/** Absolute epoch-ms deadline from {@link SegmentOptions.expiresAt}; `undefined` ⇒ this handle never expires. */
|
|
924
|
-
readonly expiresAt?: number
|
|
996
|
+
readonly expiresAt?: number;
|
|
925
997
|
/**
|
|
926
998
|
* The generation this handle is held at, when it came from {@link Segment.pin}. Read by the store so a
|
|
927
999
|
* pinned handle passed as an **operand** is still read at its pin rather than live.
|
|
928
1000
|
*/
|
|
929
|
-
readonly pinnedAt?: PinnedAt
|
|
930
|
-
private readonly metricsOn;
|
|
931
|
-
constructor(engine: SegmentEngine, ref: SegmentRef, clock: Clock, metrics: IMetricsSink, materialize: Materialize,
|
|
932
|
-
/** Build a pinned twin of this handle — injected so `Segment` stays free of store wiring. */
|
|
933
|
-
pinned: Pin, combineEngine: CombineEngine,
|
|
934
|
-
/** Absolute epoch-ms deadline from {@link SegmentOptions.expiresAt}; `undefined` ⇒ this handle never expires. */
|
|
935
|
-
expiresAt?: number | undefined,
|
|
1001
|
+
readonly pinnedAt?: PinnedAt;
|
|
936
1002
|
/**
|
|
937
|
-
*
|
|
938
|
-
*
|
|
1003
|
+
* Not constructible: a handle comes from {@link CloudRoaring.segment}, which wires it to the store's engine,
|
|
1004
|
+
* caches and write path. The constructor takes those internals, so it is not part of the surface.
|
|
939
1005
|
*/
|
|
940
|
-
|
|
1006
|
+
private constructor();
|
|
941
1007
|
/**
|
|
942
1008
|
* **Hold this segment at the generation that is current right now**, for as long as you keep the handle.
|
|
943
1009
|
*
|
|
@@ -956,21 +1022,65 @@ export declare class Segment {
|
|
|
956
1022
|
* `other` at whatever is current — pin each segment if you want the whole query held. And a pinned handle
|
|
957
1023
|
* used as an *operand* is still read at its pin, never live.
|
|
958
1024
|
*
|
|
959
|
-
* **
|
|
1025
|
+
* **One call reads a segment at one generation**, so a combine that holds this segment at two —
|
|
1026
|
+
* `snap0.andNot([snap1])`, or `live.intersect([snap])` — throws {@link ValidationError} when it is read, and an
|
|
1027
|
+
* `*Into` of one throws before it reads anything. Materialise one side first, with `intersectInto(dest, [])`.
|
|
1028
|
+
*
|
|
1029
|
+
* **A pin knows its object, not only its number.** A name purged and loaded again starts again at generation
|
|
1030
|
+
* 0, so a pin of the old segment never reads the new one: what it has already read still answers, as the
|
|
1031
|
+
* instant it pinned, and anything it would have to fetch fails with `NotFoundError`, as a swept pin's does.
|
|
1032
|
+
*
|
|
1033
|
+
* **It is a hold, not a lease.** Nothing here stops a collection deleting the generation underneath
|
|
960
1034
|
* you: a pinned read deliberately does **not** heal forward, because silently serving a different generation
|
|
961
1035
|
* is the one thing a pin exists to prevent, so it fails instead. Size `keep` to cover your longest pinned
|
|
962
|
-
* job — see [
|
|
1036
|
+
* job — see [Generations and `keep`](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/loading.md#generations-and-keep) — or take the pin on a segment
|
|
963
1037
|
* you are not collecting.
|
|
964
1038
|
*
|
|
965
|
-
* A segment with no current generation pins nothing and reads empty, exactly as it would unpinned. A
|
|
966
|
-
*
|
|
967
|
-
*
|
|
1039
|
+
* A segment with no current generation pins nothing and reads empty, exactly as it would unpinned. A pinned
|
|
1040
|
+
* segment whose row is later dropped or destroyed fails once it must open its object again, rather than go empty
|
|
1041
|
+
* part-way through a call. **A pin
|
|
1042
|
+
* keeps the key its reader unwrapped for as long as that reader stays open, and answers from the chunks it
|
|
1043
|
+
* decoded for as long as they stay cached.** This store invalidates a pin:
|
|
1044
|
+
*
|
|
1045
|
+
* - a `load`, a `rollback` or an `*Into` invalidates a pin of the segment it writes;
|
|
1046
|
+
* - `dropSegment` invalidates a pin of the segment it drops, and `retireExpired` a pin of each segment its ledger
|
|
1047
|
+
* lists, retired or not, neither on a dry run;
|
|
1048
|
+
* - `eraseSubject` invalidates a pin of each segment it scans that is not already destroyed.
|
|
1049
|
+
*
|
|
1050
|
+
* An invalidated pin opens its object again, and fails if that object is gone or replaced, or its row is gone or
|
|
1051
|
+
* destroyed.
|
|
1052
|
+
* Anything else leaves the pin as it is: after a `destroySegment` beside this store, or an erasure, a drop or a
|
|
1053
|
+
* retirement through another store, in this process or another, the pin answers from what it holds until this
|
|
1054
|
+
* store's reader cache evicts the pin's reader and its chunk cache evicts the chunks the pin decoded, or
|
|
1055
|
+
* {@link CloudRoaring.invalidate} is called on this store. Where the
|
|
1056
|
+
* object the pin reads has been deleted, by an erasure, a drop or a sweep, a chunk the pin has not cached fails at
|
|
1057
|
+
* once. No timed refresh bounds any of that, as none bounds anything else a pin holds.
|
|
1058
|
+
*
|
|
1059
|
+
* A replacement a pin has found is remembered, so its later chunk reads fail with no request, until the store
|
|
1060
|
+
* forgets it: an invalidation of the segment does, as does a later `pin()` of the same version that opens the object
|
|
1061
|
+
* again, and the store remembers at most `cache.readerMax` of them. So once a restore puts the pinned object back,
|
|
1062
|
+
* call {@link CloudRoaring.invalidate} on this store: the pin then reads the object again.
|
|
1063
|
+
*
|
|
1064
|
+
* `pin()` reads the registry row and opens the generation at once, so the pin knows its object before its first
|
|
1065
|
+
* read. With a registry, pins of one generation taken while its row is unchanged share one reader while the store
|
|
1066
|
+
* keeps it open, pins taken at the same moment included: the first costs a tail read, and a key unwrap for an
|
|
1067
|
+
* encrypted segment, whether or not it is read, and the rest cost the row read alone. Without a registry every
|
|
1068
|
+
* `pin()` lists the segment's objects and makes the tail read, since only the object can tell two incarnations of
|
|
1069
|
+
* a name apart there. It retries a
|
|
1070
|
+
* transient fault as the store's reads do, and heals a generation swept between the two, but it fails where the
|
|
1071
|
+
* generation cannot be opened: `NotFoundError` for a pointer at a missing object, `IntegrityError` for a damaged
|
|
1072
|
+
* or misfiled one.
|
|
968
1073
|
*
|
|
969
1074
|
* Needs a store built on the `.crbm` storage source (the default when you pass a backend or a raw driver). Throws
|
|
970
1075
|
* {@link UnsupportedError} on a store wired with a pre-built source that cannot pin.
|
|
971
1076
|
*/
|
|
972
1077
|
pin(): Promise<Segment>;
|
|
973
|
-
/**
|
|
1078
|
+
/**
|
|
1079
|
+
* An opaque string that names this handle's segment, namespace included: two handles of one segment have the
|
|
1080
|
+
* same key, a live handle and its pins among them. Use it as a `Map` key or a log field when you track handles
|
|
1081
|
+
* and have not kept the name you made them with. The format is not specified and is not a storage key, so
|
|
1082
|
+
* compare keys, never parse one.
|
|
1083
|
+
*/
|
|
974
1084
|
key(): string;
|
|
975
1085
|
/**
|
|
976
1086
|
* Has this handle's deadline passed? **The lazy half of expiry** — the whole check is one comparison against
|
|
@@ -985,15 +1095,15 @@ export declare class Segment {
|
|
|
985
1095
|
*
|
|
986
1096
|
* On the read verbs an expired handle answers empty, which is the point of lazy expiry. On a *write* the same
|
|
987
1097
|
* rule would be destructive in a way nobody asks for: `a.intersectInto(dest, [b])` where `b`'s deadline has
|
|
988
|
-
* quietly passed
|
|
989
|
-
* the cause (a deadline on a handle somewhere) nowhere in the result. Reads degrade to empty; writes must not.
|
|
1098
|
+
* quietly passed would, unrefused, replace `dest` with an **empty generation** — a wipe, reported as a successful
|
|
1099
|
+
* write, with the cause (a deadline on a handle somewhere) nowhere in the result. Reads degrade to empty; writes must not.
|
|
990
1100
|
*
|
|
991
1101
|
* So every handle in the call is checked, `dest` included: an expired `dest` does not change the bytes written,
|
|
992
1102
|
* but a caller who put a deadline on the thing they are writing into has said something contradictory and is
|
|
993
1103
|
* better told than guessed at. Open a handle without `expiresAt` to write, or drop the deadline.
|
|
994
1104
|
*
|
|
995
1105
|
* (The broader guard — refusing to publish an empty or implausible generation over a non-empty one, with an
|
|
996
|
-
* `allowEmpty` override —
|
|
1106
|
+
* `allowEmpty` override — covers these verbs too: they route through the same guarded write path as
|
|
997
1107
|
* {@link CloudRoaring.load}. The two stay separate because they differ in kind. That one is a REPORTED
|
|
998
1108
|
* refusal a caller may legitimately override; an expired handle is a wiring mistake, so it THROWS, before
|
|
999
1109
|
* any object is written — and `allowEmpty: true` does not reach it.)
|
|
@@ -1010,15 +1120,54 @@ export declare class Segment {
|
|
|
1010
1120
|
private timed;
|
|
1011
1121
|
/** Membership: one chunk — the cache, else one ranged GET. Throws {@link ValidationError} on a bad id. */
|
|
1012
1122
|
has(id: number): Promise<boolean>;
|
|
1013
|
-
/**
|
|
1123
|
+
/**
|
|
1124
|
+
* Cardinality — summed from the `.crbm` index with **zero payload reads** on a loaded segment.
|
|
1125
|
+
*
|
|
1126
|
+
* What this trusts: the answer is the sum of the per-chunk cardinalities the index records, and no payload is
|
|
1127
|
+
* decoded to confirm it. The index is checked for internal consistency when the object is opened (each key in
|
|
1128
|
+
* range and ascending, each cardinality in `1..65536`, each payload inside the payload region, and, on an
|
|
1129
|
+
* unencrypted object, the footer's chunk count and total agreeing with the index), and a corrupt index that is
|
|
1130
|
+
* still internally consistent yields a wrong count. `iterate()` and the combines decode the payloads, whose
|
|
1131
|
+
* structure is checked.
|
|
1132
|
+
*/
|
|
1014
1133
|
count(): Promise<number>;
|
|
1015
|
-
/** Every id, ascending, streamed one chunk at a time. */
|
|
1016
|
-
iterate(): AsyncIterable<number>;
|
|
1017
1134
|
/**
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1135
|
+
* Every id, ascending, streamed one chunk at a time. Pass a range to read part of the segment: the ids in
|
|
1136
|
+
* `(after, through]`, fetching only the chunks the range overlaps. The per-op budget is charged for every chunk in
|
|
1137
|
+
* the range, so give a page `through` as well as `after`. On a pinned handle it reads the pinned generation, as a
|
|
1138
|
+
* full read does.
|
|
1139
|
+
*
|
|
1140
|
+
* ```ts
|
|
1141
|
+
* // Keyset paging: each page resumes where the one before ended, and stops at its window's end.
|
|
1142
|
+
* for await (const id of seg.iterate({ after: previousEnd, through: windowEnd })) await send(id);
|
|
1143
|
+
* ```
|
|
1144
|
+
*
|
|
1145
|
+
* Each bound is optional and an integer in `0..4294967295`; a bad one throws {@link ValidationError} when the
|
|
1146
|
+
* stream is first read. `after >= through` is an empty range, which reads nothing. An expired handle reads empty
|
|
1147
|
+
* without checking its options, as every read of one does.
|
|
1148
|
+
*/
|
|
1149
|
+
iterate(options?: IdRange): AsyncIterable<number>;
|
|
1150
|
+
/**
|
|
1151
|
+
* The options a combine hands the engine, read once, when it is called ({@link readOptions}), with the `exclude`
|
|
1152
|
+
* it was given, already reduced to its live handles ({@link liveExcludes}) and mapped down to the plain refs
|
|
1153
|
+
* `core` takes. A method rather than a module function because `ref` is class-private — the encapsulation is
|
|
1154
|
+
* worth more than the free function.
|
|
1155
|
+
*
|
|
1156
|
+
* Each field is named rather than copied with a rest spread: a rest copy takes only own enumerable properties, so
|
|
1157
|
+
* a bound held in a getter or inherited from a prototype was dropped, and the read silently widened to the whole
|
|
1158
|
+
* segment.
|
|
1020
1159
|
*/
|
|
1021
1160
|
private refsIn;
|
|
1161
|
+
/**
|
|
1162
|
+
* The handles in `options.exclude` that have not expired. **An expired exclusion excludes nothing**, in every
|
|
1163
|
+
* combine, so it is dropped here, before the engine is asked for anything: it is never fetched, never
|
|
1164
|
+
* checked for absence, and never counted in the pin-consistency check. `exclude` is read once, so a getter sees
|
|
1165
|
+
* one call.
|
|
1166
|
+
*
|
|
1167
|
+
* With no `exclude` this is one property read and a shared empty list, and with none expired it returns the
|
|
1168
|
+
* caller's own array, so the common case allocates nothing.
|
|
1169
|
+
*/
|
|
1170
|
+
private liveExcludes;
|
|
1022
1171
|
/**
|
|
1023
1172
|
* Chunk-skipping intersection: stream the ids in **this** segment AND every segment in `others`, ascending.
|
|
1024
1173
|
* Fetches only the Storage chunks present in *all* operands (a key absent from any operand contributes nothing
|
|
@@ -1031,7 +1180,8 @@ export declare class Segment {
|
|
|
1031
1180
|
/**
|
|
1032
1181
|
* Materialize `this ∩ others…` (minus `exclude`) as a **new generation of `dest`** — `dest`'s previous contents
|
|
1033
1182
|
* are superseded, not added to. Streaming + bounded-memory; the result is one immutable object published
|
|
1034
|
-
*
|
|
1183
|
+
* as a load is (the pointer advances to it, fenced on the row), so readers of `dest` see either the old generation
|
|
1184
|
+
* or the new one, never a partial. Needs the
|
|
1035
1185
|
* store built with a backend (throws {@link UnsupportedError} otherwise).
|
|
1036
1186
|
*
|
|
1037
1187
|
* **An empty result does NOT overwrite a non-empty destination.** A combine that comes out empty is far more
|
|
@@ -1069,6 +1219,10 @@ export declare class Segment {
|
|
|
1069
1219
|
* suppression list: at most one read per surviving key of `this`, so subtracting a 61,000-chunk global
|
|
1070
1220
|
* opt-out list from a 40-chunk audience costs at most 40 reads, not 61,000.
|
|
1071
1221
|
*
|
|
1222
|
+
* An expired handle in `excludes` excludes nothing, and is skipped without being read; if every one has
|
|
1223
|
+
* expired the result is `this` whole. An expired `this` is empty. `exclude` on `intersect` and `union` follows
|
|
1224
|
+
* the same rule.
|
|
1225
|
+
*
|
|
1072
1226
|
* To filter the *result of an intersection*, do not chain — pass `exclude` to {@link intersect} instead, so
|
|
1073
1227
|
* the suppression folds into the same pass rather than materializing an intermediate segment first.
|
|
1074
1228
|
*/
|
|
@@ -1078,15 +1232,14 @@ export declare class Segment {
|
|
|
1078
1232
|
/**
|
|
1079
1233
|
* Grounded cost report for this segment: storage cost from its **real** `.crbm` size (exact, no payload
|
|
1080
1234
|
* reads); request cost from the supplied `workload` rates. A segment with no Storage generation reports zero
|
|
1081
|
-
* storage.
|
|
1235
|
+
* storage. The pointer refresh is priced at the store's own `cache.genTtlMs`, or at none when the store never
|
|
1236
|
+
* refreshes, unless the workload sets `genTtlMs`. See {@link CostReport} — it always includes a verdict (incl.
|
|
1237
|
+
* the lose-zone).
|
|
1082
1238
|
*/
|
|
1083
1239
|
costReport(options?: {
|
|
1084
1240
|
pricing?: PricingProfile;
|
|
1085
1241
|
workload?: Workload;
|
|
1086
1242
|
}): Promise<CostReport>;
|
|
1087
1243
|
}
|
|
1088
|
-
export
|
|
1089
|
-
export {
|
|
1090
|
-
export { SafeBitmap, roaringCodec } from './roaring-codec.js';
|
|
1091
|
-
/** Package version marker. Kept in sync with package.json at release. */
|
|
1092
|
-
export declare const VERSION = "0.10.0";
|
|
1244
|
+
export { MemoryStorage, LocalFsStorage, CrbmStorageChunkSource, destroySegment, eraseNamespace, excludingReservedRows, readRetentionPolicy, MIN_EXPIRES_AT_MS, InProcessKeystore, NodeAead, aadFor, CloudRoaringError, ValidationError, WriteConflictError, IntegrityError, NotFoundError, UnsupportedError, CapabilityError, TransientError, KeyUnavailableError, BudgetExceededError, isCloudRoaringError, isWriteConflictError, isTransientError, isNotFoundError, isIntegrityError, isValidationError, CrbmReader, BufferReader, CountingMetricsSink, RecordingAuditSink, DEFAULT_RETRY_POLICY, AWS_US_EAST_1_ONDEMAND, ELASTICACHE_REDIS_US_EAST_1_ONDEMAND, ONE_REDIS_HA_CLUSTER, } from '@cloudbitmaps/core';
|
|
1245
|
+
export type { Aead, AeadSealed, AuditEvent, BlobReader, BlobSink, Budget, BudgetOption, ChunkRef, Clock, CodecBitmap, CodecInterface, ConsistencyErrorEntry, ConsistencyIssue, ConsistencyReport, CostReport, CrbmCrypto, CrbmReaderOptions, CrbmStorageChunkSourceOptions, DestroyResult, DropResult, EraseDeps, EstimateInput, ExportFailure, ExportFormat, ExportManifest, ExportOptions, ExportSink, ExportWriter, ExportedSegment, GenKey, GenerationEntry, GovernanceMeta, IAuditSink, IKeystore, IMetricsSink, IRegistryDriver, IStorageDriver, IdRange, InProcessKeystoreOptions, LoadGuard, LoadOptions, LoadRefusal, LoadResult, LocalFsStorageOptions, MemoryStorageOptions, MetricEvent, MetricOpName, MetricsSnapshot, NewRegistryRecord, PinnedAt, PinnedObject, PricingProfile, RedisNodeType, RedisSizing, RegCaps, RegistryPatch, RegistryRecord, RegistryStatus, RetentionPolicy, RetireEntry, RetireExpiredOptions, RetireExpiredResult, RetryPolicy, Rng, RollbackResult, SegmentInfo, SegmentRef, SegmentSize, SegmentSizing, SetRetentionResult, StorageBackend, StorageCaps, StorageChunkSource, Token, Workload, WrappedDek, } from '@cloudbitmaps/core';
|