@cloudbitmaps/core 0.16.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.
@@ -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>>;
@@ -139,7 +139,7 @@ export interface Workload {
139
139
  /**
140
140
  * PUT-class requests one load's object write issues, each priced at the PUT rate. Default **1** (a single-object
141
141
  * PUT). A multipart write of `P` parts bills `P + 2` (initiate, the parts, complete) — set it when you know your
142
- * object sizes. The model adds what `store.load()` does around the write, at the default `keep` of 1: the pointer's
142
+ * object sizes. The model adds what `store.load()` does around the write, at any `keep` from 1 to 64: the pointer's
143
143
  * write, PUT-class on S3, and four GETs: the pointer read twice, one check that the next generation number is
144
144
  * free, and one that the current generation's object is there, which tells the collection that deletes by name it
145
145
  * may. The current generation's size comes from the row's summary of it, so the load reads no index. Collection
@@ -151,8 +151,8 @@ export interface Workload {
151
151
  * read, in place of the check that its object is there. A publish that loses a race to another writer
152
152
  * reads the pointer again, and a load whose check finds the number taken (a crashed load's object, or the
153
153
  * generations a rollback left above the pointer) lists the segment's objects to number past them and to collect,
154
- * two PUT-class requests on S3 and two more pointer reads. So does every load that keeps two or more generations,
155
- * which lists to collect: one more PUT-class request and two more pointer reads than the model counts. The counts
154
+ * two PUT-class requests on S3 and two more pointer reads. So does every load whose `keep` is above 64, which lists
155
+ * to collect: one more PUT-class request and two more pointer reads than the model counts. The counts
156
156
  * are a cleartext segment's: an encrypted segment's load reads its row once more, after its ids and before it
157
157
  * unwraps the key, one more GET ($0.40 per million at the default prices) that the model leaves out, beside the
158
158
  * key-management calls it does not price either.