@cloudbitmaps/core 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +85 -0
  2. package/dist/{chunk-RSLVTRJY.js → chunk-IGLIZBR7.js} +982 -281
  3. package/dist/chunk-IGLIZBR7.js.map +7 -0
  4. package/dist/core/absent-operands.d.ts +8 -0
  5. package/dist/core/audit.d.ts +67 -4
  6. package/dist/core/bit-route.d.ts +2 -1
  7. package/dist/core/blob.d.ts +12 -1
  8. package/dist/core/bulk-load.d.ts +148 -0
  9. package/dist/core/cas-retry.d.ts +27 -0
  10. package/dist/core/check.d.ts +22 -0
  11. package/dist/core/chunk-checks.d.ts +6 -1
  12. package/dist/core/chunk-window.d.ts +11 -4
  13. package/dist/core/combine-expr.d.ts +1 -3
  14. package/dist/core/combine-feed.d.ts +2 -16
  15. package/dist/core/combine-many.d.ts +21 -15
  16. package/dist/core/concurrency.d.ts +8 -0
  17. package/dist/core/consistency.d.ts +76 -18
  18. package/dist/core/crbm/fingerprint.d.ts +23 -0
  19. package/dist/core/crbm/format.d.ts +2 -5
  20. package/dist/core/crbm/frame.d.ts +10 -0
  21. package/dist/core/crbm/plan-reads.d.ts +9 -3
  22. package/dist/core/crbm/reader.d.ts +51 -12
  23. package/dist/core/crbm/varint.d.ts +2 -1
  24. package/dist/core/crbm/writer.d.ts +5 -1
  25. package/dist/core/crbm-open.d.ts +154 -0
  26. package/dist/core/crbm-storage-source.d.ts +243 -490
  27. package/dist/core/crbm-write.d.ts +75 -0
  28. package/dist/core/crypto.d.ts +6 -1
  29. package/dist/core/engine.d.ts +239 -47
  30. package/dist/core/erase-id.d.ts +97 -27
  31. package/dist/core/erasure.d.ts +4 -3
  32. package/dist/core/errors.d.ts +8 -2
  33. package/dist/core/generation-gc.d.ts +131 -16
  34. package/dist/core/generation-list.d.ts +11 -0
  35. package/dist/core/id-window.d.ts +46 -0
  36. package/dist/core/invalidation-scope.d.ts +77 -0
  37. package/dist/core/known-generations-shape.d.ts +13 -0
  38. package/dist/core/known-generations.d.ts +147 -0
  39. package/dist/core/leases.d.ts +9 -1
  40. package/dist/core/ledger-seed.d.ts +17 -0
  41. package/dist/core/load.d.ts +17 -36
  42. package/dist/core/lru.d.ts +2 -15
  43. package/dist/core/metrics.d.ts +9 -7
  44. package/dist/core/order.d.ts +2 -0
  45. package/dist/core/pinned-storage-source.d.ts +7 -7
  46. package/dist/core/pointer-id.d.ts +19 -0
  47. package/dist/core/ports.d.ts +210 -45
  48. package/dist/core/publish.d.ts +207 -0
  49. package/dist/core/reader-defaults.d.ts +14 -3
  50. package/dist/core/registry-scan.d.ts +0 -2
  51. package/dist/core/retention-sweep.d.ts +69 -65
  52. package/dist/core/retention.d.ts +1 -1
  53. package/dist/core/retry.d.ts +12 -19
  54. package/dist/core/rollback.d.ts +62 -32
  55. package/dist/core/summary.d.ts +74 -25
  56. package/dist/core/token.d.ts +13 -8
  57. package/dist/core/validate.d.ts +2 -0
  58. package/dist/driver-kit.d.ts +6 -1
  59. package/dist/driver-kit.js +90 -10
  60. package/dist/driver-kit.js.map +3 -3
  61. package/dist/drivers/_shared/bytes.d.ts +6 -0
  62. package/dist/drivers/_shared/faults.d.ts +15 -0
  63. package/dist/drivers/_shared/object-keys.d.ts +18 -0
  64. package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
  65. package/dist/drivers/_shared/object-registry.d.ts +3 -1
  66. package/dist/drivers/_shared/options.d.ts +19 -0
  67. package/dist/drivers/_shared/registry.d.ts +31 -19
  68. package/dist/drivers/localfs/exact-case.d.ts +0 -3
  69. package/dist/drivers/localfs/fs-util.d.ts +22 -0
  70. package/dist/drivers/localfs/registry.d.ts +10 -4
  71. package/dist/drivers/localfs/storage.d.ts +16 -8
  72. package/dist/drivers/memory.d.ts +14 -9
  73. package/dist/drivers/retry/retrying-chunk-source.d.ts +1 -5
  74. package/dist/export/index.d.ts +16 -0
  75. package/dist/index.d.ts +2 -5
  76. package/dist/index.js +4303 -2938
  77. package/dist/index.js.map +4 -4
  78. package/package.json +1 -1
  79. package/dist/chunk-RSLVTRJY.js.map +0 -7
  80. package/dist/core/cost.d.ts +0 -351
  81. package/dist/core/kept-generations.d.ts +0 -40
