@cloudbitmaps/core 0.22.0 → 0.24.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 +7 -12
- package/dist/{chunk-OSU7W2Y3.js → chunk-2B2QF4FU.js} +205 -756
- package/dist/chunk-2B2QF4FU.js.map +7 -0
- package/dist/chunk-45ML6YU4.js +641 -0
- package/dist/chunk-45ML6YU4.js.map +7 -0
- package/dist/chunk-BUDAVGBI.js +73 -0
- package/dist/chunk-BUDAVGBI.js.map +7 -0
- package/dist/chunk-WPER7R2R.js +121 -0
- package/dist/chunk-WPER7R2R.js.map +7 -0
- package/dist/core/audit.d.ts +53 -15
- package/dist/core/bit-route.d.ts +2 -0
- package/dist/core/blob.d.ts +4 -0
- package/dist/core/budget.d.ts +8 -3
- package/dist/core/check.d.ts +50 -0
- package/dist/core/codec.d.ts +13 -3
- package/dist/core/combine-many.d.ts +11 -0
- package/dist/core/consistency.d.ts +11 -2
- package/dist/core/cooperative.d.ts +15 -5
- package/dist/core/crbm/reader.d.ts +1 -1
- package/dist/core/crbm-storage-source.d.ts +7 -4
- package/dist/core/crypto.d.ts +5 -0
- package/dist/core/discover.d.ts +5 -3
- package/dist/core/engine.d.ts +5 -5
- package/dist/core/erase-id.d.ts +36 -13
- package/dist/core/erasure.d.ts +61 -27
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/leases.d.ts +2 -0
- package/dist/core/load-input.d.ts +49 -1
- package/dist/core/load.d.ts +23 -2
- package/dist/core/metrics.d.ts +21 -0
- package/dist/core/pinned-storage-source.d.ts +27 -8
- package/dist/core/pointer-id.d.ts +9 -0
- package/dist/core/ports.d.ts +100 -7
- package/dist/core/preflight.d.ts +80 -0
- package/dist/core/retention-sweep.d.ts +60 -15
- package/dist/core/retention.d.ts +17 -6
- package/dist/core/retry.d.ts +1 -2
- package/dist/core/rollback.d.ts +22 -12
- package/dist/core/validate.d.ts +33 -2
- package/dist/driver-kit.d.ts +3 -3
- package/dist/driver-kit.js +61 -20
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +24 -1
- package/dist/drivers/_shared/options.d.ts +15 -7
- package/dist/drivers/backends.d.ts +17 -2
- package/dist/drivers/crypto.d.ts +17 -2
- package/dist/export/index.d.ts +16 -2
- package/dist/index.d.ts +10 -9
- package/dist/index.js +1799 -1700
- package/dist/index.js.map +4 -4
- package/dist/node.d.ts +14 -0
- package/dist/node.js +1064 -0
- package/dist/node.js.map +7 -0
- package/package.json +11 -3
- package/dist/chunk-OSU7W2Y3.js.map +0 -7
package/dist/core/errors.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ declare const TRANSIENT_BRAND: unique symbol;
|
|
|
24
24
|
export declare class CloudRoaringError extends Error {
|
|
25
25
|
/** Cross-bundle brand — see {@link errorKind}. Non-enumerable-ish (symbol key ⇒ not in JSON). */
|
|
26
26
|
readonly [ERROR_BRAND]: true;
|
|
27
|
+
/** Builds the error from `message`, and sets `name` to the class's name as written. */
|
|
27
28
|
constructor(message: string);
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
@@ -59,6 +60,7 @@ export declare class LeaseExpiredError extends CloudRoaringError {
|
|
|
59
60
|
readonly until: number;
|
|
60
61
|
/** `'expired'` when the clock reached `until`, `'released'` after `release()`. */
|
|
61
62
|
readonly reason: 'expired' | 'released';
|
|
63
|
+
/** Builds the error from its `message`, the epoch-ms the lease ran until, and why it ended. */
|
|
62
64
|
constructor(message: string, until: number, reason: 'expired' | 'released');
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
@@ -75,11 +77,13 @@ export declare class LeaseLimitError extends CloudRoaringError {
|
|
|
75
77
|
* `'stale-operand'`. Deterministic: run the call again against the current operand.
|
|
76
78
|
*/
|
|
77
79
|
export declare class StaleOperandError extends CloudRoaringError {
|
|
80
|
+
/** The stable code `'stale-operand'`, to match on. */
|
|
78
81
|
readonly code: "stale-operand";
|
|
79
82
|
/** The operand's name in the call that found it stale. */
|
|
80
83
|
readonly operand: string;
|
|
81
84
|
/** Why it is stale. */
|
|
82
85
|
readonly reason: 'moved' | 'erased';
|
|
86
|
+
/** Builds the error from its `message`, the operand's name and why it is stale. */
|
|
83
87
|
constructor(message: string, operand: string, reason: 'moved' | 'erased');
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
@@ -134,6 +138,7 @@ export declare class BudgetExceededError extends CloudRoaringError {
|
|
|
134
138
|
* lower bound on the resident bytes it needs (`'maxBufferedBytes'`), or `limit + 1` (`'maxScanSegments'`).
|
|
135
139
|
*/
|
|
136
140
|
readonly spent: number;
|
|
141
|
+
/** Builds the error from its `message` and the `option`, `limit` and `spent` it reports. */
|
|
137
142
|
constructor(message: string, details: {
|
|
138
143
|
readonly option: 'budget.maxRequests' | 'maxBufferedBytes' | 'maxScanSegments';
|
|
139
144
|
readonly limit: number;
|
|
@@ -166,6 +171,7 @@ export declare class KeyUnavailableError extends CloudRoaringError {
|
|
|
166
171
|
export declare class TransientError extends CloudRoaringError {
|
|
167
172
|
/** A second brand so a transient fault is classifiable cross-bundle. */
|
|
168
173
|
readonly [TRANSIENT_BRAND]: true;
|
|
174
|
+
/** Builds the error from `message`; `options.cause`, when given, is kept as `cause`. */
|
|
169
175
|
constructor(message: string, options?: {
|
|
170
176
|
cause?: unknown;
|
|
171
177
|
});
|
|
@@ -174,6 +180,30 @@ export declare class TransientError extends CloudRoaringError {
|
|
|
174
180
|
export declare function isCloudRoaringError(err: unknown): err is CloudRoaringError;
|
|
175
181
|
/** An OCC conditional write/delete lost the race — retry the read-modify-write, don't fail. */
|
|
176
182
|
export declare function isWriteConflictError(err: unknown): err is WriteConflictError;
|
|
183
|
+
/**
|
|
184
|
+
* A {@link WriteConflictError} for a registry `create` or `compareAndSwap` that may have landed before it met the
|
|
185
|
+
* conflict, so the conflict does not prove the write was refused. Throw it for every conflict of a write your backend's
|
|
186
|
+
* client may have sent more than once and that you cannot prove did not land: the read-back finds another writer's
|
|
187
|
+
* write over the row (a row is overwritten, so that writer may have swapped in over this write after it landed), or
|
|
188
|
+
* finds the row gone, or the compare-and-swap itself is answered that the row is gone. Throw a plain
|
|
189
|
+
* `WriteConflictError` for a conflict that does prove it: a write sent once, or a check your driver made before sending
|
|
190
|
+
* anything. A registry that wraps another rethrows the conflict it was given, not a new one, which would lose the mark.
|
|
191
|
+
*
|
|
192
|
+
* It is a `WriteConflictError` in every other way: `errorKind` names it `'WriteConflictError'`, `instanceof` holds on
|
|
193
|
+
* the same copy of the package, and every caller that handles a conflict handles it. A load's publish reads the row
|
|
194
|
+
* again, as after any conflict, and takes the write as its own when the row shows it there. Otherwise the write is
|
|
195
|
+
* reported to the load, as a write with no answer is, so a load refused after it carries `unanswered: true` on its
|
|
196
|
+
* `segment.load-refused` audit event. The mark is a registered `Symbol.for` brand, read
|
|
197
|
+
* across copies of the package. It is lost when the error is cloned or copied into a new one, and a conflict raised
|
|
198
|
+
* anywhere but a registry write is read as the plain conflict it also is.
|
|
199
|
+
*/
|
|
200
|
+
export declare function unsettledConflict(message: string): WriteConflictError;
|
|
201
|
+
/**
|
|
202
|
+
* Whether `err` is a conflict a driver raised with {@link unsettledConflict}: a {@link WriteConflictError}, by brand
|
|
203
|
+
* and name as {@link isWriteConflictError} reads it, that carries the unsettled mark. It reads the registered brand,
|
|
204
|
+
* so it holds for an error from another copy of the package. Like the other predicates, no entry exports it.
|
|
205
|
+
*/
|
|
206
|
+
export declare function isUnsettledConflict(err: unknown): boolean;
|
|
177
207
|
/** A retryable transient infrastructure fault. The retry layer keys on this. */
|
|
178
208
|
export declare function isTransientError(err: unknown): err is TransientError;
|
|
179
209
|
/** A read came after its pin's lease ended. */
|
package/dist/core/leases.d.ts
CHANGED
|
@@ -40,9 +40,11 @@ export declare function liveLeases(record: RegistryRecord | null, now: number):
|
|
|
40
40
|
* Both are optional; without a sleep a retry follows at once.
|
|
41
41
|
*/
|
|
42
42
|
export interface ChurnDeps {
|
|
43
|
+
/** The clock whose `sleep` waits out lease churn. Without a `sleep`, a retry follows at once. */
|
|
43
44
|
readonly clock?: {
|
|
44
45
|
readonly sleep?: (ms: number) => Promise<void>;
|
|
45
46
|
} | undefined;
|
|
47
|
+
/** The source that spreads each wait over a share of its bound. Without one, each wait is its whole bound. */
|
|
46
48
|
readonly rng?: Rng | undefined;
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
@@ -17,7 +17,12 @@ import type { CombinedChunk } from './engine.js';
|
|
|
17
17
|
* (`getSerializationSizeInBytes`) is asked first, so one over the cap is refused before it is serialized.
|
|
18
18
|
*/
|
|
19
19
|
export interface PortableBitmap {
|
|
20
|
+
/** The bitmap in the portable Roaring format. */
|
|
20
21
|
serialize(format: 'portable'): Uint8Array;
|
|
22
|
+
/**
|
|
23
|
+
* The size in bytes of `serialize('portable')`, when the bitmap can say without serializing. A load asks it first, so
|
|
24
|
+
* an input over the size cap is refused before it is serialized.
|
|
25
|
+
*/
|
|
21
26
|
getSerializationSizeInBytes?(format: 'portable'): number;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
@@ -53,6 +58,49 @@ export declare class ChunkLoadInput {
|
|
|
53
58
|
readonly chunks: AsyncIterable<CombinedChunk>;
|
|
54
59
|
constructor(chunks: AsyncIterable<CombinedChunk>);
|
|
55
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Mark `err` as raised by a load's input (its ids, a bitmap's encoding, a combine's chunks) rather than by the write,
|
|
63
|
+
* and return it. A write-once conflict is a lost race only when the write raised it: a source that reads another
|
|
64
|
+
* segment (through a pin, say) can throw this library's own `WriteConflictError`, which is the source's to report.
|
|
65
|
+
*/
|
|
66
|
+
export declare function raisedBySource<T>(err: T): T;
|
|
67
|
+
/** Whether a load's input raised `err` ({@link raisedBySource}). */
|
|
68
|
+
export declare function isSourceError(err: unknown): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Ids the caller says ascend (`sorted: true`), written chunk by chunk while they are read, during the object's upload,
|
|
71
|
+
* so a load holds one chunk and the writer's index rather than the whole generation. Made only by
|
|
72
|
+
* {@link sortedLoadInput}, from ids.
|
|
73
|
+
*
|
|
74
|
+
* The first error the ids raise, or their check raises (an id out of range, or lower than the one before it), or the
|
|
75
|
+
* chunker raises building a chunk, is kept here, and the load throws that same error. The driver's upload never sees
|
|
76
|
+
* it: the chunker throws a sentinel of its own into the writer instead, so no driver maps, redacts or probes the
|
|
77
|
+
* caller's error on its way out.
|
|
78
|
+
*/
|
|
79
|
+
export declare class SortedLoadInput {
|
|
80
|
+
readonly ids: Iterable<number> | AsyncIterable<number>;
|
|
81
|
+
private failure;
|
|
82
|
+
constructor(ids: Iterable<number> | AsyncIterable<number>);
|
|
83
|
+
/** Keep `error` as what the ids raised, unless an earlier one is kept. */
|
|
84
|
+
fail(error: unknown): void;
|
|
85
|
+
/** What the load throws for a write that failed with `err`: the error the ids raised, when they raised one. */
|
|
86
|
+
thrown(err: unknown): unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether a load was asked for `sorted: true`. `undefined` and `false` mean no; anything else that is not a boolean is
|
|
90
|
+
* refused before any request, since `sorted: 'yes'` read as absent would hold the whole generation in memory instead.
|
|
91
|
+
*
|
|
92
|
+
* @throws {ValidationError} for a value that is not a boolean.
|
|
93
|
+
*/
|
|
94
|
+
export declare function wantsSorted(sorted: unknown): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The input a load reads under its `sorted` option: ids as {@link SortedLoadInput} when it is `true`, anything else
|
|
97
|
+
* unchanged.
|
|
98
|
+
*
|
|
99
|
+
* @throws {ValidationError} for a `sorted` that is not a boolean, and for `sorted: true` with `{ serialized }`,
|
|
100
|
+
* `{ bitmap }` or a bitmap of the flavor's own: those are written from their own chunks already, so the option would
|
|
101
|
+
* do nothing, and an option that does nothing is refused rather than ignored.
|
|
102
|
+
*/
|
|
103
|
+
export declare function sortedLoadInput(prepared: ReturnType<typeof prepareLoadInput>, sorted: unknown): ReturnType<typeof prepareLoadInput> | SortedLoadInput;
|
|
56
104
|
/**
|
|
57
105
|
* Check a load's input and decode a bitmap input, before the load makes any request.
|
|
58
106
|
*
|
|
@@ -71,6 +119,6 @@ export declare function prepareLoadInput(input: LoadInput, codec: CodecInterface
|
|
|
71
119
|
* are copied first. An empty or detached buffer is the empty bitmap.
|
|
72
120
|
*
|
|
73
121
|
* @param what names the input in a refusal's message.
|
|
74
|
-
* @throws {ValidationError} for anything that is not such bytes; nothing reaches the codec's
|
|
122
|
+
* @throws {ValidationError} for anything that is not such bytes; nothing reaches the codec's decoder then.
|
|
75
123
|
*/
|
|
76
124
|
export declare function decodeSerialized(bytes: unknown, codec: CodecInterface, what?: string): CodecBitmap;
|
package/dist/core/load.d.ts
CHANGED
|
@@ -140,6 +140,26 @@ export interface LoadOptions {
|
|
|
140
140
|
readonly metadata?: GenerationMetadata;
|
|
141
141
|
/** Audit sink for this load: it receives `segment.publish`, or `segment.load-refused` when the load is refused. */
|
|
142
142
|
readonly audit?: IAuditSink;
|
|
143
|
+
/**
|
|
144
|
+
* The ids ascend: write them as they are read, one chunk at a time, while the object uploads, so the load holds one
|
|
145
|
+
* chunk and the writer's index of the chunks written (up to about 7 MiB, about 8 MiB for an encrypted segment,
|
|
146
|
+
* derived, at the 65,536 chunks a segment can have) rather than the whole generation, whatever the segment's size.
|
|
147
|
+
* Repeats are fine (an id equal to the one before is skipped); an id lower than the one before throws
|
|
148
|
+
* `ValidationError`, naming its position and never the id. The object is the same, byte for byte, as an unsorted load
|
|
149
|
+
* of the same ids writes under the same write identity.
|
|
150
|
+
*
|
|
151
|
+
* For ids only (an iterable or an async iterable): with `{ serialized }`, `{ bitmap }` or a bitmap itself (an
|
|
152
|
+
* iterable with a `serialize` method, which a flavor loads as `{ bitmap }`), and on core's
|
|
153
|
+
* `loadSegmentChunks`, it throws `ValidationError`, as does a value that is not a boolean, before any request.
|
|
154
|
+
*
|
|
155
|
+
* The ids are read after the segment's row is read and judged, and after its number and key are settled, so a load
|
|
156
|
+
* that refuses a destroyed or keyless segment reads none of them. They are read while the object uploads: an error
|
|
157
|
+
* they raise, or a refused id, fails the upload, and no object is committed, but the parts uploaded before it may
|
|
158
|
+
* already have been billed (see the production guide). That error reaches the caller as the very object raised,
|
|
159
|
+
* and the storage driver never sees it. The ids can be read once: a storage driver that runs its write again gets
|
|
160
|
+
* `IntegrityError`, and nothing is written.
|
|
161
|
+
*/
|
|
162
|
+
readonly sorted?: boolean;
|
|
143
163
|
}
|
|
144
164
|
/** The bounds a guard can refuse a generation for: every {@link LoadRefusal} but a lost race. */
|
|
145
165
|
export type GuardRefusal = Exclude<LoadRefusal, 'superseded'>;
|
|
@@ -170,8 +190,9 @@ export type LoadRefusal = 'empty' | 'min-cardinality' | 'min-retained' | 'max-gr
|
|
|
170
190
|
* writing: another load published, a retention change, a rollback or an erasure wrote the row, the row was
|
|
171
191
|
* deleted, or a row appeared where the load found none. Once another write has changed the row, the object stays in
|
|
172
192
|
* 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
|
|
174
|
-
* `
|
|
193
|
+
* generation number may by then name another writer's object; until it goes, a rollback onto it needs
|
|
194
|
+
* `allowUnknown`, as onto any generation the row does not know. Once the row is `destroyed` (crypto-shredded,
|
|
195
|
+
* destroyed in the clear or dropped), the object is deleted; once the row is gone, so is an object whose footer
|
|
175
196
|
* matches what this load wrote (another load's object of the same ids is not a match: the footer carries each write's
|
|
176
197
|
* own identity), and so is a cleartext one under a row with key material.
|
|
177
198
|
*/
|
package/dist/core/metrics.d.ts
CHANGED
|
@@ -136,6 +136,10 @@ export type MetricEvent = {
|
|
|
136
136
|
* caller — `CloudRoaring` wraps a user sink so that a buggy sink can't break I/O.
|
|
137
137
|
*/
|
|
138
138
|
export interface IMetricsSink {
|
|
139
|
+
/**
|
|
140
|
+
* Receives one event, synchronously. What it throws, or an async one's rejection, is swallowed and never breaks the
|
|
141
|
+
* call it observes.
|
|
142
|
+
*/
|
|
139
143
|
onEvent(event: MetricEvent): void;
|
|
140
144
|
}
|
|
141
145
|
/** The default sink: discards everything, zero overhead. Used whenever no sink is wired. */
|
|
@@ -147,23 +151,37 @@ export declare const NOOP_METRICS: IMetricsSink;
|
|
|
147
151
|
export declare function safeMetrics(sink: IMetricsSink): IMetricsSink;
|
|
148
152
|
/** Accumulated totals — the shape returned by {@link CountingMetricsSink.snapshot}. */
|
|
149
153
|
export interface MetricsSnapshot {
|
|
154
|
+
/**
|
|
155
|
+
* Chunk requests to storage: how many, the bytes they returned, and the time they took in ms, transient-retry backoff
|
|
156
|
+
* included.
|
|
157
|
+
*/
|
|
150
158
|
readonly storage: {
|
|
151
159
|
readonly gets: number;
|
|
152
160
|
readonly bytes: number;
|
|
153
161
|
readonly totalMs: number;
|
|
154
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* Decoded-chunk cache hits and misses. A chunk counted without a lookup is a miss; `materializeMany` reads no cache, so
|
|
165
|
+
* it counts none.
|
|
166
|
+
*/
|
|
155
167
|
readonly cache: {
|
|
156
168
|
readonly hits: number;
|
|
157
169
|
readonly misses: number;
|
|
158
170
|
};
|
|
171
|
+
/** Retries taken after a transient fault. */
|
|
159
172
|
readonly retries: {
|
|
160
173
|
readonly transient: number;
|
|
161
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* Chunk-aligned combines of every operator (intersect, union and andNot): how many ran, the chunk keys they fetched
|
|
177
|
+
* and the keys they skipped without a fetch.
|
|
178
|
+
*/
|
|
162
179
|
readonly intersect: {
|
|
163
180
|
readonly calls: number;
|
|
164
181
|
readonly fetchedChunks: number;
|
|
165
182
|
readonly skippedChunks: number;
|
|
166
183
|
};
|
|
184
|
+
/** By operation name, how many calls were timed and their total time in ms. */
|
|
167
185
|
readonly ops: Readonly<Record<MetricOpName, {
|
|
168
186
|
readonly count: number;
|
|
169
187
|
readonly totalMs: number;
|
|
@@ -181,7 +199,10 @@ export declare class CountingMetricsSink implements IMetricsSink {
|
|
|
181
199
|
private intersect;
|
|
182
200
|
private ops;
|
|
183
201
|
private static zeroOps;
|
|
202
|
+
/** Adds the event to the totals. An `advisory` event is a note, not a quantity, and adds nothing. */
|
|
184
203
|
onEvent(event: MetricEvent): void;
|
|
204
|
+
/** A copy of the totals so far, which later events do not change. */
|
|
185
205
|
snapshot(): MetricsSnapshot;
|
|
206
|
+
/** Sets every total back to zero. */
|
|
186
207
|
reset(): void;
|
|
187
208
|
}
|
|
@@ -27,15 +27,34 @@ 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
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* It is one of two shapes, told apart by `generation`: a pin of a generation holds all three fields, and a pin of a
|
|
31
|
+
* segment that had no generation holds `null` in all three. Narrowing on `generation` (or on either other field)
|
|
32
|
+
* narrows the record whole, so a numbered pin passes to `pinAt` as it is:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const at = first.pinnedAt;
|
|
36
|
+
* if (at !== undefined && at.generation !== null) await store.segment('active-30d').pinAt(at);
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* `fingerprint` is never `null` for a pin of a generation: without it a read could not tell a name purged and loaded
|
|
40
|
+
* again from the segment it pinned. `seg.pin()` always records it, and a pin of a generation without a string
|
|
41
|
+
* `version` and `fingerprint` is refused with a `ValidationError`.
|
|
33
42
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
readonly
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
export type PinnedAt = {
|
|
44
|
+
/** The pinned generation. */
|
|
45
|
+
readonly generation: number;
|
|
46
|
+
/** The version of that generation's object. */
|
|
47
|
+
readonly version: string;
|
|
48
|
+
/** The fingerprint of that generation's object. */
|
|
49
|
+
readonly fingerprint: string;
|
|
50
|
+
} | {
|
|
51
|
+
/** `null`: the segment had no generation when it was pinned. */
|
|
52
|
+
readonly generation: null;
|
|
53
|
+
/** `null`, as `generation` is. */
|
|
54
|
+
readonly version: null;
|
|
55
|
+
/** `null`, as `generation` is. */
|
|
56
|
+
readonly fingerprint: null;
|
|
57
|
+
};
|
|
39
58
|
export declare class PinnedStorageChunkSource implements StorageChunkSource {
|
|
40
59
|
private readonly inner;
|
|
41
60
|
/**
|
|
@@ -17,3 +17,12 @@ export declare function renewPointer(row: Pick<RegistryRecord, 'currentGen'>): R
|
|
|
17
17
|
* cannot key a cache on it. A TypeScript registry fails to compile instead; this catches one in JavaScript.
|
|
18
18
|
*/
|
|
19
19
|
export declare function pointerIdOf(row: RegistryRecord): Token;
|
|
20
|
+
/**
|
|
21
|
+
* Whether `now` shows a write this call sent as the one that put the pointer where it is: an active row of the same
|
|
22
|
+
* incarnation as `before` (a `create` has none to compare), whose pointer names `generation` and whose `pointerId` is
|
|
23
|
+
* the token of one of the writes in `sent`, which a registry that reports its writes' tokens gave before sending each.
|
|
24
|
+
* The token decides and nothing else is asked, so another writer's write of the same pointer is not taken for this
|
|
25
|
+
* call's. A write that renews the `pointerId` over this call's own landed write (an erasure's renewal, or a write of
|
|
26
|
+
* the pointer back onto the same generation) reads as not this call's: the two cannot be told apart. Pure: no I/O.
|
|
27
|
+
*/
|
|
28
|
+
export declare function pointerSetBy(now: RegistryRecord | null, before: RegistryRecord | null, generation: number, sent: ReadonlySet<Token>): boolean;
|
package/dist/core/ports.d.ts
CHANGED
|
@@ -25,16 +25,29 @@ export type Token = string;
|
|
|
25
25
|
* segment in such a namespace would be invisible to an erasure, a consistency check, an export and a retention sweep.
|
|
26
26
|
* A driver takes the prefix, since the due index's own rows go through it. Only that exact prefix is reserved:
|
|
27
27
|
* `cbm.dueX` and `cbmdue.eu` are ordinary namespaces, and a segment name is never restricted.
|
|
28
|
+
*
|
|
29
|
+
* **A key other than `segment` and `namespace` throws `ValidationError`**, in every call of the store and of the free
|
|
30
|
+
* functions behind it that takes a ref, and at a driver port (which also takes a `generation`, a `chunkKey` and a
|
|
31
|
+
* registry row's own fields): a misspelt `namespace` would otherwise address the segment of that name in the default
|
|
32
|
+
* namespace. A `segments()` row is the one exception at the store: its `currentGen` and `status` are taken and read for
|
|
33
|
+
* nothing. A segment handle, a registry row from `registry.get()` or `list()` (a driver port takes one, a store call does
|
|
34
|
+
* not), and a result row such as a consistency issue, a ledger entry, an export manifest entry or an audit event, is not
|
|
35
|
+
* a ref at the store: pass `{ segment, namespace }`.
|
|
28
36
|
*/
|
|
29
37
|
export interface SegmentRef {
|
|
38
|
+
/** The segment's namespace, or `undefined` for the default one. */
|
|
30
39
|
readonly namespace?: string;
|
|
40
|
+
/** The segment's name. */
|
|
31
41
|
readonly segment: string;
|
|
32
42
|
}
|
|
43
|
+
/** One chunk of a segment: the segment, and the chunk's key. */
|
|
33
44
|
export interface ChunkRef extends SegmentRef {
|
|
45
|
+
/** The chunk's key: an integer from 0 to 65,535, the high 16 bits of the ids the chunk holds. */
|
|
34
46
|
readonly chunkKey: number;
|
|
35
47
|
}
|
|
36
48
|
/** Identifies one immutable `.crbm` object — a single generation of a segment. */
|
|
37
49
|
export interface GenKey extends SegmentRef {
|
|
50
|
+
/** The generation number of the object. */
|
|
38
51
|
readonly generation: number;
|
|
39
52
|
}
|
|
40
53
|
/**
|
|
@@ -43,6 +56,7 @@ export interface GenKey extends SegmentRef {
|
|
|
43
56
|
* holds for an operand's stream.
|
|
44
57
|
*/
|
|
45
58
|
export interface SegmentSize {
|
|
59
|
+
/** The size of the current generation's object, in bytes. */
|
|
46
60
|
readonly sizeBytes: number;
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
@@ -52,8 +66,11 @@ export interface SegmentSize {
|
|
|
52
66
|
* can key what it keeps by the generation the bytes came from rather than the one it resolved earlier.
|
|
53
67
|
*/
|
|
54
68
|
export interface ChunkRead {
|
|
69
|
+
/** The chunk key that was asked for. */
|
|
55
70
|
readonly key: number;
|
|
71
|
+
/** The chunk's bytes, or `null` where the generation holds none. */
|
|
56
72
|
readonly bytes: Uint8Array | null;
|
|
73
|
+
/** The version of the generation the bytes were read from, or `null` when the segment has no generation. */
|
|
57
74
|
readonly version: string | null;
|
|
58
75
|
}
|
|
59
76
|
/** Options of {@link StorageChunkSource.getChunks}. */
|
|
@@ -91,6 +108,7 @@ export interface ReadChunksOptions {
|
|
|
91
108
|
export interface StorageChunkSource {
|
|
92
109
|
/** Read-only bytes for the chunk, or `null` if absent. Callers must not mutate the buffer. */
|
|
93
110
|
getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
|
|
111
|
+
/** The chunk keys the segment's current generation holds, or an empty list when it has none. */
|
|
94
112
|
listChunkKeys(ref: SegmentRef): Promise<number[]>;
|
|
95
113
|
/**
|
|
96
114
|
* Optional: several chunks of one segment as a stream, read **from one generation** with fewer storage requests than
|
|
@@ -330,6 +348,7 @@ export interface StorageDeleteOptions {
|
|
|
330
348
|
* retry can ride it out and a write's caller can tell it from a deterministic failure.
|
|
331
349
|
*/
|
|
332
350
|
export interface IStorageDriver {
|
|
351
|
+
/** What this driver can do. Range reads and `maxObjectBytes` are required; the other capabilities are optional. */
|
|
333
352
|
capabilities(): StorageCaps;
|
|
334
353
|
/**
|
|
335
354
|
* Stream a new immutable generation. The driver opens a destination, hands `write` a {@link BlobSink},
|
|
@@ -337,6 +356,12 @@ export interface IStorageDriver {
|
|
|
337
356
|
* already exists (write-once). Call `write` once: the object committed is the bytes that one call wrote, because
|
|
338
357
|
* the caller records the write identity of that call's object (its footer) for every later proof; a driver that
|
|
339
358
|
* runs `write` again after a failed attempt must commit only the last run's bytes.
|
|
359
|
+
*
|
|
360
|
+
* What a second run of `write` does depends on what is written. A load of ids, of a bitmap or of a combine's chunks
|
|
361
|
+
* holds its chunks in memory, so each run writes the whole object again, under a write identity of its own, and the
|
|
362
|
+
* load records the run that was committed. A write that streams its chunks, a load with `sorted: true` and an
|
|
363
|
+
* erasure's rewrite, reads them once, so its second run throws {@link IntegrityError} before writing a byte: commit
|
|
364
|
+
* nothing then, and the call throws that error, with the first run's error as its `cause`.
|
|
340
365
|
*/
|
|
341
366
|
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
342
367
|
size: number;
|
|
@@ -368,8 +393,9 @@ export interface IStorageDriver {
|
|
|
368
393
|
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
369
394
|
}
|
|
370
395
|
/**
|
|
371
|
-
* Lifecycle status of a segment. `active` is the steady state; `destroyed` is the
|
|
372
|
-
* (the row is kept for audit but the segment is logically gone
|
|
396
|
+
* Lifecycle status of a segment. `active` is the steady state; `destroyed` is the tombstone left by a crypto-shred, a
|
|
397
|
+
* cleartext destroy or a drop (the row is kept for audit but the segment is logically gone: no read resolves a
|
|
398
|
+
* generation through it).
|
|
373
399
|
*/
|
|
374
400
|
export type RegistryStatus = 'active' | 'destroyed';
|
|
375
401
|
/**
|
|
@@ -404,7 +430,9 @@ export type RegistrySummary = ClearRegistrySummary | SealedRegistrySummary;
|
|
|
404
430
|
export type GenerationMetadata = Readonly<Record<string, string | number>>;
|
|
405
431
|
/** What a generation is, as one read answers it: its number, its id count and the metadata it was written with. */
|
|
406
432
|
export interface GenerationSummary {
|
|
433
|
+
/** The generation's number. */
|
|
407
434
|
readonly generation: number;
|
|
435
|
+
/** The number of ids the generation holds. */
|
|
408
436
|
readonly cardinality: number;
|
|
409
437
|
/** Absent when the generation has none. */
|
|
410
438
|
readonly metadata?: GenerationMetadata;
|
|
@@ -486,8 +514,9 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
486
514
|
readonly currentGen: number | null;
|
|
487
515
|
/**
|
|
488
516
|
* Per-segment data-key (DEK) wrappings for encryption-at-rest: the DEK envelope-wrapped under one or more KEKs
|
|
489
|
-
* (active + optional recovery). Reading unwraps with any held KEK; **crypto-shred deletes this whole list
|
|
490
|
-
*
|
|
517
|
+
* (active + optional recovery). Reading unwraps with any held KEK; **crypto-shred deletes this whole list** from
|
|
518
|
+
* the current row, so the segment's at-rest bytes are unrecoverable once no retained copy of the row (a noncurrent
|
|
519
|
+
* version, a backup) still holds it, or every KEK that wrapped it is destroyed. Absent ⇒ the segment is cleartext.
|
|
491
520
|
*/
|
|
492
521
|
readonly wrappedDeks?: readonly WrappedDek[];
|
|
493
522
|
/**
|
|
@@ -496,6 +525,10 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
496
525
|
* keystore. Clearable on crypto-shred.
|
|
497
526
|
*/
|
|
498
527
|
readonly keyId?: string;
|
|
528
|
+
/**
|
|
529
|
+
* `'active'`, or `'destroyed'` for the tombstone of a segment that was crypto-shredded, destroyed in the clear or
|
|
530
|
+
* dropped.
|
|
531
|
+
*/
|
|
499
532
|
readonly status: RegistryStatus;
|
|
500
533
|
/**
|
|
501
534
|
* Governance policy. `retention.expiresAt` drives the retention sweep (see {@link GovernanceMeta}); `residency`
|
|
@@ -503,6 +536,7 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
503
536
|
* `list()` projection — a fleet sweep reads the policy from the enumeration rather than per-segment.
|
|
504
537
|
*/
|
|
505
538
|
readonly retention?: GovernanceMeta;
|
|
539
|
+
/** Residency metadata, a plain object. It is stored and returned as given; nothing reads it yet. */
|
|
506
540
|
readonly residency?: GovernanceMeta;
|
|
507
541
|
/**
|
|
508
542
|
* A cached description of the current generation (see {@link RegistrySummary}). Optional: a row without one is
|
|
@@ -539,6 +573,7 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
539
573
|
readonly leases?: readonly LeaseEntry[];
|
|
540
574
|
/** Epoch-ms of creation / last mutation (from the driver's injected clock). */
|
|
541
575
|
readonly createdAt: number;
|
|
576
|
+
/** Epoch-ms of the row's last write, from the driver's clock. */
|
|
542
577
|
readonly updatedAt: number;
|
|
543
578
|
/**
|
|
544
579
|
* Opaque OCC token — compare-by-equality, ABA-safe. A shipped registry gives no two writes under one name the same
|
|
@@ -566,11 +601,15 @@ export interface RegistryRecord extends SegmentRef {
|
|
|
566
601
|
export interface NewRegistryRecord {
|
|
567
602
|
/** `null` ⇒ the segment has no Storage generation yet — see {@link RegistryRecord.currentGen}. */
|
|
568
603
|
readonly currentGen: number | null;
|
|
604
|
+
/** The segment's wrapped data keys, which make it an encrypted segment. Absent for a cleartext one. */
|
|
569
605
|
readonly wrappedDeks?: readonly WrappedDek[];
|
|
606
|
+
/** An external key reference. Reserved: the in-process keystore does not use it. */
|
|
570
607
|
readonly keyId?: string;
|
|
571
608
|
/** Defaults to `'active'`. */
|
|
572
609
|
readonly status?: RegistryStatus;
|
|
610
|
+
/** The retention policy, a plain object; its `expiresAt` is what the retention sweep reads. */
|
|
573
611
|
readonly retention?: GovernanceMeta;
|
|
612
|
+
/** Residency metadata, a plain object. It is stored and returned as given; nothing reads it yet. */
|
|
574
613
|
readonly residency?: GovernanceMeta;
|
|
575
614
|
/** Must name `currentGen` when given. */
|
|
576
615
|
readonly summary?: RegistrySummary;
|
|
@@ -615,6 +654,11 @@ export interface RegistryWriteOptions {
|
|
|
615
654
|
* when the write names a resolved field. A write that does not land leaves no row carrying it. A call refused before
|
|
616
655
|
* it has a token to send, on a conflict its own read finds, reports none.
|
|
617
656
|
*
|
|
657
|
+
* A publish relies on the report coming first, both ways. A write that faults after its report may have landed, and
|
|
658
|
+
* is settled by reading the row. A `TransientError` before any report is taken for a fault before sending, such as
|
|
659
|
+
* the driver's own read of the row: the publish throws it at once, with no read of the row and no fresh write. So
|
|
660
|
+
* nothing that can land is sent before the report, a client's own resend included.
|
|
661
|
+
*
|
|
618
662
|
* The token is unique to that write attempt, also against another writer racing the same version: two writers that
|
|
619
663
|
* send a write against the same row version never carry the same token, so a version counter alone is not enough.
|
|
620
664
|
* Otherwise a loser's write would pass for the winner's.
|
|
@@ -701,6 +745,38 @@ export interface StorageBackend {
|
|
|
701
745
|
* exception propagates from the store's constructor, so a backend's bug fails fast rather than going unseen.
|
|
702
746
|
*/
|
|
703
747
|
attachMetrics?(sink: IMetricsSink): void;
|
|
748
|
+
/**
|
|
749
|
+
* Optional. Reads the setup the backend cannot change but can be broken by, and says whether it will delete or
|
|
750
|
+
* archive what the store keeps: today, the bucket's lifecycle rules over the generation objects and the
|
|
751
|
+
* `<prefix>registry/` rows. It is meant for a check run by hand or at deploy time, not for the read or write path, and
|
|
752
|
+
* costs one request, plus the SDK's own retries. It never rejects: a check that could not be made resolves to `unchecked` with the
|
|
753
|
+
* reason in `detail`. A backend without it, a third-party one or a {@link brandAsBackend} wrapper that does not
|
|
754
|
+
* forward it, offers no lifecycle check, and `store.preflight()` reads that as `unchecked`.
|
|
755
|
+
*
|
|
756
|
+
* The cloud rules stay in the driver. A `detail` is for a person, carries no credential, and never changes what a
|
|
757
|
+
* caller does: act on `status`.
|
|
758
|
+
*/
|
|
759
|
+
checkLifecycle?(): Promise<LifecycleVerdict>;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* What {@link StorageBackend.checkLifecycle} found.
|
|
763
|
+
*
|
|
764
|
+
* - `ok`: no rule that could delete or archive the store's objects. `detail` may still carry advice, such as a missing
|
|
765
|
+
* rule that cleans up abandoned multipart uploads, which is never a `problem` by itself.
|
|
766
|
+
* - `problem`: a rule that deletes or archives the store's current objects, or one that cannot be told apart from it.
|
|
767
|
+
* Where a rule is unclear, the check reports a `problem`: a condition it does not interpret, or a value it does not
|
|
768
|
+
* recognise (a storage class, an action).
|
|
769
|
+
* - `unchecked`: the check could not be made (no permission to read the configuration, an error, or a backend whose
|
|
770
|
+
* rules are out of the data plane's reach). `detail` says what to grant or how to check by hand.
|
|
771
|
+
*/
|
|
772
|
+
export interface LifecycleVerdict {
|
|
773
|
+
/**
|
|
774
|
+
* `'ok'` when no rule would delete or archive the store's objects, `'problem'` when one would or may, `'unchecked'`
|
|
775
|
+
* when the check could not be made.
|
|
776
|
+
*/
|
|
777
|
+
readonly status: 'ok' | 'problem' | 'unchecked';
|
|
778
|
+
/** A sentence or two for a person. Never a credential. */
|
|
779
|
+
readonly detail: string;
|
|
704
780
|
}
|
|
705
781
|
/**
|
|
706
782
|
* The brand key, for the type-only `declare` field a backend class carries.
|
|
@@ -750,7 +826,7 @@ export declare function isStorageBackend(value: unknown): value is StorageBacken
|
|
|
750
826
|
* probability (ABA-safe across delete→recreate).
|
|
751
827
|
*
|
|
752
828
|
* **What a driver must do** — callers rely on each of these, and the registry conformance suite holds every
|
|
753
|
-
* shipped driver to them:
|
|
829
|
+
* shipped driver to them, except the rule on `unsettledConflict`, which the suite does not test:
|
|
754
830
|
*
|
|
755
831
|
* - **`create` and `compareAndSwap` are atomic conditional writes.** A `create` over a live row, and a
|
|
756
832
|
* `compareAndSwap` whose token is not the stored one, throw {@link WriteConflictError} and change nothing.
|
|
@@ -780,9 +856,19 @@ export declare function isStorageBackend(value: unknown): value is StorageBacken
|
|
|
780
856
|
* Either send each write once, with the backend client's retry off for that request, or recognise your own
|
|
781
857
|
* write when the precondition fails by reading an id you stored with it back. The shipped registries do
|
|
782
858
|
* one or the other.
|
|
859
|
+
* - **Raise a conflict that may follow your own write's landing with `unsettledConflict`** (from the driver kit). A
|
|
860
|
+
* driver whose client may have sent the write more than once cannot say, after a conflict, that this write did not
|
|
861
|
+
* land first: a row is overwritten, so another writer's write found on the read-back may have gone over this one, and
|
|
862
|
+
* a row that is gone may have been removed after it landed. Raise every conflict it cannot rule that out for (another
|
|
863
|
+
* writer's write on the read-back, a compare-and-swap that finds the row gone, a read-back that finds nothing) as
|
|
864
|
+
* `unsettledConflict`, so a load refused after it reports `unanswered: true`. A conflict that proves the write did
|
|
865
|
+
* not land (a write sent once, a check made before sending anything) is a plain {@link WriteConflictError}. A
|
|
866
|
+
* registry that wraps another rethrows the conflict it was given, unchanged: the check that reads the mark is
|
|
867
|
+
* internal, so a conflict built anew loses it.
|
|
783
868
|
* - **Raise a transient fault as {@link TransientError}.**
|
|
784
869
|
*/
|
|
785
870
|
export interface IRegistryDriver {
|
|
871
|
+
/** What this registry can do. Strongly consistent reads are required; the other capabilities are optional. */
|
|
786
872
|
capabilities(): RegCaps;
|
|
787
873
|
/**
|
|
788
874
|
* `true` when every `create` and `compareAndSwap` of this registry reports the token its write carries to
|
|
@@ -793,8 +879,15 @@ export interface IRegistryDriver {
|
|
|
793
879
|
*
|
|
794
880
|
* The flag may be a getter or a field. A registry that wraps or extends another and overrides `create` or
|
|
795
881
|
* `compareAndSwap` either passes `options` through to the write it forwards, or sets `reportsWriteTokens` to
|
|
796
|
-
* `false`. One that does neither reads
|
|
797
|
-
*
|
|
882
|
+
* `false`. One that does neither reads a write of its own that landed and then met a conflict as not its own
|
|
883
|
+
* (`superseded`), and takes every `TransientError` of its writes, a throttle and a lost answer alike, for a fault
|
|
884
|
+
* before sending: the publish throws it at once, with no read of the row and no fresh write. Either way it deletes
|
|
885
|
+
* nothing.
|
|
886
|
+
*
|
|
887
|
+
* A publish relies on the report coming first, both ways. A write that faults after its report may have landed, and
|
|
888
|
+
* is settled by reading the row. A `TransientError` before any report is taken for a fault before sending, such as
|
|
889
|
+
* the driver's own read of the row: the publish throws it at once, with no read of the row and no fresh write. So
|
|
890
|
+
* nothing that can land is sent before the report, a client's own resend included.
|
|
798
891
|
*/
|
|
799
892
|
readonly reportsWriteTokens?: boolean;
|
|
800
893
|
/**
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runPreflight` — the first-run check of a store's storage and registry, behind `store.preflight()`.
|
|
3
|
+
*
|
|
4
|
+
* ```
|
|
5
|
+
* registry ── get(probe row) must be null, canWrite !== false
|
|
6
|
+
* write ───── put marker A under the probe key
|
|
7
|
+
* write-once put marker B under the same key: must throw WriteConflictError
|
|
8
|
+
* read ────── getTail: must return A (read-after-write); gives the version
|
|
9
|
+
* list ────── list the probe segment: must name the probe
|
|
10
|
+
* delete ──── (finally) delete this run's own key, with the tail's version when it has one
|
|
11
|
+
* lifecycle ─ backend.checkLifecycle(), bounded by a timer, unchecked when the backend has none
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The probe key is `cbm.due.preflight/segments/probe.<n>.crbm`, where `<n>` is a 48-bit random number. That
|
|
15
|
+
* namespace sits inside the reserved `cbm.due.` prefix an application can never name, so no segment of the application's can collide with it, and the registry
|
|
16
|
+
* writes nothing: the probe is a storage object only, which no registry row names and no scan of the registry
|
|
17
|
+
* reaches. Storage is only ever listed one segment at a time, from rows or from refs the application gave.
|
|
18
|
+
*
|
|
19
|
+
* A finding never throws. A step that fails (a transient fault included) is a `problem` whose detail says so, and the
|
|
20
|
+
* checks that cannot run without it are `unchecked`; the lifecycle step is the exception, whose failures, its timeout
|
|
21
|
+
* included, are `unchecked`. Every step runs through one guard, so nothing a step meets (an error whose getters throw,
|
|
22
|
+
* a verdict whose getters throw, a clock whose sleep rejects, an entropy source that throws) escapes. The only throw
|
|
23
|
+
* is a store with nothing to check, which the facade refuses before it gets here.
|
|
24
|
+
*
|
|
25
|
+
* Not checked: read-after-delete, the registry's `If-Match` on compare-and-swap, multipart abort permission,
|
|
26
|
+
* versioning and backups.
|
|
27
|
+
*
|
|
28
|
+
* Time arrives through the injected {@link Clock}: the lifecycle step is bounded by {@link LIFECYCLE_TIMEOUT_MS},
|
|
29
|
+
* counted in short sleeps of {@link LIFECYCLE_TICK_MS} so that a check that answers at once leaves no long timer
|
|
30
|
+
* holding a short-lived process open.
|
|
31
|
+
*/
|
|
32
|
+
import type { Clock, Entropy } from './determinism.js';
|
|
33
|
+
import type { IRegistryDriver, IStorageDriver, LifecycleVerdict } from './ports.js';
|
|
34
|
+
/**
|
|
35
|
+
* What one check found.
|
|
36
|
+
*
|
|
37
|
+
* - `ok`: the check ran and found nothing wrong.
|
|
38
|
+
* - `problem`: the check ran and found something wrong, or the request it makes failed (a transient fault included:
|
|
39
|
+
* `detail` says to run again then). The lifecycle check is the exception: its failures, its timeout included, are
|
|
40
|
+
* `unchecked`.
|
|
41
|
+
* - `unchecked`: the check could not be made, because a step it depends on failed (`write-once`, `read`, `list` and
|
|
42
|
+
* `delete` after a failed `write`), or the backend offers no such check, or the lifecycle check did not answer in
|
|
43
|
+
* time. `ok` on the result does not vouch for it.
|
|
44
|
+
*/
|
|
45
|
+
export interface PreflightCheck {
|
|
46
|
+
/** Which check this is. A run lists all seven, in this order. */
|
|
47
|
+
readonly name: 'registry' | 'write' | 'write-once' | 'read' | 'list' | 'delete' | 'lifecycle';
|
|
48
|
+
/** What the check found. */
|
|
49
|
+
readonly status: 'ok' | 'problem' | 'unchecked';
|
|
50
|
+
/** A sentence or two for a person: what was found, and what to do. The name and message of a failed step's error. */
|
|
51
|
+
readonly detail: string;
|
|
52
|
+
}
|
|
53
|
+
/** What `store.preflight()` found: every check, and whether none of them is a `problem`. */
|
|
54
|
+
export interface PreflightResult {
|
|
55
|
+
/** True when no check is a `problem`. A check that is `unchecked` does not make it false. */
|
|
56
|
+
readonly ok: boolean;
|
|
57
|
+
/** All seven checks, in the order the type of `PreflightCheck.name` lists them. */
|
|
58
|
+
readonly checks: PreflightCheck[];
|
|
59
|
+
}
|
|
60
|
+
/** What a run needs: the store's raw drivers, an entropy source for the probe's number, and a clock for the bound. */
|
|
61
|
+
export interface PreflightDeps {
|
|
62
|
+
readonly storage: IStorageDriver;
|
|
63
|
+
readonly registry: IRegistryDriver;
|
|
64
|
+
readonly clock: Clock;
|
|
65
|
+
readonly entropy: Entropy;
|
|
66
|
+
/** The backend's lifecycle check, bound to the backend; absent for a backend that has none. */
|
|
67
|
+
readonly checkLifecycle?: (() => Promise<LifecycleVerdict>) | undefined;
|
|
68
|
+
}
|
|
69
|
+
/** The probe's namespace, inside the reserved prefix; no application ref can name it. */
|
|
70
|
+
export declare const PREFLIGHT_NAMESPACE = "cbm.due.preflight";
|
|
71
|
+
/** The probe's segment name. */
|
|
72
|
+
export declare const PREFLIGHT_SEGMENT = "probe";
|
|
73
|
+
/** How long the lifecycle step may take before it is read as `unchecked`: 30 seconds of the injected clock. */
|
|
74
|
+
export declare const LIFECYCLE_TIMEOUT_MS = 30000;
|
|
75
|
+
/** The length of each sleep the bound is counted in. */
|
|
76
|
+
export declare const LIFECYCLE_TICK_MS = 250;
|
|
77
|
+
/**
|
|
78
|
+
* Run the seven checks. Never rejects: see the module comment.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runPreflight(deps: PreflightDeps): Promise<PreflightResult>;
|