@cloudbitmaps/core 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/{chunk-YSC7NJWV.js → chunk-IGLIZBR7.js} +740 -137
- package/dist/chunk-IGLIZBR7.js.map +7 -0
- package/dist/core/absent-operands.d.ts +8 -0
- package/dist/core/audit.d.ts +12 -2
- package/dist/core/bit-route.d.ts +2 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/bulk-load.d.ts +148 -0
- package/dist/core/cas-retry.d.ts +27 -0
- package/dist/core/check.d.ts +22 -0
- package/dist/core/chunk-checks.d.ts +6 -1
- package/dist/core/chunk-window.d.ts +11 -4
- package/dist/core/combine-expr.d.ts +1 -3
- package/dist/core/combine-feed.d.ts +2 -16
- package/dist/core/combine-many.d.ts +12 -9
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +69 -17
- package/dist/core/crbm/fingerprint.d.ts +5 -0
- package/dist/core/crbm/format.d.ts +0 -1
- package/dist/core/crbm/plan-reads.d.ts +9 -3
- package/dist/core/crbm/reader.d.ts +52 -6
- package/dist/core/crbm/varint.d.ts +2 -1
- package/dist/core/crbm/writer.d.ts +5 -1
- package/dist/core/crbm-open.d.ts +154 -0
- package/dist/core/crbm-storage-source.d.ts +65 -452
- package/dist/core/crbm-write.d.ts +75 -0
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/engine.d.ts +222 -33
- package/dist/core/erase-id.d.ts +42 -7
- package/dist/core/erasure.d.ts +4 -3
- package/dist/core/errors.d.ts +8 -2
- package/dist/core/generation-gc.d.ts +129 -15
- package/dist/core/generation-list.d.ts +11 -0
- package/dist/core/id-window.d.ts +46 -0
- package/dist/core/invalidation-scope.d.ts +77 -0
- package/dist/core/known-generations-shape.d.ts +13 -0
- package/dist/core/known-generations.d.ts +147 -0
- package/dist/core/leases.d.ts +4 -3
- package/dist/core/ledger-seed.d.ts +17 -0
- package/dist/core/load.d.ts +17 -38
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +4 -2
- package/dist/core/order.d.ts +2 -0
- package/dist/core/pinned-storage-source.d.ts +4 -4
- package/dist/core/ports.d.ts +89 -16
- package/dist/core/publish.d.ts +207 -0
- package/dist/core/registry-scan.d.ts +0 -2
- package/dist/core/retention-sweep.d.ts +69 -65
- package/dist/core/retention.d.ts +1 -1
- package/dist/core/retry.d.ts +12 -19
- package/dist/core/rollback.d.ts +60 -31
- package/dist/core/summary.d.ts +50 -18
- package/dist/core/token.d.ts +2 -2
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +4 -0
- package/dist/driver-kit.js +80 -7
- 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/_shared/registry.d.ts +13 -10
- package/dist/drivers/localfs/exact-case.d.ts +0 -3
- package/dist/drivers/localfs/fs-util.d.ts +22 -0
- package/dist/drivers/localfs/registry.d.ts +10 -4
- package/dist/drivers/localfs/storage.d.ts +9 -2
- package/dist/drivers/memory.d.ts +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3647 -2410
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-YSC7NJWV.js.map +0 -7
- package/dist/core/kept-generations.d.ts +0 -40
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws {@link ValidationError} naming every one of `candidates` whose segment does not exist, asking `exists` of
|
|
3
|
+
* each, at most `limit` at once, in their order. A streamed combine asks only after operands that hold no chunk at all;
|
|
4
|
+
* the batch combine also asks after every operand it did not index (one no output names, or every operand when the range
|
|
5
|
+
* is empty). `op` begins the message (`intersect`, `materializeMany`); `nameOf` is how the caller's operands are named
|
|
6
|
+
* in it, each in double quotes. The first `exists` to reject rejects the call at once; none is asked after that.
|
|
7
|
+
*/
|
|
8
|
+
export declare function refuseAbsent<T>(op: string, candidates: readonly T[], limit: number, exists: (candidate: T) => Promise<boolean>, nameOf: (candidate: T) => string): Promise<void>;
|
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,6 +1,6 @@
|
|
|
1
1
|
export declare const U32_MAX = 4294967295;
|
|
2
2
|
export declare const CHUNK_COUNT = 65536;
|
|
3
|
-
|
|
3
|
+
interface BitRoute {
|
|
4
4
|
/** Top 16 bits — the chunk key (0..65535). */
|
|
5
5
|
readonly chunkKey: number;
|
|
6
6
|
/** Bottom 16 bits — the value within the chunk (0..65535). */
|
|
@@ -19,3 +19,4 @@ export declare const MAX_REMAINDER: number;
|
|
|
19
19
|
* invariant 5); `remainder` is masked to 16 bits.
|
|
20
20
|
*/
|
|
21
21
|
export declare function joinId(chunkKey: number, remainder: number): number;
|
|
22
|
+
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;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loader `load()` is built on: ids, a decoded bitmap or a combine's chunks in, one generation written, and published
|
|
3
|
+
* when a registry is wired and the caller does not defer it, as `load()` does to publish after its guard.
|
|
4
|
+
*/
|
|
5
|
+
import { type IAuditSink } from './audit.js';
|
|
6
|
+
import { ValidationError } from './errors.js';
|
|
7
|
+
import type { Clock } from './determinism.js';
|
|
8
|
+
import type { Aead, IKeystore, WrappedDek } from './crypto.js';
|
|
9
|
+
import type { GenerationMetadata, GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, RegistrySummary, SegmentRef } from './ports.js';
|
|
10
|
+
import type { CodecInterface } from './codec.js';
|
|
11
|
+
import { ChunkLoadInput, DecodedLoadInput } from './load-input.js';
|
|
12
|
+
/** What a bulk-load wrote — the driver's `{ size, sha256 }` plus a summary of the built generation. */
|
|
13
|
+
export interface BulkLoadResult {
|
|
14
|
+
/** Bytes written to the Storage object. */
|
|
15
|
+
readonly size: number;
|
|
16
|
+
/** The driver's content hash of the object. */
|
|
17
|
+
readonly sha256: string;
|
|
18
|
+
/** Distinct non-empty chunks written (≤ 65536). */
|
|
19
|
+
readonly chunkCount: number;
|
|
20
|
+
/**
|
|
21
|
+
* The DEK freshly minted for this generation, wrapped — present only when the segment is encrypted AND this
|
|
22
|
+
* call minted one (an existing segment reuses its key, which is already on the row).
|
|
23
|
+
*
|
|
24
|
+
* Returned so a caller that deliberately does **not** pass `registry` here — writing now and publishing later,
|
|
25
|
+
* which is how a load applies a guard while the old generation is still authoritative — can carry the key onto
|
|
26
|
+
* its own `publishGeneration`. Without it that publish stores no DEK and the generation it makes current is
|
|
27
|
+
* unreadable: the bytes are encrypted with a key nothing recorded.
|
|
28
|
+
*/
|
|
29
|
+
readonly wrappedDeks?: readonly WrappedDek[];
|
|
30
|
+
/** Whether the object was written encrypted: under the segment's key, or under one this call minted. */
|
|
31
|
+
readonly encrypted: boolean;
|
|
32
|
+
/** The written object's fingerprint, which tells it from an object stored later under the same key. */
|
|
33
|
+
readonly fingerprint: string;
|
|
34
|
+
/** Total distinct ids in the generation (post-dedup). */
|
|
35
|
+
readonly cardinality: number;
|
|
36
|
+
/**
|
|
37
|
+
* The summary of what was written, for the write that makes it current: its id count and metadata, sealed under the
|
|
38
|
+
* segment's key when the object was written with one. A caller that defers the publish (`publish: false`) passes it
|
|
39
|
+
* to {@link publishGeneration}.
|
|
40
|
+
*/
|
|
41
|
+
readonly summary: RegistrySummary;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this generation is now the segment's **current** one — `undefined` when no `registry` was wired, so
|
|
44
|
+
* there is no pointer and no publish step to report on.
|
|
45
|
+
*
|
|
46
|
+
* `false` means the object is durable but a **concurrent writer published a higher generation first**, so this
|
|
47
|
+
* one is an orphan that no reader will ever resolve. The write succeeded and the *load* did not: whatever this
|
|
48
|
+
* call was asked to make the segment contain, the segment does not contain. That is not a detail a caller can
|
|
49
|
+
* be left to infer — a `*Into` materialisation that reported the generation it wrote as "the destination's new
|
|
50
|
+
* current generation" on this path would state a plain untruth — so the flag is on the result and the verbs that
|
|
51
|
+
* promise a published generation check it.
|
|
52
|
+
*/
|
|
53
|
+
readonly becameCurrent?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* **Bulk-load** a whole immutable generation from a flat id source — the batch "seed/rebuild" entry point.
|
|
57
|
+
* Unlike {@link writeCrbmGeneration} (which takes pre-grouped bitmaps), this
|
|
58
|
+
* consumes an arbitrary, **unsorted** stream of u32 ids (sync or async) and routes each into its chunk's
|
|
59
|
+
* Roaring bitmap as it goes. The input is consumed lazily and ids dedup on insert, so memory is bounded by
|
|
60
|
+
* the **built generation** — the in-memory Roaring representation of the *distinct* set (≤ one bitmap per
|
|
61
|
+
* non-empty chunk) — not by the input length: you can stream a billion duplicate-heavy ids and hold only the
|
|
62
|
+
* distinct result. Note this is `O(distinct set)`, **not** window-bounded like {@link SegmentEngine.intersect}:
|
|
63
|
+
* bulk-load holds the whole generation in RAM, which suits batch seed/rebuild jobs but not an
|
|
64
|
+
* unbounded-cardinality stream (a segment larger than RAM needs external-merge / pre-sorted input — later phase).
|
|
65
|
+
*
|
|
66
|
+
* Each id must be an integer in `[0, 2³²)` ({@link splitId} throws {@link ValidationError} otherwise) — the
|
|
67
|
+
* source is consumed lazily, so a bad id aborts mid-stream without writing a partial object (the driver
|
|
68
|
+
* commits only after the callback resolves). An empty source writes a valid empty generation.
|
|
69
|
+
*
|
|
70
|
+
* Writes a fresh full snapshot of a segment. The caller picks the generation number in `key` — `nextGeneration`
|
|
71
|
+
* computes one from the registry and the bucket, as `nextLoadGeneration` does for a load — and re-using an existing
|
|
72
|
+
* generation throws
|
|
73
|
+
* {@link WriteConflictError} (write-once). Without a registry a `StorageChunkSource` serves the **highest**
|
|
74
|
+
* generation present, so a too-high number silently shadows real data; with one, `publishGeneration` decides.
|
|
75
|
+
*/
|
|
76
|
+
export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenKey, ids: Iterable<number> | AsyncIterable<number> | DecodedLoadInput | ChunkLoadInput, options?: {
|
|
77
|
+
registry?: IRegistryDriver;
|
|
78
|
+
keystore?: IKeystore;
|
|
79
|
+
requireEncryption?: boolean;
|
|
80
|
+
audit?: IAuditSink;
|
|
81
|
+
/** Bitmap codec. Optional in the type; a **flavor** package binds it ({@link requireCodec}). */
|
|
82
|
+
codec?: CodecInterface;
|
|
83
|
+
/**
|
|
84
|
+
* Injected clock. Supplying one makes bulk-load **cooperative**: it yields the event loop periodically so a
|
|
85
|
+
* long load does not stall everything else on the process. Without it the load still completes, just
|
|
86
|
+
* without yielding — which is the pre-existing behaviour, kept so this is purely additive.
|
|
87
|
+
*
|
|
88
|
+
* `@cloudbitmaps/roaring` supplies a real clock by default, so flavor users get cooperative behaviour with
|
|
89
|
+
* no wiring. `core/` cannot default it: it is timer-free by lint, which is exactly why waiting goes through
|
|
90
|
+
* this seam rather than `setTimeout`.
|
|
91
|
+
*/
|
|
92
|
+
clock?: Clock;
|
|
93
|
+
/**
|
|
94
|
+
* Write the object but do **not** advance the pointer (default: publish when a `registry` is wired).
|
|
95
|
+
*
|
|
96
|
+
* For a caller that has to inspect what it wrote before deciding whether it should become current — a
|
|
97
|
+
* guarded {@link loadSegment} is the one in-tree case — because the check is only meaningful while the old
|
|
98
|
+
* generation is still authoritative. The `registry` is still required for an encrypted segment and still
|
|
99
|
+
* consulted: it is where an existing segment's DEK lives, and reusing that key is not optional. The wrapped
|
|
100
|
+
* DEK comes back on the result so the deferred publish can store it.
|
|
101
|
+
*
|
|
102
|
+
* A caller that defers the publish owns what it wrote: an unpublished object sits ABOVE `currentGen`, where
|
|
103
|
+
* generation collection deliberately never looks, so nothing reclaims it until a later generation above it is
|
|
104
|
+
* current.
|
|
105
|
+
*/
|
|
106
|
+
publish?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* The generation's metadata (see {@link GenerationMetadata}), written into the object and carried in the summary.
|
|
109
|
+
* The caller has checked it against the metadata rules; the writer checks it again, and refuses a record that breaks
|
|
110
|
+
* one before a byte is written.
|
|
111
|
+
*/
|
|
112
|
+
metadata?: GenerationMetadata;
|
|
113
|
+
/**
|
|
114
|
+
* The segment's row as the caller already read it (`null`: it found none), for a caller that defers the
|
|
115
|
+
* publish (`publish: false`) and fences it on that same row. A present cleartext row is then used as read,
|
|
116
|
+
* rather than read again: a row that changes in between (a publish, a drop, a purge and re-create) makes the
|
|
117
|
+
* fenced publish lose, so the object is never published on the strength of the stale read. A row read as
|
|
118
|
+
* absent, or one carrying key material, is read again after the ids, as with no row passed: a first load must
|
|
119
|
+
* see a row another writer created meanwhile, and an encrypted segment's key is unwrapped only from a row read
|
|
120
|
+
* after the ids, so a segment shredded while they streamed is refused before its key is used.
|
|
121
|
+
*/
|
|
122
|
+
row?: RegistryRecord | null;
|
|
123
|
+
}): Promise<BulkLoadResult>;
|
|
124
|
+
/**
|
|
125
|
+
* What a load has already started when it calls {@link bulkLoadAhead}: its number, and the segment's data key, each
|
|
126
|
+
* still in flight so their round trips overlap the encoding. Both are joined where the write needs them, and neither
|
|
127
|
+
* may be left to reject unobserved.
|
|
128
|
+
*/
|
|
129
|
+
interface LoadAhead {
|
|
130
|
+
/** The number the generation takes (see {@link nextLoadGeneration}); joined just before the object is written. */
|
|
131
|
+
readonly generation: PromiseLike<number>;
|
|
132
|
+
/**
|
|
133
|
+
* The key a caller already asked the keystore to unwrap, with the wrappings it asked about. Used only when the
|
|
134
|
+
* row the write reads after the ids carries exactly these wrappings; otherwise the write unwraps for itself.
|
|
135
|
+
*/
|
|
136
|
+
readonly unwrapped?: {
|
|
137
|
+
readonly wrapped: readonly WrappedDek[];
|
|
138
|
+
readonly aead: PromiseLike<Aead>;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** What a load with `requireEncryption` meets on a segment that already has a cleartext generation. */
|
|
142
|
+
export declare function cleartextUnderRequiredEncryption(ref: SegmentRef, currentGen: number): ValidationError;
|
|
143
|
+
/**
|
|
144
|
+
* {@link bulkLoadCrbmGeneration} for a load whose number, and perhaps its key, are still being fetched: the same
|
|
145
|
+
* options, with the same meaning, and the same writes. The number is joined only where the object is written.
|
|
146
|
+
*/
|
|
147
|
+
export declare function bulkLoadAhead(driver: IStorageDriver, ref: SegmentRef, ahead: LoadAhead, ids: Parameters<typeof bulkLoadCrbmGeneration>[2], options?: NonNullable<Parameters<typeof bulkLoadCrbmGeneration>[3]>): Promise<BulkLoadResult>;
|
|
148
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WriteConflictError } from './errors.js';
|
|
2
|
+
import { type ChurnDeps } from './leases.js';
|
|
3
|
+
import type { IRegistryDriver, RegistryRecord, SegmentRef } from './ports.js';
|
|
4
|
+
/** What `casRetry` needs: the registry, and the clock and rng that spread a wait out. */
|
|
5
|
+
type CasRetryDeps = ChurnDeps & {
|
|
6
|
+
readonly registry: IRegistryDriver;
|
|
7
|
+
};
|
|
8
|
+
/** How many attempts a caller gets, and the error it throws once they are spent. */
|
|
9
|
+
interface CasRetryPlan {
|
|
10
|
+
/** Attempts that count; a wait out of lease churn does not use one. */
|
|
11
|
+
readonly attempts: number;
|
|
12
|
+
/** The error thrown when every attempt lost. */
|
|
13
|
+
readonly contention: () => WriteConflictError;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Read a segment's row, run `step` on it, and run it again on a fresh read when `step` throws a write conflict.
|
|
17
|
+
*
|
|
18
|
+
* Every lost race uses one of `plan.attempts`, except one lost by a run that followed a wait. When the row read after a
|
|
19
|
+
* lost race differs from the one `step` was given only in its leases, the loop waits the churn out (up to the churn
|
|
20
|
+
* bound), reads the row again and runs `step` against that without using an attempt. A race lost on the last attempt
|
|
21
|
+
* is not waited out. `step` returns the caller's result, or throws: a write conflict retries, anything else leaves
|
|
22
|
+
* `casRetry` untouched.
|
|
23
|
+
*
|
|
24
|
+
* Exhausting `attempts` throws `plan.contention()`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function casRetry<T>(deps: CasRetryDeps, ref: SegmentRef, plan: CasRetryPlan, step: (row: RegistryRecord | null) => Promise<T>): Promise<T>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `value` if it is a safe integer from `min` to `max` (inclusive; `max` defaults to the largest safe integer), else a
|
|
3
|
+
* `ValidationError` naming `what`: `<what> must be an integer from <min> to <max>; got <value>`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function requireInteger(value: unknown, bounds: {
|
|
6
|
+
readonly min: number;
|
|
7
|
+
readonly max?: number;
|
|
8
|
+
readonly what: string;
|
|
9
|
+
readonly note?: string;
|
|
10
|
+
}): number;
|
|
11
|
+
/** `value` if it is a safe integer of 1 or more, else a `ValidationError` naming `what`. */
|
|
12
|
+
export declare function requirePositiveInteger(value: unknown, what: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* `value` if it is a finite number within the optional `min` and `max` (inclusive), else a `ValidationError` naming
|
|
15
|
+
* `what`: `<what> must be a finite number from <min> to <max>; got <value>`. `NaN` and the infinities are refused.
|
|
16
|
+
*/
|
|
17
|
+
export declare function requireFiniteNumber(value: unknown, bounds: {
|
|
18
|
+
readonly min?: number;
|
|
19
|
+
readonly max?: number;
|
|
20
|
+
readonly what: string;
|
|
21
|
+
readonly note?: string;
|
|
22
|
+
}): number;
|
|
@@ -6,7 +6,12 @@ import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
|
6
6
|
export declare function assertChunkKeyInRange(k: number): void;
|
|
7
7
|
/** A chunk holds at least one id and at most {@link CHUNK_COUNT}; a count outside that came from a corrupt tier. */
|
|
8
8
|
export declare function assertChunkCardinalityInRange(c: number): void;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* A segment's chunk keys, ascending — a shape read off the index, no payload. Keys are untrusted (invariant 5): each is
|
|
11
|
+
* an integer in `[0, CHUNK_COUNT)` and none repeats. One pass range-checks every key and notes whether the list is
|
|
12
|
+
* already strictly ascending, which an index read off a `.crbm` always is; only a list that is not is sorted and scanned
|
|
13
|
+
* for a repeat. The caller gets a list of its own.
|
|
14
|
+
*/
|
|
10
15
|
export declare function checkedChunkKeys(listed: Iterable<number>): number[];
|
|
11
16
|
/**
|
|
12
17
|
* The other half of invariant 5: a chunk payload holds **remainders** — 16-bit offsets within one chunk — so
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
* With `ramp`, the window opens 1, 2, 4 … wide (or at `rampStart`, then doubling) up to `max` instead of `max` at once, so a consumer that stops
|
|
4
4
|
* after a few chunks has fetched a handful, not a full window.
|
|
5
5
|
*
|
|
6
|
-
* `T` is whatever one read yields (a decoded chunk, or the raw bytes of one)
|
|
6
|
+
* `T` is whatever one read yields (a decoded chunk, or the raw bytes of one), and `K` what names a read to launch: a
|
|
7
|
+
* chunk key, or a planned range. The reads to launch are a list, or an iterator pulled from only as the window opens
|
|
8
|
+
* a read, so a long plan is never made ahead of what the window can hold. Each read is wrapped to resolve and
|
|
7
9
|
* never reject, so a read nobody consumes (the consumer stopped, or an earlier chunk failed) cannot raise an
|
|
8
10
|
* unhandled rejection; its error surfaces from {@link take} only if the consumer reaches that chunk. Memory is
|
|
9
11
|
* bounded by the window: at most `max` results are held ahead.
|
|
10
12
|
*
|
|
11
13
|
* Internal to core: not exported from any entry point.
|
|
12
14
|
*/
|
|
13
|
-
export declare class ChunkWindow<T> {
|
|
14
|
-
private readonly keys;
|
|
15
|
+
export declare class ChunkWindow<T, K = number> {
|
|
15
16
|
private readonly fetch;
|
|
16
17
|
private readonly max;
|
|
17
18
|
private readonly ramp;
|
|
@@ -20,9 +21,15 @@ export declare class ChunkWindow<T> {
|
|
|
20
21
|
private readonly open;
|
|
21
22
|
private launched;
|
|
22
23
|
private taken;
|
|
23
|
-
|
|
24
|
+
private readonly list;
|
|
25
|
+
private readonly source;
|
|
26
|
+
/** What the source threw, so a later {@link take} throws it again rather than reading the spent source as done. */
|
|
27
|
+
private failure;
|
|
28
|
+
constructor(keys: readonly K[] | Iterator<K>, fetch: (key: K) => Promise<T>, max: number, ramp: boolean,
|
|
24
29
|
/** With `ramp`, the width the window opens at (default 1). */
|
|
25
30
|
rampStart?: number);
|
|
31
|
+
/** The next read to launch, or {@link DONE} when there is none. */
|
|
32
|
+
private nextKey;
|
|
26
33
|
/** Resolves once every read launched and not yet taken has settled; it never rejects, as the reads never do. */
|
|
27
34
|
settle(): Promise<void>;
|
|
28
35
|
/** The next read's result in key order, or the error that read raised. */
|
|
@@ -21,7 +21,7 @@ export type ExprNode = {
|
|
|
21
21
|
readonly kids: readonly ExprNode[];
|
|
22
22
|
};
|
|
23
23
|
/** How many nodes one {@link compileExpr} call may still allocate; shared across the expressions of one output. */
|
|
24
|
-
|
|
24
|
+
interface NodeBudget {
|
|
25
25
|
remaining: number;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
@@ -38,8 +38,6 @@ export interface KeyBounds {
|
|
|
38
38
|
readonly keys: Uint16Array;
|
|
39
39
|
readonly card: Uint32Array;
|
|
40
40
|
}
|
|
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
41
|
/** The bounds of an operand the plan cannot know. */
|
|
44
42
|
export declare const ANY_BOUNDS: KeyBounds;
|
|
45
43
|
/** What {@link planKeys} keeps of each operator node: the keys it can be non-empty at. */
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The chunk-ordered feed of a batch combine: operands that arrive as records, one chunk key at a time, and are checked
|
|
3
|
-
* and converted as they arrive. Pure: it reads through the iterable it is given and knows no store, clock or storage.
|
|
4
|
-
*
|
|
5
|
-
* ```
|
|
6
|
-
* record { key, operands: { name: Uint32Array } }
|
|
7
|
-
* check key in range and not below the last; every name declared; every value a real Uint32Array; ids strictly
|
|
8
|
-
* ascending and inside the key; each operand at most once per key (a refusal, never a repair)
|
|
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.
|
|
15
|
-
*/
|
|
16
1
|
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
17
2
|
/** One record of a feed: the ids each named operand holds at one chunk key. */
|
|
18
3
|
export interface CombineManyFeedRecord {
|
|
@@ -56,7 +41,7 @@ export declare class FeedClosed extends Error {
|
|
|
56
41
|
export declare class FeedStale extends Error {
|
|
57
42
|
}
|
|
58
43
|
/** What a {@link FeedCursor} needs from the pass that runs it. */
|
|
59
|
-
|
|
44
|
+
interface FeedCursorDeps {
|
|
60
45
|
readonly feed: CheckedFeed;
|
|
61
46
|
readonly codec: CodecInterface;
|
|
62
47
|
/** The index the first fed operand has among the call's operands. */
|
|
@@ -136,3 +121,4 @@ export declare class FeedCursor {
|
|
|
136
121
|
/** The counts and the empty rule, run once, after the iterator has ended and before anything is published. */
|
|
137
122
|
private finish;
|
|
138
123
|
}
|
|
124
|
+
export {};
|
|
@@ -11,8 +11,6 @@ export { MAX_EXPR_DEPTH, MAX_EXPR_NODES } from './combine-expr.js';
|
|
|
11
11
|
export type { CombineManyFeed, CombineManyFeedRecord } 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;
|
|
@@ -246,8 +244,6 @@ export interface CombineManyRun<R> {
|
|
|
246
244
|
export interface CompiledCombineMany<R> {
|
|
247
245
|
readonly request: CombineManyRequest<R>;
|
|
248
246
|
}
|
|
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
247
|
/**
|
|
252
248
|
* Check a request: every expression, option and number, before any request is made. Throws {@link ValidationError}
|
|
253
249
|
* naming the output index and the path. Pure.
|
|
@@ -259,7 +255,7 @@ export declare function compileCombineMany<R>(request: CombineManyRequest<R>): C
|
|
|
259
255
|
*/
|
|
260
256
|
export declare function rebindCombineMany<R>(compiled: CompiledCombineMany<R>, operands: readonly CombineManyOperand[]): CompiledCombineMany<R>;
|
|
261
257
|
/** What {@link GroupCost} reads of an output. */
|
|
262
|
-
|
|
258
|
+
interface GroupCostItem {
|
|
263
259
|
readonly bound: number;
|
|
264
260
|
readonly serializedBound: number;
|
|
265
261
|
readonly rootKeys: Uint16Array;
|
|
@@ -270,14 +266,15 @@ export interface GroupCostItem {
|
|
|
270
266
|
}
|
|
271
267
|
/**
|
|
272
268
|
* 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.
|
|
269
|
+
* operands those prices walked, and how many group plans were built. A call counts into the one it is given
|
|
270
|
+
* ({@link runCombineManyCounted}), and into none otherwise; the planner never reads them.
|
|
274
271
|
*/
|
|
275
|
-
export
|
|
272
|
+
export interface PlanWork {
|
|
276
273
|
adds: number;
|
|
277
274
|
withs: number;
|
|
278
275
|
operandSteps: number;
|
|
279
276
|
planBuilds: number;
|
|
280
|
-
}
|
|
277
|
+
}
|
|
281
278
|
/**
|
|
282
279
|
* The resident bytes a group needs, kept as outputs are added so that one is priced in work proportional to its own
|
|
283
280
|
* operands, not to the group's size: its buffered outputs, its operand streams, the chunks being evaluated, the objects
|
|
@@ -286,6 +283,8 @@ export declare const combineWork: {
|
|
|
286
283
|
export declare class GroupCost {
|
|
287
284
|
private readonly streamBytes;
|
|
288
285
|
private readonly publishConcurrency;
|
|
286
|
+
/** Where this pricing is counted, when it is. */
|
|
287
|
+
private readonly work?;
|
|
289
288
|
private outputs;
|
|
290
289
|
private depth;
|
|
291
290
|
/** The serialized bounds of the largest outputs, descending: at most `publishConcurrency` of them are written at once. */
|
|
@@ -294,7 +293,9 @@ export declare class GroupCost {
|
|
|
294
293
|
private streams;
|
|
295
294
|
private planWork;
|
|
296
295
|
private readonly operands;
|
|
297
|
-
constructor(streamBytes: (operand: number) => number, publishConcurrency: number
|
|
296
|
+
constructor(streamBytes: (operand: number) => number, publishConcurrency: number,
|
|
297
|
+
/** Where this pricing is counted, when it is. */
|
|
298
|
+
work?: PlanWork | undefined);
|
|
298
299
|
/** The cost of the group with `o` added. */
|
|
299
300
|
with(o: GroupCostItem): number;
|
|
300
301
|
add(o: GroupCostItem): void;
|
|
@@ -313,6 +314,8 @@ export declare class GroupCost {
|
|
|
313
314
|
* budget, both before any chunk is read.
|
|
314
315
|
*/
|
|
315
316
|
export declare function runCombineMany<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps): Promise<CombineManyRun<R>>;
|
|
317
|
+
/** {@link runCombineMany}, counting the planner's work into `work`. Not exported from the package. */
|
|
318
|
+
export declare function runCombineManyCounted<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps, work: PlanWork): Promise<CombineManyRun<R>>;
|
|
316
319
|
/**
|
|
317
320
|
* The `pointerId` of a row's version (`<generation>:<pointerId>`, as a pin holds it), or `undefined` where the version
|
|
318
321
|
* 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>;
|
|
@@ -11,6 +11,11 @@ export declare function fingerprintParts(fingerprint: unknown): {
|
|
|
11
11
|
readonly size: number;
|
|
12
12
|
readonly footerCrc: number;
|
|
13
13
|
} | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The footer CRC of a fingerprint a writer or a reader produced, which a row's ledger records beside the generation.
|
|
16
|
+
* Throws {@link IntegrityError} for text that is not a fingerprint, which neither produces.
|
|
17
|
+
*/
|
|
18
|
+
export declare function footerCrcOf(fingerprint: string): number;
|
|
14
19
|
/**
|
|
15
20
|
* A fingerprint spelled short, for a key a read builds on every chunk: its size and its footer's CRC in base 36,
|
|
16
21
|
* `<size>.<crc>`. Two compact spellings are equal exactly when the fingerprints are.
|