@cloudbitmaps/core 0.19.1 → 0.21.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/README.md +85 -0
- package/dist/{chunk-RSLVTRJY.js → chunk-IGLIZBR7.js} +982 -281
- package/dist/chunk-IGLIZBR7.js.map +7 -0
- package/dist/core/absent-operands.d.ts +8 -0
- package/dist/core/audit.d.ts +67 -4
- package/dist/core/bit-route.d.ts +2 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/bulk-load.d.ts +148 -0
- package/dist/core/cas-retry.d.ts +27 -0
- package/dist/core/check.d.ts +22 -0
- package/dist/core/chunk-checks.d.ts +6 -1
- package/dist/core/chunk-window.d.ts +11 -4
- package/dist/core/combine-expr.d.ts +1 -3
- package/dist/core/combine-feed.d.ts +2 -16
- package/dist/core/combine-many.d.ts +21 -15
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +76 -18
- package/dist/core/crbm/fingerprint.d.ts +23 -0
- package/dist/core/crbm/format.d.ts +2 -5
- package/dist/core/crbm/frame.d.ts +10 -0
- package/dist/core/crbm/plan-reads.d.ts +9 -3
- package/dist/core/crbm/reader.d.ts +51 -12
- package/dist/core/crbm/varint.d.ts +2 -1
- package/dist/core/crbm/writer.d.ts +5 -1
- package/dist/core/crbm-open.d.ts +154 -0
- package/dist/core/crbm-storage-source.d.ts +243 -490
- package/dist/core/crbm-write.d.ts +75 -0
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/engine.d.ts +239 -47
- package/dist/core/erase-id.d.ts +97 -27
- package/dist/core/erasure.d.ts +4 -3
- package/dist/core/errors.d.ts +8 -2
- package/dist/core/generation-gc.d.ts +131 -16
- package/dist/core/generation-list.d.ts +11 -0
- package/dist/core/id-window.d.ts +46 -0
- package/dist/core/invalidation-scope.d.ts +77 -0
- package/dist/core/known-generations-shape.d.ts +13 -0
- package/dist/core/known-generations.d.ts +147 -0
- package/dist/core/leases.d.ts +9 -1
- package/dist/core/ledger-seed.d.ts +17 -0
- package/dist/core/load.d.ts +17 -36
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +9 -7
- package/dist/core/order.d.ts +2 -0
- package/dist/core/pinned-storage-source.d.ts +7 -7
- package/dist/core/pointer-id.d.ts +19 -0
- package/dist/core/ports.d.ts +210 -45
- package/dist/core/publish.d.ts +207 -0
- package/dist/core/reader-defaults.d.ts +14 -3
- package/dist/core/registry-scan.d.ts +0 -2
- package/dist/core/retention-sweep.d.ts +69 -65
- package/dist/core/retention.d.ts +1 -1
- package/dist/core/retry.d.ts +12 -19
- package/dist/core/rollback.d.ts +62 -32
- package/dist/core/summary.d.ts +74 -25
- package/dist/core/token.d.ts +13 -8
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +6 -1
- package/dist/driver-kit.js +90 -10
- package/dist/driver-kit.js.map +3 -3
- package/dist/drivers/_shared/bytes.d.ts +6 -0
- package/dist/drivers/_shared/faults.d.ts +15 -0
- package/dist/drivers/_shared/object-keys.d.ts +18 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
- package/dist/drivers/_shared/object-registry.d.ts +3 -1
- package/dist/drivers/_shared/options.d.ts +19 -0
- package/dist/drivers/_shared/registry.d.ts +31 -19
- package/dist/drivers/localfs/exact-case.d.ts +0 -3
- package/dist/drivers/localfs/fs-util.d.ts +22 -0
- package/dist/drivers/localfs/registry.d.ts +10 -4
- package/dist/drivers/localfs/storage.d.ts +16 -8
- package/dist/drivers/memory.d.ts +14 -9
- package/dist/drivers/retry/retrying-chunk-source.d.ts +1 -5
- package/dist/export/index.d.ts +16 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +4303 -2938
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-RSLVTRJY.js.map +0 -7
- package/dist/core/cost.d.ts +0 -351
- package/dist/core/kept-generations.d.ts +0 -40
|
@@ -4,8 +4,6 @@ import type { IRegistryDriver, RegistryRecord } from './ports.js';
|
|
|
4
4
|
* the fleet really is that large *and* the memory is there; narrow the scan with a `namespace` otherwise.
|
|
5
5
|
*/
|
|
6
6
|
export declare const DEFAULT_MAX_SCAN_SEGMENTS = 250000;
|
|
7
|
-
/** Fail fast on a bad ceiling BEFORE the (possibly huge) scan, not after. */
|
|
8
|
-
export declare function validateMaxScanSegments(value: number, op: string): void;
|
|
9
7
|
/**
|
|
10
8
|
* Drain every registry record (optionally one namespace) into an array, refusing past `maxScanSegments`.
|
|
11
9
|
*
|
|
@@ -1,69 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The retention **sweep** — the thing that acts on the policies `setSegmentRetention` records.
|
|
3
|
-
*
|
|
4
|
-
* `retireExpired` enumerates the registry, selects the segments whose `expiresAt` has passed, and retires each
|
|
5
|
-
* one through {@link dropSegment}. It deliberately **delegates rather than reimplements**: the registry → Storage
|
|
6
|
-
* ordering, the re-sweep for an object a load was still writing, and the `generationsRemaining` report are all
|
|
7
|
-
* load-bearing and already live there. A sweep that open-coded the deletions would be a second
|
|
8
|
-
* implementation of the most dangerous ordering in the library.
|
|
9
|
-
*
|
|
10
|
-
* **This is a call, not a daemon.** Nothing here schedules itself. You run it from whatever heartbeat your
|
|
11
|
-
* deployment already has — an EventBridge rule, a Kubernetes CronJob, a queue consumer, the job that runs your
|
|
12
|
-
* loads — and the library stays a library: it has to behave identically in a Lambda, an edge isolate and a
|
|
13
|
-
* long-lived server, and a timer that only works in one of those is worse than none.
|
|
14
|
-
*
|
|
15
|
-
* Three properties make it safe to point at a fleet:
|
|
16
|
-
*
|
|
17
|
-
* - **`dryRun` at the sweep level.** `dropSegment`'s `confirmSegment` guard is vacuous in a loop (it is the same
|
|
18
|
-
* variable twice), so the meaningful preview is here: it reports what it *would* retire, per segment, and
|
|
19
|
-
* touches nothing.
|
|
20
|
-
* - **A per-cycle `limit`.** Clock skew, a bad backfill, or a namespace-wide policy mistake should cost one
|
|
21
|
-
* bounded batch, not the fleet. When the limit bites, the result says so (`limited: true`) rather than looking
|
|
22
|
-
* like a completed sweep.
|
|
23
|
-
* - **A ledger, never an exception.** A per-segment fault is recorded and the sweep continues — the caller's
|
|
24
|
-
* load-bearing question is *which* segments were retired, and a throw from the middle of the loop answers it
|
|
25
|
-
* for none of them while having already retired some. Same posture as `eraseNamespace`.
|
|
26
|
-
*
|
|
27
|
-
* It also **purges the tombstones its own retirements leave behind**. A retired segment that had a registry row
|
|
28
|
-
* gets a `destroyed` row, and one dead row per retired daily bucket — or per retired dedup wave — is exactly the
|
|
29
|
-
* registry litter `dropSegment` already refuses to create for a row-less accumulator. Purging is narrow on
|
|
30
|
-
* purpose: only a tombstone carrying the **stamp this sweep writes on its own retirements** (so it is attributably
|
|
31
|
-
* ours: a crypto-shred's tombstone does not carry it, unless the shred lands between this sweep's re-read of the
|
|
32
|
-
* row and its drop, which then reports the tombstone dropped and stamps it), only after a grace period, and only
|
|
33
|
-
* once Storage is provably empty for it —
|
|
34
|
-
* because deleting the row is what makes the name reusable and takes the segment out of reach of
|
|
35
|
-
* the generation collection.
|
|
36
|
-
*
|
|
37
|
-
* **On a registry that reports `conditionalDelete`, the purge removes the row for good**, so a full scan stops paying a
|
|
38
|
-
* read for every name a namespace ever held: the scan costs what is live and what is inside its grace. The delete is
|
|
39
|
-
* fenced on the token the purge judged and applied by the store only to that version of the row. Each retirement
|
|
40
|
-
* files a pointer in the due index under the day its tombstone's grace ends, so `scan: 'index'` purges as well as
|
|
41
|
-
* retires; nothing on the row records that day, which the purge derives from the stamp. The purge removes every pointer
|
|
42
|
-
* it read to the row, and a pointer that outlives its row anyway (a purge that ran with another grace, a delete that
|
|
43
|
-
* landed and lost its response, a pointer older than an index scan's lookback) is removed by the next scan that reads
|
|
44
|
-
* it: an index scan in its buckets, and an unscoped fleet scan, which reads every pointer, in all of them. Where a
|
|
45
|
-
* delete only rewrites the row as a tombstone, none of this is done: nothing is removed for good, so a pointer would
|
|
46
|
-
* only add a row for every scan to read.
|
|
47
|
-
*/
|
|
48
1
|
import { type IAuditSink } from './audit.js';
|
|
49
2
|
import type { DropDeps, DropResult } from './erasure.js';
|
|
50
3
|
/** Default cap on retirements per sweep — a bounded batch, so a policy mistake costs one batch, not the fleet. */
|
|
51
4
|
export declare const DEFAULT_RETIRE_LIMIT = 100;
|
|
52
|
-
/**
|
|
53
|
-
* How many purges in a row the registry may refuse in one call before the sweep stops purging for the rest of it. A
|
|
54
|
-
* blanket refusal (a policy that denies delete) costs this many attempts and no more; a refusal particular to one row
|
|
55
|
-
* (a blob with a snapshot, an object under a legal hold) does not hold the purges behind it, since a purge that succeeds
|
|
56
|
-
* starts the count again.
|
|
57
|
-
*/
|
|
58
|
-
export declare const MAX_CONSECUTIVE_PURGE_FAULTS = 3;
|
|
59
|
-
/**
|
|
60
|
-
* Default delay before a retirement's own tombstone row is purged: 24 h.
|
|
61
|
-
*
|
|
62
|
-
* The row is a fence — while it exists, a load refuses the segment, so
|
|
63
|
-
* a load that was mid-write when the drop landed cannot resurrect it. That window is
|
|
64
|
-
* seconds to minutes in practice; a day of margin costs one tiny row and removes any need to reason about it.
|
|
65
|
-
*/
|
|
66
|
-
export declare const DEFAULT_TOMBSTONE_GRACE_MS = 86400000;
|
|
67
5
|
export interface RetireExpiredOptions {
|
|
68
6
|
/** Scope the sweep to one namespace. Omit to sweep every namespace the registry knows. */
|
|
69
7
|
readonly namespace?: string;
|
|
@@ -74,8 +12,9 @@ export interface RetireExpiredOptions {
|
|
|
74
12
|
readonly now: number;
|
|
75
13
|
/**
|
|
76
14
|
* Maximum segments to retire in this cycle (default 100). It also bounds the purges attempted (a purge the registry
|
|
77
|
-
* refuses is not charged; {@link RetireExpiredResult.purgeFaults} says how purging stops after refusals)
|
|
78
|
-
* due-index pointers to nothing one call removes
|
|
15
|
+
* refuses is not charged; {@link RetireExpiredResult.purgeFaults} says how purging stops after refusals), the
|
|
16
|
+
* due-index pointers to nothing one call removes, and, counted apart from the retirements, the tombstones this sweep
|
|
17
|
+
* did not write whose pointers one call removes.
|
|
79
18
|
*/
|
|
80
19
|
readonly limit?: number;
|
|
81
20
|
/** Report what would be retired and change nothing. */
|
|
@@ -209,7 +148,10 @@ export interface RetireExpiredResult {
|
|
|
209
148
|
readonly wouldRetire: number;
|
|
210
149
|
/** Tombstone rows actually deleted. Zero under `dryRun`; the `would-purge-tombstone` entries carry the preview. */
|
|
211
150
|
readonly tombstonesPurged: number;
|
|
212
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* True when `limit` cut the cycle short — **more segments are still eligible**. Re-run. The cap on removing pointers
|
|
153
|
+
* of tombstones this sweep did not write leaves it false.
|
|
154
|
+
*/
|
|
213
155
|
readonly limited: boolean;
|
|
214
156
|
/**
|
|
215
157
|
* Deletes this sweep attempted that were **refused for a reason other than a lost race**: a tombstone's purge
|
|
@@ -238,6 +180,67 @@ export interface RetireExpiredResult {
|
|
|
238
180
|
* or a paused schedule recovers on its own, short enough that a cycle is eight list calls rather than hundreds.
|
|
239
181
|
*/
|
|
240
182
|
export declare const DEFAULT_LOOKBACK_BUCKETS = 7;
|
|
183
|
+
/** The options of a sweep, validated, with every default filled in. */
|
|
184
|
+
interface ResolvedRetireOptions {
|
|
185
|
+
readonly audit: IAuditSink | undefined;
|
|
186
|
+
readonly namespace: string | undefined;
|
|
187
|
+
readonly now: number;
|
|
188
|
+
readonly limit: number;
|
|
189
|
+
readonly purgeTombstones: boolean;
|
|
190
|
+
readonly grace: number;
|
|
191
|
+
readonly maxScanSegments: number;
|
|
192
|
+
readonly dryRun: boolean;
|
|
193
|
+
readonly scan: 'fleet' | 'index';
|
|
194
|
+
readonly lookbackBuckets: number;
|
|
195
|
+
readonly shards: readonly number[] | undefined;
|
|
196
|
+
readonly totalShards: number;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check a sweep's options and fill in the defaults. Pure: it reads nothing but `options`, and throws
|
|
200
|
+
* `ValidationError` for the first bad one, in the order the checks below run.
|
|
201
|
+
*/
|
|
202
|
+
export declare function resolveRetireOptions(options: RetireExpiredOptions): ResolvedRetireOptions;
|
|
203
|
+
/**
|
|
204
|
+
* The counters and the ledger of one sweep, and the two budgets that bound it.
|
|
205
|
+
*
|
|
206
|
+
* The budget is charged on ATTEMPT, not on success, and that distinction is the whole guard. `dropSegment`
|
|
207
|
+
* writes the tombstone BEFORE sweeping Storage, so a fault in the Storage phase is a segment that is
|
|
208
|
+
* already retired. Counting only successes would let a partial storage outage march through the entire fleet
|
|
209
|
+
* with the cap never engaging, reporting `retired: 0, limited: false` (a "completed sweep that retired nothing")
|
|
210
|
+
* while every segment in the namespace is tombstoned. So a step charges `attempted` before the call that can delete
|
|
211
|
+
* (the drop, or the purge's collection), after any re-read that finds nothing due, and gives the charge back in two
|
|
212
|
+
* places only: a purge the registry or storage refused (`refusedPurge`), and a drop that wrote and deleted nothing
|
|
213
|
+
* because the row was no longer the sweep's to retire (`dropSegmentFor` returned `null`).
|
|
214
|
+
*/
|
|
215
|
+
export declare class SweepTally {
|
|
216
|
+
readonly entries: RetireEntry[];
|
|
217
|
+
eligible: number;
|
|
218
|
+
retired: number;
|
|
219
|
+
wouldRetire: number;
|
|
220
|
+
tombstonesPurged: number;
|
|
221
|
+
limited: boolean;
|
|
222
|
+
/** Deletes the registry refused for a reason other than a lost race. */
|
|
223
|
+
purgeFaults: number;
|
|
224
|
+
/** The first one's reason. A lost race is the row or pointer changing under a fenced delete, which is the fence working. */
|
|
225
|
+
firstPurgeFault: `failed: ${string}` | undefined;
|
|
226
|
+
/** Retirements and tombstone purges begun, charged against `limit`. */
|
|
227
|
+
attempted: number;
|
|
228
|
+
/** Tombstones the sweep did not write whose pointers it worked, bounded by `limit` apart from `attempted`. */
|
|
229
|
+
attemptedForeign: number;
|
|
230
|
+
/**
|
|
231
|
+
* Purging stops after this many refused purges in a row, and a purge that succeeds starts the count again. Each
|
|
232
|
+
* refused attempt costs reads before it fails, and a blanket refusal would fail them all; one row's refusal must
|
|
233
|
+
* not hold the rest.
|
|
234
|
+
*/
|
|
235
|
+
purgeRun: number;
|
|
236
|
+
purging: boolean;
|
|
237
|
+
/** Record a step's ledger entry; `null` adds nothing (a step with nothing to report, or one that recorded its own). */
|
|
238
|
+
add(entry: RetireEntry | null): void;
|
|
239
|
+
/** Count a delete the registry refused, unless it was a lost race. */
|
|
240
|
+
readonly noteFault: (err: unknown) => void;
|
|
241
|
+
/** A purge the registry refused is not charged to `limit`, and after enough in a row purging stops. */
|
|
242
|
+
refusedPurge(): void;
|
|
243
|
+
}
|
|
241
244
|
/**
|
|
242
245
|
* Retire every segment whose retention policy has expired, and clean up the tombstones earlier sweeps left.
|
|
243
246
|
*
|
|
@@ -246,3 +249,4 @@ export declare const DEFAULT_LOOKBACK_BUCKETS = 7;
|
|
|
246
249
|
* condition, not a partial result to be mistaken for a complete sweep.
|
|
247
250
|
*/
|
|
248
251
|
export declare function retireExpired(deps: DropDeps, options: RetireExpiredOptions): Promise<RetireExpiredResult>;
|
|
252
|
+
export {};
|
package/dist/core/retention.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ChurnDeps } from './leases.js';
|
|
2
2
|
import type { GovernanceMeta, IRegistryDriver, SegmentRef } from './ports.js';
|
|
3
3
|
/**
|
|
4
4
|
* The smallest accepted `expiresAt`: 2001-09-09, the point below which an epoch-**seconds** value would land.
|
package/dist/core/retry.d.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `withRetry` — the one retry/backoff primitive.
|
|
3
|
-
*
|
|
4
|
-
* A pure, storage-agnostic helper: it knows nothing about HTTP, AWS, or any SDK. It retries an operation
|
|
5
|
-
* while the operation keeps failing with a **retryable** error (by default {@link TransientError}), waiting
|
|
6
|
-
* a bounded, jittered, exponentially-growing delay between attempts. Time and randomness are **injected**
|
|
7
|
-
* (the determinism seam) — production wires a `setTimeout`-backed clock + a real RNG; the
|
|
8
|
-
* tests wire virtual ones — so a backoff schedule is replayable from a seed and unit tests run with no
|
|
9
|
-
* real sleeping.
|
|
10
|
-
*
|
|
11
|
-
* It deliberately does **not** retry deterministic failures ({@link WriteConflictError},
|
|
12
|
-
* {@link ValidationError}, {@link IntegrityError}, {@link NotFoundError}, …): retrying those either can't
|
|
13
|
-
* help or would be incorrect. A pointer conflict is retried by a *separate* loop inside
|
|
14
|
-
* the publish, because each attempt must re-read the row and re-decide — a blind replay of the same
|
|
15
|
-
* compare-and-swap would either fail again on a stale token or, worse, advance a pointer whose state has
|
|
16
|
-
* changed underneath it. A write-once object collision is never replayed at all: the generation number is
|
|
17
|
-
* taken, so the caller has to pick a new one.
|
|
18
|
-
*/
|
|
19
1
|
import type { Clock, Rng } from './determinism.js';
|
|
20
2
|
export interface RetryPolicy {
|
|
21
3
|
/** Total attempts including the first (so `maxAttempts: 4` = 1 try + 3 retries). Must be ≥ 1. */
|
|
@@ -61,6 +43,15 @@ export interface RetryDeps {
|
|
|
61
43
|
export declare function backoffDelayMs(policy: RetryPolicy, attempt: number): number;
|
|
62
44
|
/** Apply the policy's jitter to a computed delay using the injected RNG. */
|
|
63
45
|
export declare function applyJitter(policy: RetryPolicy, delayMs: number, rng: Rng): number;
|
|
46
|
+
/** A clock that can wait: the one member {@link jitteredWait} uses. */
|
|
47
|
+
interface Sleeper {
|
|
48
|
+
readonly sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Wait a uniformly spread share of `boundMs`, whole milliseconds, by the clock's `sleep`. With no `rng` it waits the
|
|
52
|
+
* whole bound; with no `sleep` it does not wait.
|
|
53
|
+
*/
|
|
54
|
+
export declare function jitteredWait(clock: Sleeper | undefined, rng: Rng | undefined, boundMs: number): Promise<void>;
|
|
64
55
|
/**
|
|
65
56
|
* The store's read retry, as a write path takes it for the reads it makes along the way: a policy (the default when
|
|
66
57
|
* absent) and what {@link withRetry} needs to run it.
|
|
@@ -78,7 +69,8 @@ export declare function retryRead<T>(op: () => Promise<T>, retry: ReadRetry | un
|
|
|
78
69
|
/**
|
|
79
70
|
* `policy`, checked when it is wired rather than at the first retry: a delay or factor that is not a finite number, a
|
|
80
71
|
* negative delay, a factor below 1 or a jitter mode it does not know would make the backoff `NaN`, which a timer reads
|
|
81
|
-
* as 1 ms, so bounded backoff silently became a hot retry loop.
|
|
72
|
+
* as 1 ms, so bounded backoff silently became a hot retry loop. `maxAttempts` is a whole number, and `maxDelayMs` is not
|
|
73
|
+
* below `baseDelayMs`, which it would otherwise cap every delay under.
|
|
82
74
|
*/
|
|
83
75
|
export declare function checkedRetryPolicy(policy: RetryPolicy): RetryPolicy;
|
|
84
76
|
/**
|
|
@@ -87,3 +79,4 @@ export declare function checkedRetryPolicy(policy: RetryPolicy): RetryPolicy;
|
|
|
87
79
|
* the *operation's* error — never a wrapper — so callers keep their typed-error branching.
|
|
88
80
|
*/
|
|
89
81
|
export declare function withRetry<T>(op: () => Promise<T>, policy: RetryPolicy, deps: RetryDeps): Promise<T>;
|
|
82
|
+
export {};
|
package/dist/core/rollback.d.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `listGenerations` and `rollbackSegment` — see what a segment has been, and put it back.
|
|
3
|
-
*
|
|
4
|
-
* Immutable generations mean the previous version of a segment is usually still sitting in the bucket: a load
|
|
5
|
-
* that replaced it did not overwrite anything, it wrote a new object and moved a pointer. So recovering from a
|
|
6
|
-
* bad load is, in principle, moving the pointer back, which no load, publish or sweep can do, because every other
|
|
7
|
-
* write path in the library only ever advances the pointer, and refuses a move that would lower it.
|
|
8
|
-
*
|
|
9
|
-
* That refusal is right for a *writer*: a load whose ids came from upstream loses nothing by being out-raced, and
|
|
10
|
-
* letting it regress the pointer would let a slow loader silently undo a fast one. It is wrong for an *operator*,
|
|
11
|
-
* who has looked at the segment, decided the current generation is wrong, and knows which one they want. So
|
|
12
|
-
* rollback is the one call that moves the pointer backwards, and it is deliberately not reachable from any
|
|
13
|
-
* automatic path — no sweep, no retry, no reconciliation calls it.
|
|
14
|
-
*
|
|
15
|
-
* It is audited for the same reason. Every other pointer move can be reconstructed from "a load happened"; this
|
|
16
|
-
* one is a human overriding the ordering rule the rest of the system relies on, which is exactly the event an
|
|
17
|
-
* Art. 30 record or an incident review wants to find.
|
|
18
|
-
*/
|
|
19
1
|
import { type IAuditSink } from './audit.js';
|
|
20
2
|
import type { IKeystore } from './crypto.js';
|
|
21
3
|
import { type ChurnDeps } from './leases.js';
|
|
@@ -26,8 +8,10 @@ export interface GenerationListDeps extends ChurnDeps {
|
|
|
26
8
|
readonly registry: IRegistryDriver;
|
|
27
9
|
/**
|
|
28
10
|
* The key of an encrypted segment, so a rollback can read its target's count and metadata and seal them into the
|
|
29
|
-
* row. Without it, a rollback of an encrypted segment
|
|
30
|
-
* generation it moves to, which a reader then takes from the object
|
|
11
|
+
* row. Without it, a rollback of an encrypted segment onto a generation the row knows still works and leaves the
|
|
12
|
+
* row with no summary of the generation it moves to, which a reader then takes from the object; `allowUnknown` needs
|
|
13
|
+
* it, and is refused with `KeyUnavailableError`, or throws `TransientError` when the key service did not answer in
|
|
14
|
+
* time (the keystore's own, or one whose `cause` is the timeout it threw).
|
|
31
15
|
*/
|
|
32
16
|
readonly keystore?: IKeystore;
|
|
33
17
|
}
|
|
@@ -36,6 +20,15 @@ export interface GenerationEntry {
|
|
|
36
20
|
readonly generation: number;
|
|
37
21
|
/** Whether the registry pointer currently names this generation. */
|
|
38
22
|
readonly current: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the row's record of the generations it published, `knownGens`, names this generation. A rollback onto a known
|
|
25
|
+
* generation needs no `allowUnknown`; an unknown one needs it, except below the pointer in the two cases the pointer
|
|
26
|
+
* alone decides (a row with no usable `knownGens`, and a target below a full one's bound), where it needs none. The
|
|
27
|
+
* row's word, read with the listing's one registry read: the object is not opened, so
|
|
28
|
+
* its checksum is not compared. `false` for every generation of a row whose record is missing or unusable, and for a
|
|
29
|
+
* segment with no row. The current generation of a row with a usable record is always known.
|
|
30
|
+
*/
|
|
31
|
+
readonly known: boolean;
|
|
39
32
|
/**
|
|
40
33
|
* The current generation's id count, from its registry row, with no read of the object. Present only on the current
|
|
41
34
|
* entry, and only when the row's summary can be used: for an encrypted segment that takes a keystore that opens the
|
|
@@ -65,11 +58,20 @@ export interface RollbackResult {
|
|
|
65
58
|
readonly generation: number;
|
|
66
59
|
}
|
|
67
60
|
/**
|
|
68
|
-
* Move a segment's pointer
|
|
61
|
+
* Move a segment's pointer to a generation that is still in the bucket and that the segment's row knows it published.
|
|
69
62
|
*
|
|
70
63
|
* The only non-forward-only publish in the library, and the only one an operator has to ask for by name. It
|
|
71
64
|
* refuses rather than guesses:
|
|
72
65
|
*
|
|
66
|
+
* - a generation the **row does not know** is refused with `ValidationError`, before any request to storage. The row
|
|
67
|
+
* records, in `knownGens`, the generations its loads and rollbacks made current, with the footer checksum of each
|
|
68
|
+
* object; a target outside that record is where objects live that no row ever published: a load that wrote its object
|
|
69
|
+
* and died before the publish, a refused load's object, an object sealed under a key no row holds, a load published
|
|
70
|
+
* after the point a registry was restored to. {@link rollbackSegment}'s `allowUnknown` is the one explicit override;
|
|
71
|
+
* - a known generation whose object is **not the one the row recorded** (its footer checksum differs: another object was
|
|
72
|
+
* stored under the number) is refused with `IntegrityError`, with or without a key, since the footer is in the clear.
|
|
73
|
+
* The check runs before the target is opened, so an object sealed under another key reports as another object, not as a
|
|
74
|
+
* failed decryption. `allowUnknown` moves onto it anyway;
|
|
73
75
|
* - a generation whose object is **not in the bucket** — collected, or never written — is refused with
|
|
74
76
|
* `NotFoundError` rather than pointed at, because a pointer naming a missing object is the one state the whole
|
|
75
77
|
* design exists to avoid;
|
|
@@ -78,29 +80,57 @@ export interface RollbackResult {
|
|
|
78
80
|
* - a target that is **not what the row says the segment is** is refused with `IntegrityError`: a cleartext object
|
|
79
81
|
* under a row with keys, or an encrypted one under a row with none, either of which every read would then refuse.
|
|
80
82
|
* One tail read of the target decides it, as does a footer that fails its own checks;
|
|
81
|
-
* -
|
|
83
|
+
* - `allowUnknown` on an **encrypted** segment, outside the two cases the pointer alone decides (below), needs the key,
|
|
84
|
+
* and is refused with `KeyUnavailableError` when the store has
|
|
85
|
+
* no keystore, or its keystore holds none of the key's KEKs or one that does not unwrap the key: an object the row
|
|
86
|
+
* does not vouch for and that nothing opens may be one sealed under a key no row holds. A key service that did not
|
|
87
|
+
* answer in time is thrown as `TransientError`, to retry;
|
|
88
|
+
* - rolling to the generation **already current** is a no-op that reports itself, not an error, once one footer read finds
|
|
89
|
+
* its object in the bucket and, when the row knows it, with the checksum recorded. A pointer that names a missing
|
|
90
|
+
* object is refused with `NotFoundError`, as any missing target is, and one that names another object than the row
|
|
91
|
+
* recorded with `IntegrityError`.
|
|
92
|
+
*
|
|
93
|
+
* Which generations are known: the current one, the ones a load's `keep` keeps below it, those a live lease holds, and
|
|
94
|
+
* the ones a rollback left above the pointer, until a load numbers above them. A known target needs no flag and, for
|
|
95
|
+
* the same reason, no key: moving onto an object the row published, forward or back. Two cases are decided by the
|
|
96
|
+
* pointer alone, so a target below it needs no flag and no key, and are audited with `unknown: true`: a row with no
|
|
97
|
+
* usable `knownGens` (a registry that drops the field, or a row whose pointer was moved by hand); and a target below the
|
|
98
|
+
* bound of a full record (128 entries or more, leased ones included), which is its least recently current entry that no
|
|
99
|
+
* lease entry names. Neither gets the checks above. A generation a publish with a smaller `keep` no longer names but that
|
|
100
|
+
* stays in the bucket (a lease spared it, or its delete failed) needs `allowUnknown` until a later listing collects it.
|
|
82
101
|
*
|
|
83
102
|
* The same tail read opens the target when the segment's key is at hand, and the rollback writes the target's own id
|
|
84
103
|
* count and metadata into the row, in the compare-and-swap that moves the pointer: a reader that sees the target as
|
|
85
104
|
* current sees what describes it. (One tail read, and a range read as well when the target's index is longer than the
|
|
86
|
-
* tail read: a target of very many chunks.) A store with no keystore, or
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* the
|
|
90
|
-
* the
|
|
91
|
-
*
|
|
105
|
+
* tail read: a target of very many chunks.) A store with no keystore, or whose keystore cannot open the segment's key
|
|
106
|
+
* (it holds none of the key's KEKs, one it holds does not unwrap it, or it throws `TransientError` or times out as
|
|
107
|
+
* `AbortSignal.timeout()` and the AWS SDK report a timeout), still rolls an encrypted segment onto a known generation, or
|
|
108
|
+
* below the pointer in the two judged-as-before cases, and leaves the row with no summary of the target. Any other
|
|
109
|
+
* failure of the keystore is thrown. After the swap the target's footer is read once more, and the pointer put back,
|
|
110
|
+
* restoring the summary and the record the row had, unless it is still the object that was read: collection can delete a
|
|
111
|
+
* target below the old pointer between the read and the swap, and an erasure can delete one above it and a load take its
|
|
112
|
+
* number. A swap that gets no answer is settled by reading the row: one the row shows landed, the row being the one the
|
|
113
|
+
* swap leaves, is checked as any swap is, and otherwise its `TransientError` is thrown, since it may have landed or may
|
|
114
|
+
* still land, and a re-run finds the pointer where it is. A write by another writer is not taken for it, unless it leaves
|
|
115
|
+
* the very row this swap would. On a cleartext segment two writes can: another rollback onto the same target, whatever
|
|
116
|
+
* row it read, and a load that wrote under that number the very object the rollback read and carried the ledger the
|
|
117
|
+
* rollback would (a load that carries a usable ledger, as one with a `keep` above 64 does; over a row with no usable
|
|
118
|
+
* ledger the rollback writes none and a load seeds one, so those rows differ). On an encrypted segment a rollback with no
|
|
119
|
+
* key at hand writes no summary, so another such rollback onto the same target can too. That call then reports and
|
|
120
|
+
* audits the move as its own; the pointer is where it asked for it.
|
|
92
121
|
*
|
|
93
122
|
* What it does **not** do is delete anything. The generations above the new pointer stay in the bucket, which is
|
|
94
123
|
* what makes the rollback reversible — roll forward again by naming one of them. They are also then *above*
|
|
95
124
|
* `currentGen`, where generation collection never looks. They stay until loads pass them (the first load whose
|
|
96
|
-
* number one of them holds numbers above them all, and collection then keeps
|
|
97
|
-
*
|
|
98
|
-
* deletes them: all of those present when it rewrites, and
|
|
125
|
+
* number one of them holds numbers above them all, and collection then keeps `keep` of what is below its pointer, the
|
|
126
|
+
* generation that load superseded first), `dropSegment` deletes them, or a subject erasure
|
|
127
|
+
* deletes them: all of those present when it rewrites, and, when the current generation does not hold the id, only those
|
|
128
|
+
* holding it and any no read of the segment can open, whatever the id.
|
|
99
129
|
* Not collecting them is a deliberate trade: an operator who has just undone a bad load should not have the
|
|
100
130
|
* evidence collected out from under them, while a rollback target that still holds erased data would make the
|
|
101
131
|
* erasure undoable by an ordinary operator action.
|
|
102
132
|
*/
|
|
103
133
|
export declare function rollbackSegment(ref: SegmentRef, toGeneration: number, deps: GenerationListDeps, options?: {
|
|
104
134
|
audit?: IAuditSink;
|
|
105
|
-
|
|
135
|
+
allowUnknown?: boolean;
|
|
106
136
|
}): Promise<RollbackResult>;
|
package/dist/core/summary.d.ts
CHANGED
|
@@ -2,50 +2,86 @@
|
|
|
2
2
|
* A generation's summary on its registry row: how a writer builds it, how a reader opens it, and how it is held
|
|
3
3
|
* against the object it describes.
|
|
4
4
|
*
|
|
5
|
-
* The row carries a cached description of the current generation, `{ generation, cardinality,
|
|
6
|
-
* the same compare-and-swap that moves the pointer.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The row carries a cached description of the current generation, `{ generation, cardinality, fingerprint, tailBytes,
|
|
6
|
+
* metadata? }`, written by the same compare-and-swap that moves the pointer. The fingerprint names the generation's
|
|
7
|
+
* object, by its size and footer checksum, as `CrbmReader.fingerprint` does, and `tailBytes` is how much of the object's
|
|
8
|
+
* end one open reads. On a cleartext segment the values are in the clear. On an encrypted one the row holds
|
|
9
|
+
* `{ generation, sealed }` instead: the cardinality as a fixed-width little-endian u64, the object's size as a u64 and
|
|
10
|
+
* its footer checksum as a u32, then `tailBytes` as a u32, then the metadata's canonical JSON if there is any, sealed
|
|
11
|
+
* under the segment's data key with the summary scope of the associated data, which binds it to its namespace, segment
|
|
12
|
+
* and generation. The numbers are fixed-width so the ciphertext's length reveals only the metadata's size, never how
|
|
13
|
+
* many digits the count or the size has, and `tailBytes`, which tracks the number of chunks, is sealed for the same
|
|
14
|
+
* reason the size is. Pure: no I/O.
|
|
11
15
|
*
|
|
12
16
|
* The generation's `.crbm` object stays the truth, and the summary is a copy of it. {@link summaryAgrees} is the
|
|
13
|
-
* comparison of one with the other.
|
|
17
|
+
* comparison of one with the other, and the fingerprint is what a reader holds every object it opens against.
|
|
14
18
|
*/
|
|
15
19
|
import type { Aead } from './crypto.js';
|
|
16
20
|
import type { ClearRegistrySummary, GenerationMetadata, RegistryRecord, RegistrySummary, SealedRegistrySummary, SegmentRef } from './ports.js';
|
|
17
|
-
/**
|
|
21
|
+
/** A generation holds at most every 32-bit id. */
|
|
22
|
+
export declare const MAX_SUMMARY_CARDINALITY: number;
|
|
23
|
+
/**
|
|
24
|
+
* The most a summary's `tailBytes` can be: the reader's own default caps on what an open fetches, an index of at most
|
|
25
|
+
* 8 MiB, an extension block of at most its cap and its trailer, and the footer. The library's writer cannot reach it. A
|
|
26
|
+
* reader can report more for an object it did not write, since it accepts bytes between the index and the footer. A
|
|
27
|
+
* literal, so the registry validators that use it do not import the format module; a test holds it to the sum of the
|
|
28
|
+
* format's constants.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MAX_TAIL_BYTES = 8392820;
|
|
31
|
+
/** Whether `tailBytes` is one a summary of an object of `size` bytes can carry. */
|
|
32
|
+
export declare function tailBytesFit(tailBytes: unknown, size: number): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* What a summary says about a generation: its id count, and its metadata when it has any. `tailBytes`, when given, is
|
|
35
|
+
* the bytes of the object's end one open reads.
|
|
36
|
+
*/
|
|
18
37
|
export interface GenerationDescription {
|
|
19
38
|
readonly cardinality: number;
|
|
20
39
|
readonly metadata?: GenerationMetadata | undefined;
|
|
40
|
+
readonly tailBytes?: number | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* What a row's summary says: the generation's description, the fingerprint of the object it describes and the
|
|
44
|
+
* bytes of that object's end one open reads.
|
|
45
|
+
*/
|
|
46
|
+
export interface SummaryDescription extends GenerationDescription {
|
|
47
|
+
readonly fingerprint: string;
|
|
48
|
+
readonly tailBytes: number;
|
|
21
49
|
}
|
|
22
|
-
/** The
|
|
23
|
-
export declare function
|
|
50
|
+
/** The size of the object a fingerprint names, in bytes: what `stat()` reports from a row's summary. */
|
|
51
|
+
export declare function sizeOfFingerprint(fingerprint: string): number;
|
|
24
52
|
/**
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
53
|
+
* The clear summary of a generation on a cleartext segment, naming its object by `fingerprint`. `metadata` is left off
|
|
54
|
+
* when there is none.
|
|
27
55
|
*/
|
|
28
|
-
export declare function
|
|
56
|
+
export declare function clearSummary(generation: number, cardinality: number, fingerprint: string, tailBytes: number, metadata?: GenerationMetadata): ClearRegistrySummary;
|
|
57
|
+
/**
|
|
58
|
+
* The sealed summary of a generation on an encrypted segment: the cardinality as a u64, the object's size as a u64,
|
|
59
|
+
* its footer checksum as a u32 and its `tailBytes` as a u32, then the metadata's canonical JSON, sealed under `aead` and
|
|
60
|
+
* bound to `ref` and `generation`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function sealSummary(aead: Aead, ref: SegmentRef, generation: number, cardinality: number, fingerprint: string, tailBytes: number, metadata?: GenerationMetadata): SealedRegistrySummary;
|
|
29
63
|
/**
|
|
30
64
|
* What a sealed summary says, opened with `aead` under the summary scope of `ref` and the summary's own generation.
|
|
31
65
|
* The bytes are untrusted: a blob that is not base64, is the wrong size, fails its authentication (tampered, or moved
|
|
32
|
-
* from another generation's row), or holds a count outside 0 to 2^32
|
|
33
|
-
* {@link IntegrityError}.
|
|
66
|
+
* from another generation's row), or holds a count outside 0 to 2^32, a size no object can have, or metadata that
|
|
67
|
+
* breaks the rules, is an {@link IntegrityError}.
|
|
34
68
|
*/
|
|
35
|
-
export declare function openSummary(aead: Aead, ref: SegmentRef, summary: SealedRegistrySummary):
|
|
69
|
+
export declare function openSummary(aead: Aead, ref: SegmentRef, summary: SealedRegistrySummary): SummaryDescription;
|
|
36
70
|
/**
|
|
37
71
|
* What a row's summary says of its current generation, or `undefined` when it says nothing a reader may use. It is
|
|
38
72
|
* used only for the generation it names, on an active row, and in the shape the row's keys call for: clear on a row
|
|
39
73
|
* with no wrapped keys, sealed on a row with them. A sealed one is opened with `aead`, and is unusable without it,
|
|
40
|
-
* or when it does not open, so a summary a tamperer moved or rewrote is never believed
|
|
41
|
-
* summary: the row's copy is a cache, and a copy that cannot be
|
|
74
|
+
* or when it does not open, so a summary a tamperer moved or rewrote is never believed; a clear one whose fingerprint
|
|
75
|
+
* names no object is unusable too. Never throws for a bad summary: the row's copy is a cache, and a copy that cannot be
|
|
76
|
+
* used is a copy that is not there.
|
|
42
77
|
*/
|
|
43
|
-
export declare function usableSummary(ref: SegmentRef, row: RegistryRecord, aead: Aead | undefined):
|
|
78
|
+
export declare function usableSummary(ref: SegmentRef, row: Pick<RegistryRecord, 'summary' | 'status' | 'currentGen' | 'wrappedDeks'>, aead: Aead | undefined): SummaryDescription | undefined;
|
|
44
79
|
/**
|
|
45
80
|
* Whether a summary's description of a generation is what its object holds: the same id count and the same metadata,
|
|
46
|
-
* compared in canonical form, so key order never matters
|
|
47
|
-
*
|
|
48
|
-
*
|
|
81
|
+
* compared in canonical form, so key order never matters, and the same `tailBytes` when both say one (a cold open sizes
|
|
82
|
+
* its tail read from the row's, so a number the object does not bear out is not believed again). A summary with
|
|
83
|
+
* metadata over an object with none, and the reverse, disagree: whether an encrypted object's metadata block is present
|
|
84
|
+
* is not authenticated (stripping it needs no key), so the row's sealed copy is what a missing block is held against.
|
|
49
85
|
*/
|
|
50
86
|
export declare function summaryAgrees(summary: GenerationDescription, object: GenerationDescription): boolean;
|
|
51
87
|
/**
|
|
@@ -54,8 +90,21 @@ export declare function summaryAgrees(summary: GenerationDescription, object: Ge
|
|
|
54
90
|
* metadata block is not authenticated, so a block stripped from one reads as none, and the sealed summary on the row is
|
|
55
91
|
* the one copy that says there was a block; carrying it keeps the rewrite from erasing the record of what it dropped.
|
|
56
92
|
* Pass `authenticated` only for a summary that was opened (a clear one has no more authority than the block it sits
|
|
57
|
-
* beside). Never refuses
|
|
93
|
+
* beside). Never refuses. The erasure reaches this with a stripped block only past a fingerprint forged to match the
|
|
94
|
+
* row's, since stripping the block changes the object's size and the open is held to the row's fingerprint.
|
|
58
95
|
*/
|
|
59
96
|
export declare function metadataToCarry(object: GenerationMetadata | undefined, authenticated: GenerationDescription | undefined): GenerationMetadata | undefined;
|
|
60
|
-
/**
|
|
61
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The summary of a generation of `ref`, describing the object `description.fingerprint` names, clear or sealed under
|
|
99
|
+
* `aead` as the segment's keys call for. `undefined` when the object's `tailBytes` is above {@link MAX_TAIL_BYTES}: a
|
|
100
|
+
* reader accepts bytes between an object's index and its footer, so an object this library did not write can report a
|
|
101
|
+
* tail the row cannot record, and a row is legal without a summary (a read then opens the object with the default tail).
|
|
102
|
+
* The description's size, footer checksum and `tailBytes` must all come from the one object.
|
|
103
|
+
*/
|
|
104
|
+
export declare function summaryOf(ref: SegmentRef, generation: number, description: SummaryDescription, aead: Aead | undefined): RegistrySummary | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* {@link summaryOf} for an object this library wrote, whose tail is within {@link MAX_TAIL_BYTES} by construction (its
|
|
107
|
+
* index holds at most 65,536 entries and its extension block at most 4 KiB). Throws {@link IntegrityError} for a tail
|
|
108
|
+
* that is not, which the writer cannot produce.
|
|
109
|
+
*/
|
|
110
|
+
export declare function ownSummary(ref: SegmentRef, generation: number, description: SummaryDescription, aead: Aead | undefined): RegistrySummary;
|
package/dist/core/token.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* A shipped registry gives a row it creates a token `<incarnation>.<counter>.<write>`: 32 lowercase hex digits drawn
|
|
5
5
|
* from injected entropy when the row is created, a decimal counter, and 16 hex digits drawn for each write. Every write
|
|
6
6
|
* of the row keeps the incarnation, and a name created again draws a new one, so two incarnations are told apart by it
|
|
7
|
-
* even when their rows were created in the same millisecond or one is gone. A
|
|
8
|
-
*
|
|
7
|
+
* even when their rows were created in the same millisecond or one is gone. A registry of someone else's issues tokens
|
|
8
|
+
* in any form, and one whose tokens carry no incarnation id is told apart only by the row's creation stamp.
|
|
9
9
|
*
|
|
10
10
|
* This lives in `core/` so the publish can read it, and the registry drivers, which build the tokens, import it from
|
|
11
11
|
* here: the form has one definition.
|
|
@@ -14,18 +14,23 @@ import type { Token } from './ports.js';
|
|
|
14
14
|
/** An incarnation-form token: the incarnation id, the counter and a write part, `.`-separated, in lowercase hex. */
|
|
15
15
|
export declare const INCARNATION_TOKEN: RegExp;
|
|
16
16
|
/**
|
|
17
|
-
* The incarnation id in a token, or `undefined` for one that has none: a
|
|
18
|
-
*
|
|
17
|
+
* The incarnation id in a token, or `undefined` for one that has none: a token a registry of someone else's issues in
|
|
18
|
+
* another form. Two tokens with the same incarnation are writes of one incarnation of a row.
|
|
19
19
|
*/
|
|
20
20
|
export declare function incarnationOf(token: Token): string | undefined;
|
|
21
|
+
/** An audit event's `incarnation`, from the token of the row it is about: absent for a token that carries none. */
|
|
22
|
+
export declare function incarnationField(token: Token | undefined): {
|
|
23
|
+
readonly incarnation?: string;
|
|
24
|
+
};
|
|
21
25
|
/**
|
|
22
26
|
* Whether two reads of one name's row are one incarnation of it.
|
|
23
27
|
*
|
|
24
28
|
* - Both tokens carry an incarnation id: the ids decide, and the clock stamps do not enter.
|
|
25
|
-
* - Only one does: different incarnations. A
|
|
26
|
-
*
|
|
27
|
-
* - Neither does (a
|
|
28
|
-
* is the only signal left. It is a clock reading, so
|
|
29
|
+
* - Only one does: different incarnations. A registry gives its rows one form of token, so a row without an id never
|
|
30
|
+
* becomes a row with one, nor the reverse.
|
|
31
|
+
* - Neither does (a registry of someone else's whose tokens carry none): there is no id to compare, and the creation
|
|
32
|
+
* stamp is the only signal left. It is a clock reading, so `landedHere` also checks the object under the number by
|
|
33
|
+
* its footer, and `onlyLeasesDiffer` also requires every field but the leases to match.
|
|
29
34
|
*/
|
|
30
35
|
export declare function sameIncarnation(a: {
|
|
31
36
|
readonly token: Token;
|
package/dist/core/validate.d.ts
CHANGED
|
@@ -49,3 +49,5 @@ export declare function validateUserRef(ref: SegmentRef): void;
|
|
|
49
49
|
export declare function validateChunkKeyOrder(keys: readonly number[]): void;
|
|
50
50
|
/** Validate a chunk ref: the segment/namespace rules plus `chunkKey` ∈ `[0, 65535]` (a u16). A driver-boundary check. */
|
|
51
51
|
export declare function validateChunkRef(ref: ChunkRef): void;
|
|
52
|
+
/** The chunk-key half of {@link validateChunkRef}, for a caller that has already validated the segment: an integer in `[0, 65535]`. */
|
|
53
|
+
export declare function validateChunkKey(chunkKey: number): void;
|
package/dist/driver-kit.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
export type { GenKey, IRegistryDriver, IStorageDriver, SegmentRef, StorageBackend, StorageCaps, } from './core/ports.js';
|
|
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
3
|
export type { IMetricsSink, MetricEvent } from './core/metrics.js';
|
|
4
4
|
export { IntegrityError, isIntegrityError, isNotFoundError, isTransientError, isValidationError, isWriteConflictError, NotFoundError, TransientError, ValidationError, WriteConflictError, } from './core/errors.js';
|
|
5
|
+
export { RESOLVED_FIELDS, pointerIdOf, renewPointer, renewsPointer } from './core/pointer-id.js';
|
|
5
6
|
export { validateSegmentRef } from './core/validate.js';
|
|
6
7
|
export type { BlobSink } from './core/blob.js';
|
|
7
8
|
export { MAX_ROW_BYTES, ObjectStoreRegistry, ObjectVersionRaced, } from './drivers/_shared/object-registry.js';
|
|
8
9
|
export type { ObjectRegistryStore, ObjectRow } from './drivers/_shared/object-registry.js';
|
|
9
10
|
export { encodeNameForKey, encodeNameForPath, namespaceKeyPart, namespacePathPart, } from './drivers/_shared/keys.js';
|
|
10
11
|
export { normalizeObjectPrefix, prefixPart } from './drivers/_shared/object-registry-keys.js';
|
|
12
|
+
export { parseGenerationFromKey, segmentObjectPrefix, storageObjectKey, } from './drivers/_shared/object-keys.js';
|
|
13
|
+
export { refuseUnknownOptions, requireBool, requirePositiveInt } from './drivers/_shared/options.js';
|
|
14
|
+
export { isTransportFault, toTransient } from './drivers/_shared/faults.js';
|
|
15
|
+
export { concatBytes } from './drivers/_shared/bytes.js';
|