@cloudbitmaps/core 0.17.0 → 0.18.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/dist/{chunk-CV7GJO3B.js → chunk-CCP4U32U.js} +18 -1
- package/dist/chunk-CCP4U32U.js.map +7 -0
- package/dist/core/chunk-checks.d.ts +34 -0
- package/dist/core/codec.d.ts +1 -1
- package/dist/core/combine-expr.d.ts +75 -0
- package/dist/core/combine-feed.d.ts +142 -0
- package/dist/core/combine-held.d.ts +43 -0
- package/dist/core/combine-ledger.d.ts +43 -0
- package/dist/core/combine-many.d.ts +319 -0
- package/dist/core/crbm-storage-source.d.ts +8 -1
- package/dist/core/engine.d.ts +1 -30
- package/dist/core/errors.d.ts +16 -0
- package/dist/core/load-input.d.ts +2 -1
- package/dist/driver-kit.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2304 -75
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-CV7GJO3B.js.map +0 -7
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** A string names an operand; a node holds exactly one operator over a non-empty list of expressions. */
|
|
2
|
+
export type CombineExpr = string | {
|
|
3
|
+
and: CombineExpr[];
|
|
4
|
+
} | {
|
|
5
|
+
or: CombineExpr[];
|
|
6
|
+
} | {
|
|
7
|
+
andNot: CombineExpr[];
|
|
8
|
+
};
|
|
9
|
+
/** The most operator nodes one expression may nest. A literal that contains itself is refused by it. */
|
|
10
|
+
export declare const MAX_EXPR_DEPTH = 64;
|
|
11
|
+
/** The most nodes (operators and names) one expression may hold, so a shared sub-object cannot expand without bound. */
|
|
12
|
+
export declare const MAX_EXPR_NODES = 4096;
|
|
13
|
+
declare const OPERATORS: readonly ["and", "or", "andNot"];
|
|
14
|
+
type Operator = (typeof OPERATORS)[number];
|
|
15
|
+
/** A checked expression: operand indexes at the leaves, operators inside. */
|
|
16
|
+
export type ExprNode = {
|
|
17
|
+
readonly kind: 'leaf';
|
|
18
|
+
readonly operand: number;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: Operator;
|
|
21
|
+
readonly kids: readonly ExprNode[];
|
|
22
|
+
};
|
|
23
|
+
/** How many nodes one {@link compileExpr} call may still allocate; shared across the expressions of one output. */
|
|
24
|
+
export interface NodeBudget {
|
|
25
|
+
remaining: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check `expr` and lower it. `path` names where it sits (`outputs[2].expr`), and every failure is a
|
|
29
|
+
* {@link ValidationError} that carries it plus the path inside the expression.
|
|
30
|
+
*/
|
|
31
|
+
export declare function compileExpr(expr: unknown, path: string, operands: ReadonlyMap<string, number>, budget?: NodeBudget, depth?: number): ExprNode;
|
|
32
|
+
/** The deepest operator nesting in `node`: 0 for a name. */
|
|
33
|
+
export declare function depthOf(node: ExprNode): number;
|
|
34
|
+
/** The operand indexes `node` names, each once, ascending. */
|
|
35
|
+
export declare function operandsOf(node: ExprNode, into?: Set<number>): number[];
|
|
36
|
+
/** Ascending chunk keys, and an upper bound on the ids each holds. */
|
|
37
|
+
export interface KeyBounds {
|
|
38
|
+
readonly keys: Uint16Array;
|
|
39
|
+
readonly card: Uint32Array;
|
|
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
|
+
/** The bounds of an operand the plan cannot know. */
|
|
44
|
+
export declare const ANY_BOUNDS: KeyBounds;
|
|
45
|
+
/** What {@link planKeys} keeps of each operator node: the keys it can be non-empty at. */
|
|
46
|
+
export type NodeKeys = Map<ExprNode, Uint16Array>;
|
|
47
|
+
/**
|
|
48
|
+
* Bottom up: the keys at which each node of `node` can be non-empty, with an upper bound on its ids at each. The
|
|
49
|
+
* keys of every operator node are kept in `into` for {@link collectFetch}; the bounds are returned for the root alone,
|
|
50
|
+
* so memory is the keys of one expression, not their bounds.
|
|
51
|
+
*/
|
|
52
|
+
export declare function planKeys(node: ExprNode, operands: readonly KeyBounds[], into?: NodeKeys): KeyBounds;
|
|
53
|
+
/**
|
|
54
|
+
* Top down: the keys of each operand an output needs, given the keys `demanded` of its root. A leaf is fetched where
|
|
55
|
+
* it is demanded and holds a chunk; `and` and `or` pass down what they can be non-empty at; `andNot` passes its left
|
|
56
|
+
* child the demand it can meet and each subtrahend that, so an exclude is read only where the left side can overlap it.
|
|
57
|
+
* `fetch(operand, keys)` is called once per operand occurrence.
|
|
58
|
+
*/
|
|
59
|
+
export declare function collectFetch(node: ExprNode, demanded: Uint16Array, operands: readonly KeyBounds[], planned: NodeKeys, fetch: (operand: number, keys: Uint16Array) => void): void;
|
|
60
|
+
/**
|
|
61
|
+
* Whether `node` can be non-empty at one key, given which operands hold it: an `and` needs every child, an `or` any,
|
|
62
|
+
* and an `andNot` its left child. Exact for the key, where {@link planKeys} is a bound over all of them.
|
|
63
|
+
*/
|
|
64
|
+
export declare function canHold(node: ExprNode, holds: (operand: number) => boolean): boolean;
|
|
65
|
+
/** A set of chunk keys as bits, for the keys an operand is fetched at across the outputs of a group. */
|
|
66
|
+
export declare class KeyBits {
|
|
67
|
+
private readonly words;
|
|
68
|
+
private count;
|
|
69
|
+
add(keys: Uint16Array): void;
|
|
70
|
+
get size(): number;
|
|
71
|
+
has(key: number): boolean;
|
|
72
|
+
/** The keys, ascending. */
|
|
73
|
+
toKeys(): number[];
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
17
|
+
/** One record of a feed: the ids each named operand holds at one chunk key. */
|
|
18
|
+
export interface CombineManyFeedRecord {
|
|
19
|
+
/** The chunk key, `id >>> 16`, from 0 to 65,535. Non-decreasing over the feed; a key may arrive as several records. */
|
|
20
|
+
readonly key: number;
|
|
21
|
+
/** Ids by operand name: ascending, unique, every one inside `key`. An operand is named at most once per key. */
|
|
22
|
+
readonly operands: Readonly<Record<string, Uint32Array>>;
|
|
23
|
+
}
|
|
24
|
+
/** The fed operands of a batch combine. */
|
|
25
|
+
export interface CombineManyFeed {
|
|
26
|
+
/** The fed operand names, declared up front. */
|
|
27
|
+
readonly names: readonly string[];
|
|
28
|
+
/** The records, read once, in key order. */
|
|
29
|
+
readonly records: AsyncIterable<CombineManyFeedRecord>;
|
|
30
|
+
/**
|
|
31
|
+
* The ids each fed operand holds over the whole feed, keyed by exactly `names`: an object, or a function called once
|
|
32
|
+
* after the last record. A count that differs from what the feed held refuses every fed output.
|
|
33
|
+
*/
|
|
34
|
+
readonly counts: Readonly<Record<string, number>> | (() => Readonly<Record<string, number>> | Promise<Readonly<Record<string, number>>>);
|
|
35
|
+
/** Fed names allowed to hold no id anywhere in the feed. */
|
|
36
|
+
readonly mayBeEmpty?: readonly string[];
|
|
37
|
+
/** Whether an erasure ran in the store since the call began, or is running: read at every record and before each fed publish. */
|
|
38
|
+
readonly epoch?: {
|
|
39
|
+
readonly moved: () => boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** A feed that passed {@link checkFeed}: read once, so what was checked is what runs. */
|
|
43
|
+
export interface CheckedFeed {
|
|
44
|
+
readonly names: readonly string[];
|
|
45
|
+
readonly records: AsyncIterable<unknown>;
|
|
46
|
+
readonly counts: CombineManyFeed['counts'];
|
|
47
|
+
readonly mayBeEmpty: ReadonlySet<string>;
|
|
48
|
+
readonly epoch: CombineManyFeed['epoch'];
|
|
49
|
+
}
|
|
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
|
+
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
|
+
/** Thrown inside the cursor when it was closed under an operation in flight; never reaches a caller. */
|
|
60
|
+
export declare class FeedClosed extends Error {
|
|
61
|
+
}
|
|
62
|
+
/** Thrown when the store's erasure counter moved: the run turns it into one typed error per output. */
|
|
63
|
+
export declare class FeedStale extends Error {
|
|
64
|
+
}
|
|
65
|
+
/** What a {@link FeedCursor} needs from the pass that runs it. */
|
|
66
|
+
export interface FeedCursorDeps {
|
|
67
|
+
readonly feed: CheckedFeed;
|
|
68
|
+
readonly codec: CodecInterface;
|
|
69
|
+
/** The index the first fed operand has among the call's operands. */
|
|
70
|
+
readonly base: number;
|
|
71
|
+
/** Charge `bytes` to the ledger, making room as the pass does, or throw when they do not fit. */
|
|
72
|
+
readonly charge: (bytes: number) => void;
|
|
73
|
+
readonly release: (bytes: number) => void;
|
|
74
|
+
/** One call per operand converted: the pass's cooperative yield. */
|
|
75
|
+
readonly tick: () => Promise<void> | null;
|
|
76
|
+
/** Whether a record at `key` is inside the call's id range; one outside is checked and counted but not kept. */
|
|
77
|
+
readonly inWindow: (key: number) => boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reads a feed one record at a time: checks it, converts it, and hands the records of one key to the pass as a group.
|
|
81
|
+
* At most two records are resident, the group being built and the one that proved it complete.
|
|
82
|
+
*/
|
|
83
|
+
export declare class FeedCursor {
|
|
84
|
+
private readonly deps;
|
|
85
|
+
private iterator;
|
|
86
|
+
private group;
|
|
87
|
+
private look;
|
|
88
|
+
private current;
|
|
89
|
+
private ended;
|
|
90
|
+
private closed;
|
|
91
|
+
private finished;
|
|
92
|
+
private held;
|
|
93
|
+
private returned;
|
|
94
|
+
private prevKey;
|
|
95
|
+
private readonly index;
|
|
96
|
+
private readonly seen;
|
|
97
|
+
private readonly touched;
|
|
98
|
+
private readonly counts;
|
|
99
|
+
private readonly scratch;
|
|
100
|
+
private recordCount;
|
|
101
|
+
private keyCount;
|
|
102
|
+
private idCount;
|
|
103
|
+
constructor(deps: FeedCursorDeps);
|
|
104
|
+
/** Whether the feed can still yield a key: not closed, and neither ended nor drained. */
|
|
105
|
+
get active(): boolean;
|
|
106
|
+
/** Whether the whole feed was read and every end-of-feed check passed. */
|
|
107
|
+
get complete(): boolean;
|
|
108
|
+
/** The ledger bytes the cursor holds. */
|
|
109
|
+
get heldBytes(): number;
|
|
110
|
+
get stats(): {
|
|
111
|
+
readonly records: number;
|
|
112
|
+
readonly keys: number;
|
|
113
|
+
readonly ids: number;
|
|
114
|
+
};
|
|
115
|
+
/** The key the feed holds next, pulling the first record of it if none is held; `undefined` once nothing is left. */
|
|
116
|
+
nextKey(): Promise<number | undefined>;
|
|
117
|
+
/** Read until a record with a higher key arrives, or the feed ends: the group of {@link nextKey} is then whole. */
|
|
118
|
+
completeKey(): Promise<void>;
|
|
119
|
+
/** Take the whole group of {@link nextKey}: its bitmaps by operand index, held until {@link release}. */
|
|
120
|
+
take(): ReadonlyMap<number, CodecBitmap>;
|
|
121
|
+
/** Give back what the taken group holds. */
|
|
122
|
+
release(): void;
|
|
123
|
+
/** Stop reading: release everything held and return the iterator. Safe to call at any time, and again. */
|
|
124
|
+
close(): void;
|
|
125
|
+
/** Resolves once the iterator's `return` has settled. */
|
|
126
|
+
drained(): Promise<void>;
|
|
127
|
+
private fill;
|
|
128
|
+
/** The next record, checked and converted (`ops` is `null` when its key is outside the range); `undefined` at the end. */
|
|
129
|
+
private pull;
|
|
130
|
+
private end;
|
|
131
|
+
private refuse;
|
|
132
|
+
private charge;
|
|
133
|
+
private giveBack;
|
|
134
|
+
private convert;
|
|
135
|
+
/**
|
|
136
|
+
* Check `view` and copy its low 16 bits into the scratch array in one pass, each id read once: a buffer that changes
|
|
137
|
+
* under the check (a shared one) cannot make the checked value differ from the converted one.
|
|
138
|
+
*/
|
|
139
|
+
private scan;
|
|
140
|
+
/** The counts and the empty rule, run once, after the iterator has ended and before anything is published. */
|
|
141
|
+
private finish;
|
|
142
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An operand held in memory, for a batch combine: ids checked and encoded as a load checks and encodes them, then kept
|
|
3
|
+
* as the per-chunk payload bytes a stored generation would hold. Pure: it knows no store and no storage.
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* input ids | { bitmap } | { serialized }
|
|
7
|
+
* check what a load checks: the size cap, the structure, the safe deserializer and each id's range; only a real
|
|
8
|
+
* Uint32Array skips the per-id range check, by a brand a spoof cannot carry
|
|
9
|
+
* encode the load's own encoder, so a chunk is the bytes a load would store
|
|
10
|
+
* held keys, cardinalities and one payload per chunk, read by the pass as a stored operand's chunks are
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import type { CodecInterface, EncodedChunk } from './codec.js';
|
|
14
|
+
import type { Clock } from './determinism.js';
|
|
15
|
+
/** A held operand's chunks: ascending keys, each with its exact cardinality and its payload bytes. */
|
|
16
|
+
export declare class HeldChunks {
|
|
17
|
+
/** The chunk keys, ascending. */
|
|
18
|
+
readonly keys: Uint16Array;
|
|
19
|
+
/** How many ids each chunk holds. */
|
|
20
|
+
readonly cardinalities: Uint32Array;
|
|
21
|
+
/** What it holds in memory, for a call's resident-bytes ledger. */
|
|
22
|
+
readonly residentBytes: number;
|
|
23
|
+
private payloads;
|
|
24
|
+
constructor(chunks: Iterable<EncodedChunk>);
|
|
25
|
+
/** Whether it holds no id. */
|
|
26
|
+
get empty(): boolean;
|
|
27
|
+
/** Whether {@link release} ran. */
|
|
28
|
+
get released(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The payload of the chunk at `key`, `null` when it holds none there. A view into what is held: not to be written to.
|
|
31
|
+
* @throws {ValidationError} once released.
|
|
32
|
+
*/
|
|
33
|
+
payload(key: number): Uint8Array | null;
|
|
34
|
+
/** Zero and drop the bytes. Every later read throws; a second call does nothing. */
|
|
35
|
+
release(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check a held operand's input as a load checks it, and hold its chunks.
|
|
39
|
+
*
|
|
40
|
+
* @throws {ValidationError} where a load of the same input would, and for an object with an `ascending` key, which
|
|
41
|
+
* belongs to a feed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function prepareHeld(input: unknown, codec: CodecInterface, clock: Clock | undefined): Promise<HeldChunks>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resident-bytes ledger of a batch combine. Pure: no I/O, time or randomness.
|
|
3
|
+
*
|
|
4
|
+
* It counts what is **resident**, not what a bitmap serializes to: a native bitmap costs a fixed amount over its
|
|
5
|
+
* serialized bytes, so a million small chunks hold far more than their serialized size says. Measured on the shipped
|
|
6
|
+
* codec with one bitmap per chunk, the cost over serialized size was about 440 bytes for a chunk of up to 50 ids,
|
|
7
|
+
* rising to about 1,700 bytes around 3,000 ids (an array container's spare capacity) and falling to about 300 bytes for
|
|
8
|
+
* a bitset. {@link residentBytes} is a model that stays above every measured point.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The fixed resident cost of one native bitmap beyond its serialized bytes. Measured on the shipped codec, over 50,000
|
|
12
|
+
* bitmaps of one chunk each, it was 430 to 450 bytes for a chunk of up to 50 ids on macOS (arm64) and 357 to 373 on Linux
|
|
13
|
+
* (arm64, glibc 2.36), and 279 to 433 for a bitset; the constant sits above the highest of those.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RESIDENT_BITMAP_OVERHEAD = 512;
|
|
16
|
+
/** An upper bound on the serialized size of a chunk of `cardinality` ids: an array container, capped at a bitset. */
|
|
17
|
+
export declare function serializedBound(cardinality: number): number;
|
|
18
|
+
/** The resident bytes of one bitmap whose serialized size is `serialized`: above every measured point. */
|
|
19
|
+
export declare function residentBytes(serialized: number): number;
|
|
20
|
+
/** The resident bytes of one chunk of at most `cardinality` ids. */
|
|
21
|
+
export declare function residentBound(cardinality: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* A byte budget with a high-water mark. Everything the pass keeps resident is charged to it and released when it is
|
|
24
|
+
* let go, so `used` is the pass's own count of what it holds, and `maxBufferedBytes` is a bound on it.
|
|
25
|
+
*/
|
|
26
|
+
export declare class ResidentLedger {
|
|
27
|
+
readonly limit: number;
|
|
28
|
+
private held;
|
|
29
|
+
private peak;
|
|
30
|
+
constructor(limit: number);
|
|
31
|
+
get used(): number;
|
|
32
|
+
get highWater(): number;
|
|
33
|
+
/** Bytes still free. */
|
|
34
|
+
get room(): number;
|
|
35
|
+
/** Charge `bytes` if they fit. */
|
|
36
|
+
tryCharge(bytes: number): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Charge `bytes`, asking `makeRoom` to free some while they do not fit. `makeRoom` releases what it frees (through
|
|
39
|
+
* {@link release}) and answers whether it freed anything; the charge is refused when it cannot.
|
|
40
|
+
*/
|
|
41
|
+
reserve(bytes: number, makeRoom: () => boolean): boolean;
|
|
42
|
+
release(bytes: number): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
2
|
+
import type { HeldChunks } from './combine-held.js';
|
|
3
|
+
import type { CombineManyFeed } from './combine-feed.js';
|
|
4
|
+
import type { CombineExpr } from './combine-expr.js';
|
|
5
|
+
import type { Clock } from './determinism.js';
|
|
6
|
+
import type { Budget } from './budget.js';
|
|
7
|
+
import type { LoadGuard } from './load.js';
|
|
8
|
+
import type { GenerationMetadata, SegmentRef, StorageChunkSource } from './ports.js';
|
|
9
|
+
export type { CombineExpr } from './combine-expr.js';
|
|
10
|
+
export { MAX_EXPR_DEPTH, MAX_EXPR_NODES } from './combine-expr.js';
|
|
11
|
+
export type { CombineManyFeed, CombineManyFeedRecord } from './combine-feed.js';
|
|
12
|
+
/** The range requests a pass keeps in flight at once across all its operands, inside a client's socket pool. */
|
|
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
|
+
/** One named stored operand, as the pass reads it. */
|
|
17
|
+
export interface CombineManyOperand {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly ref: SegmentRef;
|
|
20
|
+
/**
|
|
21
|
+
* Throws the typed error when this operand's handle can no longer be read: a lease that ended or was released, or a
|
|
22
|
+
* deadline that passed. Called before each chunk key the operand is read at, so a lapse fails the outputs that read
|
|
23
|
+
* it and never reads empty.
|
|
24
|
+
*/
|
|
25
|
+
readonly check?: () => void;
|
|
26
|
+
/** The generation the operand is pinned to: `null` when pinned to a segment with none, `undefined` when not pinned. */
|
|
27
|
+
readonly pinnedGeneration?: number | null;
|
|
28
|
+
/**
|
|
29
|
+
* The pin's version: the generation and the registry row's token it was taken under (`<generation>:<token>`), or just
|
|
30
|
+
* the generation where there is no row. It is what tells a name deleted and created again, which restarts at
|
|
31
|
+
* generation 0, from the one that was pinned.
|
|
32
|
+
*/
|
|
33
|
+
readonly pinnedVersion?: string | null;
|
|
34
|
+
/** The fingerprint of the object the pin holds: the identity of the generation's bytes, where a number can be taken again. */
|
|
35
|
+
readonly pinnedFingerprint?: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* A fresh read of the segment's registry row: its current generation and token, or `null` when it has no row. Read
|
|
38
|
+
* for every subtracted pinned operand just before the publishes, and once more at the end of the call for the rest.
|
|
39
|
+
*/
|
|
40
|
+
readonly current?: (options?: {
|
|
41
|
+
readonly fingerprint?: boolean;
|
|
42
|
+
}) => Promise<{
|
|
43
|
+
readonly generation: number | null;
|
|
44
|
+
readonly token: string;
|
|
45
|
+
readonly fingerprint?: string | null;
|
|
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
|
+
}
|
|
67
|
+
/** What one output asks for. */
|
|
68
|
+
export interface CombineManyOutput<R> {
|
|
69
|
+
readonly expr: CombineExpr;
|
|
70
|
+
/** Subtracted from the result: a root `andNot`. */
|
|
71
|
+
readonly exclude?: readonly CombineExpr[];
|
|
72
|
+
readonly allowEmpty?: boolean;
|
|
73
|
+
readonly guard?: LoadGuard;
|
|
74
|
+
readonly metadata?: GenerationMetadata;
|
|
75
|
+
/** Overrides the call's `keep`. */
|
|
76
|
+
readonly keep?: number;
|
|
77
|
+
/** Throws to refuse the publish: the destination's lease or deadline, checked again just before it. */
|
|
78
|
+
readonly beforePublish?: () => void;
|
|
79
|
+
/**
|
|
80
|
+
* The requests a settled publish made that its value proves, for the call's totals by class; absent, a publish adds
|
|
81
|
+
* none. A publish that threw is not counted: what it sent is unknown.
|
|
82
|
+
*/
|
|
83
|
+
readonly requestsOf?: (value: R) => {
|
|
84
|
+
readonly get: number;
|
|
85
|
+
readonly put: number;
|
|
86
|
+
};
|
|
87
|
+
/** Publish the finished chunks, ascending by key. What it returns or throws is the output's outcome. */
|
|
88
|
+
readonly publish: (chunks: AsyncIterable<{
|
|
89
|
+
readonly chunkKey: number;
|
|
90
|
+
readonly bitmap: CodecBitmap;
|
|
91
|
+
}>, write: CombineManyWrite) => Promise<R>;
|
|
92
|
+
}
|
|
93
|
+
/** The write options of one output, checked and defaulted: what its publish passes to the load. */
|
|
94
|
+
export interface CombineManyWrite {
|
|
95
|
+
readonly keep: number;
|
|
96
|
+
readonly allowEmpty?: boolean;
|
|
97
|
+
readonly guard?: LoadGuard;
|
|
98
|
+
readonly metadata?: GenerationMetadata;
|
|
99
|
+
}
|
|
100
|
+
export interface CombineManyRequest<R> {
|
|
101
|
+
readonly operands: readonly CombineManyOperand[];
|
|
102
|
+
readonly outputs: readonly CombineManyOutput<R>[];
|
|
103
|
+
/** The call's `keep`; required. */
|
|
104
|
+
readonly keep: number;
|
|
105
|
+
readonly after?: number;
|
|
106
|
+
readonly through?: number;
|
|
107
|
+
/** Required with a feed: the feed's records and the fed outputs are counted against it as they arrive. */
|
|
108
|
+
readonly maxBufferedBytes: number;
|
|
109
|
+
readonly publishConcurrency: number;
|
|
110
|
+
readonly concurrency: number;
|
|
111
|
+
/** `undefined`: the plan's chunk reads with headroom. `null`: none. */
|
|
112
|
+
readonly budget?: Budget | null;
|
|
113
|
+
readonly allowAbsentOperands?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Operands fed as records in chunk-key order, beside the stored ones. A feed is read once, so a call with one runs its
|
|
116
|
+
* outputs as one group; a fed output publishes only after the whole feed was read and checked.
|
|
117
|
+
*/
|
|
118
|
+
readonly feed?: CombineManyFeed;
|
|
119
|
+
}
|
|
120
|
+
export interface CombineManyDeps {
|
|
121
|
+
/** Reads the operands: already pinned where they are, and retrying as the store's reads do. */
|
|
122
|
+
readonly source: StorageChunkSource;
|
|
123
|
+
readonly codec: CodecInterface;
|
|
124
|
+
readonly clock: Clock;
|
|
125
|
+
readonly maxBitmapBytes?: number;
|
|
126
|
+
}
|
|
127
|
+
/** What one output came to. */
|
|
128
|
+
export type CombineManyOutcome<R> = {
|
|
129
|
+
readonly ok: true;
|
|
130
|
+
readonly value: R;
|
|
131
|
+
} | {
|
|
132
|
+
readonly ok: false;
|
|
133
|
+
readonly error: unknown;
|
|
134
|
+
};
|
|
135
|
+
/** What the call did to one operand. */
|
|
136
|
+
export interface CombineManyOperandStats {
|
|
137
|
+
/** Whether an output of the call read it. An operand no output names is not read. */
|
|
138
|
+
readonly read: boolean;
|
|
139
|
+
/** Whether the operand was read at a pinned generation. */
|
|
140
|
+
readonly pinned: boolean;
|
|
141
|
+
/** The generation pinned, `null` when the pin holds none, `undefined` when not pinned. */
|
|
142
|
+
readonly pinnedGeneration: number | null | undefined;
|
|
143
|
+
/** The generation the call began reading it at: the pin's, or the one live when its index was read. */
|
|
144
|
+
readonly startGeneration: number | null | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* The generation current when the call ended, from one registry read of the operand's row: the re-read made before
|
|
147
|
+
* the publishes for an operand an output subtracts, one more at the end of the call for the rest. `undefined` where the
|
|
148
|
+
* operand was not read, or its row could not be.
|
|
149
|
+
*/
|
|
150
|
+
readonly endGeneration: number | null | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Whether the operand was replaced while the call ran: for a pinned operand, its pin no longer names what the
|
|
153
|
+
* registry holds (another generation, the name deleted and created again, or the generation's number taken again by
|
|
154
|
+
* other bytes, which an operand an output subtracts is compared for by the object's fingerprint); for one read live, its
|
|
155
|
+
* generation or its incarnation changed from where the call began. `undefined` where `endGeneration` is, and for a live
|
|
156
|
+
* operand the call never opened, whose start is unknown.
|
|
157
|
+
*/
|
|
158
|
+
readonly moved: boolean | undefined;
|
|
159
|
+
/** Chunk keys in its index, inside the range. */
|
|
160
|
+
readonly keys: number;
|
|
161
|
+
/** Chunk reads made of it, over all groups. */
|
|
162
|
+
readonly chunkReads: number;
|
|
163
|
+
}
|
|
164
|
+
/** What the call did to one output. */
|
|
165
|
+
export interface CombineManyOutputStats {
|
|
166
|
+
/** The names of the operands its expression reads: the stored ones in call order, then the fed ones in `feed.names` order. */
|
|
167
|
+
readonly operands: readonly string[];
|
|
168
|
+
/** The group that computed it (from 0), or `null` when it never ran. */
|
|
169
|
+
readonly group: number | null;
|
|
170
|
+
/** Clock time the group's pass began and the output's publish settled (or it failed), when it ran. */
|
|
171
|
+
readonly startedAt: number | null;
|
|
172
|
+
readonly endedAt: number | null;
|
|
173
|
+
/** Chunks the output holds. */
|
|
174
|
+
readonly chunks: number;
|
|
175
|
+
/** What the index said its buffer could reach resident, which is what groups are planned on. */
|
|
176
|
+
readonly boundBytes: number;
|
|
177
|
+
/** The most its buffer held resident, in the ledger's own measure; compare with `boundBytes` to see how tight a plan was. */
|
|
178
|
+
readonly peakBytes: number;
|
|
179
|
+
}
|
|
180
|
+
export interface CombineManyStats {
|
|
181
|
+
/** Groups run, the re-runs of outputs a lying index made too big included. */
|
|
182
|
+
readonly groups: number;
|
|
183
|
+
readonly requests: {
|
|
184
|
+
/** Range requests sent for operand chunks (retries of one request count once). */
|
|
185
|
+
readonly rangeReads: number;
|
|
186
|
+
readonly rangeBytes: number;
|
|
187
|
+
/** (operand, key) chunk reads, the unit `budget` is counted in. */
|
|
188
|
+
readonly chunkReads: number;
|
|
189
|
+
/** Registry reads of the call itself: the re-checks, the end-of-call reads and the existence checks. */
|
|
190
|
+
readonly registryReads: number;
|
|
191
|
+
/**
|
|
192
|
+
* Operands whose index the call opened. An open is one tail read of the operand's object plus at most one registry
|
|
193
|
+
* row read, made by the store's readers, which a pin taken before the call may already have made.
|
|
194
|
+
*/
|
|
195
|
+
readonly opens: number;
|
|
196
|
+
/**
|
|
197
|
+
* Outputs handed to a publish. A publish is one object write and one pointer write where it succeeds, plus the row and
|
|
198
|
+
* tail reads the load makes for its guard, which the call does not see.
|
|
199
|
+
*/
|
|
200
|
+
readonly publishes: number;
|
|
201
|
+
/**
|
|
202
|
+
* The requests the call itself issues or can prove, a lower bound on what the drivers saw: `get` is range reads and
|
|
203
|
+
* registry reads, `put` the object write and pointer write of each published output and the object write of each
|
|
204
|
+
* refused one. To reconstruct the total GET-class requests add two reads (a tail and a row) for each operand a pin took,
|
|
205
|
+
* every named operand when `pin` is on, or for each of `opens` when it is off, and for each publish the reads of its load:
|
|
206
|
+
* two for a destination with a generation, three for one with none, and one more when its guard refuses it. Unknown to the call: a multipart upload's extra requests, a listing, and what a publish
|
|
207
|
+
* that threw sent.
|
|
208
|
+
*/
|
|
209
|
+
readonly attributed: {
|
|
210
|
+
readonly get: number;
|
|
211
|
+
readonly put: number;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
readonly budget: {
|
|
215
|
+
/** The limit used: `null` when lifted. */
|
|
216
|
+
readonly maxRequests: number | null;
|
|
217
|
+
/** Chunk reads the plan needed. */
|
|
218
|
+
readonly planned: number;
|
|
219
|
+
/** Chunk reads made. */
|
|
220
|
+
readonly used: number;
|
|
221
|
+
};
|
|
222
|
+
readonly memory: {
|
|
223
|
+
readonly maxBufferedBytes: number;
|
|
224
|
+
/** The most the ledger held at once. */
|
|
225
|
+
readonly highWaterBytes: number;
|
|
226
|
+
/** What the plan holds for the whole call, charged to the ledger: operand indexes, and each output's keys and expression. */
|
|
227
|
+
readonly planBytes: number;
|
|
228
|
+
};
|
|
229
|
+
readonly chunks: {
|
|
230
|
+
/** Index keys not read because no output needed them there, summed over groups. */
|
|
231
|
+
readonly pruned: number;
|
|
232
|
+
};
|
|
233
|
+
/** The most range requests in flight at once. */
|
|
234
|
+
readonly maxRangesInFlight: number;
|
|
235
|
+
/** What the feed delivered, when the call had one: records read, chunk keys evaluated from it, ids checked. */
|
|
236
|
+
readonly feed?: {
|
|
237
|
+
readonly records: number;
|
|
238
|
+
readonly keys: number;
|
|
239
|
+
readonly ids: number;
|
|
240
|
+
};
|
|
241
|
+
/** By operand name, an object with no prototype, so any name is a key. */
|
|
242
|
+
readonly operands: Readonly<Record<string, CombineManyOperandStats>>;
|
|
243
|
+
readonly outputs: readonly CombineManyOutputStats[];
|
|
244
|
+
}
|
|
245
|
+
export interface CombineManyRun<R> {
|
|
246
|
+
readonly outputs: readonly CombineManyOutcome<R>[];
|
|
247
|
+
readonly stats: CombineManyStats;
|
|
248
|
+
}
|
|
249
|
+
/** A request that passed every check that needs no I/O: what {@link runCombineMany} runs. */
|
|
250
|
+
export interface CompiledCombineMany<R> {
|
|
251
|
+
readonly request: CombineManyRequest<R>;
|
|
252
|
+
}
|
|
253
|
+
/** The most nodes the expressions of one call may hold in all, so that a thousand large outputs cannot be planned at once. */
|
|
254
|
+
export declare const MAX_CALL_NODES = 200000;
|
|
255
|
+
/**
|
|
256
|
+
* Check a request: every expression, option and number, before any request is made. Throws {@link ValidationError}
|
|
257
|
+
* naming the output index and the path. Pure.
|
|
258
|
+
*/
|
|
259
|
+
export declare function compileCombineMany<R>(request: CombineManyRequest<R>): CompiledCombineMany<R>;
|
|
260
|
+
/**
|
|
261
|
+
* `compiled` with its operands replaced, the same names in the same order: what a flavor does once it has pinned them,
|
|
262
|
+
* without checking the expressions again.
|
|
263
|
+
*/
|
|
264
|
+
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
|
+
/** What {@link GroupCost} reads of an output. */
|
|
271
|
+
export interface GroupCostItem {
|
|
272
|
+
readonly bound: number;
|
|
273
|
+
readonly serializedBound: number;
|
|
274
|
+
readonly rootKeys: Uint16Array;
|
|
275
|
+
readonly compiled: {
|
|
276
|
+
readonly operands: readonly number[];
|
|
277
|
+
readonly depth: number;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Work counters of the planner, for the tests that hold it to its cost: how many times a group was priced, how many
|
|
282
|
+
* operands those prices walked, and how many group plans were built. Plain numbers, never read by the planner.
|
|
283
|
+
*/
|
|
284
|
+
export declare const combineWork: {
|
|
285
|
+
adds: number;
|
|
286
|
+
withs: number;
|
|
287
|
+
operandSteps: number;
|
|
288
|
+
planBuilds: number;
|
|
289
|
+
};
|
|
290
|
+
export declare class GroupCost {
|
|
291
|
+
private readonly streamBytes;
|
|
292
|
+
private readonly publishConcurrency;
|
|
293
|
+
private outputs;
|
|
294
|
+
private depth;
|
|
295
|
+
/** The serialized bounds of the largest outputs, descending: at most `publishConcurrency` of them are written at once. */
|
|
296
|
+
private top;
|
|
297
|
+
private count;
|
|
298
|
+
private streams;
|
|
299
|
+
private planWork;
|
|
300
|
+
private readonly operands;
|
|
301
|
+
constructor(streamBytes: (operand: number) => number, publishConcurrency: number);
|
|
302
|
+
/** The cost of the group with `o` added. */
|
|
303
|
+
with(o: GroupCostItem): number;
|
|
304
|
+
add(o: GroupCostItem): void;
|
|
305
|
+
get size(): number;
|
|
306
|
+
private withLargest;
|
|
307
|
+
/**
|
|
308
|
+
* The bytes of objects being written beside the buffers, at most: the `publishConcurrency` largest outputs, each as
|
|
309
|
+
* large as its bound, with `bound` among the candidates.
|
|
310
|
+
*/
|
|
311
|
+
private written;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Run a compiled request: read what its outputs need, evaluate them key by key and publish each. Never throws for a
|
|
315
|
+
* fault of one output or one operand: that is the output's outcome. Throws {@link ValidationError} for an operand that
|
|
316
|
+
* names no segment (unless allowed), and {@link BudgetExceededError} when its own plan needs more chunk reads than the
|
|
317
|
+
* budget, both before any chunk is read.
|
|
318
|
+
*/
|
|
319
|
+
export declare function runCombineMany<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps): Promise<CombineManyRun<R>>;
|
|
@@ -6,7 +6,7 @@ import type { Aead, CrbmCrypto, IKeystore, WrappedDek } from './crypto.js';
|
|
|
6
6
|
import type { ChunkRef, ChunkRead, ReadChunksOptions, StorageChunkSource, GenerationMetadata, GenerationSummary, GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, RegistrySummary, SegmentRef, SegmentSize, Token } from './ports.js';
|
|
7
7
|
import { CrbmReader } from './crbm/reader.js';
|
|
8
8
|
import type { CrbmReaderOptions } from './crbm/reader.js';
|
|
9
|
-
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
9
|
+
import type { CodecBitmap, CodecInterface, EncodedChunk } from './codec.js';
|
|
10
10
|
import { ChunkLoadInput, DecodedLoadInput } from './load-input.js';
|
|
11
11
|
export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
|
|
12
12
|
/**
|
|
@@ -445,6 +445,13 @@ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey,
|
|
|
445
445
|
sha256: string;
|
|
446
446
|
fingerprint: string;
|
|
447
447
|
}>;
|
|
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>>;
|
|
448
455
|
/**
|
|
449
456
|
* Whether the object under `key` is provably the one `fingerprint` names ({@link CrbmReader.fingerprint}), from one
|
|
450
457
|
* read of its footer. False when it is another object, when it is gone, and when the read fails in any way: a caller
|