@@ -0,0 +1,75 @@
1
+ import type { Yielder } from './cooperative.js';
2
+ import type { CrbmCrypto } from './crypto.js';
3
+ import type { GenerationMetadata, GenKey, IStorageDriver } from './ports.js';
4
+ import type { CodecBitmap, EncodedChunk } from './codec.js';
5
+ /**
6
+ * Write one immutable generation from in-memory bitmaps. Chunks are
7
+ * sorted ascending and empty bitmaps skipped (empty chunks are never stored). Returns the driver's
8
+ * `{ size, sha256 }` for the written object. Pass `options.crypto` to AES-256-GCM-encrypt the generation
9
+ * (built from the segment's DEK, with associated data bound to `(segment, generation)`).
10
+ */
11
+ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey, chunks: Iterable<{
12
+ chunkKey: number;
13
+ bitmap: CodecBitmap;
14
+ }>, options?: {
15
+ crypto?: CrbmCrypto;
16
+ clock?: Yielder;
17
+ metadata?: GenerationMetadata;
18
+ }): Promise<{
19
+ size: number;
20
+ sha256: string;
21
+ fingerprint: string;
22
+ tailBytes: number;
23
+ }>;
24
+ /** Each non-empty bitmap as the chunk it stores, in the order given. */
25
+ export declare function encodeEach(chunks: Iterable<{
26
+ chunkKey: number;
27
+ bitmap: CodecBitmap;
28
+ }>): Generator<EncodedChunk>;
29
+ /**
30
+ * Write one immutable generation from chunks already encoded, which must ascend by key (the writer refuses one
31
+ * that does not). Returns the driver's `{ size, sha256 }` and a tally of what was written.
32
+ */
33
+ export declare function writeEncodedChunks(driver: IStorageDriver, key: GenKey, chunks: Iterable<EncodedChunk>, options: {
34
+ crypto?: CrbmCrypto;
35
+ clock?: Yielder;
36
+ metadata?: GenerationMetadata;
37
+ }): Promise<{
38
+ size: number;
39
+ sha256: string;
40
+ chunkCount: number;
41
+ cardinality: number;
42
+ fingerprint: string;
43
+ /** The bytes after the chunk payloads ({@link CrbmReader.tailBytes}). */
44
+ tailBytes: number;
45
+ }>;
46
+ /** What {@link writeCrbmGenerationStream} wrote: the driver's `{ size, sha256 }` + a tally of the generation. */
47
+ interface StreamWriteResult {
48
+ readonly size: number;
49
+ readonly sha256: string;
50
+ /** Non-empty chunk keys written, in ascending order (for the post-write verify, since the stream is consumed). */
51
+ readonly chunkKeys: number[];
52
+ /** Total ids written. */
53
+ readonly cardinality: number;
54
+ /** The fingerprint of what this call wrote ({@link CrbmReader.fingerprint}), which tells it from another object under its key. */
55
+ readonly fingerprint: string;
56
+ /** The bytes after the chunk payloads ({@link CrbmReader.tailBytes}). */
57
+ readonly tailBytes: number;
58
+ }
59
+ /**
60
+ * Streaming variant of {@link writeCrbmGeneration} for a **constant-memory** rewrite (the erasure rewrite, a
61
+ * sorted load): consumes an **already-ascending** async stream of `{ chunkKey, bitmap }`, feeding each to the
62
+ * codec and freeing it, instead of materializing the whole generation. Paired with a streaming storage sink (S3
63
+ * multipart / LocalFs temp file), the writer itself holds ~one chunk + one part. Returns a tally so the caller can verify
64
+ * the re-opened object without re-iterating the (now-consumed) stream. Input **must** be ascending by `chunkKey`
65
+ * — the codec rejects an out-of-order chunk; empty bitmaps are skipped.
66
+ */
67
+ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: GenKey, chunks: AsyncIterable<{
68
+ chunkKey: number;
69
+ bitmap: CodecBitmap;
70
+ }>, options?: {
71
+ crypto?: CrbmCrypto;
72
+ clock?: Yielder;
73
+ metadata?: GenerationMetadata;
74
+ }): Promise<StreamWriteResult>;
75
+ export {};
@@ -56,7 +56,10 @@ export interface IKeystore {
56
56
  /**
57
57
  * Reconstruct the {@link Aead} from a segment's stored wrappings by unwrapping with any KEK currently held.
58
58
  * Throws {@link KeyUnavailableError} if it holds none of the referenced KEKs (lost key, or a shredded
59
- * segment whose wrappings were deleted).
59
+ * segment whose wrappings were deleted). Throw {@link TransientError} for a fault worth riding out (a throttle, a 5xx,
60
+ * a dropped connection, a request timeout): a rollback below a segment's pointer goes ahead without the key on that
61
+ * error, as on `KeyUnavailableError`, an `IntegrityError` or an `Error` named `TimeoutError`, and is stopped by any
62
+ * other error the keystore throws.
60
63
  */
61
64
  openDek(wrapped: readonly WrappedDek[]): Promise<Aead>;
62
65
  }
