@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/audit.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type AuditEvent = {
|
|
|
38
38
|
} | {
|
|
39
39
|
/**
|
|
40
40
|
* A segment's pointer was moved by a rollback to a generation still in the bucket that the caller named:
|
|
41
|
-
* **backwards**, or forward
|
|
41
|
+
* **backwards**, or forward onto a generation a rollback left above the pointer, which undoes an earlier rollback.
|
|
42
42
|
*
|
|
43
43
|
* The only non-forward-only pointer move in the library, and the only one no automatic path can perform —
|
|
44
44
|
* a human decided the current generation was wrong and named the one they wanted. Every other pointer move
|
|
@@ -54,6 +54,14 @@ export type AuditEvent = {
|
|
|
54
54
|
readonly incarnation?: string;
|
|
55
55
|
readonly fromGeneration: number | null;
|
|
56
56
|
readonly generation: number;
|
|
57
|
+
/**
|
|
58
|
+
* Present, and `true`, when the row did not vouch for the target: the call passed `allowUnknown` for a generation the
|
|
59
|
+
* row does not know or whose object is not the one it recorded, or the row has no usable record of its generations
|
|
60
|
+
* (a registry that drops the field, or a pointer moved by hand) or the target is below a full record's bound, where the
|
|
61
|
+
* pointer alone decides for a target below it. Absent when the move was
|
|
62
|
+
* checked against the row's record.
|
|
63
|
+
*/
|
|
64
|
+
readonly unknown?: true;
|
|
57
65
|
} | {
|
|
58
66
|
/**
|
|
59
67
|
* A load was **refused**: it did not publish, because its result failed a guard or another writer got there
|
|
@@ -109,7 +117,9 @@ export type AuditEvent = {
|
|
|
109
117
|
* exists only for a finished erasure. A call that ends otherwise throws or reports `erased: false` with another
|
|
110
118
|
* reason and emits nothing, even when it had deleted a holder; its re-run emits when it finishes, and reports
|
|
111
119
|
* `'not-member'` with no event when another collection deleted the last holder meanwhile, as its ledger entry
|
|
112
|
-
* does.
|
|
120
|
+
* does. One finished erasure emits it and then throws `NotFoundError`: one that found, after its deletes, the
|
|
121
|
+
* segment's pointer on a generation it had deleted (a rollback moved it there meanwhile), so the deletion is on
|
|
122
|
+
* the record although the call cannot answer.
|
|
113
123
|
*
|
|
114
124
|
* `fromGeneration` is the newest generation the call found holding the id, and is absent when it found the id in
|
|
115
125
|
* none and deleted only objects it could not search: then the ledger lists no entry for the segment, and this
|
package/dist/core/bit-route.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BitRouteSplitter — maps a 32-bit id to its chunk (top 16 bits) and remainder
|
|
3
|
+
* (bottom 16 bits), per the data model.
|
|
4
|
+
*/
|
|
5
|
+
import { ValidationError } from './errors.js';
|
|
1
6
|
export declare const U32_MAX = 4294967295;
|
|
2
7
|
export declare const CHUNK_COUNT = 65536;
|
|
3
|
-
|
|
8
|
+
interface BitRoute {
|
|
4
9
|
/** Top 16 bits — the chunk key (0..65535). */
|
|
5
10
|
readonly chunkKey: number;
|
|
6
11
|
/** Bottom 16 bits — the value within the chunk (0..65535). */
|
|
7
12
|
readonly remainder: number;
|
|
8
13
|
}
|
|
14
|
+
/** Was `err` thrown by {@link splitId} for an id outside the u32 range? */
|
|
15
|
+
export declare function isBadIdError(err: unknown): err is ValidationError;
|
|
9
16
|
/** Split a u32 id into `{ chunkKey, remainder }`. Throws `ValidationError` for non-u32 input. */
|
|
10
17
|
export declare function splitId(id: number): BitRoute;
|
|
11
18
|
/**
|
|
@@ -19,3 +26,4 @@ export declare const MAX_REMAINDER: number;
|
|
|
19
26
|
* invariant 5); `remainder` is masked to 16 bits.
|
|
20
27
|
*/
|
|
21
28
|
export declare function joinId(chunkKey: number, remainder: number): number;
|
|
29
|
+
export {};
|
package/dist/core/blob.d.ts
CHANGED
|
@@ -12,12 +12,23 @@ export interface BlobSink {
|
|
|
12
12
|
export interface BlobReader {
|
|
13
13
|
/** Exactly `length` bytes starting at `offset`. Out-of-range is a `ValidationError`, never a short read. */
|
|
14
14
|
getRange(offset: number, length: number): Promise<Uint8Array>;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The last `min(maxBytes, size)` bytes plus the total size — the one-GET footer+index path. `maxBytes` is a safe
|
|
17
|
+
* integer, zero or less for an empty tail; any other length is refused with `ValidationError`.
|
|
18
|
+
*/
|
|
16
19
|
getTail(maxBytes: number): Promise<{
|
|
17
20
|
bytes: Uint8Array;
|
|
18
21
|
size: number;
|
|
19
22
|
}>;
|
|
20
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Check the length a tail read asks for, the one rule every tail read holds to (each storage driver's `getTail` and
|
|
26
|
+
* {@link BufferReader.getTail}): a safe integer. Zero or less answers an empty tail with the size, and a length past
|
|
27
|
+
* the object's size answers the whole object. Anything else is refused with `ValidationError` before anything is read:
|
|
28
|
+
* `NaN`, a fraction (a negative one too), an infinity of either sign, and a whole number past the safe range (2^53 or
|
|
29
|
+
* more, or -2^53 or less), so no reader takes one for some other length.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkTailLength(maxBytes: number): void;
|
|
21
32
|
/** In-memory `BlobSink` that accumulates written bytes; `bytes()` returns the concatenation. */
|
|
22
33
|
export declare class BufferSink implements BlobSink {
|
|
23
34
|
private readonly chunks;
|
package/dist/core/codec.d.ts
CHANGED
|
@@ -103,7 +103,7 @@ export interface CodecBitmap {
|
|
|
103
103
|
/**
|
|
104
104
|
* This set as storage chunks: ascending by chunk key, empty chunks left out, each payload exactly the bytes that
|
|
105
105
|
* {@link CodecInterface.fromValues} of that chunk's low 16 bits, then {@link optimize}, then {@link serialize}
|
|
106
|
-
* would give. So a generation written from these
|
|
106
|
+
* would give. So a generation written from these holds chunk for chunk what the same ids write. It may re-encode
|
|
107
107
|
* this bitmap in place first, as `optimize()` does; membership is untouched.
|
|
108
108
|
*
|
|
109
109
|
* **Flavor-author surface, and optional.** A load from a bitmap or from serialized bytes writes these, never
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* An expression over the operands of a batch combine (a store's `materializeMany`): a string names an operand; a node
|
|
3
|
+
* holds exactly one of `and`, `or` or `andNot` over a non-empty list of expressions. `andNot` subtracts every entry after
|
|
4
|
+
* the first from the first, so it takes at least two. Plain JSON, so it can be logged and sent between tasks.
|
|
5
|
+
*/
|
|
6
|
+
export type Expr = string | {
|
|
7
|
+
and: Expr[];
|
|
4
8
|
} | {
|
|
5
|
-
or:
|
|
9
|
+
or: Expr[];
|
|
6
10
|
} | {
|
|
7
|
-
andNot:
|
|
11
|
+
andNot: Expr[];
|
|
8
12
|
};
|
|
9
13
|
/** The most operator nodes one expression may nest. A literal that contains itself is refused by it. */
|
|
10
14
|
export declare const MAX_EXPR_DEPTH = 64;
|
|
@@ -21,7 +25,7 @@ export type ExprNode = {
|
|
|
21
25
|
readonly kids: readonly ExprNode[];
|
|
22
26
|
};
|
|
23
27
|
/** How many nodes one {@link compileExpr} call may still allocate; shared across the expressions of one output. */
|
|
24
|
-
|
|
28
|
+
interface NodeBudget {
|
|
25
29
|
remaining: number;
|
|
26
30
|
}
|
|
27
31
|
/**
|
|
@@ -38,8 +42,6 @@ export interface KeyBounds {
|
|
|
38
42
|
readonly keys: Uint16Array;
|
|
39
43
|
readonly card: Uint32Array;
|
|
40
44
|
}
|
|
41
|
-
/** The keys of an operand the plan cannot know (a fed one): every key. Compared by identity, never by content. */
|
|
42
|
-
export declare const ANY_KEYS: Uint16Array;
|
|
43
45
|
/** The bounds of an operand the plan cannot know. */
|
|
44
46
|
export declare const ANY_BOUNDS: KeyBounds;
|
|
45
47
|
/** What {@link planKeys} keeps of each operator node: the keys it can be non-empty at. */
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
+
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* convert each operand's ids into one compressed bitmap, charged to the ledger as it is built
|
|
10
|
-
* group the records of one key, held until a record with a higher key arrives or the feed ends
|
|
11
|
-
* end of feed counts equal what was seen; a declared operand that never appeared is refused unless allowed
|
|
12
|
-
* ```
|
|
13
|
-
*
|
|
14
|
-
* Nothing here ever reads a bad feed as fewer members: every check is a refusal, and a refusal ends the feed.
|
|
3
|
+
* One record of the feed of a batch combine (a store's `materializeMany`): the ids some of the fed operands hold at one
|
|
4
|
+
* chunk key. `key` is `id >>> 16`, an integer from 0 to 65,535, never below the previous record's; a key may arrive as
|
|
5
|
+
* several consecutive records, each carrying some of the operands, and one record per key is the usual shape. `operands`
|
|
6
|
+
* maps a declared fed name to its ids at the key: a real `Uint32Array` (not an `Int32Array`, `Buffer`, array or
|
|
7
|
+
* look-alike), strictly ascending, every id inside the key. A name is given at most once per key, and an empty array is
|
|
8
|
+
* the same as leaving the name out. The record is copied as it arrives, so the caller may drop or reuse its arrays once
|
|
9
|
+
* it has yielded it.
|
|
15
10
|
*/
|
|
16
|
-
|
|
17
|
-
/** One record of a feed: the ids each named operand holds at one chunk key. */
|
|
18
|
-
export interface CombineManyFeedRecord {
|
|
11
|
+
export interface MaterializeManyFeedRecord {
|
|
19
12
|
/** The chunk key, `id >>> 16`, from 0 to 65,535. Non-decreasing over the feed; a key may arrive as several records. */
|
|
20
13
|
readonly key: number;
|
|
21
14
|
/** Ids by operand name: ascending, unique, every one inside `key`. An operand is named at most once per key. */
|
|
@@ -26,7 +19,7 @@ export interface CombineManyFeed {
|
|
|
26
19
|
/** The fed operand names, declared up front. */
|
|
27
20
|
readonly names: readonly string[];
|
|
28
21
|
/** The records, read once, in key order. */
|
|
29
|
-
readonly records: AsyncIterable<
|
|
22
|
+
readonly records: AsyncIterable<MaterializeManyFeedRecord>;
|
|
30
23
|
/**
|
|
31
24
|
* The ids each fed operand holds over the whole feed, keyed by exactly `names`: an object, or a function called once
|
|
32
25
|
* after the last record. A count that differs from what the feed held refuses every fed output.
|
|
@@ -56,7 +49,7 @@ export declare class FeedClosed extends Error {
|
|
|
56
49
|
export declare class FeedStale extends Error {
|
|
57
50
|
}
|
|
58
51
|
/** What a {@link FeedCursor} needs from the pass that runs it. */
|
|
59
|
-
|
|
52
|
+
interface FeedCursorDeps {
|
|
60
53
|
readonly feed: CheckedFeed;
|
|
61
54
|
readonly codec: CodecInterface;
|
|
62
55
|
/** The index the first fed operand has among the call's operands. */
|
|
@@ -136,3 +129,4 @@ export declare class FeedCursor {
|
|
|
136
129
|
/** The counts and the empty rule, run once, after the iterator has ended and before anything is published. */
|
|
137
130
|
private finish;
|
|
138
131
|
}
|
|
132
|
+
export {};
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
2
2
|
import type { IMetricsSink } from './metrics.js';
|
|
3
3
|
import type { CombineManyFeed } from './combine-feed.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Expr } from './combine-expr.js';
|
|
5
5
|
import type { Clock } from './determinism.js';
|
|
6
6
|
import type { Budget } from './budget.js';
|
|
7
7
|
import type { LoadGuard } from './load.js';
|
|
8
8
|
import type { GenerationMetadata, SegmentRef, StorageChunkSource } from './ports.js';
|
|
9
|
-
export type {
|
|
9
|
+
export type { Expr } from './combine-expr.js';
|
|
10
10
|
export { MAX_EXPR_DEPTH, MAX_EXPR_NODES } from './combine-expr.js';
|
|
11
|
-
export type { CombineManyFeed,
|
|
11
|
+
export type { CombineManyFeed, MaterializeManyFeedRecord } from './combine-feed.js';
|
|
12
12
|
/** The range requests a pass keeps in flight at once across all its operands, inside a client's socket pool. */
|
|
13
13
|
export declare const MAX_RANGES_IN_FLIGHT = 64;
|
|
14
|
-
/** The largest `publishConcurrency` and `concurrency` a call may ask for. */
|
|
15
|
-
export declare const MAX_BATCH_PARALLELISM = 1024;
|
|
16
14
|
/** One named stored operand, as the pass reads it. */
|
|
17
15
|
export interface CombineManyOperand {
|
|
18
16
|
readonly name: string;
|
|
@@ -50,9 +48,9 @@ export interface CombineManyOperand {
|
|
|
50
48
|
}
|
|
51
49
|
/** What one output asks for. */
|
|
52
50
|
export interface CombineManyOutput<R> {
|
|
53
|
-
readonly expr:
|
|
51
|
+
readonly expr: Expr;
|
|
54
52
|
/** Subtracted from the result: a root `andNot`. */
|
|
55
|
-
readonly exclude?: readonly
|
|
53
|
+
readonly exclude?: readonly Expr[];
|
|
56
54
|
readonly allowEmpty?: boolean;
|
|
57
55
|
readonly guard?: LoadGuard;
|
|
58
56
|
readonly metadata?: GenerationMetadata;
|
|
@@ -128,8 +126,8 @@ export type CombineManyOutcome<R> = {
|
|
|
128
126
|
readonly ok: false;
|
|
129
127
|
readonly error: unknown;
|
|
130
128
|
};
|
|
131
|
-
/** What
|
|
132
|
-
export interface
|
|
129
|
+
/** What a batch combine (a store's `materializeMany`) did to one operand: see {@link MaterializeManyStats.operands}. */
|
|
130
|
+
export interface MaterializeManyOperandStats {
|
|
133
131
|
/** Whether an output of the call read it. An operand no output names is not read. */
|
|
134
132
|
readonly read: boolean;
|
|
135
133
|
/** Whether the operand was read at a pinned generation. */
|
|
@@ -157,8 +155,8 @@ export interface CombineManyOperandStats {
|
|
|
157
155
|
/** Chunk reads made of it, over all groups. */
|
|
158
156
|
readonly chunkReads: number;
|
|
159
157
|
}
|
|
160
|
-
/** What
|
|
161
|
-
export interface
|
|
158
|
+
/** What a batch combine (a store's `materializeMany`) did to one output: see {@link MaterializeManyStats.outputs}. */
|
|
159
|
+
export interface MaterializeManyOutputStats {
|
|
162
160
|
/** The names of the operands its expression reads: the stored ones in call order, then the fed ones in `feed.names` order. */
|
|
163
161
|
readonly operands: readonly string[];
|
|
164
162
|
/** The group that computed it (from 0), or `null` when it never ran. */
|
|
@@ -173,7 +171,11 @@ export interface CombineManyOutputStats {
|
|
|
173
171
|
/** The most its buffer held resident, in the ledger's own measure; compare with `boundBytes` to see how tight a plan was. */
|
|
174
172
|
readonly peakBytes: number;
|
|
175
173
|
}
|
|
176
|
-
|
|
174
|
+
/**
|
|
175
|
+
* What a batch combine (a store's `materializeMany`) did: the groups it ran, its requests by class, the chunk-read budget
|
|
176
|
+
* it used, the most it held resident, and, for each operand, the generation it was pinned at, began at and ended at.
|
|
177
|
+
*/
|
|
178
|
+
export interface MaterializeManyStats {
|
|
177
179
|
/** Groups run, the re-runs of outputs a lying index made too big included. */
|
|
178
180
|
readonly groups: number;
|
|
179
181
|
readonly requests: {
|
|
@@ -235,19 +237,17 @@ export interface CombineManyStats {
|
|
|
235
237
|
readonly ids: number;
|
|
236
238
|
};
|
|
237
239
|
/** By operand name, an object with no prototype, so any name is a key. */
|
|
238
|
-
readonly operands: Readonly<Record<string,
|
|
239
|
-
readonly outputs: readonly
|
|
240
|
+
readonly operands: Readonly<Record<string, MaterializeManyOperandStats>>;
|
|
241
|
+
readonly outputs: readonly MaterializeManyOutputStats[];
|
|
240
242
|
}
|
|
241
243
|
export interface CombineManyRun<R> {
|
|
242
244
|
readonly outputs: readonly CombineManyOutcome<R>[];
|
|
243
|
-
readonly stats:
|
|
245
|
+
readonly stats: MaterializeManyStats;
|
|
244
246
|
}
|
|
245
247
|
/** A request that passed every check that needs no I/O: what {@link runCombineMany} runs. */
|
|
246
248
|
export interface CompiledCombineMany<R> {
|
|
247
249
|
readonly request: CombineManyRequest<R>;
|
|
248
250
|
}
|
|
249
|
-
/** The most nodes the expressions of one call may hold in all, so that a thousand large outputs cannot be planned at once. */
|
|
250
|
-
export declare const MAX_CALL_NODES = 200000;
|
|
251
251
|
/**
|
|
252
252
|
* Check a request: every expression, option and number, before any request is made. Throws {@link ValidationError}
|
|
253
253
|
* naming the output index and the path. Pure.
|
|
@@ -259,7 +259,7 @@ export declare function compileCombineMany<R>(request: CombineManyRequest<R>): C
|
|
|
259
259
|
*/
|
|
260
260
|
export declare function rebindCombineMany<R>(compiled: CompiledCombineMany<R>, operands: readonly CombineManyOperand[]): CompiledCombineMany<R>;
|
|
261
261
|
/** What {@link GroupCost} reads of an output. */
|
|
262
|
-
|
|
262
|
+
interface GroupCostItem {
|
|
263
263
|
readonly bound: number;
|
|
264
264
|
readonly serializedBound: number;
|
|
265
265
|
readonly rootKeys: Uint16Array;
|
|
@@ -270,14 +270,15 @@ export interface GroupCostItem {
|
|
|
270
270
|
}
|
|
271
271
|
/**
|
|
272
272
|
* Work counters of the planner, for the tests that hold it to its cost: how many times a group was priced, how many
|
|
273
|
-
* operands those prices walked, and how many group plans were built.
|
|
273
|
+
* operands those prices walked, and how many group plans were built. A call counts into the one it is given
|
|
274
|
+
* ({@link runCombineManyCounted}), and into none otherwise; the planner never reads them.
|
|
274
275
|
*/
|
|
275
|
-
export
|
|
276
|
+
export interface PlanWork {
|
|
276
277
|
adds: number;
|
|
277
278
|
withs: number;
|
|
278
279
|
operandSteps: number;
|
|
279
280
|
planBuilds: number;
|
|
280
|
-
}
|
|
281
|
+
}
|
|
281
282
|
/**
|
|
282
283
|
* The resident bytes a group needs, kept as outputs are added so that one is priced in work proportional to its own
|
|
283
284
|
* operands, not to the group's size: its buffered outputs, its operand streams, the chunks being evaluated, the objects
|
|
@@ -286,6 +287,8 @@ export declare const combineWork: {
|
|
|
286
287
|
export declare class GroupCost {
|
|
287
288
|
private readonly streamBytes;
|
|
288
289
|
private readonly publishConcurrency;
|
|
290
|
+
/** Where this pricing is counted, when it is. */
|
|
291
|
+
private readonly work?;
|
|
289
292
|
private outputs;
|
|
290
293
|
private depth;
|
|
291
294
|
/** The serialized bounds of the largest outputs, descending: at most `publishConcurrency` of them are written at once. */
|
|
@@ -294,7 +297,9 @@ export declare class GroupCost {
|
|
|
294
297
|
private streams;
|
|
295
298
|
private planWork;
|
|
296
299
|
private readonly operands;
|
|
297
|
-
constructor(streamBytes: (operand: number) => number, publishConcurrency: number
|
|
300
|
+
constructor(streamBytes: (operand: number) => number, publishConcurrency: number,
|
|
301
|
+
/** Where this pricing is counted, when it is. */
|
|
302
|
+
work?: PlanWork | undefined);
|
|
298
303
|
/** The cost of the group with `o` added. */
|
|
299
304
|
with(o: GroupCostItem): number;
|
|
300
305
|
add(o: GroupCostItem): void;
|
|
@@ -313,6 +318,8 @@ export declare class GroupCost {
|
|
|
313
318
|
* budget, both before any chunk is read.
|
|
314
319
|
*/
|
|
315
320
|
export declare function runCombineMany<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps): Promise<CombineManyRun<R>>;
|
|
321
|
+
/** {@link runCombineMany}, counting the planner's work into `work`. Not exported from the package. */
|
|
322
|
+
export declare function runCombineManyCounted<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps, work: PlanWork): Promise<CombineManyRun<R>>;
|
|
316
323
|
/**
|
|
317
324
|
* The `pointerId` of a row's version (`<generation>:<pointerId>`, as a pin holds it), or `undefined` where the version
|
|
318
325
|
* names no row. A live version from the `.crbm` source ends with the opened object's fingerprint: read it through
|
|
@@ -13,3 +13,11 @@
|
|
|
13
13
|
* S3 page, chunk map), so an array keeps the pool trivial and race-free (no shared-iterator hazard).
|
|
14
14
|
*/
|
|
15
15
|
export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
16
|
+
/**
|
|
17
|
+
* `Promise.all` over `items` with at most `limit` calls of `fn` in flight: the results in input order, or the first
|
|
18
|
+
* failure, at once. Unlike {@link mapWithConcurrency} it does not wait for the calls still in flight before it rejects,
|
|
19
|
+
* as `Promise.all` does not; it starts no call after a failure, and what the calls in flight answer is dropped. For reads
|
|
20
|
+
* a caller abandons on the first fault, never for work that must finish or be accounted for once begun. When every item
|
|
21
|
+
* fits under `limit` it is `Promise.all` itself, so a small fan-out costs and interleaves exactly as `Promise.all` does.
|
|
22
|
+
*/
|
|
23
|
+
export declare function boundedAll<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]>;
|
|
@@ -3,18 +3,23 @@
|
|
|
3
3
|
* generations can be restored **independently**, so a failover can recover the registry *ahead of* the storage
|
|
4
4
|
* objects — leaving `currentGen` pointing at a generation whose `.crbm` isn't present yet. That is likelier
|
|
5
5
|
* than it sounds even with both in one bucket: a restore scoped to a prefix, or replayed
|
|
6
|
-
* per-object from a version history, recovers the two prefixes at different points. That's a torn restore: reads of
|
|
6
|
+
* per-object from a version history, recovers the two prefixes at different points. That's a torn restore: reads of
|
|
7
|
+
* the affected segment then throw. This scan
|
|
7
8
|
* detects it up front (run it at startup after a restore) instead of discovering it on the first read.
|
|
8
9
|
*
|
|
9
10
|
* Read-only; bounded fan-out. `destroyed` (crypto-shredded) segments are skipped — their Storage is intentionally
|
|
10
11
|
* gone/unreadable, not a torn restore. A segment whose Storage/registry can't be read this pass is recorded in
|
|
11
|
-
* `errored` (never aborts the scan).
|
|
12
|
+
* `errored` (never aborts the scan). One segment can add several entries to `inconsistent` (its current object, and with
|
|
13
|
+
* `generations` one per known generation whose object is not the one recorded) and can also be in `errored`, when a
|
|
14
|
+
* read failed after those were found; with `generations` a segment costs up to one read per entry of its ledger.
|
|
15
|
+
* Each segment is checked against its **authoritative live pointer** — one
|
|
12
16
|
* strong `registry.get` per segment — never the enumeration snapshot from `registry.list`, which a driver may
|
|
13
17
|
* serve eventually-consistently and so lag a recent in-place pointer advance: trusting it would both
|
|
14
18
|
* miss a torn *live* generation and cry torn on a generation the pointer has already advanced past (GC'd during
|
|
15
|
-
* the scan). Residual: a load's publish plus a GC landing in the
|
|
16
|
-
*
|
|
17
|
-
* to confirm a
|
|
19
|
+
* the scan). Residual: a load's publish plus a GC landing in the per-segment gap between the pointer read and the
|
|
20
|
+
* object reads (one read, or with `generations` as many as the ledger names) can still yield a transient false
|
|
21
|
+
* positive — run the scan against a quiesced fleet (the documented restore procedure), or re-run to confirm a
|
|
22
|
+
* reported tear.
|
|
18
23
|
*/
|
|
19
24
|
import type { IStorageDriver, IRegistryDriver } from './ports.js';
|
|
20
25
|
import type { IKeystore } from './crypto.js';
|
|
@@ -33,7 +38,11 @@ export { DEFAULT_MAX_SCAN_SEGMENTS } from './registry-scan.js';
|
|
|
33
38
|
export interface ConsistencyIssue {
|
|
34
39
|
readonly segment: string;
|
|
35
40
|
readonly namespace?: string;
|
|
36
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The registry's `currentGen` for the segment. For `missing-storage-generation` it is the generation whose `.crbm` is
|
|
43
|
+
* missing from Storage; for the other issues it is the segment's current generation, whichever generation the issue
|
|
44
|
+
* is about (see `generation`).
|
|
45
|
+
*/
|
|
37
46
|
readonly currentGen: number;
|
|
38
47
|
/**
|
|
39
48
|
* `missing-storage-generation`: `currentGen` references a Storage generation that is not present (torn restore).
|
|
@@ -44,8 +53,20 @@ export interface ConsistencyIssue {
|
|
|
44
53
|
* that opens the object refuses it; one whose count or metadata disagrees is answered from until a read opens the
|
|
45
54
|
* object, which stops that store using it; one whose fingerprint names no object is not used, so every read opens the
|
|
46
55
|
* object unchecked.
|
|
56
|
+
* `known-generation-mismatch` (only when asked for): the object under the number `generation` is not the one the row's
|
|
57
|
+
* ledger of known generations recorded for it: its footer checksum differs, or its footer fails its own checks (it is
|
|
58
|
+
* not a `.crbm` object, or is cut short). It says nothing of what a rollback does with that generation.
|
|
59
|
+
* What it covers: each object's footer is held to the recorded checksum, so another object, or bytes that are not a
|
|
60
|
+
* `.crbm`, under a known number is found. It does not find damage inside an object whose footer is intact (a cut or a
|
|
61
|
+
* flipped byte in the body: a read of that generation reports it as `IntegrityError`), nor a metadata change that keeps
|
|
62
|
+
* the footer's bytes.
|
|
47
63
|
*/
|
|
48
|
-
readonly issue: 'missing-storage-generation' | 'summary-mismatch';
|
|
64
|
+
readonly issue: 'missing-storage-generation' | 'summary-mismatch' | 'known-generation-mismatch';
|
|
65
|
+
/**
|
|
66
|
+
* With `known-generation-mismatch` only: the generation whose object is not the one the row recorded. It is
|
|
67
|
+
* `currentGen` when the current object is the one that differs. Absent on every other issue.
|
|
68
|
+
*/
|
|
69
|
+
readonly generation?: number;
|
|
49
70
|
}
|
|
50
71
|
/** A segment that could not be checked this pass (Storage/registry read fault) — not proof of a torn restore. */
|
|
51
72
|
export interface ConsistencyErrorEntry {
|
|
@@ -57,12 +78,17 @@ export interface ConsistencyErrorEntry {
|
|
|
57
78
|
export interface ConsistencyReport {
|
|
58
79
|
/** Registered segments scanned (a `destroyed` segment counts as scanned but is never an issue). */
|
|
59
80
|
readonly checked: number;
|
|
60
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The issues found, one entry each: a segment whose `currentGen` `.crbm` is absent (recover the object store, or
|
|
83
|
+
* restore to a coherent point), and with the options asked for the other kinds. One segment can have several entries:
|
|
84
|
+
* at most one for its current object plus one per entry of its ledger, so at most 1 plus the ledger's length (384 at
|
|
85
|
+
* most). The ledger's length is bounded by the registry, and the report is not capped.
|
|
86
|
+
*/
|
|
61
87
|
readonly inconsistent: ConsistencyIssue[];
|
|
62
88
|
/**
|
|
63
89
|
* Segments that couldn't be read this pass (a transient/partial object store during a restore is exactly when
|
|
64
|
-
* this runs) — **triage these too**: an unread segment is neither proven-coherent nor proven-torn.
|
|
65
|
-
* clean pass.
|
|
90
|
+
* this runs) — **triage these too**: an unread segment is neither proven-coherent nor proven-torn. A segment can be in
|
|
91
|
+
* this list and in `inconsistent` too: what was found before the read that failed is kept. Empty on a clean pass.
|
|
66
92
|
*/
|
|
67
93
|
readonly errored: ConsistencyErrorEntry[];
|
|
68
94
|
/**
|
|
@@ -71,19 +97,44 @@ export interface ConsistencyReport {
|
|
|
71
97
|
* only when summaries were asked for.
|
|
72
98
|
*/
|
|
73
99
|
readonly summariesUnchecked?: number;
|
|
100
|
+
/**
|
|
101
|
+
* With `generations: true`, the rows with a pointer whose ledger of known generations could not be used (absent, or
|
|
102
|
+
* failing the validation every reader of it applies, or not shaped as a ledger a registry of this library stores):
|
|
103
|
+
* none of their known generations was held against its object. A count, not an issue. Present only when generations
|
|
104
|
+
* were asked for.
|
|
105
|
+
*/
|
|
106
|
+
readonly generationsUnchecked?: number;
|
|
74
107
|
}
|
|
75
108
|
/**
|
|
76
109
|
* Verify every registered segment's `currentGen` `.crbm` actually exists in Storage. Enumerates the registry
|
|
77
|
-
* (optionally one namespace) and, for each non-`destroyed` segment,
|
|
78
|
-
*
|
|
110
|
+
* (optionally one namespace) and, for each non-`destroyed` segment, asks the object store for that generation with a
|
|
111
|
+
* zero-length tail read. Returns the torn segments in `inconsistent` (empty ⇒ coherent) and any unreadable segments in
|
|
79
112
|
* `errored`.
|
|
80
113
|
*
|
|
114
|
+
* When that read fails, in any way, the segment's listing decides and its own error is the segment's, so a missing
|
|
115
|
+
* object, a missing bucket and a failing store are reported as when every segment was listed. One difference: a store
|
|
116
|
+
* that answers reads but refuses listings (an identity without list permission, or a listing-only outage) reports each
|
|
117
|
+
* object that is there as present, where every segment was reported errored.
|
|
118
|
+
*
|
|
81
119
|
* With `summaries: true` it also opens each segment's current object (one tail read, and a second for an index longer
|
|
82
|
-
* than it) and holds the row's summary against it: the object it names by its fingerprint, and the count and metadata
|
|
83
|
-
* says, reporting `summary-mismatch` where they disagree, and for a clear summary whose fingerprint names no
|
|
84
|
-
* which no read uses.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
120
|
+
* than it) and holds the row's summary against it: the object it names by its fingerprint, and the count and metadata
|
|
121
|
+
* it says, reporting `summary-mismatch` where they disagree, and for a clear summary whose fingerprint names no
|
|
122
|
+
* object, which no read uses. For a row with a clear summary that open is the only request of the object; if it fails
|
|
123
|
+
* before its first read answered, the listing decides, and once that read has answered a failure is an error of the
|
|
124
|
+
* segment. A sealed summary needs `deps.keystore`; without it the segment is counted in `summariesUnchecked`. Off by
|
|
125
|
+
* default, since the default check only looks for the object.
|
|
126
|
+
*
|
|
127
|
+
* With `generations: true` it also holds every generation a row's ledger of known generations names, the current one
|
|
128
|
+
* included, against its object: one footer read each (104 bytes, no key, not retried), reporting
|
|
129
|
+
* `known-generation-mismatch` with the `generation` where the object's footer checksum is not the one the ledger records
|
|
130
|
+
* or its footer fails its own checks. That finds another object, or bytes that are not a `.crbm`, under a known number;
|
|
131
|
+
* it does not find damage inside an object whose footer is intact (a read of it reports that as `IntegrityError`), nor a
|
|
132
|
+
* metadata change that keeps the footer's bytes. A segment adds at most 1 plus its ledger's length issues. A named
|
|
133
|
+
* generation that is not in the bucket is not reported. A row with a pointer whose ledger cannot be used (absent, failing
|
|
134
|
+
* the validation every reader of it applies, or not shaped as a ledger a registry of this library stores) is counted in
|
|
135
|
+
* `generationsUnchecked` and none of its objects is read. The listing is not used: a read of a missing object
|
|
136
|
+
* answers `NotFoundError`, which is how an absent one is told. A segment whose current object is missing is reported as
|
|
137
|
+
* ever and its older generations are still checked. Off by default.
|
|
87
138
|
*/
|
|
88
139
|
export declare function runConsistencyCheck(deps: {
|
|
89
140
|
readonly storage: IStorageDriver;
|
|
@@ -94,4 +145,5 @@ export declare function runConsistencyCheck(deps: {
|
|
|
94
145
|
concurrency?: number;
|
|
95
146
|
maxScanSegments?: number;
|
|
96
147
|
summaries?: boolean;
|
|
148
|
+
generations?: boolean;
|
|
97
149
|
}): Promise<ConsistencyReport>;
|
|
@@ -7,7 +7,7 @@ import type { ChunkRead, GenerationMetadata, ReadChunksOptions } from '../ports.
|
|
|
7
7
|
* `cardinalityMinusOne` stores `cardinality - 1` because a chunk's cardinality runs `[1, 65536]` and 65,536 does
|
|
8
8
|
* not fit a `u16`; an offset is a `f64` because an object past 4 GiB puts a payload beyond a `u32`.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
interface ParsedIndex {
|
|
11
11
|
readonly keys: Uint16Array;
|
|
12
12
|
readonly cardinalityMinusOne: Uint16Array;
|
|
13
13
|
readonly lengths: Uint32Array;
|
|
@@ -29,7 +29,12 @@ export declare const RETAINED_BYTES_PER_INDEX_ENTRY = 20;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function indexCapacity(indexLength: number): number;
|
|
31
31
|
export interface CrbmReaderOptions {
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* How many bytes the first tail read asks for, a whole number (default 256 KiB; clamped up to at least the footer
|
|
34
|
+
* size). Any value of at least an object's `tailBytes` opens it in that one read. Where an open keeps a small object's
|
|
35
|
+
* chunks (a `CrbmStorageChunkSource`'s does), it keeps them only when this read covers the whole object, so a value
|
|
36
|
+
* below the default can leave them to reads of their own.
|
|
37
|
+
*/
|
|
33
38
|
readonly tailBytes?: number;
|
|
34
39
|
/** Hard cap on a single chunk payload length (default: the 1 MiB decode cap, plus the 28-byte nonce and tag on an encrypted object). The engine decodes under `maxBitmapBytes`; raise this with it. */
|
|
35
40
|
readonly maxPayloadBytes?: number;
|
|
@@ -49,6 +54,20 @@ export interface CrbmReaderOptions {
|
|
|
49
54
|
*/
|
|
50
55
|
readonly lineage?: unknown;
|
|
51
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Refuses a `size` that is not a whole byte count its own tail fits in. The size is the tier's word too: one that
|
|
59
|
+
* is not would turn off every bounds check an open makes against it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function checkSize(tail: Uint8Array, size: number): void;
|
|
62
|
+
/**
|
|
63
|
+
* What the footer of the object behind `blob` says with no key and no index, from one tail read of a footer's worth:
|
|
64
|
+
* whether it is encrypted, and its CRC (the one in {@link CrbmReader.fingerprint}). A footer that fails its own checks
|
|
65
|
+
* throws, as an open would.
|
|
66
|
+
*/
|
|
67
|
+
export declare function readFooter(blob: BlobReader): Promise<{
|
|
68
|
+
readonly encrypted: boolean;
|
|
69
|
+
readonly footerCrc: number;
|
|
70
|
+
}>;
|
|
52
71
|
/**
|
|
53
72
|
* Whether the object behind `blob` says it is encrypted, from one tail read of a footer's worth, with no key and no
|
|
54
73
|
* index; a footer that fails its own checks throws, as an open would. For a caller that must not open an object with
|
|
@@ -61,6 +80,14 @@ export declare function footerSaysEncrypted(blob: BlobReader): Promise<boolean>;
|
|
|
61
80
|
* object and that region is at most `keepChunkBytesUpTo` bytes. Not exported from a package entry.
|
|
62
81
|
*/
|
|
63
82
|
export declare function openCrbmReaderKeeping(blob: BlobReader, options: CrbmReaderOptions, keepChunkBytesUpTo: number | undefined): Promise<CrbmReader>;
|
|
83
|
+
/**
|
|
84
|
+
* How many bytes the first tail read of an open asks for: the caller's `tailBytes` or the default, and a footer's worth
|
|
85
|
+
* at least. Not exported from a package entry: {@link CrbmReader.open} and a caller that sends that read ahead of it
|
|
86
|
+
* ask for the same size.
|
|
87
|
+
*/
|
|
88
|
+
export declare function openTailBytes(options: {
|
|
89
|
+
readonly tailBytes?: number;
|
|
90
|
+
}): number;
|
|
64
91
|
export declare class CrbmReader {
|
|
65
92
|
private readonly blob;
|
|
66
93
|
private readonly objectSize;
|
|
@@ -102,6 +129,11 @@ export declare class CrbmReader {
|
|
|
102
129
|
get fingerprint(): string;
|
|
103
130
|
/** Total object bytes (from the one-GET tail read): the `sizeBytes` a segment's `stat()` reports. */
|
|
104
131
|
get sizeBytes(): number;
|
|
132
|
+
/**
|
|
133
|
+
* The bytes of the object after its chunk payloads: any extension block, the index and the footer. A tail read of
|
|
134
|
+
* this many bytes covers all of them.
|
|
135
|
+
*/
|
|
136
|
+
get tailBytes(): number;
|
|
105
137
|
/**
|
|
106
138
|
* Retained JS heap of this reader's parsed index — the weight the storage reader cache bounds on (a wide
|
|
107
139
|
* segment's parsed index, not its payloads, dominates the reader's footprint): the exact byte length of the
|
|
@@ -134,6 +166,16 @@ export declare class CrbmReader {
|
|
|
134
166
|
count(): number;
|
|
135
167
|
/** The slot holding `chunkKey` (binary search over the ascending keys), or -1 when this generation has none. */
|
|
136
168
|
private slotOf;
|
|
169
|
+
/**
|
|
170
|
+
* {@link slotOf} for a key at or after the one that held slot `at`, which a walk over ascending keys has: the slot
|
|
171
|
+
* itself or the next are tried first, so a read of nearly every chunk costs no search.
|
|
172
|
+
*/
|
|
173
|
+
private slotFrom;
|
|
174
|
+
/**
|
|
175
|
+
* The extents of the chunks `chunkKeys` name that this generation holds, in key order, each once (a key given twice
|
|
176
|
+
* is read once). A walk, so nothing is looked up until the plan pulls it.
|
|
177
|
+
*/
|
|
178
|
+
private neededExtents;
|
|
137
179
|
has(chunkKey: number): boolean;
|
|
138
180
|
/**
|
|
139
181
|
* Range-GET one chunk's payload, verifying its CRC32C before returning. Returns `null` if the chunk is
|
|
@@ -168,10 +210,13 @@ export declare class CrbmReader {
|
|
|
168
210
|
* as {@link getChunk} answers for an absent key. A key given twice is read once and answered at both positions with
|
|
169
211
|
* the same view.
|
|
170
212
|
*
|
|
171
|
-
* The
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* it
|
|
213
|
+
* The key list is planned against this one generation's index ({@link planChunkReads}), as a walk the window pulls
|
|
214
|
+
* from only as it opens a range, so a stream that stops early has planned little: the same ranges, in the same
|
|
215
|
+
* order, as a plan of the whole list would make, and each chunk's extent is checked before the range that carries
|
|
216
|
+
* it is requested. The ranges run through a window of `options.concurrency` reads ({@link MAX_RANGES_IN_FLIGHT} by
|
|
217
|
+
* default) that counts a range until the consumer has taken it, so a stream never holds more than that many ranges,
|
|
218
|
+
* in flight or landed, however many keys it was given or how slowly it is read; with `options.ramp` the window opens
|
|
219
|
+
* 1, 2, 4 … wide. A range is at most 1 MiB
|
|
175
220
|
* unless it is one chunk, which is at most the payload cap, so the bound is that many times the per-chunk cap
|
|
176
221
|
* (1 MiB, and 28 bytes more for an encrypted chunk). Nothing is read until the first item is asked for, and a
|
|
177
222
|
* consumer that stops launches no more: ranges already in flight finish and are dropped, and a failure of one of
|
|
@@ -223,3 +268,4 @@ export type ChunkStream = AsyncGenerator<Omit<ChunkRead, 'version'>> & {
|
|
|
223
268
|
*/
|
|
224
269
|
export declare function parseExtension(sections: Uint8Array, crypto: CrbmCrypto | undefined): GenerationMetadata | undefined;
|
|
225
270
|
export declare function parseIndex(indexBytes: Uint8Array, payloadEnd: number, maxPayloadBytes: number, minPayloadBytes?: number): ParsedIndex;
|
|
271
|
+
export {};
|