@cloudbitmaps/core 0.18.3 → 0.19.1
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/dist/{chunk-ML6AT53O.js → chunk-RSLVTRJY.js} +50 -9
- package/dist/chunk-RSLVTRJY.js.map +7 -0
- package/dist/core/audit.d.ts +13 -3
- package/dist/core/bit-route.d.ts +5 -5
- package/dist/core/blob.d.ts +4 -1
- package/dist/core/budget.d.ts +7 -2
- package/dist/core/combine-feed.d.ts +3 -7
- package/dist/core/combine-many.d.ts +28 -29
- package/dist/core/consistency.d.ts +0 -1
- package/dist/core/cost.d.ts +13 -2
- package/dist/core/crbm/reader.d.ts +9 -3
- package/dist/core/crbm-storage-source.d.ts +115 -29
- package/dist/core/discover.d.ts +3 -5
- package/dist/core/engine.d.ts +20 -0
- package/dist/core/erase-id.d.ts +8 -5
- package/dist/core/erasure.d.ts +22 -2
- package/dist/core/errors.d.ts +10 -2
- package/dist/core/generation-gc.d.ts +2 -2
- package/dist/core/keys.d.ts +28 -0
- package/dist/core/load-input.d.ts +1 -2
- package/dist/core/load.d.ts +54 -7
- package/dist/core/metrics.d.ts +19 -7
- package/dist/core/ports.d.ts +25 -62
- package/dist/core/retention-sweep.d.ts +9 -4
- package/dist/core/retention.d.ts +17 -0
- package/dist/core/retry.d.ts +6 -0
- package/dist/driver-kit.d.ts +1 -1
- package/dist/driver-kit.js +8 -101
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +1 -19
- package/dist/drivers/_shared/registry.d.ts +5 -2
- package/dist/drivers/localfs/fs-util.d.ts +7 -0
- package/dist/drivers/localfs/registry.d.ts +1 -0
- package/dist/index.d.ts +5 -9
- package/dist/index.js +975 -631
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-ML6AT53O.js.map +0 -7
- package/dist/core/combine-held.d.ts +0 -43
- package/dist/core/registry-reap.d.ts +0 -22
package/dist/core/audit.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export type AuditEvent = {
|
|
|
47
47
|
* downstream system reconciling "the segment should now contain X" needs as much as it needs the publish.
|
|
48
48
|
*
|
|
49
49
|
* `reason` is `'empty'` (an empty result over a non-empty segment, with no `allowEmpty`),
|
|
50
|
-
* `'min-cardinality'`, `'min-retained'`, or `'superseded'` (another load took the generation number, or the
|
|
50
|
+
* `'min-cardinality'`, `'min-retained'`, `'max-growth'`, or `'superseded'` (another load took the generation number, or the
|
|
51
51
|
* segment's registry row changed while the load was writing). `cardinality` is what the refused generation
|
|
52
52
|
* would have contained, and `0` for a load that lost its generation number and wrote nothing.
|
|
53
53
|
*
|
|
@@ -60,7 +60,7 @@ export type AuditEvent = {
|
|
|
60
60
|
readonly namespace?: string;
|
|
61
61
|
readonly segment: string;
|
|
62
62
|
readonly generation: number;
|
|
63
|
-
readonly reason: 'empty' | 'min-cardinality' | 'min-retained' | 'superseded';
|
|
63
|
+
readonly reason: 'empty' | 'min-cardinality' | 'min-retained' | 'max-growth' | 'superseded';
|
|
64
64
|
readonly cardinality: number;
|
|
65
65
|
readonly unanswered?: true;
|
|
66
66
|
} | {
|
|
@@ -121,9 +121,19 @@ export interface IAuditSink {
|
|
|
121
121
|
export declare const NOOP_AUDIT: IAuditSink;
|
|
122
122
|
/**
|
|
123
123
|
* Wrap a sink so a throwing/buggy `onEvent` can never break the lifecycle operation it observes — audit is
|
|
124
|
-
* strictly observation.
|
|
124
|
+
* strictly observation. That covers an `async onEvent` that rejects, whose rejection would otherwise surface as an
|
|
125
|
+
* unhandled one, which ends a Node process by default. Returns {@link NOOP_AUDIT} unchanged (so the no-op case
|
|
126
|
+
* skips even the try/catch alloc).
|
|
125
127
|
*/
|
|
126
128
|
export declare function safeAudit(sink: IAuditSink): IAuditSink;
|
|
129
|
+
/**
|
|
130
|
+
* Refuse an `audit` that is not a sink. One without an `onEvent` method (a bare callback, a method named `emit`) would
|
|
131
|
+
* receive nothing and say nothing, since {@link safeAudit} swallows what calling it throws. Called at the top of each
|
|
132
|
+
* entry that takes one, before anything irreversible; `undefined` and `null` read as none.
|
|
133
|
+
*/
|
|
134
|
+
export declare function checkedAuditSink(sink: unknown, op: string): void;
|
|
135
|
+
/** Observe the rejection of what a sink's `onEvent` returned, when it returned a promise, so it is never unhandled. */
|
|
136
|
+
export declare function ignoreRejection(returned: unknown): void;
|
|
127
137
|
/** A ready-made sink that records events into an in-memory list — handy for tests + simple audit trails. */
|
|
128
138
|
export declare class RecordingAuditSink implements IAuditSink {
|
|
129
139
|
private readonly recorded;
|
package/dist/core/bit-route.d.ts
CHANGED
|
@@ -8,14 +8,14 @@ export interface BitRoute {
|
|
|
8
8
|
}
|
|
9
9
|
/** Split a u32 id into `{ chunkKey, remainder }`. Throws `ValidationError` for non-u32 input. */
|
|
10
10
|
export declare function splitId(id: number): BitRoute;
|
|
11
|
-
/**
|
|
12
|
-
* Recombine a chunk key + remainder back into the u32 id. Precondition: `chunkKey` is a validated
|
|
13
|
-
* 16-bit value (tier-derived keys are validated upstream in the engine before reaching here, per
|
|
14
|
-
* invariant 5); `remainder` is masked to 16 bits.
|
|
15
|
-
*/
|
|
16
11
|
/**
|
|
17
12
|
* Largest legal value inside a chunk payload. A chunk covers `CHUNK_COUNT` ids, so remainders run
|
|
18
13
|
* `[0, CHUNK_COUNT - 1]`. Exported so the engine can assert it on untrusted tier bytes.
|
|
19
14
|
*/
|
|
20
15
|
export declare const MAX_REMAINDER: number;
|
|
16
|
+
/**
|
|
17
|
+
* Recombine a chunk key + remainder back into the u32 id. Precondition: `chunkKey` is a validated
|
|
18
|
+
* 16-bit value (tier-derived keys are validated upstream in the engine before reaching here, per
|
|
19
|
+
* invariant 5); `remainder` is masked to 16 bits.
|
|
20
|
+
*/
|
|
21
21
|
export declare function joinId(chunkKey: number, remainder: number): number;
|
package/dist/core/blob.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Streaming append target for a single object being written. A caller may reuse the buffer it passed to `write` once
|
|
3
|
+
* the write resolves, so a sink that holds bytes past that copies them.
|
|
4
|
+
*/
|
|
2
5
|
export interface BlobSink {
|
|
3
6
|
write(bytes: Uint8Array): Promise<void>;
|
|
4
7
|
}
|
package/dist/core/budget.d.ts
CHANGED
|
@@ -27,11 +27,16 @@ export interface Budget {
|
|
|
27
27
|
export declare const DEFAULT_BUDGET: Budget;
|
|
28
28
|
/** A store/per-op budget option: partial overrides, or `false` to disable entirely. */
|
|
29
29
|
export type BudgetOption = Partial<Budget> | false;
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a budget option against a fallback to a concrete `Budget`, or `null` when disabled. Validates: anything but
|
|
32
|
+
* `false`, an absent value or an object of `{ maxRequests }` is refused, since a cap of the wrong shape (`5`,
|
|
33
|
+
* `{ maxRequest: 5 }`) would otherwise read as no override and leave the fallback in force. `null` and a key whose value
|
|
34
|
+
* is `undefined` read as absent.
|
|
35
|
+
*/
|
|
31
36
|
export declare function resolveBudget(opt: BudgetOption | undefined, fallback: Budget): Budget | null;
|
|
32
37
|
/**
|
|
33
38
|
* Resolve a **per-op** budget override against the store's own resolved budget (never the raw global default):
|
|
34
|
-
* `undefined` inherits the store budget as-is (including a disabled `null`); a partial `{}` / omitted
|
|
39
|
+
* `undefined` or `null` inherits the store budget as-is (including a disabled `null`); a partial `{}` / omitted
|
|
35
40
|
* `maxRequests` inherits the store's *tightening* (so a per-op passthrough of an absent config value can't
|
|
36
41
|
* silently lift a tight tenant ceiling back to the generous default); `{ maxRequests }` replaces it; `false`
|
|
37
42
|
* disables. Falls back to {@link DEFAULT_BUDGET} only when the store itself has no budget (disabled).
|
|
@@ -49,13 +49,6 @@ export interface CheckedFeed {
|
|
|
49
49
|
}
|
|
50
50
|
/** Check a feed's shape, before any record is read. `stored` are the stored operand names, which a fed one may not repeat. */
|
|
51
51
|
export declare function checkFeed(feed: unknown, stored: ReadonlySet<string>): CheckedFeed;
|
|
52
|
-
/**
|
|
53
|
-
* The view a real `Uint32Array` holds, as a plain `Uint32Array` over the same bytes, or `undefined` for anything else.
|
|
54
|
-
* The brand is the `%TypedArray%` `@@toStringTag` getter, which reads the internal slot: it names only a real typed array
|
|
55
|
-
* of that kind (a subclass or another realm's included) and throws for a spoof, a proxy or any other object. The view's
|
|
56
|
-
* extent is then read through the intrinsic getters, so a subclass that overrides `length` or the iterator is never asked.
|
|
57
|
-
*/
|
|
58
|
-
export declare function plainUint32(value: unknown): Uint32Array | 'detached' | 'out of bounds' | undefined;
|
|
59
52
|
/** Thrown inside the cursor when it was closed under an operation in flight; never reaches a caller. */
|
|
60
53
|
export declare class FeedClosed extends Error {
|
|
61
54
|
}
|
|
@@ -128,6 +121,9 @@ export declare class FeedCursor {
|
|
|
128
121
|
/** The next record, checked and converted (`ops` is `null` when its key is outside the range); `undefined` at the end. */
|
|
129
122
|
private pull;
|
|
130
123
|
private end;
|
|
124
|
+
/** How many records the feed has handed over, the one being read included: what names a record in an error. */
|
|
125
|
+
private records;
|
|
126
|
+
/** Refuse the record being read, named by its position in the feed: its key narrows the ids it holds to a range. */
|
|
131
127
|
private refuse;
|
|
132
128
|
private charge;
|
|
133
129
|
private giveBack;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IMetricsSink } from './metrics.js';
|
|
3
3
|
import type { CombineManyFeed } from './combine-feed.js';
|
|
4
4
|
import type { CombineExpr } from './combine-expr.js';
|
|
5
5
|
import type { Clock } from './determinism.js';
|
|
@@ -18,9 +18,9 @@ export interface CombineManyOperand {
|
|
|
18
18
|
readonly name: string;
|
|
19
19
|
readonly ref: SegmentRef;
|
|
20
20
|
/**
|
|
21
|
-
* Throws the typed error when this operand's handle can no longer be read: a lease that ended or was released
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Throws the typed error when this operand's handle can no longer be read: a lease that ended or was released.
|
|
22
|
+
* Called before each chunk key the operand is read at, so a lapse fails the outputs that read it and never reads
|
|
23
|
+
* empty.
|
|
24
24
|
*/
|
|
25
25
|
readonly check?: () => void;
|
|
26
26
|
/** The generation the operand is pinned to: `null` when pinned to a segment with none, `undefined` when not pinned. */
|
|
@@ -44,25 +44,6 @@ export interface CombineManyOperand {
|
|
|
44
44
|
readonly token: string;
|
|
45
45
|
readonly fingerprint?: string | null;
|
|
46
46
|
} | null>;
|
|
47
|
-
/**
|
|
48
|
-
* Set for an operand held in memory: its chunks are read from here, never from the source, and `ref`, `pinned*` and
|
|
49
|
-
* `current` are not used. It has no registry row, so nothing about it can move; what can invalidate it is an erasure in
|
|
50
|
-
* the store that made it ({@link CombineManyHeld.moved}) or its release.
|
|
51
|
-
*/
|
|
52
|
-
readonly held?: CombineManyHeld;
|
|
53
|
-
}
|
|
54
|
-
/** An operand held in memory, as the pass reads it. */
|
|
55
|
-
export interface CombineManyHeld {
|
|
56
|
-
/** The operand's chunks. Once released, reading one throws. */
|
|
57
|
-
readonly chunks: HeldChunks;
|
|
58
|
-
/**
|
|
59
|
-
* Whether an erasure has run in the store since the operand began to be made, one still running included, so what it holds may include an erased id. Checked
|
|
60
|
-
* before the call's first request, before each chunk key is read and immediately before each publish of an output that
|
|
61
|
-
* reads it.
|
|
62
|
-
*/
|
|
63
|
-
readonly moved: () => boolean;
|
|
64
|
-
/** Whether holding no id is accepted: an include then empties an AND and an exclude subtracts nothing. */
|
|
65
|
-
readonly mayBeEmpty: boolean;
|
|
66
47
|
}
|
|
67
48
|
/** What one output asks for. */
|
|
68
49
|
export interface CombineManyOutput<R> {
|
|
@@ -74,7 +55,7 @@ export interface CombineManyOutput<R> {
|
|
|
74
55
|
readonly metadata?: GenerationMetadata;
|
|
75
56
|
/** Overrides the call's `keep`. */
|
|
76
57
|
readonly keep?: number;
|
|
77
|
-
/** Throws to refuse the publish: the destination's lease
|
|
58
|
+
/** Throws to refuse the publish: the destination's lease, checked again just before it. */
|
|
78
59
|
readonly beforePublish?: () => void;
|
|
79
60
|
/**
|
|
80
61
|
* The requests a settled publish made that its value proves, for the call's totals by class; absent, a publish adds
|
|
@@ -116,6 +97,12 @@ export interface CombineManyRequest<R> {
|
|
|
116
97
|
* outputs as one group; a fed output publishes only after the whole feed was read and checked.
|
|
117
98
|
*/
|
|
118
99
|
readonly feed?: CombineManyFeed;
|
|
100
|
+
/**
|
|
101
|
+
* The outputs' publish callbacks judge their finished chunks and must write nothing. Each is still charged the object
|
|
102
|
+
* a write would hold, so a dry run admits, defers and refuses for memory exactly where a publish would, and none
|
|
103
|
+
* counts as a publish in the stats.
|
|
104
|
+
*/
|
|
105
|
+
readonly dryRun?: boolean;
|
|
119
106
|
}
|
|
120
107
|
export interface CombineManyDeps {
|
|
121
108
|
/** Reads the operands: already pinned where they are, and retrying as the store's reads do. */
|
|
@@ -123,6 +110,12 @@ export interface CombineManyDeps {
|
|
|
123
110
|
readonly codec: CodecInterface;
|
|
124
111
|
readonly clock: Clock;
|
|
125
112
|
readonly maxBitmapBytes?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Where the call reports a `storage.get` event for each range request it sends, as a combine does; absent, it reports
|
|
115
|
+
* none. The call never looks up the chunk cache, so it reports no `cache` event. A sink that throws is ignored, as
|
|
116
|
+
* {@link safeMetrics} makes it.
|
|
117
|
+
*/
|
|
118
|
+
readonly metrics?: IMetricsSink;
|
|
126
119
|
}
|
|
127
120
|
/** What one output came to. */
|
|
128
121
|
export type CombineManyOutcome<R> = {
|
|
@@ -262,11 +255,6 @@ export declare function compileCombineMany<R>(request: CombineManyRequest<R>): C
|
|
|
262
255
|
* without checking the expressions again.
|
|
263
256
|
*/
|
|
264
257
|
export declare function rebindCombineMany<R>(compiled: CompiledCombineMany<R>, operands: readonly CombineManyOperand[]): CompiledCombineMany<R>;
|
|
265
|
-
/**
|
|
266
|
-
* The resident bytes a group needs, kept as outputs are added so that one is priced in work proportional to its own
|
|
267
|
-
* operands, not to the group's size: its buffered outputs, its operand streams, the chunks being evaluated, the objects
|
|
268
|
-
* being published beside the buffers, and the key lists of its own plan.
|
|
269
|
-
*/
|
|
270
258
|
/** What {@link GroupCost} reads of an output. */
|
|
271
259
|
export interface GroupCostItem {
|
|
272
260
|
readonly bound: number;
|
|
@@ -287,6 +275,11 @@ export declare const combineWork: {
|
|
|
287
275
|
operandSteps: number;
|
|
288
276
|
planBuilds: number;
|
|
289
277
|
};
|
|
278
|
+
/**
|
|
279
|
+
* The resident bytes a group needs, kept as outputs are added so that one is priced in work proportional to its own
|
|
280
|
+
* operands, not to the group's size: its buffered outputs, its operand streams, the chunks being evaluated, the objects
|
|
281
|
+
* being published beside the buffers, and the key lists of its own plan.
|
|
282
|
+
*/
|
|
290
283
|
export declare class GroupCost {
|
|
291
284
|
private readonly streamBytes;
|
|
292
285
|
private readonly publishConcurrency;
|
|
@@ -317,3 +310,9 @@ export declare class GroupCost {
|
|
|
317
310
|
* budget, both before any chunk is read.
|
|
318
311
|
*/
|
|
319
312
|
export declare function runCombineMany<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps): Promise<CombineManyRun<R>>;
|
|
313
|
+
/**
|
|
314
|
+
* The token of a row's version (`<generation>:<token>`, as a pin holds it), or `undefined` where the version names no
|
|
315
|
+
* row. A live version from the `.crbm` source ends with the opened object's fingerprint: read it through
|
|
316
|
+
* `rowVersionOf` first.
|
|
317
|
+
*/
|
|
318
|
+
export declare function tokenOfVersion(version: string | null | undefined): string | undefined;
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { IStorageDriver, IRegistryDriver } from './ports.js';
|
|
20
20
|
import type { IKeystore } from './crypto.js';
|
|
21
|
-
/** Default in-flight fan-out for the consistency scan — bounded, no thundering herd. */
|
|
22
21
|
/**
|
|
23
22
|
* Default ceiling on how many registry records one consistency check may hold resident: 250,000.
|
|
24
23
|
*
|
package/dist/core/cost.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ export interface RedisSizing {
|
|
|
83
83
|
* because an `m6g` or `r6g` with the same memory costs less. It is the cheapest cluster of THIS kind; Redis bought
|
|
84
84
|
* another way costs less — one replica a shard a third less, ElastiCache for Valkey 20% less a node, reserved nodes
|
|
85
85
|
* less again — so against those the saving the verdict reports is smaller. Pass their prices to compare with them.
|
|
86
|
+
* @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
|
|
87
|
+
* needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
|
|
86
88
|
*/
|
|
87
89
|
export declare const ELASTICACHE_REDIS_US_EAST_1_ONDEMAND: RedisSizing;
|
|
88
90
|
/**
|
|
@@ -90,6 +92,9 @@ export declare const ELASTICACHE_REDIS_US_EAST_1_ONDEMAND: RedisSizing;
|
|
|
90
92
|
* at $0.158 an hour each: 3 × 730 h × $0.158 = $346.02, published to the dollar. The per-request crossovers on the
|
|
91
93
|
* benchmarks page are drawn against this one cluster, whatever the data size; pass it as `pricing.redis` to compare
|
|
92
94
|
* with it. It is not the cheapest cluster for any size of data: three cache.m6g.large hold the same memory for less.
|
|
95
|
+
*
|
|
96
|
+
* @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
|
|
97
|
+
* needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
|
|
93
98
|
*/
|
|
94
99
|
export declare const ONE_REDIS_HA_CLUSTER: Readonly<{
|
|
95
100
|
monthlyUSD: number;
|
|
@@ -97,6 +102,8 @@ export declare const ONE_REDIS_HA_CLUSTER: Readonly<{
|
|
|
97
102
|
/**
|
|
98
103
|
* Default profile — **AWS us-east-1, on-demand**, mid-2026, from the fact-checked published pricing rather than
|
|
99
104
|
* copied from a blog post, with Redis sized to the data. Override it for your region, cloud, or committed term.
|
|
105
|
+
* @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
|
|
106
|
+
* needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
|
|
100
107
|
*/
|
|
101
108
|
export declare const AWS_US_EAST_1_ONDEMAND: PricingProfile;
|
|
102
109
|
/** Sustained access pattern. All rates default to 0; unspecified ⇒ that op contributes nothing. */
|
|
@@ -186,8 +193,8 @@ export interface Workload {
|
|
|
186
193
|
*/
|
|
187
194
|
readonly genTtlMs?: number;
|
|
188
195
|
/**
|
|
189
|
-
* Segments the retention sweep (`retireExpired`) retires a month. Each costs
|
|
190
|
-
* delete with {@link Workload.conditionalDelete} on, and
|
|
196
|
+
* Segments the retention sweep (`retireExpired`) retires a month. Each costs 7 registry reads, 2 writes and a
|
|
197
|
+
* delete with {@link Workload.conditionalDelete} on, and 6 reads and 2 writes with it off, priced at the GET and PUT
|
|
191
198
|
* rates, a delete unbilled, as S3 leaves it. Default
|
|
192
199
|
* **0**.
|
|
193
200
|
*/
|
|
@@ -324,12 +331,16 @@ export declare const RETENTION_SWEEP_REQUESTS: Readonly<{
|
|
|
324
331
|
* **Planning** cost estimate — pure, no instance or live data needed (sizing, sales, what-if). Segment sizes
|
|
325
332
|
* are taken as given (or roughly derived from cardinality); use the grounded `segment.costReport()` for
|
|
326
333
|
* exact, real sizes. See {@link CostReport}.
|
|
334
|
+
* @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
|
|
335
|
+
* needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
|
|
327
336
|
*/
|
|
328
337
|
export declare function estimateCost(input: EstimateInput): CostReport;
|
|
329
338
|
/**
|
|
330
339
|
* **Grounded** report from a real segment byte total (from the `.crbm` index) + a supplied workload. Used by
|
|
331
340
|
* `Segment.costReport()` in the facade. `grounded` defaults to true (the size is exact, not estimated); the
|
|
332
341
|
* caller passes `grounded: false` + a note when the Storage source can't measure size.
|
|
342
|
+
* @deprecated Moves to `@cloudbitmaps/tools`, a package of its own, with a `size` on `stat()` so a grounded report
|
|
343
|
+
* needs nothing internal. It is a planning tool, and its price list is as old as the release that ships it.
|
|
333
344
|
*/
|
|
334
345
|
export declare function groundedReport(input: {
|
|
335
346
|
readonly storageBytes: number;
|
|
@@ -51,6 +51,11 @@ export interface CrbmReaderOptions {
|
|
|
51
51
|
}
|
|
52
52
|
/** An object's fingerprint, from its size and its footer's CRC (see {@link CrbmReader.fingerprint}). */
|
|
53
53
|
export declare const fingerprintFor: (size: number, footerCrc: number) => string;
|
|
54
|
+
/**
|
|
55
|
+
* A fingerprint spelled short, for a key a read builds on every chunk: its size and its footer's CRC in base 36,
|
|
56
|
+
* `<size>.<crc>`. Two compact spellings are equal exactly when the fingerprints are.
|
|
57
|
+
*/
|
|
58
|
+
export declare function compactFingerprint(fingerprint: string): string;
|
|
54
59
|
/**
|
|
55
60
|
* Whether the object behind `blob` says it is encrypted, from one tail read of a footer's worth, with no key and no
|
|
56
61
|
* index; a footer that fails its own checks throws, as an open would. For a caller that must not open an object with
|
|
@@ -140,8 +145,8 @@ export declare class CrbmReader {
|
|
|
140
145
|
/**
|
|
141
146
|
* Range-GET one chunk's payload, verifying its CRC32C before returning. Returns `null` if the chunk is
|
|
142
147
|
* absent. Throws `IntegrityError` on a CRC mismatch (the bytes must never reach the native
|
|
143
|
-
* deserializer). The returned
|
|
144
|
-
* not
|
|
148
|
+
* deserializer). The returned bytes may be shared (a small generation's chunks are kept by the reader and handed out
|
|
149
|
+
* again), so callers must not change them.
|
|
145
150
|
*/
|
|
146
151
|
getChunk(chunkKey: number): Promise<Uint8Array | null>;
|
|
147
152
|
/**
|
|
@@ -189,7 +194,8 @@ export declare class CrbmReader {
|
|
|
189
194
|
* others; `options.now` times each request for `options.onRequest`, which hears of every range request that was sent, once, when
|
|
190
195
|
* it settles: taken, abandoned after the consumer stopped, or failed (a reader has no clock of its own, so `ms` is 0
|
|
191
196
|
* without one).
|
|
192
|
-
* A plain chunk is a
|
|
197
|
+
* A plain chunk is a view into the range it was read in, so keeping it keeps that whole range: copy a chunk to keep it
|
|
198
|
+
* alone. Callers must not change it, as with {@link getChunk}.
|
|
193
199
|
*
|
|
194
200
|
* A stream that fails (a range that errors, a chunk that fails its check) raises at once and sends nothing further:
|
|
195
201
|
* the ranges it still had in flight finish in the background, are not retried and never raise. Its `settled()`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IAuditSink } from './audit.js';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
2
3
|
import type { Yielder } from './cooperative.js';
|
|
3
4
|
import type { Clock, Rng } from './determinism.js';
|
|
4
5
|
import type { PublishedKept } from './generation-gc.js';
|
|
@@ -6,7 +7,7 @@ import type { Aead, CrbmCrypto, IKeystore, WrappedDek } from './crypto.js';
|
|
|
6
7
|
import type { ChunkRef, ChunkRead, ReadChunksOptions, StorageChunkSource, GenerationMetadata, GenerationSummary, GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, RegistrySummary, SegmentRef, SegmentSize, Token } from './ports.js';
|
|
7
8
|
import { CrbmReader } from './crbm/reader.js';
|
|
8
9
|
import type { CrbmReaderOptions } from './crbm/reader.js';
|
|
9
|
-
import type { CodecBitmap, CodecInterface
|
|
10
|
+
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
10
11
|
import { ChunkLoadInput, DecodedLoadInput } from './load-input.js';
|
|
11
12
|
export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
|
|
12
13
|
/**
|
|
@@ -80,10 +81,14 @@ export interface PinnedObject {
|
|
|
80
81
|
readonly fingerprint?: string;
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
* What
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* What a live version's resolution named, without the object's fingerprint: `<generation>:<row token>`, or
|
|
85
|
+
* `<generation>` with no registry. For a caller that compares a live version, from
|
|
86
|
+
* {@link CrbmStorageChunkSource.currentVersion} or a chunk of its `getChunks`, with a row it read, which names no
|
|
87
|
+
* object. A live version always ends with the suffix, which holds no `#`, so the match starts at its last `#` and a row
|
|
88
|
+
* token holding `#` of its own is kept whole. Known limit: a string with no suffix, such as a pin's version or one
|
|
89
|
+
* from another source, loses its end when its row token itself ends in `#<base 36>.<base 36>`; no caller passes one.
|
|
86
90
|
*/
|
|
91
|
+
export declare function rowVersionOf(version: string): string;
|
|
87
92
|
/** What a pin that leases its generation does between reading the row and opening the object. */
|
|
88
93
|
export interface PinLease {
|
|
89
94
|
/** Write the lease against the row the pin read: its token, or `'moved'` when the pointer is no longer at the generation. */
|
|
@@ -98,7 +103,8 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
98
103
|
* needs a clock). Within the TTL a segment's Storage bytes are treated as an immutable snapshot; when the TTL
|
|
99
104
|
* elapses the next read cheaply re-resolves `currentGen` and, only if it advanced (a load published),
|
|
100
105
|
* opens the new generation — so a long-lived source observes new generations within the TTL. The engine pairs
|
|
101
|
-
* this with a
|
|
106
|
+
* this with a cache keyed by {@link currentVersion}, which names the object a reader opened, so a bump never serves
|
|
107
|
+
* a stale decoded chunk, and neither does a number taken again by another object. Without a clock or a
|
|
102
108
|
* registry there is no timed refresh, and a segment's reader is replaced only when it is evicted, when a read
|
|
103
109
|
* has to fetch from a generation a sweep deleted, or when {@link invalidate} drops it. A segment with no generation yet is not memoized, so
|
|
104
110
|
* it's re-checked until one exists. **Bounded** by a {@link BoundedLru} ({@link CrbmStorageChunkSourceOptions.maxOpenSegments}, default
|
|
@@ -143,7 +149,20 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
143
149
|
private readonly keepChunkBytesUpTo;
|
|
144
150
|
private readonly clock;
|
|
145
151
|
private readonly currentGenTtlMs;
|
|
152
|
+
/**
|
|
153
|
+
* Whether the segment's pointer is re-read on a timer: it needs a clock (the TTL), a registry (the cheap row read; with
|
|
154
|
+
* none, resolving is a listing of the bucket) and a TTL above 0. It decides the keeping of chunk bytes, the TTL lapse
|
|
155
|
+
* and the refresh interval the cost report prices, so it is decided once.
|
|
156
|
+
*/
|
|
157
|
+
private readonly timedRefresh;
|
|
146
158
|
constructor(driver: IStorageDriver, options?: CrbmStorageChunkSourceOptions);
|
|
159
|
+
/**
|
|
160
|
+
* The keys of the segments this source reads, kept rather than encoded again: a read asks for its segment's snapshot
|
|
161
|
+
* once per operand, and a stream before every cached chunk it serves, and encoding the names cost about a
|
|
162
|
+
* microsecond each time.
|
|
163
|
+
*/
|
|
164
|
+
private readonly keys;
|
|
165
|
+
private keyOf;
|
|
147
166
|
/**
|
|
148
167
|
* The segment's live snapshot, refreshing on the TTL. Cheap within the TTL window. The snapshot holds the resolved
|
|
149
168
|
* target, and its reader once a read has asked for one.
|
|
@@ -176,6 +195,13 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
176
195
|
* this.
|
|
177
196
|
*/
|
|
178
197
|
get pointerRefreshMs(): number;
|
|
198
|
+
/**
|
|
199
|
+
* Whether a stream reading from `snap` must resolve its segment again before its next chunk, as a read of one chunk
|
|
200
|
+
* would: the snapshot's TTL has lapsed, an invalidation has happened since `epoch`, or the snapshot is no longer the
|
|
201
|
+
* one the reader cache holds for the segment (it let it go, or another read installed a newer one). Else nothing can
|
|
202
|
+
* have moved it, and the check costs a compare and a lookup.
|
|
203
|
+
*/
|
|
204
|
+
private mayHaveMoved;
|
|
179
205
|
private expired;
|
|
180
206
|
/**
|
|
181
207
|
* The segment's current generation number — the engine keys its chunk cache by this so a generation bump
|
|
@@ -205,13 +231,16 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
205
231
|
*/
|
|
206
232
|
invalidate(ref: SegmentRef): void;
|
|
207
233
|
/**
|
|
208
|
-
* `<generation>` for a registry-less source, or `<generation>:<row token>` with one
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
234
|
+
* `<generation>#<object>` for a registry-less source, or `<generation>:<row token>#<object>` with one: the generation
|
|
235
|
+
* the segment's snapshot resolved, and the object its reader opened, by that object's size and footer checksum in
|
|
236
|
+
* base 36, which the open read anyway. The token moves on every row write, so an unrelated write (a `setRetention`)
|
|
237
|
+
* costs the segment's decoded chunks once — bounded, and on an admin path. Two objects do not share a version
|
|
238
|
+
* string, as far as their sizes and footer checksums tell them apart, whichever way a number came to name a second
|
|
239
|
+
* one: a name purged and loaded again (a new row, or with no registry the same bare number), a number taken again
|
|
240
|
+
* within one row once its object was deleted, or an object put back under its key from outside the library. That
|
|
241
|
+
* holds however long after its row read a reader opens: a snapshot that a `count()` resolved from the row opens its
|
|
242
|
+
* reader only when a later read needs it, and the object under the key may have changed in between. The version is
|
|
243
|
+
* opaque: compare two for equality.
|
|
215
244
|
*
|
|
216
245
|
* This is the call the engine makes **once per operand of every read**, before any chunk fetch, to key its
|
|
217
246
|
* chunk cache, so it heals a swept generation exactly as {@link currentGeneration} does: unhealed, a cold read
|
|
@@ -445,13 +474,6 @@ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey,
|
|
|
445
474
|
sha256: string;
|
|
446
475
|
fingerprint: string;
|
|
447
476
|
}>;
|
|
448
|
-
/**
|
|
449
|
-
* The chunks a load of ids, or of a decoded bitmap, writes: ascending by key, each non-empty and run-optimized. A decoded
|
|
450
|
-
* bitmap writes its own chunks, which never touches an id. Ids, and the bitmap of a codec that cannot encode its own
|
|
451
|
-
* chunks, are bucketed per chunk instead; both give the same bytes. Exported for a caller that holds the chunks itself
|
|
452
|
-
* instead of writing them.
|
|
453
|
-
*/
|
|
454
|
-
export declare function encodedChunksOfInput(ids: Iterable<number> | AsyncIterable<number> | DecodedLoadInput, codec: CodecInterface, clock: Clock | undefined): Promise<Iterable<EncodedChunk>>;
|
|
455
477
|
/**
|
|
456
478
|
* Whether the object under `key` is provably the one `fingerprint` names ({@link CrbmReader.fingerprint}), from one
|
|
457
479
|
* read of its footer. False when it is another object, when it is gone, and when the read fails in any way: a caller
|
|
@@ -498,6 +520,68 @@ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: G
|
|
|
498
520
|
clock?: Yielder;
|
|
499
521
|
metadata?: GenerationMetadata;
|
|
500
522
|
}): Promise<StreamWriteResult>;
|
|
523
|
+
/**
|
|
524
|
+
* Point a segment's registry `currentGen` at `key.generation` — the publish step that makes a freshly-written
|
|
525
|
+
* generation the authoritative latest (so registry-aware readers see it). **Forward-only and idempotent:**
|
|
526
|
+
* if the registry has no row it creates one; if it's already at/ahead of `key.generation` it's a no-op (an
|
|
527
|
+
* out-of-order/duplicate publish never regresses the pointer); otherwise it advances via compare-and-swap,
|
|
528
|
+
* retrying a few times under contention. Separated from the Storage write so callers can publish atomically
|
|
529
|
+
* after the immutable object is durable (write-then-publish).
|
|
530
|
+
*
|
|
531
|
+
* **Throws** rather than returning, in two cases, because each would otherwise create a state no reader can
|
|
532
|
+
* recover from: a `destroyed` (crypto-shredded) row, where advancing the pointer would name an object encrypted
|
|
533
|
+
* under a shredded key; and an advance carrying **new** `wrappedDeks`, which would either strand that key or make
|
|
534
|
+
* the row advertise encryption over cleartext generations. Contention that never clears throws
|
|
535
|
+
* {@link WriteConflictError} after the retries.
|
|
536
|
+
*
|
|
537
|
+
* **`expectFrom` turns forward-only into read-modify-write.** Forward-only is the right rule for a writer whose
|
|
538
|
+
* content does not depend on what was current — a load computes its ids upstream, so publishing over a newer
|
|
539
|
+
* generation loses nothing the loader knew about. It is the WRONG rule for a writer that *derived* its content
|
|
540
|
+
* from a particular generation: the erasure rewrite streams generation `from` and clears one bit, so publishing
|
|
541
|
+
* it over a newer generation silently discards whatever that generation added. Passing `expectFrom` makes the
|
|
542
|
+
* publish land only while the pointer is still exactly there, and return `false` otherwise, so the caller can
|
|
543
|
+
* report `superseded` and re-derive. The compare-and-swap itself carries the token read in the same iteration,
|
|
544
|
+
* so the check and the write see the same row.
|
|
545
|
+
*
|
|
546
|
+
* **`expectToken` fences the LINEAGE, and a derived writer needs both.** A generation number identifies a
|
|
547
|
+
* generation only within one incarnation of a name: the numbering restarts at `0` once the row is purged and
|
|
548
|
+
* the bucket empty, so a name that is retired and re-created has a *different* segment wearing the *same*
|
|
549
|
+
* `currentGen`. `expectFrom` alone matched it — and an erasure rewrite then published one incarnation's content
|
|
550
|
+
* over another's, deleted the live objects with its `keep: 0` collection, and returned `erased: true`. The row's
|
|
551
|
+
* OCC token is the identity that survives this: a shipped registry's later `create` gets a token never issued before
|
|
552
|
+
* under that name, but for a collision of probability 2^-128 per pair of incarnations, so no token is reused across them.
|
|
553
|
+
*
|
|
554
|
+
* Pass the token read alongside `expectFrom` and the publish lands only on the same row it was derived from.
|
|
555
|
+
* The check is deliberately **conservative**: the token also changes on writes that are not supersessions at
|
|
556
|
+
* all (a `setRetention`, a due-index reindex), so such a write makes a derived publish report `superseded` and
|
|
557
|
+
* the caller re-derive. That costs a re-run on a rare, unrelated write; the alternative costs a segment.
|
|
558
|
+
*
|
|
559
|
+
* **The driver sends a registry write once, and an unanswered one is reconciled by its effect.** A `create` or
|
|
560
|
+
* `compareAndSwap` that fails with {@link TransientError} (a throttle, a lost response, a timeout) may or may not
|
|
561
|
+
* have landed, and may still be in flight. The publish reads the row once more and decides from what it holds:
|
|
562
|
+
*
|
|
563
|
+
* - the pointer names `key.generation`, on the incarnation the write was made against (the row's `createdAt`), and
|
|
564
|
+
* `holdsOwnObject` proves the object under it the caller's: the write landed, and the publish returns `true`. Only
|
|
565
|
+
* the writer of a write-once object publishes its number, and a number is never taken while an object holds it;
|
|
566
|
+
* - the row is still the one the write was made against (the same token, or still no row): the write may land yet. The
|
|
567
|
+
* publish then waits, on the injected clock, and sends a **new** write from the row it just read: a request of its
|
|
568
|
+
* own, not a replay of the driver's, carrying the same version, so under the registry's fence at most one of the two
|
|
569
|
+
* lands and the other meets a conflict, which the next read settles. At most {@link UNANSWERED_RESENDS} follow, after
|
|
570
|
+
* a wait under 500 ms, then 1 s, then 2 s (full jitter when an `rng` is given); each is settled the same way. Still
|
|
571
|
+
* unanswered, or with no clock to wait on, this throws the registry's own `TransientError` and writes nothing more.
|
|
572
|
+
* A caller must not delete its object on it;
|
|
573
|
+
* - anything else: the row has moved past the state the write was conditioned on, so that write can never land, and
|
|
574
|
+
* the publish goes on as after a lost race, from the row it just read. A pointer at `key.generation` that is not over
|
|
575
|
+
* the caller's own object, or is on another incarnation, is a lost race too and answers `false`, never an already
|
|
576
|
+
* current re-publish.
|
|
577
|
+
*
|
|
578
|
+
* A failed read throws `TransientError` too. The same check of the pointer runs after a {@link WriteConflictError},
|
|
579
|
+
* so a write that landed and then met itself (a registry whose client re-sent it) is not taken for a lost race.
|
|
580
|
+
*
|
|
581
|
+
* **A `summary` goes in the same write that moves the pointer**, so a reader that sees this generation as current
|
|
582
|
+
* sees the count and metadata that describe it, and every attempt sends the same one. It must be the shape the row's
|
|
583
|
+
* keys call for: sealed for an object written with a key, clear for one written without.
|
|
584
|
+
*/
|
|
501
585
|
export declare function publishGenerationKept(registry: IRegistryDriver, key: GenKey, options?: {
|
|
502
586
|
wrappedDeks?: readonly WrappedDek[];
|
|
503
587
|
expectFrom?: number;
|
|
@@ -512,17 +596,17 @@ export declare function publishGenerationKept(registry: IRegistryDriver, key: Ge
|
|
|
512
596
|
/**
|
|
513
597
|
* Publish only while the segment still has **no registry row**.
|
|
514
598
|
*
|
|
515
|
-
* The fence for a caller whose decision rests on the row being ABSENT
|
|
516
|
-
* cannot express it: both compare against a value read from a
|
|
517
|
-
* nothing to compare and both are simply omitted — leaving the publish a
|
|
518
|
-
* happily lands over whatever appeared in the meantime.
|
|
599
|
+
* The fence for a caller whose decision rests on the row being ABSENT: every load that found no row sets it,
|
|
600
|
+
* guarded or not. `expectFrom` and `expectToken` cannot express it: both compare against a value read from a
|
|
601
|
+
* row, so when there was no row there is nothing to compare and both are simply omitted — leaving the publish a
|
|
602
|
+
* bare forward-only advance that happily lands over whatever appeared in the meantime.
|
|
519
603
|
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
604
|
+
* Without it, a guarded load into a segment that did not exist yet would judge "no row", pass its empty and
|
|
605
|
+
* `minRetained` bounds vacuously, and land an EMPTY generation over the ids a concurrent writer had just loaded,
|
|
606
|
+
* reporting `published: true`. An unguarded load would move the pointer over another first load's generation or
|
|
607
|
+
* onto a row `setRetention` made, and, where an erasure had deleted its object above that row's pointer, onto an
|
|
608
|
+
* object that is gone. Materialising into a destination that does not exist yet is the ordinary first run of a
|
|
609
|
+
* pipeline, so the `*Into` verbs reach this as often as `loadSegment` does.
|
|
526
610
|
*/
|
|
527
611
|
expectAbsent?: boolean;
|
|
528
612
|
/**
|
|
@@ -735,6 +819,8 @@ export interface LoadAhead {
|
|
|
735
819
|
readonly aead: PromiseLike<Aead>;
|
|
736
820
|
};
|
|
737
821
|
}
|
|
822
|
+
/** What a load with `requireEncryption` meets on a segment that already has a cleartext generation. */
|
|
823
|
+
export declare function cleartextUnderRequiredEncryption(ref: SegmentRef, currentGen: number): ValidationError;
|
|
738
824
|
/**
|
|
739
825
|
* {@link bulkLoadCrbmGeneration} for a load whose number, and perhaps its key, are still being fetched: the same
|
|
740
826
|
* options, with the same meaning, and the same writes. The number is joined only where the object is written.
|
package/dist/core/discover.d.ts
CHANGED
|
@@ -38,10 +38,9 @@ export interface SegmentInfo extends SegmentRef {
|
|
|
38
38
|
* distinction between "never loaded" and "loaded, and genuinely empty" is the one `count()` cannot make and
|
|
39
39
|
* the main reason this function is worth having.
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* One state answers `true` where a read still gives you nothing, and it is not this call's job to detect: a
|
|
42
42
|
* **torn restore** (`missing-storage-generation` — a live pointer whose object was deleted) makes reads *throw*
|
|
43
|
-
* `NotFoundError` rather than answer empty, and `runConsistencyCheck` is the call that looks for it
|
|
44
|
-
* handle carrying an expired `expiresAt` reads as empty by a rule that lives on the handle, not on the row.
|
|
43
|
+
* `NotFoundError` rather than answer empty, and `runConsistencyCheck` is the call that looks for it.
|
|
45
44
|
*
|
|
46
45
|
* Not a lock. A segment can be created or dropped between this call and whatever you do next; if the answer
|
|
47
46
|
* has to hold, use the fences that exist for it — `load`'s guard, or `expectFrom`/`expectToken` on a publish.
|
|
@@ -69,8 +68,7 @@ export declare function segmentExists(ref: SegmentRef, registry: IRegistryDriver
|
|
|
69
68
|
* disagree with the registry it reports on — a filtered enumeration that looks complete is how a retention
|
|
70
69
|
* sweep ends up permanently skipping rows nobody can see. Filter on `status`/`currentGen` yourself, or use
|
|
71
70
|
* {@link segmentExists} for the "does the pointer resolve" question. Internal bookkeeping rows are the one
|
|
72
|
-
* exclusion
|
|
73
|
-
* sees them.
|
|
71
|
+
* exclusion: they are not segments, an unscoped scan skips them, and their namespace is one a caller cannot name.
|
|
74
72
|
*/
|
|
75
73
|
export declare function listSegments(registry: IRegistryDriver, options?: {
|
|
76
74
|
namespace?: string;
|
package/dist/core/engine.d.ts
CHANGED
|
@@ -106,6 +106,11 @@ export declare class SegmentEngine {
|
|
|
106
106
|
private readonly openStreams;
|
|
107
107
|
/** How many times {@link invalidate} has been called: a read compares it with the count it began under. */
|
|
108
108
|
private invalidations;
|
|
109
|
+
/**
|
|
110
|
+
* The cache-key prefixes ({@link segmentPrefix}) of the segments this engine reads, kept rather than encoded for each
|
|
111
|
+
* chunk. A kept prefix is the one {@link invalidate} finds the segment's chunks by.
|
|
112
|
+
*/
|
|
113
|
+
private readonly prefixes;
|
|
109
114
|
constructor(deps: EngineDeps);
|
|
110
115
|
/** Membership: one chunk lookup — the cache, else one Storage fetch of that chunk. */
|
|
111
116
|
has(seg: SegmentRef, id: number): Promise<boolean>;
|
|
@@ -310,6 +315,17 @@ export declare class SegmentEngine {
|
|
|
310
315
|
* lookup by generation number would never find a chunk cached under a stream's version.
|
|
311
316
|
*/
|
|
312
317
|
private streamedVersion;
|
|
318
|
+
/**
|
|
319
|
+
* `hit`, a chunk cached under the version a multi-chunk read planned under, if that is still the segment's version;
|
|
320
|
+
* else the chunk as it is now. A read resolves the segment again before it serves each chunk, which the source's
|
|
321
|
+
* stream does for the chunks it delivers; a chunk served from the cache is checked here, or a read whose chunks are
|
|
322
|
+
* all cached would go on serving the generation it planned under, an erased id included, for as long as it is pulled.
|
|
323
|
+
* Within `cache.genTtlMs` the source answers from its snapshot, so the check is a lookup, not a request. `report`:
|
|
324
|
+
* emit the `cache` event for this lookup, which a key counted when its stream opened does not. `streamed`: the read
|
|
325
|
+
* this chunk belongs to, which a move carries to the version now current, so the rest of it looks there: a read whose
|
|
326
|
+
* stream is not yet open reads the rest as one stream, not a chunk at a time.
|
|
327
|
+
*/
|
|
328
|
+
private cachedIfCurrent;
|
|
313
329
|
/** The cache key of a chunk: by the version it was read under, or by segment and key alone for a source with none. */
|
|
314
330
|
private chunkCacheKey;
|
|
315
331
|
/** The segment's chunk keys, ascending, checked as untrusted tier data. */
|
|
@@ -376,6 +392,10 @@ export declare class SegmentEngine {
|
|
|
376
392
|
*
|
|
377
393
|
* The returned instance is **shared** (it may be the cached one, and every caller that joined a read gets the same
|
|
378
394
|
* one): callers read it or clone it, never mutate it.
|
|
395
|
+
*
|
|
396
|
+
* `recheck`: `gen` was planned for a read of many chunks, so a cached chunk is served only while it is still the
|
|
397
|
+
* segment's version ({@link cachedIfCurrent}). A point read resolves `gen` just before it asks, and passes none.
|
|
398
|
+
* `report`: emit the `cache` event for this lookup; a key already counted for this read does not.
|
|
379
399
|
*/
|
|
380
400
|
private storageChunk;
|
|
381
401
|
/**
|