@@ -82,3 +85,5 @@ export declare function aadFor(ref: {
82
85
  readonly namespace?: string;
83
86
  readonly segment: string;
84
87
  }, generation: number, scope: number | 'index' | 'metadata' | 'summary'): Uint8Array;
88
+ /** Whether two rows carry the same wrapped keys. */
89
+ export declare const sameKeys: (a: readonly WrappedDek[] | undefined, b: readonly WrappedDek[]) => boolean;
@@ -3,7 +3,7 @@ import type { Budget, BudgetOption } from './budget.js';
3
3
  import type { Clock } from './determinism.js';
4
4
  import type { BoundedLru } from './lru.js';
5
5
  import type { IMetricsSink } from './metrics.js';
6
- import type { GenerationMetadata, StorageChunkSource, SegmentRef, SegmentSize } from './ports.js';
6
+ import type { GenerationMetadata, StorageChunkSource, SegmentRef } from './ports.js';
7
7
  /**
8
8
  * Range requests a stream holds ahead of a combine or `iterate` by default (chunk keys, on a source that reads chunk by
9
9
  * chunk, and for `count`'s fallback) — bounds memory and concurrent reads (invariant 6). A read that does not fit
@@ -13,7 +13,11 @@ import type { GenerationMetadata, StorageChunkSource, SegmentRef, SegmentSize }
13
13
  export declare const DEFAULT_INTERSECT_CONCURRENCY = 32;
14
14
  export interface EngineDeps {
15
15
  readonly storage: StorageChunkSource;
16
- /** Optional cache of decoded (immutable) Storage chunks. */
16
+ /**
17
+ * Optional cache of decoded (immutable) Storage chunks. A cache whose `size` is 0 is taken to hold nothing: a read
18
+ * that opens a stream on it looks up the chunk the stream starts at and counts each later chunk a miss without
19
+ * looking it up.
20
+ */
17
21
  readonly cache?: BoundedLru<string, CodecBitmap>;
18
22
  /** Per-chunk decode ceiling (invariant 5); defaults to 1 MiB. The `.crbm` reader refuses an entry above its own `maxPayloadBytes` (1 MiB, plus 28 bytes when encrypted) at open, so raise that on the chunk source too. */
19
23
  readonly maxBitmapBytes?: number;
@@ -33,6 +37,19 @@ export interface EngineDeps {
33
37
  * Read ops refuse before fan-out if they'd exceed it.
34
38
  */
35
39
  readonly budget?: Budget | null;
40
+ /**
41
+ * An engine over the same `cache` whose invalidations this one shares: an {@link SegmentEngine.invalidate} through
42
+ * either reaches the reads running on both. It is opt-in, and engines that read through one chunk cache must be
43
+ * built with it: two built without it share nothing, so a read running on one when the other invalidates its
44
+ * segment writes the chunks it read before the invalidation back into the cache, and later reads through it are
45
+ * answered from them, an erased id included, until the cache evicts them. The `CloudRoaring` facade builds the
46
+ * engines of its pins and of its combines with a pinned operand this way.
47
+ *
48
+ * The engines share their requests in flight too: a cache miss on one joins a request the other opened for the same
49
+ * cache key, and gets what that engine's storage, codec and `maxBitmapBytes` made of it, as a cache hit hands over
50
+ * the chunk the other decoded. Throws {@link ValidationError} unless it is a `SegmentEngine` over the same `cache`.
51
+ */
52
+ readonly sharesInvalidationsWith?: SegmentEngine;
36
53
  }
37
54
  /**
38
55
  * A range of ids for a read to yield: those greater than `after` and no greater than `through`, `(after, through]`.
@@ -44,8 +61,9 @@ export interface EngineDeps {
44
61
  * a combine's operands exist.
45
62
  *
46
63
  * A read with a range fetches only the chunks the range overlaps, on a combine for every operand and every
47
- * `exclude`. The per-op budget is charged once, before the first fetch, for every chunk in the range, so with
48
- * `after` alone it is charged to the end of the segment however early the caller stops; pass `through` to bound it.
64
+ * `exclude`. The per-op budget is charged before the first fetch, for every chunk in the range, so with `after` alone
65
+ * it is charged to the end of the segment however early the caller stops; pass `through` to bound it. A read that
66
+ * lists its keys again after a move is charged again, with the chunks it has already taken.
49
67
  */
