@cloudbitmaps/core 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -5
- package/dist/{chunk-YSC7NJWV.js → chunk-OSU7W2Y3.js} +811 -164
- package/dist/chunk-OSU7W2Y3.js.map +7 -0
- package/dist/core/audit.d.ts +12 -2
- package/dist/core/bit-route.d.ts +9 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/codec.d.ts +1 -1
- package/dist/core/combine-expr.d.ts +10 -8
- package/dist/core/combine-feed.d.ts +12 -18
- package/dist/core/combine-many.d.ts +29 -22
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +69 -17
- package/dist/core/crbm/reader.d.ts +52 -6
- package/dist/core/crbm-storage-source.d.ts +65 -452
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/determinism.d.ts +2 -1
- package/dist/core/engine.d.ts +222 -33
- package/dist/core/erase-id.d.ts +60 -15
- package/dist/core/erasure.d.ts +5 -4
- package/dist/core/errors.d.ts +61 -11
- package/dist/core/leases.d.ts +4 -3
- package/dist/core/load.d.ts +58 -45
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +5 -3
- package/dist/core/pinned-storage-source.d.ts +4 -4
- package/dist/core/ports.d.ts +96 -22
- package/dist/core/registry-scan.d.ts +0 -7
- package/dist/core/retention-sweep.d.ts +70 -66
- package/dist/core/retention.d.ts +2 -2
- package/dist/core/retry.d.ts +15 -18
- package/dist/core/rollback.d.ts +61 -31
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +11 -2
- package/dist/driver-kit.js +84 -16
- 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 +2 -0
- package/dist/drivers/_shared/options.d.ts +19 -0
- package/dist/drivers/backends.d.ts +2 -0
- package/dist/drivers/crypto.d.ts +1 -1
- package/dist/export/index.d.ts +9 -8
- package/dist/index.d.ts +11 -10
- package/dist/index.js +4202 -2833
- package/dist/index.js.map +4 -4
- package/package.json +7 -2
- package/dist/chunk-YSC7NJWV.js.map +0 -7
- package/dist/core/chunk-checks.d.ts +0 -34
- package/dist/core/chunk-stream.d.ts +0 -22
- package/dist/core/chunk-window.d.ts +0 -30
- package/dist/core/combine-ledger.d.ts +0 -43
- package/dist/core/crbm/crc32c.d.ts +0 -10
- package/dist/core/crbm/fingerprint.d.ts +0 -18
- package/dist/core/crbm/format.d.ts +0 -151
- package/dist/core/crbm/frame.d.ts +0 -10
- package/dist/core/crbm/plan-reads.d.ts +0 -44
- package/dist/core/crbm/varint.d.ts +0 -13
- package/dist/core/crbm/writer.d.ts +0 -68
- package/dist/core/due-index.d.ts +0 -53
- package/dist/core/generation-gc.d.ts +0 -179
- package/dist/core/item-pull.d.ts +0 -29
- package/dist/core/kept-generations.d.ts +0 -40
- package/dist/core/metadata.d.ts +0 -42
- package/dist/core/reader-defaults.d.ts +0 -29
- package/dist/core/summary.d.ts +0 -78
- package/dist/core/token.d.ts +0 -41
- package/dist/drivers/_shared/entropy.d.ts +0 -5
- package/dist/drivers/_shared/registry.d.ts +0 -104
- package/dist/drivers/localfs/exact-case.d.ts +0 -37
- package/dist/drivers/localfs/fs-util.d.ts +0 -33
- package/dist/drivers/localfs/paths.d.ts +0 -40
- package/dist/drivers/localfs/registry.d.ts +0 -44
- package/dist/drivers/localfs/storage.d.ts +0 -21
- package/dist/drivers/memory.d.ts +0 -61
- package/dist/testing/fuzz-core.d.ts +0 -15
package/dist/core/errors.d.ts
CHANGED
|
@@ -15,20 +15,26 @@
|
|
|
15
15
|
* does not match — and silently, because a `catch` that stops matching just falls through, defeating
|
|
16
16
|
* transient retry and the publish path's conflict handling with no error of its own.
|
|
17
17
|
*
|
|
18
|
-
* `Symbol.for` is identity-stable across copies, bundles and realms, so classify errors with
|
|
19
|
-
*
|
|
18
|
+
* `Symbol.for` is identity-stable across copies, bundles and realms, so classify errors with `errorKind`
|
|
19
|
+
* (never `instanceof`) anywhere an error may cross that boundary.
|
|
20
20
|
*/
|
|
21
21
|
declare const ERROR_BRAND: unique symbol;
|
|
22
22
|
declare const TRANSIENT_BRAND: unique symbol;
|
|
23
23
|
/** Base class for every error CloudRoaring throws. */
|
|
24
24
|
export declare class CloudRoaringError extends Error {
|
|
25
|
-
/** Cross-bundle brand — see
|
|
25
|
+
/** Cross-bundle brand — see {@link errorKind}. Non-enumerable-ish (symbol key ⇒ not in JSON). */
|
|
26
26
|
readonly [ERROR_BRAND]: true;
|
|
27
27
|
constructor(message: string);
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Invalid input, which the same call fails on again. Most is refused before any storage call (a bad id, segment name
|
|
31
|
+
* or option); some is found by the call itself: a read past the end of an object, or a bucket or container a cloud
|
|
32
|
+
* driver finds does not exist.
|
|
33
|
+
*/
|
|
30
34
|
export declare class ValidationError extends CloudRoaringError {
|
|
31
35
|
}
|
|
36
|
+
/** What a contention {@link WriteConflictError} ends with: the call is safe to run again. */
|
|
37
|
+
export declare const CONTENTION_NEXT_STEP = "; another writer changed the segment's row while this call ran. Re-run the call: it reads the row afresh";
|
|
32
38
|
/** An OCC conditional write/delete lost the race — the row changed since it was read. */
|
|
33
39
|
export declare class WriteConflictError extends CloudRoaringError {
|
|
34
40
|
}
|
|
@@ -93,14 +99,46 @@ export declare class UnsupportedError extends CloudRoaringError {
|
|
|
93
99
|
export declare class CapabilityError extends CloudRoaringError {
|
|
94
100
|
}
|
|
95
101
|
/**
|
|
96
|
-
* An operation would exceed its per-op **denial-of-wallet
|
|
97
|
-
* `
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
+
* An operation would exceed one of its per-op **denial-of-wallet bounds** — too many backend requests
|
|
103
|
+
* (`budget.maxRequests`), too many bytes held at once by a combine (`maxBufferedBytes`), or too many registry rows
|
|
104
|
+
* in a scan (`maxScanSegments`) — so it is refused, before the fan-out where it can be known beforehand (hard
|
|
105
|
+
* invariant 6). Default-on but generous (normal ops never hit it); tune each per store or per op, or disable the
|
|
106
|
+
* request budget with `budget: false`. Deterministic (never retried): the op is too big by policy, not by luck.
|
|
107
|
+
* Each request's bytes are separately capped (the safe-deserialize ceiling), so bounding the request count
|
|
108
|
+
* transitively bounds bytes.
|
|
109
|
+
*
|
|
110
|
+
* Carries an option name and two numbers, and no data: `option` names the option to raise, `limit` is the bound in
|
|
111
|
+
* force, and `spent` is what the call had reached when it was refused. What `spent` measures depends on `option`:
|
|
112
|
+
*
|
|
113
|
+
* - `'budget.maxRequests'`: the requests the call needs, which raising the option to it lets through. For a plan, it
|
|
114
|
+
* is the planned reads (with the reads already taken, when a run plans again); an enumeration that
|
|
115
|
+
* stops at the bound reports `limit + 1`, a lower bound.
|
|
116
|
+
* - `'maxBufferedBytes'`: a **lower bound** on the bytes the call needs resident: what it held plus what it asked
|
|
117
|
+
* for at the moment it was refused. Raising the limit to `spent` can be refused again, further on, because the
|
|
118
|
+
* call had not finished growing. The one figure that lets an output through is the per-output refusal's, which
|
|
119
|
+
* the message gives as "raise maxBufferedBytes to at least N". Otherwise raise the limit with room to spare, or
|
|
120
|
+
* split the outputs across calls.
|
|
121
|
+
* - `'maxScanSegments'`: `limit + 1`, a lower bound: the scan stops at the first row past the bound (for the retention
|
|
122
|
+
* sweep's due-index scan, the first due-index pointer, unread, which may be stale), so it never learns the fleet's
|
|
123
|
+
* size.
|
|
124
|
+
*
|
|
125
|
+
* The message states the same limit.
|
|
102
126
|
*/
|
|
103
127
|
export declare class BudgetExceededError extends CloudRoaringError {
|
|
128
|
+
/** The option to raise to let the call through. */
|
|
129
|
+
readonly option: 'budget.maxRequests' | 'maxBufferedBytes' | 'maxScanSegments';
|
|
130
|
+
/** The bound in force: requests, bytes or rows, by `option`. */
|
|
131
|
+
readonly limit: number;
|
|
132
|
+
/**
|
|
133
|
+
* What the call had reached when refused. By `option`: the requests the call needs (`'budget.maxRequests'`), a
|
|
134
|
+
* lower bound on the resident bytes it needs (`'maxBufferedBytes'`), or `limit + 1` (`'maxScanSegments'`).
|
|
135
|
+
*/
|
|
136
|
+
readonly spent: number;
|
|
137
|
+
constructor(message: string, details: {
|
|
138
|
+
readonly option: 'budget.maxRequests' | 'maxBufferedBytes' | 'maxScanSegments';
|
|
139
|
+
readonly limit: number;
|
|
140
|
+
readonly spent: number;
|
|
141
|
+
});
|
|
104
142
|
}
|
|
105
143
|
/**
|
|
106
144
|
* An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the
|
|
@@ -148,7 +186,9 @@ export declare function isLeaseLimitError(err: unknown): err is LeaseLimitError;
|
|
|
148
186
|
export declare function isNotFoundError(err: unknown): err is NotFoundError;
|
|
149
187
|
/** Corrupt/oversized/failed-checksum bytes from a tier. */
|
|
150
188
|
export declare function isIntegrityError(err: unknown): err is IntegrityError;
|
|
151
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Invalid caller input; also the error a cloud driver raises for a bucket or container it finds does not exist.
|
|
191
|
+
*/
|
|
152
192
|
export declare function isValidationError(err: unknown): err is ValidationError;
|
|
153
193
|
/** This build or this store's wiring cannot do what was asked, though nothing is malformed. */
|
|
154
194
|
export declare function isUnsupportedError(err: unknown): err is UnsupportedError;
|
|
@@ -158,4 +198,14 @@ export declare function isCapabilityError(err: unknown): err is CapabilityError;
|
|
|
158
198
|
export declare function isBudgetExceededError(err: unknown): err is BudgetExceededError;
|
|
159
199
|
/** No key the keystore holds can unwrap an encrypted segment's data key. */
|
|
160
200
|
export declare function isKeyUnavailableError(err: unknown): err is KeyUnavailableError;
|
|
201
|
+
/** The name of one of the library's error classes, as `errorKind` returns it. */
|
|
202
|
+
export type ErrorKind = 'CloudRoaringError' | 'ValidationError' | 'WriteConflictError' | 'IntegrityError' | 'NotFoundError' | 'UnsupportedError' | 'CapabilityError' | 'TransientError' | 'KeyUnavailableError' | 'BudgetExceededError' | 'LeaseExpiredError' | 'LeaseLimitError' | 'StaleOperandError';
|
|
203
|
+
/**
|
|
204
|
+
* Which of the library's error classes `err` is, or `undefined` when it is none of them. Works on every copy of
|
|
205
|
+
* the package, every bundle and every realm, where `instanceof` can silently stop matching: it reads the
|
|
206
|
+
* `Symbol.for` brands and the error's `name`. An application's own subclass of one class returns
|
|
207
|
+
* `'CloudRoaringError'`, except a subclass of `TransientError`, which returns `'TransientError'`; an error
|
|
208
|
+
* cloned out of a worker (the brand is gone) returns `undefined`.
|
|
209
|
+
*/
|
|
210
|
+
export declare function errorKind(err: unknown): ErrorKind | undefined;
|
|
161
211
|
export {};
|
package/dist/core/leases.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export declare const MAX_STORED_LEASES = 256;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const LEASE_CAS_ATTEMPTS: number;
|
|
20
20
|
/**
|
|
21
|
-
* Fresh writes sent after one that got no answer and left the row as it was, by the lease writers
|
|
22
|
-
* renewal of the row.
|
|
21
|
+
* Fresh writes sent after one that got no answer and left the row as it was, by the lease writers, by an erasure's
|
|
22
|
+
* renewal of the row and by a publish: four sends of the row write in all.
|
|
23
23
|
*/
|
|
24
24
|
export declare const UNANSWERED_RESENDS = 3;
|
|
25
25
|
/** The upper bound of the first of those waits, in ms; each next one doubles it. */
|
|
@@ -65,7 +65,7 @@ export declare const LEASE_ONLY_RETRIES: number;
|
|
|
65
65
|
*/
|
|
66
66
|
export declare function onlyLeasesDiffer(held: RegistryRecord, now: RegistryRecord, alsoIgnore?: readonly (keyof RegistryRecord)[]): boolean;
|
|
67
67
|
/** A writer's count of the lease-only changes it has waited out, and the wait. One per call of the writer. */
|
|
68
|
-
|
|
68
|
+
interface LeaseChurn {
|
|
69
69
|
/**
|
|
70
70
|
* When `now` is `held` changed only in its leases and the bound is not spent: wait, then read the row again with
|
|
71
71
|
* `reread`, and return what it found. The wait comes before the read, so the writer's next write is made against a row
|
|
@@ -124,3 +124,4 @@ export declare function takeLease(ref: SegmentRef, deps: LeaseDeps, take: LeaseT
|
|
|
124
124
|
* races and the registry's `TransientError` for a write that stayed unanswered over an unchanged row.
|
|
125
125
|
*/
|
|
126
126
|
export declare function releaseLease(ref: SegmentRef, deps: LeaseDeps, holder: string): Promise<void>;
|
|
127
|
+
export {};
|
package/dist/core/load.d.ts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `loadSegment` — the loaded store's primary write path, as one call.
|
|
3
|
-
*
|
|
4
|
-
* A load is always the same four steps: take the next generation number, write one immutable object, move the
|
|
5
|
-
* pointer, collect what the move superseded. Inside the library each is its own internal function, and the
|
|
6
|
-
* step a hand-composed load leaves out is the last one, so a store that composed it would accumulate superseded
|
|
7
|
-
* generations it keeps paying for.
|
|
8
|
-
* Composing it here, once, is the point.
|
|
9
|
-
*
|
|
10
|
-
* The other reason it is one call is the **guard**. A load REPLACES a segment: whatever the stream contains is
|
|
11
|
-
* what the segment contains afterwards. That makes an upstream query returning fewer rows than usual — or none —
|
|
12
|
-
* a silent, successful-looking wipe, and nothing downstream can tell it from a legitimate shrink. The guard is
|
|
13
|
-
* the place to say what a plausible result looks like, and it has to sit **between the write and the publish**,
|
|
14
|
-
* because that is the only moment where the new content is known and the old one is still authoritative.
|
|
15
|
-
*
|
|
16
|
-
* Refusing therefore has to clean up after itself. The object is already durable at that point, and it sits
|
|
17
|
-
* ABOVE `currentGen`, where generation collection deliberately never looks (it deletes strictly below the
|
|
18
|
-
* pointer), so a refused load deletes its own object before returning, while the segment's row is unchanged (or
|
|
19
|
-
* differs only in its leases), gone or `destroyed`, or has key material when the load wrote cleartext. Under a row
|
|
20
|
-
* that is gone or keyed, a read of the object's footer must prove it the load's own first. Once another write has
|
|
21
|
-
* otherwise changed the row, the object's number may name a re-created segment's live object, so it stays, and
|
|
22
|
-
* collection takes it like any other generation once one above it is current.
|
|
23
|
-
*/
|
|
24
1
|
import { type IAuditSink } from './audit.js';
|
|
25
2
|
import { type CodecBitmap, type CodecInterface } from './codec.js';
|
|
26
|
-
import type { Clock, Rng } from './determinism.js';
|
|
3
|
+
import type { Clock, Entropy, Rng } from './determinism.js';
|
|
27
4
|
import type { IKeystore } from './crypto.js';
|
|
28
5
|
import { type LoadInput } from './load-input.js';
|
|
29
6
|
import { type ReadRetry } from './retry.js';
|
|
@@ -41,6 +18,14 @@ export interface LoadDeps {
|
|
|
41
18
|
* bound). Absent, the read retry's source is used if it has one, and otherwise each wait is its bound.
|
|
42
19
|
*/
|
|
43
20
|
readonly rng?: Rng;
|
|
21
|
+
/**
|
|
22
|
+
* The source of the write's identity: 12 random bytes the new generation's footer carries (for a cleartext generation;
|
|
23
|
+
* an encrypted one carries its index's AEAD nonce), so two loads of the same ids at the same number are two objects
|
|
24
|
+
* and every check that a footer is "mine" names this write. Required: a load without one (a JavaScript caller can
|
|
25
|
+
* leave it out) is refused with {@link ValidationError} before any read, keystore call or write.
|
|
26
|
+
* `@cloudbitmaps/roaring` supplies Web Crypto's by default.
|
|
27
|
+
*/
|
|
28
|
+
readonly entropy: Entropy;
|
|
44
29
|
/**
|
|
45
30
|
* The store's read retry, for the read the guard makes of the current generation: a transient fault there is run
|
|
46
31
|
* again under it rather than failing the load. Absent, the read is made once. It does not govern the write: a
|
|
@@ -98,12 +83,20 @@ export interface LoadGuard {
|
|
|
98
83
|
* `'outputs[3].'`).
|
|
99
84
|
*/
|
|
100
85
|
export declare function checkedGuard(guard: LoadGuard | undefined, where: string): LoadGuard | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Options of `store.load` and of core's `loadSegment`: what a load may publish (`allowEmpty`, `guard`), what it keeps
|
|
88
|
+
* (`keep`) and what it records (`metadata`, `audit`).
|
|
89
|
+
*/
|
|
101
90
|
export interface LoadOptions {
|
|
102
91
|
/**
|
|
103
92
|
* Publish an empty generation over a non-empty one. Off by default: an empty result is far more often an
|
|
104
93
|
* upstream failure than an intent, and the failure is indistinguishable from success once it lands.
|
|
105
94
|
*/
|
|
106
95
|
readonly allowEmpty?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Bounds a plausible result must meet, judged before the pointer moves; a load outside them is refused
|
|
98
|
+
* (`published: false`), not thrown. See {@link LoadGuard}.
|
|
99
|
+
*/
|
|
107
100
|
readonly guard?: LoadGuard;
|
|
108
101
|
/**
|
|
109
102
|
* Generations to keep below the new pointer as a grace window (default 1). A non-negative integer: anything
|
|
@@ -116,14 +109,21 @@ export interface LoadOptions {
|
|
|
116
109
|
* longest pinned job runs, on every writer that loads the segment — see "Generations and `keep`" in the loading
|
|
117
110
|
* guide.
|
|
118
111
|
*
|
|
119
|
-
* The
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
112
|
+
* The window is the `keep` most recently current generations below the one the load publishes, so the generation the
|
|
113
|
+
* load superseded comes first. After a rollback that is the one the rollback restored, and it stays in the window of
|
|
114
|
+
* every load after it, ahead of the generations the rollback moved away from, for as many loads as `keep` says.
|
|
115
|
+
*
|
|
116
|
+
* The segment's row records the generations a load keeps, up to 127.
|
|
117
|
+
*
|
|
118
|
+
* With a `keep` of at most 64, a load whose number one existence check found free deletes by name the generations
|
|
119
|
+
* its publish pushed out of the window, and lists the segment's objects on every sixteenth generation to take
|
|
120
|
+
* whatever that pass leaves, such as an object a crashed load left below the pointer.
|
|
121
|
+
*
|
|
122
|
+
* A load lists instead on every load when `keep` is above 64, when the row has a pointer and no usable record (the
|
|
123
|
+
* load starts one from the bucket), when the row's record names more than 64 generations below the pointer that no
|
|
124
|
+
* lease holds (as after a `keep` above 64, or `materializeMany`), or when the guard's read, or the zero-byte read
|
|
125
|
+
* before a delete by name, finds the current generation's object missing. A publish that a read of the row settles
|
|
126
|
+
* after its last attempt lists too. A `keep` at least the generation published collects nothing and asks for nothing.
|
|
127
127
|
*/
|
|
128
128
|
readonly keep?: number;
|
|
129
129
|
/**
|
|
@@ -138,15 +138,11 @@ export interface LoadOptions {
|
|
|
138
138
|
* over as it is. Sealed under the segment's key when the segment is encrypted.
|
|
139
139
|
*/
|
|
140
140
|
readonly metadata?: GenerationMetadata;
|
|
141
|
+
/** Audit sink for this load: it receives `segment.publish`, or `segment.load-refused` when the load is refused. */
|
|
141
142
|
readonly audit?: IAuditSink;
|
|
142
143
|
}
|
|
143
144
|
/** The bounds a guard can refuse a generation for: every {@link LoadRefusal} but a lost race. */
|
|
144
145
|
export type GuardRefusal = Exclude<LoadRefusal, 'superseded'>;
|
|
145
|
-
/**
|
|
146
|
-
* The bound a generation of `cardinality` ids breaks over a current one of `before` (`null`: none, or not read), in the
|
|
147
|
-
* order a load checks them, or `undefined`. Pure: the one judgement a load and a dry run share.
|
|
148
|
-
*/
|
|
149
|
-
export declare function guardRefusal(cardinality: number, before: number | null, guard: LoadGuard | undefined, allowEmpty: boolean | undefined): GuardRefusal | undefined;
|
|
150
146
|
/** What a dry run learns about one write: the size it would replace, and the bound that would refuse it. */
|
|
151
147
|
export interface LoadJudgement {
|
|
152
148
|
/**
|
|
@@ -165,7 +161,7 @@ export interface LoadJudgement {
|
|
|
165
161
|
* would, and an encrypted one with no keystore the `KeyUnavailableError`. What it says holds for now only: a load
|
|
166
162
|
* made later is judged against the segment as it is then.
|
|
167
163
|
*/
|
|
168
|
-
export declare function judgeLoad(ref: SegmentRef, cardinality: number, deps: LoadDeps, options?: Pick<LoadOptions, 'allowEmpty' | 'guard'>): Promise<LoadJudgement>;
|
|
164
|
+
export declare function judgeLoad(ref: SegmentRef, cardinality: number, deps: Omit<LoadDeps, 'entropy'>, options?: Pick<LoadOptions, 'allowEmpty' | 'guard'>): Promise<LoadJudgement>;
|
|
169
165
|
/** Why a load did not become current. */
|
|
170
166
|
export type LoadRefusal = 'empty' | 'min-cardinality' | 'min-retained' | 'max-growth'
|
|
171
167
|
/**
|
|
@@ -173,16 +169,23 @@ export type LoadRefusal = 'empty' | 'min-cardinality' | 'min-retained' | 'max-gr
|
|
|
173
169
|
* put refused this one and it wrote nothing (`size: 0`); or the segment's registry row changed while the load was
|
|
174
170
|
* writing: another load published, a retention change, a rollback or an erasure wrote the row, the row was
|
|
175
171
|
* deleted, or a row appeared where the load found none. Once another write has changed the row, the object stays in
|
|
176
|
-
* the bucket, still billed, until a later load's collection deletes it
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
172
|
+
* the bucket, still billed, until a later load's collection deletes it or the segment is dropped, because its
|
|
173
|
+
* generation number may by then name another writer's object; until it goes, a rollback onto it needs `allowUnknown`, as onto any generation the row does not know. Once the row is
|
|
174
|
+
* `destroyed` (dropped or crypto-shredded), the object is deleted; once the row is gone, so is an object whose footer
|
|
175
|
+
* matches what this load wrote (another load's object of the same ids is not a match: the footer carries each write's
|
|
176
|
+
* own identity), and so is a cleartext one under a row with key material.
|
|
180
177
|
*/
|
|
181
178
|
| 'superseded';
|
|
179
|
+
/**
|
|
180
|
+
* What a load did: the generation it wrote, whether that is now current (`published`, and `reason` when not), the
|
|
181
|
+
* object it wrote and what it collected.
|
|
182
|
+
*/
|
|
182
183
|
export interface LoadResult {
|
|
183
184
|
/**
|
|
184
185
|
* The generation written. Present even when refused: a refusal deletes that object while the segment's row is
|
|
185
|
-
* unchanged or `destroyed`,
|
|
186
|
+
* unchanged or `destroyed`, and, once one read of its footer matches what the load wrote, when the row is gone or,
|
|
187
|
+
* for a cleartext object, has key material. Under any other row another write changed, the object stays in the
|
|
188
|
+
* bucket, an orphan a later load's collection takes, and until then a rollback onto it needs `allowUnknown`. A load that lost its
|
|
186
189
|
* generation number to another wrote nothing, and reports `size: 0`.
|
|
187
190
|
*/
|
|
188
191
|
readonly generation: number;
|
|
@@ -190,10 +193,19 @@ export interface LoadResult {
|
|
|
190
193
|
readonly published: boolean;
|
|
191
194
|
/** Set only when `published` is false. */
|
|
192
195
|
readonly reason?: LoadRefusal;
|
|
196
|
+
/**
|
|
197
|
+
* Bytes of the generation's object as stored, or 0 when another load took the generation number first and
|
|
198
|
+
* nothing was written.
|
|
199
|
+
*/
|
|
193
200
|
readonly size: number;
|
|
201
|
+
/**
|
|
202
|
+
* SHA-256 of the object as stored, or `''` when another load took the generation number first. The library neither
|
|
203
|
+
* keeps nor checks it; reads are checked with CRC32C.
|
|
204
|
+
*/
|
|
194
205
|
readonly sha256: string;
|
|
206
|
+
/** Non-empty chunks in the generation written (at most 65,536), or 0 when nothing was written. */
|
|
195
207
|
readonly chunkCount: number;
|
|
196
|
-
/** Distinct ids in the generation that was written, post-dedup. */
|
|
208
|
+
/** Distinct ids in the generation that was written, post-dedup, or 0 when nothing was written. */
|
|
197
209
|
readonly cardinality: number;
|
|
198
210
|
/**
|
|
199
211
|
* What the segment held when the guard judged it — `null` when there was no current generation, or when no
|
|
@@ -218,7 +230,8 @@ export interface LoadResult {
|
|
|
218
230
|
*
|
|
219
231
|
* `input` is ids, or a whole bitmap as `{ serialized }` portable Roaring bytes or a `{ bitmap }` with
|
|
220
232
|
* `serialize('portable')` ({@link LoadInput}). A bitmap input is checked, decoded and serialized before the first
|
|
221
|
-
* request, and is written from its own chunks; the generation
|
|
233
|
+
* request, and is written from its own chunks; the generation holds chunk for chunk what its ids would write, and carries its own write
|
|
234
|
+
* identity in the footer like any other.
|
|
222
235
|
*
|
|
223
236
|
* Returns what was written and whether it became current. A refusal is **not** an exception: `published: false`
|
|
224
237
|
* with a `reason` is a normal outcome a caller branches on, in the same shape as a successful load, because the
|
package/dist/core/lru.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bounded LRU cache with optional TTL — the cache tier's memory ceiling.
|
|
3
|
-
*
|
|
4
|
-
* Time comes from an injected `Clock` (never `Date.now()`) so the cache is deterministic
|
|
5
|
-
* under simulation.
|
|
6
|
-
*
|
|
7
|
-
* The cache can be bounded by entry **count** (`maxEntries`, always) and, optionally, by an aggregate
|
|
8
|
-
* **byte weight** (`maxBytes`). The byte bound exists for values whose footprint is large and uneven — the
|
|
9
|
-
* storage reader cache, where one entry pins a fully-parsed `.crbm` index that can be ~6 MB for a wide segment
|
|
10
|
-
* yet a few hundred bytes for a small one (a count-only bound lets 1024 wide indices blow a Lambda's
|
|
11
|
-
* memory even though the count is "in bounds"). Weights arrive **after** insertion (a cached reader is a
|
|
12
|
-
* pending promise; its size is known only once it resolves), so weight is reported via {@link setWeight} and
|
|
13
|
-
* eviction re-runs then.
|
|
14
|
-
*/
|
|
15
1
|
import type { Clock } from './determinism.js';
|
|
16
|
-
|
|
2
|
+
interface LruOptions {
|
|
17
3
|
/** Hard ceiling on entries; the least-recently-used is evicted past it. */
|
|
18
4
|
readonly maxEntries: number;
|
|
19
5
|
/**
|
|
@@ -67,3 +53,4 @@ export declare class BoundedLru<K, V> {
|
|
|
67
53
|
/** Current aggregate reported weight (bytes) across all entries. */
|
|
68
54
|
get weightBytes(): number;
|
|
69
55
|
}
|
|
56
|
+
export {};
|
package/dist/core/metrics.d.ts
CHANGED
|
@@ -49,8 +49,10 @@ export type MetricEvent = {
|
|
|
49
49
|
readonly ms: number;
|
|
50
50
|
} | {
|
|
51
51
|
/**
|
|
52
|
-
* One decoded-chunk cache lookup
|
|
53
|
-
*
|
|
52
|
+
* One decoded-chunk cache lookup, or one chunk counted a miss without a lookup (emitted only when a cache is
|
|
53
|
+
* configured). `hit: false` means the cache did not hold the chunk. A combine or `iterate` counts every chunk it
|
|
54
|
+
* needs when it opens its streams: a lookup each, except that on a cache that holds nothing each chunk after the
|
|
55
|
+
* one a stream starts at is counted a miss without one. A caller that
|
|
54
56
|
* missed while another caller's read of the same chunk was open waits on that read and adds no `storage.get`, and a
|
|
55
57
|
* range carries several chunks, so the misses can outnumber the `storage.get` events; the `storage.get` count is
|
|
56
58
|
* the number of requests for chunks. `materializeMany` never looks up the cache, so it emits none.
|
|
@@ -131,7 +133,7 @@ export type MetricEvent = {
|
|
|
131
133
|
};
|
|
132
134
|
/**
|
|
133
135
|
* The sink you plug in. One method, synchronous, fire-and-forget. It must never throw back into the
|
|
134
|
-
* caller — `CloudRoaring` wraps a user sink
|
|
136
|
+
* caller — `CloudRoaring` wraps a user sink so that a buggy sink can't break I/O.
|
|
135
137
|
*/
|
|
136
138
|
export interface IMetricsSink {
|
|
137
139
|
onEvent(event: MetricEvent): void;
|
|
@@ -27,14 +27,14 @@ import type { CrbmStorageChunkSource } from './crbm-storage-source.js';
|
|
|
27
27
|
* What a pin holds for one segment: the generation, the version identifying those exact bytes, and the pinned
|
|
28
28
|
* object's fingerprint, so a read can tell that the generation it opens is still that object.
|
|
29
29
|
*
|
|
30
|
-
* `fingerprint` is
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* `fingerprint` is required for a pin of a generation (`null` only when `generation` is `null`): without it a read
|
|
31
|
+
* could not tell a name purged and loaded again from the segment it pinned. `seg.pin()` always records it, and a
|
|
32
|
+
* pin of a generation without a string `version` and `fingerprint` is refused with a `ValidationError`.
|
|
33
33
|
*/
|
|
34
34
|
export interface PinnedAt {
|
|
35
35
|
readonly generation: number | null;
|
|
36
36
|
readonly version: string | null;
|
|
37
|
-
readonly fingerprint
|
|
37
|
+
readonly fingerprint: string | null;
|
|
38
38
|
}
|
|
39
39
|
export declare class PinnedStorageChunkSource implements StorageChunkSource {
|
|
40
40
|
private readonly inner;
|
package/dist/core/ports.d.ts
CHANGED
|
@@ -281,12 +281,18 @@ export interface StorageDeleteOptions {
|
|
|
281
281
|
* `getRange` may answer empty without reaching the backend, so it proves neither that the object exists nor
|
|
282
282
|
* that the offset lies inside it: the cloud drivers answer it empty, the memory and local-filesystem drivers
|
|
283
283
|
* throw for a missing object or an offset past the end.)
|
|
284
|
+
* - **A deleted object is missing to the next read**: once `delete(key)` has resolved, `getTail` and a non-empty `getRange`
|
|
285
|
+
* of that key throw {@link NotFoundError} (a zero-length `getRange` is the exemption above), with no stale answer from a
|
|
286
|
+
* cache or a replica. A rollback reads its target's footer once
|
|
287
|
+
* after moving the pointer, in place of listing the bucket, and takes a read that succeeds as proof that the object is
|
|
288
|
+
* there.
|
|
284
289
|
* - **An out-of-range read throws {@link ValidationError}**: a range that runs past the end of the object, or a
|
|
285
290
|
* negative or non-integer offset or length. Never a clamped, short or adjacent read. A pin whose object
|
|
286
291
|
* was purged and loaded again as a smaller one asks for a range past its end, and this error is what sends it
|
|
287
292
|
* to check that the object is still the one it pinned.
|
|
288
293
|
* - **`getTail` reports the object's true total size**, not the number of bytes it returned, and, where the driver has
|
|
289
|
-
* one, the object's `version` ({@link TailRead.version}) from the same response as the bytes.
|
|
294
|
+
* one, the object's `version` ({@link TailRead.version}) from the same response as the bytes. Its length is a safe
|
|
295
|
+
* integer, zero or less for an empty tail; any other length throws {@link ValidationError} before anything is read.
|
|
290
296
|
* - **`delete` is idempotent.** Deleting an absent key is a no-op, not an error: collection passes race each
|
|
291
297
|
* other and retry. That holds with `ifVersion` too: an absent object is a no-op whatever version is given.
|
|
292
298
|
* - **`delete` with `ifVersion` does what {@link StorageCaps.conditionalDelete} says.** A driver that reports `true`
|
|
@@ -299,7 +305,7 @@ export interface StorageDeleteOptions {
|
|
|
299
305
|
* under the key. A number can be taken again once its object is deleted, so this is what keeps a delete decided from
|
|
300
306
|
* one read, and delayed, from removing an object stored under the number since: the erasure's delete of a holder
|
|
301
307
|
* above the pointer passes the version it read when it searched that object, and a refused load's delete of an
|
|
302
|
-
* object
|
|
308
|
+
* object whose footer, read once, matched what it wrote passes the version of that read.
|
|
303
309
|
* - **A driver that wraps another forwards `ifVersion` and the `version` of a tail read, or reports
|
|
304
310
|
* `conditionalDelete: false`.** A wrapper that hands `delete` the key alone, and reports the inner driver's
|
|
305
311
|
* capability, makes every conditional delete through it unconditional without a word.
|
|
@@ -328,7 +334,9 @@ export interface IStorageDriver {
|
|
|
328
334
|
/**
|
|
329
335
|
* Stream a new immutable generation. The driver opens a destination, hands `write` a {@link BlobSink},
|
|
330
336
|
* then atomically commits (and computes the content hash). Throws {@link WriteConflictError} if the key
|
|
331
|
-
* already exists (write-once).
|
|
337
|
+
* already exists (write-once). Call `write` once: the object committed is the bytes that one call wrote, because
|
|
338
|
+
* the caller records the write identity of that call's object (its footer) for every later proof; a driver that
|
|
339
|
+
* runs `write` again after a failed attempt must commit only the last run's bytes.
|
|
332
340
|
*/
|
|
333
341
|
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
334
342
|
size: number;
|
|
@@ -341,7 +349,10 @@ export interface IStorageDriver {
|
|
|
341
349
|
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
342
350
|
/**
|
|
343
351
|
* Speculative tail read: the last `min(maxBytes, size)` bytes + the object's **true total size**, and its `version`
|
|
344
|
-
* where the driver has one ({@link TailRead}).
|
|
352
|
+
* where the driver has one ({@link TailRead}). `maxBytes` must be a safe integer: zero or less answers an empty tail
|
|
353
|
+
* with the size, and one past the size the whole object. Any other `maxBytes` (`NaN`, a fraction, a negative one
|
|
354
|
+
* included, an infinity of either sign, a whole number past the safe range (2^53 or more, or -2^53 or less)) throws
|
|
355
|
+
* {@link ValidationError} before anything is read. A missing object throws {@link NotFoundError}.
|
|
345
356
|
*/
|
|
346
357
|
getTail(key: GenKey, maxBytes: number): Promise<TailRead>;
|
|
347
358
|
/**
|
|
@@ -405,11 +416,16 @@ export interface ClearRegistrySummary {
|
|
|
405
416
|
/** How many ids the generation holds: an integer from 0 to 2^32. */
|
|
406
417
|
readonly cardinality: number;
|
|
407
418
|
/**
|
|
408
|
-
* The fingerprint of the generation's object, `<size>:<footer checksum>` in decimal, as `
|
|
409
|
-
*
|
|
410
|
-
* footer stores.
|
|
419
|
+
* The fingerprint of the generation's object, `<size>:<footer checksum>` in decimal, as a pin's `pinnedAt.fingerprint`
|
|
420
|
+
* spells it: the object's size in bytes, which `stat()` reports, and the CRC32C its footer stores.
|
|
411
421
|
*/
|
|
412
422
|
readonly fingerprint: string;
|
|
423
|
+
/**
|
|
424
|
+
* The bytes from the end of the last chunk payload to the end of the object: the extension block, if any, the index and
|
|
425
|
+
* the footer. A cold open reads this many bytes from the object's end to open it in one request. An integer from 104,
|
|
426
|
+
* the footer's size, to the object's size less its 8-byte preamble, and at most 8,392,820.
|
|
427
|
+
*/
|
|
428
|
+
readonly tailBytes: number;
|
|
413
429
|
/** The generation's metadata, when it has any. Never the empty object. */
|
|
414
430
|
readonly metadata?: GenerationMetadata;
|
|
415
431
|
}
|
|
@@ -419,11 +435,24 @@ export interface SealedRegistrySummary {
|
|
|
419
435
|
readonly generation: number;
|
|
420
436
|
/**
|
|
421
437
|
* Base64 of `nonce(12) ‖ ciphertext ‖ tag(16)`, sealing the cardinality as a little-endian u64, the object's size as a
|
|
422
|
-
* little-endian u64 and its footer checksum as a little-endian u32 (its fingerprint),
|
|
423
|
-
* JSON, if any. The numbers are fixed-width, so the length reveals only
|
|
438
|
+
* little-endian u64 and its footer checksum as a little-endian u32 (its fingerprint), its `tailBytes` as a
|
|
439
|
+
* little-endian u32, then the metadata's canonical JSON, if any. The numbers are fixed-width, so the length reveals only
|
|
440
|
+
* the metadata's size.
|
|
424
441
|
*/
|
|
425
442
|
readonly sealed: string;
|
|
426
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* A generation a row knows it published: its number, and the checksum its object's footer stores. A rollback moves only
|
|
446
|
+
* onto a generation the row knows, and an object whose footer carries another checksum is another object under that
|
|
447
|
+
* number. The footer's checksum covers the index's checksum, which covers each chunk's, so it commits to the object's ids
|
|
448
|
+
* and shape; for a cleartext object it does not cover the extension block's bytes.
|
|
449
|
+
*/
|
|
450
|
+
export interface KnownGeneration {
|
|
451
|
+
/** A non-negative safe integer. */
|
|
452
|
+
readonly generation: number;
|
|
453
|
+
/** The object's footer CRC32C (the second half of its fingerprint `<size>:<crc>`), an integer from 0 to 2^32 - 1. */
|
|
454
|
+
readonly footerCrc: number;
|
|
455
|
+
}
|
|
427
456
|
/**
|
|
428
457
|
* One lease on a generation. It names no fingerprint: a collector spares a generation by number and cannot compare an
|
|
429
458
|
* object without reading it, and a purge and re-create drops the whole list with the row. A pin's own fingerprint,
|
|
@@ -481,15 +510,28 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
481
510
|
*/
|
|
482
511
|
readonly summary?: RegistrySummary;
|
|
483
512
|
/**
|
|
484
|
-
* The generations
|
|
485
|
-
*
|
|
486
|
-
*
|
|
513
|
+
* The generations this row knows and has not decided to delete, the current one included, with each object's footer
|
|
514
|
+
* checksum: the ones it made current, in the order they were made current, oldest first and the current one last, and,
|
|
515
|
+
* when a publish over a row with no usable ledger seeds one, the ones a count by `keep` keeps in the bucket, which can
|
|
516
|
+
* include an object no load published (an orphan or a stray). An entry says the library knows the generation and its
|
|
517
|
+
* footer checksum, not that a load published it. A generation appears once,
|
|
518
|
+
* at its most recent time current (a rollback onto it moves it to the end). The entries before the last are the kept
|
|
519
|
+
* window, the generations a live lease holds, and what a rollback left; those numbered above `currentGen` are what a
|
|
520
|
+
* rollback left. In the clear on every
|
|
521
|
+
* row, an encrypted one included: a footer checksum of an encrypted object covers a random nonce and says nothing about
|
|
522
|
+
* its count, size or content, but the entries above the pointer show anyone who reads the registry that a rollback
|
|
523
|
+
* happened, and from which generation.
|
|
524
|
+
*
|
|
525
|
+
* Optional: absent means the row does not know, and a load then lists the bucket to collect. A shipped registry stores
|
|
526
|
+
* the list in the order given, at most 192 entries written and 384 read, and refuses a list that repeats a
|
|
527
|
+
* generation, one whose last entry is not the row's `currentGen`, and one whose current entry's checksum disagrees with
|
|
528
|
+
* a clear `summary`'s fingerprint. A stored list that disagrees with its row is read and not used. A patch that moves
|
|
487
529
|
* `currentGen` without naming it drops it.
|
|
488
530
|
*/
|
|
489
|
-
readonly
|
|
531
|
+
readonly knownGens?: readonly KnownGeneration[];
|
|
490
532
|
/**
|
|
491
533
|
* The leases on this segment's generations: each keeps one generation out of a load's collection until it ends.
|
|
492
|
-
* Optional; absent means none. Unlike {@link
|
|
534
|
+
* Optional; absent means none. Unlike {@link knownGens} it does not follow the pointer: a patch that moves
|
|
493
535
|
* `currentGen` leaves it as it was, and only a patch that names it, or the row going away, changes it. Entries
|
|
494
536
|
* past their end are ignored by every collector and pruned by the next write that names the list. A row's identity
|
|
495
537
|
* is its token, so a purged and re-created segment starts with none.
|
|
@@ -510,7 +552,7 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
510
552
|
/**
|
|
511
553
|
* The token of the most recent write that named a field a read resolves through: the row's create, or a compare-and-swap
|
|
512
554
|
* whose patch named `currentGen`, `status`, `wrappedDeks`, `keyId` or `summary` (the resolved fields), at any value,
|
|
513
|
-
* the one the field already had included. A write that names only `leases`, `retention`, `residency` or `
|
|
555
|
+
* the one the field already had included. A write that names only `leases`, `retention`, `residency` or `knownGens`
|
|
514
556
|
* leaves it. The registry sets it; a caller never does. Compared by equality only, and never reused under one name,
|
|
515
557
|
* since a token is not: a reader keys what it caches on a generation with it, so a lease or a policy write leaves a
|
|
516
558
|
* warm reader's cache standing, and a write that changes the resolution never does.
|
|
@@ -532,8 +574,8 @@ export interface NewRegistryRecord {
|
|
|
532
574
|
readonly residency?: GovernanceMeta;
|
|
533
575
|
/** Must name `currentGen` when given. */
|
|
534
576
|
readonly summary?: RegistrySummary;
|
|
535
|
-
/**
|
|
536
|
-
readonly
|
|
577
|
+
/** In the order made current, the last entry the new row's `currentGen`; see {@link RegistryRecord.knownGens}. */
|
|
578
|
+
readonly knownGens?: readonly KnownGeneration[];
|
|
537
579
|
}
|
|
538
580
|
/**
|
|
539
581
|
* Fields a {@link IRegistryDriver.compareAndSwap} may mutate (identity, audit, `token` and `pointerId` are
|
|
@@ -542,14 +584,14 @@ export interface NewRegistryRecord {
|
|
|
542
584
|
* Presence-based: a field the patch does not mention is left as it was. `summary` is the one exception, because it
|
|
543
585
|
* describes the current generation: a patch that moves `currentGen` and does not mention `summary` drops the old
|
|
544
586
|
* one rather than keep a description of another generation. A `summary` the patch gives must name the
|
|
545
|
-
* `currentGen` the row will have. `
|
|
546
|
-
* not mention it drops it, and one that gives it must
|
|
587
|
+
* `currentGen` the row will have. `knownGens` follows the pointer the same way: a patch that moves `currentGen` and does
|
|
588
|
+
* not mention it drops it, and one that gives it must end with the `currentGen` the row will have.
|
|
547
589
|
* "Moves" means changes value: a patch that names `currentGen` at the value it has keeps both.
|
|
548
590
|
*
|
|
549
591
|
* A patch that names a resolved field (`currentGen`, `status`, `wrappedDeks`, `keyId`, `summary`), by presence and at any
|
|
550
592
|
* value, sets the row's {@link RegistryRecord.pointerId} to the token the write returns; any other patch leaves it.
|
|
551
593
|
*/
|
|
552
|
-
export type RegistryPatch = Partial<Pick<RegistryRecord, 'currentGen' | 'wrappedDeks' | 'keyId' | 'status' | 'retention' | 'residency' | 'summary' | '
|
|
594
|
+
export type RegistryPatch = Partial<Pick<RegistryRecord, 'currentGen' | 'wrappedDeks' | 'keyId' | 'status' | 'retention' | 'residency' | 'summary' | 'knownGens' | 'leases'>>;
|
|
553
595
|
/**
|
|
554
596
|
* What a caller passes {@link IRegistryDriver.create} or {@link IRegistryDriver.compareAndSwap} besides the write itself.
|
|
555
597
|
*/
|
|
@@ -566,6 +608,25 @@ export interface RegistryWriteOptions {
|
|
|
566
608
|
* settled by reading the row, never by this record. A driver that ignores the option reads the row.
|
|
567
609
|
*/
|
|
568
610
|
readonly held?: RegistryRecord | null;
|
|
611
|
+
/**
|
|
612
|
+
* Called with the token this write carries, before the write is sent. A write sent again with a new token reports
|
|
613
|
+
* it; one that repeats the same body repeats the token already reported. The token is the one the write records:
|
|
614
|
+
* when the write lands, the row's {@link RegistryRecord.token} is it, and so is its {@link RegistryRecord.pointerId}
|
|
615
|
+
* when the write names a resolved field. A write that does not land leaves no row carrying it. A call refused before
|
|
616
|
+
* it has a token to send, on a conflict its own read finds, reports none.
|
|
617
|
+
*
|
|
618
|
+
* The token is unique to that write attempt, also against another writer racing the same version: two writers that
|
|
619
|
+
* send a write against the same row version never carry the same token, so a version counter alone is not enough.
|
|
620
|
+
* Otherwise a loser's write would pass for the winner's.
|
|
621
|
+
*
|
|
622
|
+
* It tells a writer which row writes are its own, whatever became of its request: a write that failed, or got no
|
|
623
|
+
* answer, may still have landed, and the row's `pointerId` names its writer when no later write has renewed it: it is
|
|
624
|
+
* then one of the tokens the writer was told. Only a registry that reports {@link IRegistryDriver.reportsWriteTokens}
|
|
625
|
+
* calls it; one that does not ignores it, and a writer then settles such a write by reading the object it published.
|
|
626
|
+
* A registry that wraps or extends another and overrides `create` or `compareAndSwap` passes this option through to
|
|
627
|
+
* the write it forwards, or sets the flag to `false`.
|
|
628
|
+
*/
|
|
629
|
+
readonly onToken?: (token: Token) => void;
|
|
569
630
|
}
|
|
570
631
|
/** Capabilities a registry driver advertises; validated fail-fast at wiring time. */
|
|
571
632
|
export interface RegCaps {
|
|
@@ -610,8 +671,8 @@ declare const STORAGE_BACKEND_BRAND: unique symbol;
|
|
|
610
671
|
* Where a store keeps everything: the generations, and the pointer saying which one is current.
|
|
611
672
|
*
|
|
612
673
|
* **Branded on purpose.** The two fields alone are not enough to qualify, and that is the whole point. This
|
|
613
|
-
*
|
|
614
|
-
* object literal
|
|
674
|
+
* same two fields are the heart of the deps core's standalone functions take, so without the brand any `{ storage, registry }`
|
|
675
|
+
* object literal would satisfy it — including one assembling halves from two *unrelated* stores, which the store
|
|
615
676
|
* accepted and then answered **empty** for a segment that holds data, because it read a pointer from a place
|
|
616
677
|
* nothing had ever been written. That is the exact silent-empty failure one-class-per-backend exists to
|
|
617
678
|
* remove, and it was reachable in five lines.
|
|
@@ -723,6 +784,19 @@ export declare function isStorageBackend(value: unknown): value is StorageBacken
|
|
|
723
784
|
*/
|
|
724
785
|
export interface IRegistryDriver {
|
|
725
786
|
capabilities(): RegCaps;
|
|
787
|
+
/**
|
|
788
|
+
* `true` when every `create` and `compareAndSwap` of this registry reports the token its write carries to
|
|
789
|
+
* {@link RegistryWriteOptions.onToken} before sending it, a resend included. A load, an erasure's rewrite and every
|
|
790
|
+
* other publish then knows whether a write that conflicted or got no answer landed by whether the row's `pointerId`
|
|
791
|
+
* is a token it was told, which names the writer and not only the bytes. Absent or `false`: the registry reports
|
|
792
|
+
* none, and such a write is settled by reading the object the publish wrote. A shipped registry says which it is.
|
|
793
|
+
*
|
|
794
|
+
* The flag may be a getter or a field. A registry that wraps or extends another and overrides `create` or
|
|
795
|
+
* `compareAndSwap` either passes `options` through to the write it forwards, or sets `reportsWriteTokens` to
|
|
796
|
+
* `false`. One that does neither reads every write of its own that landed after an error or a lost answer as not its
|
|
797
|
+
* own (`superseded`), and deletes nothing.
|
|
798
|
+
*/
|
|
799
|
+
readonly reportsWriteTokens?: boolean;
|
|
726
800
|
/**
|
|
727
801
|
* The segment's record, or `null` if it doesn't exist (or was deleted). Throw {@link TransientError} for a fault
|
|
728
802
|
* worth riding out (throttling, a 5xx, a dropped connection): a reader whose pointer refresh meets one keeps
|