50
68
  export interface IdRange {
51
69
  /** Exclusive lower bound: the read yields only ids greater than this. */
@@ -63,7 +81,7 @@ export interface CombineOptions extends IdRange {
63
81
  * is larger), about `concurrency × operands` MiB are held, whatever the segment's size. The stream opens 4 ranges wide
64
82
  * (or `concurrency`, if lower) and widens as ranges are taken, so a read that stops early has asked for little past
65
83
  * where it stopped, and a read that fits one range makes one request however many chunks it needs. A source that
66
- * reads chunk by chunk (a custom one) is read as before: `concurrency` chunk keys at once, opening 8 wide. A running
84
+ * reads chunk by chunk (a custom one) is read `concurrency` chunk keys at once, opening 8 wide. A running
67
85
  * stream holds its operand's reader outside the reader cache's bounds until it ends or the segment moves on.
68
86
  */
69
87
  readonly concurrency?: number;
@@ -94,18 +112,10 @@ export declare class SegmentEngine {
94
112
  /** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. */
95
113
  private readonly budget;
96
114
  /**
97
- * The chunk reads open right now, by cache key: a caller that misses the cache and finds its key here awaits that
98
- * read instead of making its own. An entry lives from the request until it settles, so the map holds at most one
99
- * promise per distinct key in flight.
100
- */
101
- private readonly openReads;
102
- /**
103
- * The chunk streams open right now. An {@link invalidate} of a segment marks its streams, which then cache no chunk
104
- * they deliver, as a read whose entry an invalidation dropped does not (streams hold no `openReads` entry).
115
+ * The invalidations this engine shares with every engine over its chunk cache that was built to share them
116
+ * ({@link EngineDeps.sharesInvalidationsWith}), and the chunk reads open on all of them.
105
117
  */
106
- private readonly openStreams;
107
- /** How many times {@link invalidate} has been called: a read compares it with the count it began under. */
108
- private invalidations;
118
+ private readonly scope;
109
119
  /**
110
120
  * The cache-key prefixes ({@link segmentPrefix}) of the segments this engine reads, kept rather than encoded for each
111
121
  * chunk. A kept prefix is the one {@link invalidate} finds the segment's chunks by.
@@ -128,26 +138,32 @@ export declare class SegmentEngine {
128
138
  */
129
139
  count(seg: SegmentRef): Promise<number>;
130
140
  /**
131
- * What the segment's current generation is: its number, its id count and its metadata, from one resolution, so the
132
- * three cannot straddle a publish. A source with no summary answers from `count()` and the generation it resolves.
141
+ * What the segment's current generation is: its number, its id count, its metadata and the size of its object in
142
+ * bytes. A source with a `stat` (the `.crbm` source) answers all four from one resolution, so they cannot straddle
143
+ * a publish: from the registry row's summary when it can use it, else from the opened generation's footer and
144
+ * index, with no payload read. A source without one answers from its other methods, each its own resolution: its
145
+ * summary when it has one, else `count()` and the generation it resolves, and its `sizeOf`, or `sizeBytes: null`
146
+ * when it has none. A segment with no generation answers `{ generation: null, cardinality: 0, sizeBytes: null }`.
133
147
  */
134
148
  stat(seg: SegmentRef): Promise<{
135
149
  generation: number | null;
136
150
  cardinality: number;
137
151
  metadata?: GenerationMetadata;
152
+ sizeBytes: number | null;
138
153
  }>;
139
- /** Whether the Storage source can measure segment size (for grounded cost); false ⇒ storage isn't grounded. */
140
- get supportsStorageSize(): boolean;
141
- /** How often the Storage source re-reads a pointer while reading (cost reporting); undefined if it does not say. */
142
- get pointerRefreshMs(): number | undefined;
143
- /** Grounded Storage size of a segment's current generation (cost reporting), or null if it has no generation. */
144
- segmentSize(seg: SegmentRef): Promise<SegmentSize | null>;
145
- /** The chunks of `seg` at `chunkKeys`, read through a window of {@link DEFAULT_INTERSECT_CONCURRENCY}. */
154
+ /** The size of the segment's current generation from the source's `sizeOf`, or `null` when it cannot say. */
155
+ private sizeOf;
156
+ /**
157
+ * The chunks of `seg` at `chunkKeys`, read through a window of {@link DEFAULT_INTERSECT_CONCURRENCY}. A cached chunk
158
+ * that is no longer the segment's version marks `moves`, when given.
159
+ */
146
160
  private chunkWindow;
147
161
  /**
148
162
  * The chunks of `seg` at `chunkKeys`, taken in ascending key order by a read that may stop at any point: one stream of
149
163
  * coalesced ranges when the source has `getChunks`, opening 1, 2, 4 … ranges wide up to {@link DEFAULT_INTERSECT_CONCURRENCY},
150
- * else the chunk-by-chunk window of that many reads. `close` stops what is left of it.
164
+ * else the chunk-by-chunk window of that many reads. `close` stops what is left of it. A chunk that shows the segment
165
+ * moved marks `moves`: one of the stream named by another version, or one cached under `gen` that is no longer the
166
+ * segment's.
151
167
  */
152
168
  private chunkSequence;
153
169
  /**
@@ -157,9 +173,13 @@ export declare class SegmentEngine {
157
173
  * chunks the range overlaps (see {@link IdRange}). A source that reads chunk by chunk is read through a window of up to
158
174
  * 32 chunk fetches that opens 1, 2, 4 … 32 wide.
159
175
  *
176
+ * The chunk keys are listed once, before the first fetch. A move of the segment the read meets while it runs (see
177
+ * {@link Moves}) lists them again, from the generation current then, and the read goes on with the keys above the one
178
+ * it last took: it reads a chunk the new generation added, and not one it dropped.
179
+ *
160
180
  * Two generators, not one with a branch: a second `yield` site in the full read's generator grows its frame, and
161
- * every id of a read that asked for no range paid for it (measured at about 5% per id). The full read keeps the
162
- * loop it always had.
181
+ * every id of a read that asked for no range paid for it (measured at about 5% per id). The full read keeps one
182
+ * `yield`, in a loop of its own.
163
183
  */
164
184
  iterate(seg: SegmentRef, range?: IdRange): AsyncGenerator<number>;
165
185
  private iterateAll;
@@ -167,10 +187,71 @@ export declare class SegmentEngine {
167
187
  /**
168
188
  * {@link iterate}, one `Uint32Array` per chunk instead of one id at a time: the same chunks, in the same order, with
169
189
  * the same ids (a range cut at its edges the same way), and no array for a chunk that holds none of them. The
170
- * reads, the budget charge and the window are the per-id read's. Each array is at most 65,536 ids (256 KiB) and is
171
- * the caller's to keep.
190
+ * reads, the budget charge, the window and a move met while it runs are the per-id read's. Each array is at most
191
+ * 65,536 ids (256 KiB) and is the caller's to keep.
172
192
  */
173
193
  iterateBatches(seg: SegmentRef, range?: IdRange): AsyncGenerator<Uint32Array>;
194
+ /**
195
+ * Plan an `iterate` of the keys of `seg` above `after` and inside `w`: list them with their version
196
+ * ({@link listKeys}), charge the budget one storage fetch for each and for each of the `spent` keys earlier plans
197
+ * took, before any fetch, and open their chunks.
198
+ */
199
+ private iteratePlan;
200
+ /** The first plan of an `iterate`: every key in `w`. */
201
+ private planIterate;
202
+ /**
203
+ * The chunk at the next key of `run`, and `run.key` that key, or `undefined` once its keys are used up. Not `async`:
204
+ * when nothing has moved and no look is due, the chunk's own promise is all a key costs.
205
+ */
206
+ private nextChunk;
207
+ private takeNext;
208
+ /**
209
+ * {@link nextChunk} once the read has looked at its segment's version, and planned again if it is not the one the
210
+ * plan was made under. A chunk that suggested a move is confirmed so: a source whose chunks name their version
211
+ * otherwise than its `currentVersion` does costs a look, never a plan.
212
+ */
213
+ private nextChunkAfterLook;
214
+ /**
215
+ * Plan the rest of `run` again, after a move: the keys above the one it last took, from the generation current now.
216
+ * What the plan's stream still has in flight is dropped, not served.
217
+ */
218
+ private replanIterate;
219
+ /**
220
+ * A segment's chunk keys, ascending and checked, and the version they are read under, with the invalidation epoch
221
+ * they were listed from. The version is resolved AFTER the shape read (`listChunkKeys`): a non-empty shape read means
222
+ * the source resolved and cached a non-null snapshot, so the version cannot then come back null while storage data is
223
+ * present, and `storageChunk` cannot skip every chunk on a stale null.
224
+ *
225
+ * A write of this store that moves the segment while the two are read would leave the keys of one generation under
226
+ * the version of the next, and nothing later could tell: the read's chunks and its looks would all name that newer
227
+ * version. So when the segment was invalidated in this store's scope while they were read, they are read again. Only
228
+ * that segment's invalidations count, so a store busy invalidating other segments costs a listing nothing more; with
229
+ * none since the listing began the check is a number compare.
230
+ *
231
+ * Honest note on how much the ordering buys, so a future reader does not take an untested claim for a tested one.
232
+ * Against a `CrbmStorageChunkSource`, the only source with a generation to resolve, both reads go through the same
233
+ * resolved-reader memo, so the pathological state (keys non-empty, version null) does not arise and swapping the two
234
+ * is observably identical. The ordering is kept because it is the order that is correct for *any* source satisfying
235
+ * the port (a custom source that resolves its shape and its generation independently can still produce that state),
236
+ * not because a test can currently tell the difference. Within a call shorter than `cache.genTtlMs` the two share one
237
+ * snapshot, so the read is generation-consistent, absent cache-pressure eviction (see `intersect`).
238
+ */
239
+ private listKeys;
240
+ /**
241
+ * A read's {@link Looks}, from the invalidation epoch `epoch` its plan began at: due at the first key after a later
242
+ * invalidation, or after `every` keys.
243
+ */
244
+ private looks;
245
+ /**
246
+ * Whether a look is due before the next key: an invalidation since the read last looked, or its keys counted down. The
247
+ * caller counts the key, as it takes one, before it asks.
248
+ */
249
+ private lookDue;
250
+ /**
251
+ * The read looks now, before the key it takes next: the next look is due `every` keys on, or at the next
252
+ * invalidation. Called before the look awaits, so an invalidation while it waits is met at the next key.
253
+ */
254
+ private looked;
174
255
  /**
175
256
  * The ids at 1-based ranks `n`, `2n`, `3n` … counted over the ids in `range`, ascending. The rank of each boundary
176
257
  * is placed from the index's per-chunk counts, so only the chunks that hold a boundary are read, each once however
@@ -219,13 +300,19 @@ export declare class SegmentEngine {
219
300
  * the result — every chunk read is a whole, checksum-verified, immutable generation. The edge a *long* call can
220
301
  * hit is invariant 3's: if it straddles a mid-call `cache.genTtlMs` boundary and a load has published, an
221
302
  * operand's not-yet-requested chunks may re-resolve forward to the newer generation (a generation hop within one long
222
- * call) — the call never crashes or returns a torn object, but may mix generations. Three things hop it without
223
- * waiting for the TTL, so a shorter call can meet them too: **the reader cache evicting an operand mid-call**
224
- * (`maxOpenSegments`), whose re-read re-resolves fresh; a sweep deleting the generation it was reading, which
225
- * heals the read forward; and an invalidation, which this store's own `load`, `rollback` and `eraseSubject` make
226
- * and `invalidate()` makes on request. Still whole/immutable per read, never torn. An operand's stream resolves its
303
+ * call) — the call never crashes or returns a torn object, but may mix generations. Some things hop it without
304
+ * waiting for the TTL, so a shorter call can meet them too: **the reader cache evicting an operand mid-call** on a
305
+ * store with no timed refresh (`maxOpenSegments`), whose re-read re-resolves fresh, and on a store with one the
306
+ * resolution cache letting the operand's resolution go; a sweep deleting the generation it was reading, or another
307
+ * object under its number, met by this read or another, which heals the read forward, as another read's failed open
308
+ * of the operand does; and an invalidation, which this store's own `load`, `rollback` and `eraseSubject` make and
309
+ * `invalidate()` makes on request. Still whole/immutable per read, never torn. An operand's stream resolves its
227
310
  * segment again before it serves each chunk, so each of those moves a running read as it would a read of one chunk, and
228
- * the ranges it had requested of the earlier generation are dropped, not served.
311
+ * the ranges it had requested of the earlier generation are dropped, not served. The read then lists every operand's
312
+ * keys again above the key it last took, from the generation current then, so a key an operand gained is read and one
313
+ * it lost is not asked for. It meets a move through a chunk it is served that comes from another generation than it
314
+ * listed its keys under, and by looking at its operands' generations before the first key after an invalidation, and,
315
+ * with an `exclude` or as a union, every `concurrency` keys.
229
316
  */
230
317
  intersect(segs: readonly SegmentRef[], options?: CombineOptions): AsyncGenerator<number>;
231
318
  /**
@@ -270,12 +357,46 @@ export declare class SegmentEngine {
270
357
  * read and the batch read share it, so the two cannot disagree about what is read or charged.
271
358
  */
272
359
  private combinePlan;
360
+ /**
361
+ * Each operand's chunk-key set inside the read's range and above the key `after`, and the version it was listed under
362
+ * ({@link listKeys}), include operands first, each in its order. At most {@link OPERANDS_AT_ONCE} are listed at once;
363
+ * the first listing to fail rejects the plan at once, and no other is begun after it.
364
+ */
365
+ private combineOperands;
366
+ /**
367
+ * The operands' streams over `keys` and the window that reads them. Each include is read as a stream of coalesced
368
+ * ranges, and so is each exclude that is read with the includes (one include, or a union). An exclude that waits on
369
+ * an AND of two or more includes is read in batches of the keys whose AND is not empty ({@link ExcludeBatch}), since
370
+ * an emptied AND never needs its chunk. The window starts its first reads at once, so a plan is opened only once its
371
+ * budget has passed.
372
+ */
373
+ private openCombine;
374
+ /**
375
+ * The next key's combined chunk of `run`, `undefined` once its keys are used up. Not `async`: when nothing has moved
376
+ * and no look is due, the window's own promise is all a key costs.
377
+ */
378
+ private nextSlot;
379
+ /**
380
+ * {@link nextSlot} once the read has looked at every operand's version, and planned again if one is not the version
381
+ * the plan was made under. An operand the keys left do not read would otherwise never show the read its move: an
382
+ * exclude whose keys the survivors do not reach, or an include of a union past its last key. A chunk that suggested a
383
+ * move is confirmed so, as in {@link nextChunkAfterLook}. The versions are looked up at most {@link OPERANDS_AT_ONCE}
384
+ * at once, all of them: a look that finds no move has compared every one.
385
+ */
386
+ private nextSlotAfterLook;
387
+ /**
388
+ * Plan the rest of `run` again, after a move: every operand's keys above the key it last took, from the generation
389
+ * current now, aligned, charged to the budget with the reads of the keys already taken, and read through new streams
390
+ * and a new window. What the plan it replaces still has in flight is dropped, not served. The absent-operand refusal
391
+ * and the metric are the first plan's alone: a segment dropped while the read runs reads empty from there on.
392
+ */
393
+ private replanCombine;
394
+ /** Stop the streams of `plan`; the reads its window holds settle on their own and are dropped. */
395
+ private closeCombine;
273
396
  private combine;
274
397
  private combineBatches;
275
398
  /** The chunk read: the plan every combine shares, then each key's result, cut to the range at its edges. */
276
399
  private combineChunks;
277
- /** An edge chunk's bitmap cut to the window, as a new bitmap: the remainders `edgeIds` would yield. */
278
- private cutToWindow;
279
400
  /**
280
401
  * The AND (or OR) of one chunk key across all operands, minus the excludes that hold it, or `null` if empty.
281
402
  * Operand chunks are fetched **in parallel** (the spec's parallel byte-range reads).
@@ -287,8 +408,64 @@ export declare class SegmentEngine {
287
408
  */
288
409
  private combineChunk;
289
410
  /**
290
- * One operand's chunk at `key`: from its stream when it has one, else a read of that chunk on its own. Not `async`,
291
- * so a stream's take is made before anything is awaited: concurrent takes of one stream must be made in key order.
411
+ * Read `exclude` in batches, when the source can read ranges and the exclude has a generation; one that names the same
412
+ * segment and version as an exclude already opened shares its batch. Nothing is requested until a key asks.
413
+ */
414
+ private openBatch;
415
+ /**
416
+ * The chunk `exclude` holds at `chunkKey`, for a key whose AND is not empty. One the cache holds, or another read has
417
+ * in flight, is read as a read of that chunk alone is (cache lookup and version check, or joining the read); only a
418
+ * chunk that would be requested waits for the batch.
419
+ */
420
+ private excludeChunk;
421
+ /**
422
+ * Ask `batch` for the exclude chunk at `key`. A batch is read when it holds half the combine's concurrency in keys; when
423
+ * the asking has gone quiet for {@link BATCH_QUIET_HOPS} turns of the microtask queue (checked as each key asks and as
424
+ * the caller asks for each slot) and the caller is waiting on a slot, or has come within half a
425
+ * window of the oldest key held, so a held key is read at least that far ahead of it; and when every key of the combine
426
+ * has been started (none is left that could still ask).
427
+ * Refused once the combine has stopped or planned again, so a key it was still working on sends nothing more.
428
+ */
429
+ private askBatch;
430
+ /** Start the wait of `batch` for its asking to go quiet, unless it is running. */
431
+ private settleSoon;
432
+ /**
433
+ * Wait until no key has asked for {@link BATCH_QUIET_HOPS} turns of the microtask queue. If the caller is waiting on a
434
+ * slot, or is within half a window of the oldest key held, or every key of the combine has been started (so no more
435
+ * can ask), the keys held are read; otherwise they are held for the keys the combine has yet to start, until half a
436
+ * window of them is held or the caller comes near. The wait is of microtask turns, so it always ends, and the loop is left rather than spun while
437
+ * keys are held.
438
+ */
439
+ private settleBatch;
440
+ /** Read the keys `batch` holds now. */
441
+ private flushBatch;
442
+ /**
443
+ * Read the keys that asked: one whose chunk is already being read (by another read, or by an earlier key of this batch
444
+ * that names it again) joins that read, and the rest are one stream of coalesced ranges over those keys alone, so a
445
+ * request is sent for each range of them and never for a chunk nobody asked for. The stream is a
446
+ * {@link StreamedChunks} like an include's, so a chunk is cached under the version it was read from, counted as a cache
447
+ * hit or miss once, checked against the segment's version when it comes from the cache, and marks `moves` when it
448
+ * shows the segment moved. Each key it reads is registered as the open read of its chunk, so a read of that chunk by
449
+ * anything else joins it. Every ask is answered, with its own key's chunk or the error its read raised.
450
+ */
451
+ private readBatch;
452
+ /**
453
+ * Register the read of the chunk `ref` that a batch's stream makes as its open read, so a read of it by anything else
454
+ * joins it, until it settles. A joiner does not share the stream's failure: a stream ends at its first failed range
455
+ * and fails every take after it, those of keys whose own ranges did not fail included, so when the read fails the
456
+ * entry goes and a joiner reads the chunk on its own, one request for each joined key that failed.
457
+ */
458
+ private registerRead;
459
+ /**
460
+ * Stop `batch`: the keys still waiting are refused. The streams of the batches in flight are not closed: every range of
461
+ * one was requested when it was read, a read of one of its chunks by another call may be joined to it, and each closes
462
+ * itself once its last key is answered.
463
+ */
464
+ private closeBatch;
465
+ /**
466
+ * One operand's chunk at `key`: from its stream when it has one, else a read of that chunk on its own, whose cached
467
+ * chunk marks `moves` when it is no longer the operand's version. Not `async`, so a stream's take is made before
468
+ * anything is awaited: concurrent takes of one stream must be made in key order.
292
469
  */
293
470
  private operandChunk;
294
471
  /**
@@ -302,6 +479,10 @@ export declare class SegmentEngine {
302
479
  * not hold. Each later key is counted as a cache hit or miss here, so a key is counted once whichever way it is served.
303
480
  */
304
481
  private startStream;
482
+ /**
483
+ * Stop the stream of `streamed`. Nothing else is kept for an open stream, so a read whose caller drops it without
484
+ * closing it leaves nothing behind in the engine.
485
+ */
305
486
  private closeStreamed;
306
487
  /**
307
488
  * The decoded chunk at `chunkKey` of a streamed read: from the cache while the stream is unopened and the cache holds
@@ -321,9 +502,10 @@ export declare class SegmentEngine {
321
502
  * stream does for the chunks it delivers; a chunk served from the cache is checked here, or a read whose chunks are
322
503
  * all cached would go on serving the generation it planned under, an erased id included, for as long as it is pulled.
323
504
  * 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.
505
+ * emit the `cache` event for this lookup, which a key counted when its stream opened does not. `moves`: where the read
506
+ * this chunk belongs to learns of a move, so it plans its keys again. `streamed`: the read's stream, which a move
507
+ * carries to the version now current, so the rest of it looks there: a read whose stream is not yet open reads the
508
+ * rest as one stream, not a chunk at a time.
327
509
  */
328
510
  private cachedIfCurrent;
329
511
  /** The cache key of a chunk: by the version it was read under, or by segment and key alone for a source with none. */
@@ -348,11 +530,20 @@ export declare class SegmentEngine {
348
530
  * chunks — a segment with data is self-evidently registered — so the check is a registry read exactly when
349
531
  * a combine was about to do something suspicious, and no reads at all otherwise. A source that cannot answer
350
532
  * `exists` (it has no notion of registration) skips the check rather than guessing. An operand is judged by
351
- * all its chunks, not those inside a range: one with nothing in range but data elsewhere is not suspicious.
533
+ * all its chunks, not those inside a range: one with nothing in range but data elsewhere is not suspicious. At most
534
+ * {@link OPERANDS_AT_ONCE} are asked after at once.
352
535
  */
353
536
  private refuseAbsentOperands;
354
537
  /**
355
- * Drop every piece of state this engine derived from `ref`, and tell the Storage source to do the same.
538
+ * Forget what was derived from `ref`: drop its decoded chunks from the chunk cache, which every engine sharing this
539
+ * one's invalidations reads through, record the invalidation where they all see it, and tell this engine's Storage
540
+ * source to forget the segment too. An engine sharing the invalidations over another source instance does not tell
541
+ * its own source: engines that read one source, as the facade's do, need only one of them told. A read of the
542
+ * segment already running on any of them caches nothing more of what it began reading before the call, whether a
543
+ * request already in flight or a stream already open; a stream that opens after the call, or a request sent after
544
+ * it, reads afresh and, on a source that names versions (`currentVersion`), caches what it reads. On one that does
545
+ * not, a read that began before the call caches nothing from a stream it opens after it, unless the call landed while
546
+ * the read listed the segment's keys: the read then lists them again, and caches as a read begun after the call.
356
547
  *
357
548
  * The decoded-chunk cache is keyed by generation, which handles a *publish* (the new generation misses) but
358
549
  * not a *destruction*: a segment that was erased from, dropped, shredded or retired has no newer generation
@@ -394,8 +585,9 @@ export declare class SegmentEngine {
394
585
  * one): callers read it or clone it, never mutate it.
395
586
  *
396
587
  * `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.
588
+ * segment's version ({@link cachedIfCurrent}), and one that is not marks `moves`. A point read resolves `gen` just
589
+ * before it asks, and passes none. `report`: emit the `cache` event for this lookup; a key already counted for this
590
+ * read does not.
399
591
  */
400
592
  private storageChunk;
401
593
  /**