@cloudbitmaps/core 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +85 -0
  2. package/dist/{chunk-YSC7NJWV.js → chunk-IGLIZBR7.js} +740 -137
  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 +12 -2
  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 +12 -9
  16. package/dist/core/concurrency.d.ts +8 -0
  17. package/dist/core/consistency.d.ts +69 -17
  18. package/dist/core/crbm/fingerprint.d.ts +5 -0
  19. package/dist/core/crbm/format.d.ts +0 -1
  20. package/dist/core/crbm/plan-reads.d.ts +9 -3
  21. package/dist/core/crbm/reader.d.ts +52 -6
  22. package/dist/core/crbm/varint.d.ts +2 -1
  23. package/dist/core/crbm/writer.d.ts +5 -1
  24. package/dist/core/crbm-open.d.ts +154 -0
  25. package/dist/core/crbm-storage-source.d.ts +65 -452
  26. package/dist/core/crbm-write.d.ts +75 -0
  27. package/dist/core/crypto.d.ts +6 -1
  28. package/dist/core/engine.d.ts +222 -33
  29. package/dist/core/erase-id.d.ts +42 -7
  30. package/dist/core/erasure.d.ts +4 -3
  31. package/dist/core/errors.d.ts +8 -2
  32. package/dist/core/generation-gc.d.ts +129 -15
  33. package/dist/core/generation-list.d.ts +11 -0
  34. package/dist/core/id-window.d.ts +46 -0
  35. package/dist/core/invalidation-scope.d.ts +77 -0
  36. package/dist/core/known-generations-shape.d.ts +13 -0
  37. package/dist/core/known-generations.d.ts +147 -0
  38. package/dist/core/leases.d.ts +4 -3
  39. package/dist/core/ledger-seed.d.ts +17 -0
  40. package/dist/core/load.d.ts +17 -38
  41. package/dist/core/lru.d.ts +2 -15
  42. package/dist/core/metrics.d.ts +4 -2
  43. package/dist/core/order.d.ts +2 -0
  44. package/dist/core/pinned-storage-source.d.ts +4 -4
  45. package/dist/core/ports.d.ts +89 -16
  46. package/dist/core/publish.d.ts +207 -0
  47. package/dist/core/registry-scan.d.ts +0 -2
  48. package/dist/core/retention-sweep.d.ts +69 -65
  49. package/dist/core/retention.d.ts +1 -1
  50. package/dist/core/retry.d.ts +12 -19
  51. package/dist/core/rollback.d.ts +60 -31
  52. package/dist/core/summary.d.ts +50 -18
  53. package/dist/core/token.d.ts +2 -2
  54. package/dist/core/validate.d.ts +2 -0
  55. package/dist/driver-kit.d.ts +4 -0
  56. package/dist/driver-kit.js +80 -7
  57. package/dist/driver-kit.js.map +3 -3
  58. package/dist/drivers/_shared/bytes.d.ts +6 -0
  59. package/dist/drivers/_shared/faults.d.ts +15 -0
  60. package/dist/drivers/_shared/object-keys.d.ts +18 -0
  61. package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
  62. package/dist/drivers/_shared/object-registry.d.ts +2 -0
  63. package/dist/drivers/_shared/options.d.ts +19 -0
  64. package/dist/drivers/_shared/registry.d.ts +13 -10
  65. package/dist/drivers/localfs/exact-case.d.ts +0 -3
  66. package/dist/drivers/localfs/fs-util.d.ts +22 -0
  67. package/dist/drivers/localfs/registry.d.ts +10 -4
  68. package/dist/drivers/localfs/storage.d.ts +9 -2
  69. package/dist/drivers/memory.d.ts +7 -4
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +3647 -2410
  72. package/dist/index.js.map +4 -4
  73. package/package.json +1 -1
  74. package/dist/chunk-YSC7NJWV.js.map +0 -7
  75. package/dist/core/kept-generations.d.ts +0 -40
@@ -1,9 +1,84 @@
1
- import type { GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, SegmentRef, Token } from './ports.js';
1
+ import { NotFoundError } from './errors.js';
2
+ import type { GenKey, IStorageDriver, IRegistryDriver, KnownGeneration, RegistryRecord, SegmentRef, Token } from './ports.js';
2
3
  /** The two ports generation bookkeeping needs: the objects, and the pointer that says which one is current. */
3
- export interface GenerationDeps {
4
+ interface GenerationDeps {
4
5
  readonly storage: IStorageDriver;
5
6
  readonly registry: IRegistryDriver;
7
+ /** What a delete loop waits on before it reads the row again ({@link watchDeletes}). Absent, it does not wait. */
8
+ readonly clock?: {
9
+ readonly sleep?: (ms: number) => Promise<void>;
10
+ };
6
11
  }
12
+ /**
13
+ * The waits, in milliseconds, before each further read of the row when a delete loop finds the pointer on a generation
14
+ * it deleted, with no object under it ({@link watchDeletes}). A rollback that moved the pointer there checks the bucket
15
+ * after its move, and when the check runs after the delete it finds the object gone and puts the pointer back: one
16
+ * listing of the segment's objects (one page while the segment holds under a thousand) and one compare-and-swap, two
17
+ * round trips, three when a lease write lands first. A rollback client farther from the store than the deleting
18
+ * client takes that much longer, so the loop reads the row once at once and, with a clock that can wait, three more
19
+ * times over 1.75 s in all, which covers clients a few hundred milliseconds away. Paid only when a read finds the
20
+ * pointer on a missing generation the loop deleted, never on a pass where nothing raced.
21
+ */
22
+ export declare const DELETE_SETTLE_WAITS_MS: readonly number[];
23
+ /** What a delete loop has deleted, held to every read of the segment's row it makes after a delete. */
24
+ interface DeleteWatch {
25
+ /** Note a delete that landed. */
26
+ deleted(generation: number): void;
27
+ /** Every generation noted so far. */
28
+ readonly gone: ReadonlySet<number>;
29
+ /**
30
+ * `row`, a read of the row made after `held`, the row a delete was decided on, held to what was deleted: the deleted
31
+ * generation it leaves the pointer on, with no object under it once the row is read again, or `undefined`.
32
+ */
33
+ check(held: RegistryRecord, row: RegistryRecord | null): Promise<number | undefined>;
34
+ /** The same for one more read of the row, made only when a delete landed since the last {@link check}. */
35
+ settle(held: RegistryRecord): Promise<number | undefined>;
36
+ }
37
+ /**
38
+ * Hold a delete loop's reads of the row to what it deleted.
39
+ *
40
+ * Every loop that deletes generations reads the row before each delete, and one round trip remains between that read
41
+ * and the delete. A rollback that lands inside it onto the generation being deleted passes its own check (a listing made
42
+ * before the delete) and leaves the pointer on an object that is gone. Nothing can put the object back, so the loop
43
+ * detects it instead: every read of the row it makes after a delete, and one more after its last delete when it made no
44
+ * read since, is held to what it deleted.
45
+ *
46
+ * A read names a deleted generation when its row is active, of the incarnation `held` was read from, and its pointer
47
+ * names one. A tombstone never does, since no rollback moves a `destroyed` row's pointer, and neither does a row purged
48
+ * and made again, whose numbers are its own. Then:
49
+ *
50
+ * - The bucket is asked, with one zero-byte read, a metadata request on every shipped driver. A number can be taken
51
+ * again once its object is deleted (invariant 2): after a rollback below it, a load can number it afresh and
52
+ * publish, and then the object there is that load's, and nothing is missing. Where the storage driver ignores a
53
+ * delete's condition, the loop's own delete can take that object too, and the read finds it missing, which it is.
54
+ * - A missing object is not yet the answer. A rollback whose own check ran after the delete finds the object gone and
55
+ * puts the pointer back, and the loop's read can fall between its move and that undo. So the row is read again, at
56
+ * once and, with a clock that can wait, after each of {@link DELETE_SETTLE_WAITS_MS}, each read judged as the first
57
+ * was, the bucket asked again; the answer is the generation the last of them names, only if every one names a
58
+ * deleted generation with no object under it.
59
+ *
60
+ * A read of the bucket that fails in any other way than "not found" throws: it says nothing about the object.
61
+ */
62
+ export declare function watchDeletes(deps: GenerationDeps, ref: SegmentRef): DeleteWatch;
63
+ /** Who deleted the generation a {@link pointerOnDeletedError} names, which leads its message. */
64
+ type Deleter = {
65
+ readonly kind: 'collection';
66
+ } | {
67
+ readonly kind: 'erasure';
68
+ } | {
69
+ readonly kind: 'load';
70
+ readonly published: number;
71
+ } | {
72
+ readonly kind: 'refused-load';
73
+ readonly refusal: string;
74
+ };
75
+ /**
76
+ * The error for a pointer a delete loop found on a generation it deleted ({@link watchDeletes}). The writer that moved
77
+ * the pointer there is a rollback, or, where the storage driver ignores a delete's condition, a load that took the
78
+ * number again and published. A rollback slower than the loop's further reads can still put the pointer back after
79
+ * them, so the message says to look before acting.
80
+ */
81
+ export declare function pointerOnDeletedError(ref: SegmentRef, deleter: Deleter, generation: number): NotFoundError;
7
82
  /**
8
83
  * The generation number a writer should use for the segment's **next** object: one above the highest generation
9
84
  * the registry points at *or* that is present in Storage — whichever is higher. The erasure rewrite numbers this
@@ -20,7 +95,7 @@ export interface GenerationDeps {
20
95
  */
21
96
  export declare function nextGeneration(ref: SegmentRef, deps: GenerationDeps): Promise<number>;
22
97
  /** The number a load takes, and whether one existence check proved it free (so nothing is above the pointer). */
23
- export interface LoadNumber {
98
+ interface LoadNumber {
24
99
  readonly generation: number;
25
100
  /**
26
101
  * `true` when the number is `currentGen + 1` and the check found no object holding it. Collection by name rests
@@ -28,6 +103,8 @@ export interface LoadNumber {
28
103
  * the listing would have kept is a name the pass takes. `false` when the listing numbered the load.
29
104
  */
30
105
  readonly checked: boolean;
106
+ /** The generations that listing found, when the load listed: a seed of the ledger reads them instead of listing again. */
107
+ readonly listed?: readonly number[];
31
108
  }
32
109
  /**
33
110
  * The generation number a load takes, from the row it has already read: `currentGen + 1` (0 with no row or no
@@ -73,6 +150,11 @@ export declare const LIST_COLLECTION_CADENCE = 16;
73
150
  * a pin taken while it was still active re-checks the status on every open — and nothing else in the library
74
151
  * would ever collect them, so without this those objects are billed forever.
75
152
  *
153
+ * **Throws {@link NotFoundError} when a rollback moved the pointer onto a generation it deleted**: the rollback landed
154
+ * between the pass's re-proof of the row and that delete, so the pointer names an object that is gone. The pass finds
155
+ * it at its next re-proof, or, after its last delete, by reading the row once more. A deleted object cannot be
156
+ * restored: the answer is a rollback to a generation still in the bucket.
157
+ *
76
158
  * **Throws {@link WriteConflictError} when it cannot act on the row it read**: the row is gone when it is re-read,
77
159
  * after the listing or before a delete; on an active segment, the pointer has fallen below the cutoff before a
78
160
  * delete; on a `destroyed` segment, the row's token has changed. Not to be confused with the empty array it still
@@ -97,33 +179,52 @@ export declare const LIST_COLLECTION_CADENCE = 16;
97
179
  export declare function gcOrphanGenerations(ref: SegmentRef, deps: GenerationDeps, options?: {
98
180
  keep?: number;
99
181
  }): Promise<number[]>;
182
+ /**
183
+ * {@link gcOrphanGenerations}, reporting a rollback onto a generation it deleted in `missing` instead of throwing it: the
184
+ * pass stopped there. For a caller that records what it deleted before it raises, as an erasure does. With `skipKnown` the
185
+ * pass also spares every generation the row names when it reads the row before a delete (an erasure's passes on an active
186
+ * row, which remove from the row what they mean to delete first).
187
+ */
188
+ export declare function collectBelowPointer(ref: SegmentRef, deps: GenerationDeps, options?: {
189
+ keep?: number;
190
+ skipKnown?: boolean;
191
+ }): Promise<{
192
+ readonly deleted: number[];
193
+ readonly missing?: number;
194
+ readonly knownSpared?: number[];
195
+ }>;
100
196
  /**
101
197
  * What makes a collection spare leased generations: the clock that says whether a lease has ended. Only a load's
102
198
  * collection passes one, and a load with no clock passes none: it cannot tell a live lease from an ended one, so it
103
199
  * reads none. A pass without a guard never reads the row's leases, which is what keeps erasure, shred and drop deleting
104
200
  * what a lease names.
105
201
  */
106
- export interface LeaseGuard {
202
+ interface LeaseGuard {
107
203
  readonly now: () => number;
108
204
  }
109
205
  /** What a load's publish recorded in the row, as `publishGeneration` reports it. */
110
206
  export interface PublishedKept {
111
- /** The list the publish wrote, or `undefined` when it wrote none (the row then does not know which are kept). */
207
+ /** The window the publish recorded, as numbers, or `undefined` when it recorded none (the row then does not know which are kept). */
112
208
  readonly list: readonly number[] | undefined;
209
+ /** The ledger the publish wrote, in the order the generations were made current, when it wrote one. */
210
+ readonly known?: readonly KnownGeneration[];
113
211
  /** The names the publish pushed out of the window, ascending. */
114
212
  readonly evict: readonly number[];
115
213
  /** The token of the row the publish wrote, when it is known: a write settled by reading the row has none. */
116
214
  readonly token: Token | undefined;
117
215
  }
118
216
  /**
119
- * Collect after a load's publish. The row records the generations the load keeps, so the pass deletes by name what the
120
- * publish pushed out of that window ({@link deleteEvicted}) and lists the segment ({@link gcOrphanGenerations}'s
217
+ * Collect after a load's publish. The row's ledger records the generations the load keeps, so the pass deletes by name
218
+ * what the publish pushed out of that window ({@link deleteEvicted}) and lists the segment ({@link gcOrphanGenerations}'s
121
219
  * pass) only when it cannot rely on the row or must catch what a name does not:
122
220
  *
123
- * - the row recorded no list (a `keep` above {@link MAX_KEPT_GENERATIONS}, a row an earlier schema wrote, one a
124
- * rollback or an erasure moved, or a publish whose write was settled by reading the row): the pass keeps the
125
- * newest `keep` generations present below the pointer, and records them with one compare-and-swap, fenced on the
126
- * publish's own token, so the next load collects by name;
221
+ * - the publish recorded no window to collect by name (a `keep` above {@link MAX_KEPT_GENERATIONS}, a publish that
222
+ * seeded its ledger from the bucket, a row with no usable ledger, or a publish whose write was settled by reading the
223
+ * row): the pass keeps `keep` of the generations present below the pointer, the ledger's own most recently current
224
+ * names first and then the newest others by number, or, over a row with no usable ledger, `previous` first and then
225
+ * the newest by number ({@link keptWindow}), and writes nothing: the publish already wrote the ledger, which is what
226
+ * the next load derives its window from. It spares every name the row knows when it re-reads the row before a
227
+ * delete, which a publish or a rollback that landed since can have added to;
127
228
  * - the guard found the current generation's object gone, or, for a `keep` of 1 or more when the guard took the size
128
229
  * from the row's summary and opened nothing, one zero-byte read of it does not find it (`proveCurrent`): the
129
230
  * window then counts a generation that is not there, so the same pass keeps what is;
@@ -135,10 +236,13 @@ export interface PublishedKept {
135
236
  * below it. A load onto a `destroyed` row is refused at its publish and never reaches here; a drop that lands after
136
237
  * the publish leaves this pass collecting the tombstone's generations, and the drop's own sweep takes the rest.
137
238
  *
138
- * The seed's compare-and-swap moves the row's token, so a derived writer in flight on the row (an erasure rewrite)
139
- * meets a lost fence and re-derives: it happens once per row that recorded no list. A `WriteConflictError` from it is
140
- * a lost race, and the load took effect, so it is not raised; the next load records the list. Any other fault is
141
- * raised, as every fault of a collection after a landed publish is.
239
+ * Any fault of the collection is raised, as every fault of a collection after a landed publish is.
240
+ *
241
+ * So is a rollback onto a generation the pass deleted, which the pass finds by holding every read of the row it makes
242
+ * after a delete to what it deleted, and by reading the row once more after its last delete
243
+ * ({@link watchDeletes}): it throws {@link NotFoundError}, saying the load took effect at `generation` and the
244
+ * generation the pointer names is missing. That read is the one request this adds to a load whose pass deleted
245
+ * anything.
142
246
  */
143
247
  export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps, options: {
144
248
  generation: number;
@@ -156,6 +260,11 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
156
260
  proveCurrent?: GenKey;
157
261
  /** Spare the generations the row's live leases hold. Absent, the pass ignores leases. */
158
262
  leases?: LeaseGuard;
263
+ /**
264
+ * The generation the row named as current when the load read it, which its publish superseded. A window kept by
265
+ * listing holds it first, while the pointer is still the load's. Absent: the load found no pointer.
266
+ */
267
+ previous?: number;
159
268
  }): Promise<number[]>;
160
269
  /**
161
270
  * Delete, with no listing, the generations a publish of `generation` pushed out of its row's window, `evict`, taken
@@ -171,9 +280,14 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
171
280
  * from taking a generation that a later publish, after a rollback, put in the window. A pointer that has moved above
172
281
  * `generation` does not stop it: a publish landing in the meantime leaves routine collection working. A fault, a
173
282
  * registry read or a delete that throws, propagates, with the pointer at `generation`.
283
+ *
284
+ * A rollback onto a name it deleted is not a stop: every read of the row after a delete is held to what it deleted, and
285
+ * so is one more read after its last delete when it made none since ({@link watchDeletes}). One that names a
286
+ * deleted generation throws {@link NotFoundError}, saying the load took effect at `generation`.
174
287
  */
175
288
  export declare function deleteEvicted(ref: SegmentRef, deps: GenerationDeps, options: {
176
289
  generation: number;
177
290
  evict: readonly number[];
178
291
  leases?: LeaseGuard;
179
292
  }): Promise<number[]>;
293
+ export {};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The generations a segment's bucket holds.
3
+ */
4
+ import type { IStorageDriver, SegmentRef } from './ports.js';
5
+ /**
6
+ * The generations the bucket holds for `ref`, from one listing, each once and in ascending order. A listing that spans
7
+ * a purge and a re-create can yield one number twice (the objects are re-created under the numbers just swept), so the
8
+ * duplicates are dropped here rather than counted by each caller. A caller that stops at the first object it wants
9
+ * reads the listing itself.
10
+ */
11
+ export declare function generationsOf(storage: IStorageDriver, ref: SegmentRef): Promise<number[]>;
@@ -0,0 +1,46 @@
1
+ import type { CodecBitmap, CodecInterface } from './codec.js';
2
+ /** A validated range: inclusive id bounds, as the chunk key and the remainder at each end. */
3
+ export interface IdWindow {
4
+ readonly loKey: number;
5
+ readonly loRem: number;
6
+ readonly hiKey: number;
7
+ readonly hiRem: number;
8
+ }
9
+ /** A range as a read is asked for it: each bound optional. */
10
+ interface IdBounds {
11
+ readonly after?: number | undefined;
12
+ readonly through?: number | undefined;
13
+ }
14
+ /**
15
+ * Throws {@link ValidationError} unless `bound` is left out or is an id: an integer from 0 to 4294967295. The message
16
+ * says what is wrong and never carries the value, which is an id.
17
+ */
18
+ export declare function assertIdBound(name: 'after' | 'through', bound: number | undefined): void;
19
+ /**
20
+ * Validate a read's range. `null` when none was asked for, so the read does no extra work; `'empty'` when no id can be
21
+ * in it (`after >= through`, which is a cursor at the end of its window, not an error).
22
+ */
23
+ export declare function windowOf(range: IdBounds | undefined): IdWindow | 'empty' | null;
24
+ /** The window a range with neither bound set describes: every id. */
25
+ export declare const WHOLE_ID_SPACE: IdWindow;
26
+ /**
27
+ * Whether the window cuts inside `chunkKey`: one of its two edge chunks, and only where the bound falls inside the chunk
28
+ * rather than on its boundary. A chunk the window covers whole is read as it is.
29
+ */
30
+ export declare const isEdge: (chunkKey: number, w: IdWindow) => boolean;
31
+ /** The ids of an edge chunk that lie inside the window. The chunk iterates ascending, so it stops at the top. */
32
+ export declare function edgeIds(chunk: CodecBitmap, chunkKey: number, w: IdWindow): Generator<number>;
33
+ /** The ascending `keys` inside the window's chunk span, as a new array: two binary searches, no scan. */
34
+ export declare function keysWithin(keys: readonly number[], w: IdWindow): number[];
35
+ /**
36
+ * The ascending `keys` inside the window's chunk span (every key, for `null`) and above the key `after`: what a read
37
+ * that plans again after taking `after` still reads.
38
+ */
39
+ export declare function keysAbove(keys: readonly number[], w: IdWindow | null, after: number): number[];
40
+ /** The index of the first element of ascending `a` greater than `value`: a binary search. */
41
+ export declare function firstAbove(a: Uint32Array, value: number): number;
42
+ /** The part of an edge chunk's ascending remainders `rem` inside the window, as a view of `rem`. */
43
+ export declare function remaindersWithin(rem: Uint32Array, chunkKey: number, w: IdWindow): Uint32Array;
44
+ /** An edge chunk cut to the window, as a new bitmap: the remainders {@link edgeIds} would yield. */
45
+ export declare function cutToWindow(codec: CodecInterface, chunk: CodecBitmap, chunkKey: number, w: IdWindow): CodecBitmap;
46
+ export {};
@@ -0,0 +1,77 @@
1
+ /**
2
+ * What the engines reading through one chunk cache share about invalidations: how many there have been, the segment
3
+ * each of the latest was of, and the chunk reads open right now. A store's own engine, each pin's and each combine's
4
+ * with a pinned operand read through one cache, and an invalidation through any of them must reach a read running on
5
+ * another: a read that had its bytes before the invalidation would otherwise write them back into the cache it
6
+ * shares, and later reads would be answered from them, an erased id included, until the cache evicted them.
7
+ *
8
+ * ```
9
+ * engine (store) ──┐
10
+ * engine (pin) ──┼── one InvalidationScope ── epoch · latest invalidation of each segment · open reads
11
+ * engine (combine)─┘ └── one chunk cache
12
+ * ```
13
+ *
14
+ * A stream records the epoch it opened at and asks {@link InvalidationRecord.invalidatedSince} before it caches a
15
+ * chunk, so the scope holds no reference to a stream: a read its caller drops without closing holds nothing here. The
16
+ * `.crbm` source keeps an {@link InvalidationRecord} of its own, by segment, which a pinned stream asks before it hands
17
+ * out a chunk, so only its own segment's invalidation makes it open its object again.
18
+ *
19
+ * Internal to core: not exported from any entry point.
20
+ */
21
+ import type { CodecBitmap } from './codec.js';
22
+ /**
23
+ * How many segments' latest invalidation a record remembers. Past it the oldest is forgotten, and a segment the record
24
+ * does not remember, never-invalidated ones included, counts as invalidated at the newest invalidation it has
25
+ * forgotten, which is always on the safe side. In the engines' scope it means a stream that opened before then caches
26
+ * no more of that segment's chunks, which costs it cache entries, never correctness: so a sweep that invalidates more
27
+ * than this many segments at once (a `retireExpired` or an `eraseSubject` over a large fleet) stops every stream open
28
+ * at the time caching, whatever segment it reads. In the `.crbm` source it means a pinned stream looks its pin up
29
+ * again, which costs a request only where the reader cache has let the pin's reader go. With names at their
30
+ * 256-character limit a record is about 0.6 MB, and about 0.1 MB with 30-character names; a store keeps two.
31
+ */
32
+ export declare const REMEMBERED_INVALIDATIONS = 1024;
33
+ /** A chunk read in flight: `token` tells the read whether its entry is still the registered one. */
34
+ export interface OpenRead {
35
+ readonly token: object;
36
+ readonly read: Promise<CodecBitmap | null>;
37
+ }
38
+ /**
39
+ * How many invalidations there have been, and the epoch of each of the latest {@link REMEMBERED_INVALIDATIONS}
40
+ * segments' latest one, by whatever key names a segment to its owner: a cache-key prefix in the engines' scope, a
41
+ * segment key in the `.crbm` source.
42
+ */
43
+ export declare class InvalidationRecord {
44
+ private count;
45
+ /** The epoch of each remembered segment's latest invalidation, the oldest first. */
46
+ private readonly latest;
47
+ /** The epoch of the newest invalidation the record has forgotten: what a segment it does not remember counts as. */
48
+ private floor;
49
+ /** How many invalidations there have been: a read compares it with the epoch it began under. */
50
+ get epoch(): number;
51
+ /** Record an invalidation of the segment `key` names. */
52
+ invalidate(key: string): void;
53
+ /** Whether the segment `key` names has been invalidated since epoch `since`. */
54
+ invalidatedSince(key: string, since: number): boolean;
55
+ }
56
+ /** The engines' record, by cache-key prefix, and the chunk reads open on all of them. */
57
+ export declare class InvalidationScope extends InvalidationRecord {
58
+ /**
59
+ * The chunk reads open right now, by cache key: a caller that misses the cache and finds its key here awaits that
60
+ * read instead of making its own. An entry lives from the request until it settles, so the map holds at most one
61
+ * promise per distinct key in flight. An invalidation drops the segment's entries, and a read whose entry is gone
62
+ * caches nothing.
63
+ */
64
+ private readonly openReads;
65
+ /** Record an invalidation of the segment whose cache keys start with `prefix`, and drop its open reads. */
66
+ invalidate(prefix: string): void;
67
+ /** The read of `key` open now, for a caller that misses the cache to await instead of making its own. */
68
+ openRead(key: string): Promise<CodecBitmap | null> | undefined;
69
+ /** Whether a read of `key` is open, without joining it. */
70
+ hasOpenRead(key: string): boolean;
71
+ /** Register `entry` as the open read of `key`. */
72
+ beginRead(key: string, entry: OpenRead): void;
73
+ /** `entry` has settled: forget it, unless an invalidation dropped it and a newer read has taken the key. */
74
+ endRead(key: string, entry: OpenRead): void;
75
+ /** Whether the read `token` names is still the open read of `key`: one an invalidation dropped caches nothing. */
76
+ isOpen(key: string, token: object): boolean;
77
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The shape of a row's known-generations ledger (`knownGens`), in one place. A registry holds a row to it when it writes
3
+ * and when it reads back, and `checkConsistency` holds a row to it before it reads or walks a ledger a registry of your
4
+ * own handed back, so the two cannot part on what a ledger is. Pure: no I/O, time or randomness.
5
+ */
6
+ /**
7
+ * Why `value` is not a ledger, or `undefined` when it is one (an absent ledger, `undefined`, passes: whether a row
8
+ * without one is usable is another rule). A ledger is an array of at most `max` entries, each exactly `{ generation,
9
+ * footerCrc }`, the generation a non-negative safe integer and unique, the checksum an integer from 0 to 2^32 - 1. The
10
+ * order is not checked: it is not derivable from the numbers. `show` renders a value for the description, which a
11
+ * caller can truncate.
12
+ */
13
+ export declare function knownGensShapeFault(value: unknown, max: number, show?: (v: unknown) => string): string | undefined;
@@ -0,0 +1,147 @@
1
+ import type { KnownGeneration, RegistryRecord } from './ports.js';
2
+ /**
3
+ * The most generations a load that collects by name records as its window. A `keep` above it collects by listing.
4
+ */
5
+ export declare const MAX_KEPT_GENERATIONS = 64;
6
+ /**
7
+ * A writer keeps entries that nothing protects and no lease holds only while the ledger holds fewer than this many.
8
+ */
9
+ export declare const KNOWN_FILL = 128;
10
+ /** The most entries a writer writes, and the most the shared validator accepts at a write. */
11
+ export declare const MAX_KNOWN_GENERATIONS = 192;
12
+ /** The most entries a stored row may hold: twice what a writer writes. */
13
+ export declare const MAX_STORED_KNOWN_GENERATIONS = 384;
14
+ /**
15
+ * The row's ledger when a collection or a rollback may rely on it: present and not empty, on an active row with a
16
+ * pointer, its last entry the pointer, and, where the row's summary is in the clear and describes that generation, its
17
+ * checksum the one the summary's fingerprint carries. A well-formed ledger that disagrees with its row is read but not
18
+ * used (the row counts as one with no ledger), as a summary that names another generation is.
19
+ */
20
+ export declare function usableKnownGens(record: RegistryRecord | null): readonly KnownGeneration[] | undefined;
21
+ /**
22
+ * The generations the row keeps as its window: the numbers of the usable ledger's entries below the pointer, in the order
23
+ * they were made current, or `undefined` when the ledger is not usable. A collection compares against these. An entry
24
+ * numbered above the pointer is one a rollback left; no collection can take it, so it is no part of the window.
25
+ */
26
+ export declare function windowOf(record: RegistryRecord | null): readonly number[] | undefined;
27
+ /**
28
+ * Whether the row's ledger is a window a load with a small `keep` can extend: usable, and naming no more than
29
+ * {@link MAX_KEPT_GENERATIONS} generations below the pointer that no live lease holds. A ledger carried through
30
+ * publishes with a larger `keep` (`*Into`, `materializeMany`) names up to 127, and a count by a small `keep` would keep
31
+ * fewer than it names, so the next small-`keep` load collects by listing instead of by name, over the same ledger. A
32
+ * generation held only for a lease is no part of the window and does not count toward the limit.
33
+ */
34
+ export declare function carriesWindow(record: RegistryRecord | null, leased: ReadonlySet<number>): boolean;
35
+ /**
36
+ * How many generations below its own a publish vouches for at most: what the writer cap leaves beside the new
37
+ * generation and the 64 generations live leases can hold.
38
+ */
39
+ export declare const KNOWN_SPAN = 127;
40
+ /** Which generations below a new pointer a grace window holds, and which it lets go: both ascending. */
41
+ interface KeptWindow {
42
+ readonly kept: number[];
43
+ readonly evict: number[];
44
+ }
45
+ /**
46
+ * The generations below a new pointer that a grace window of `keep` holds, out of `below`, when no ledger says which
47
+ * were made current most recently: the generation the publish superseded first, when it is among them, then the newest
48
+ * of the rest by number. When it is not among them (no generation was current, or its object has gone from the bucket),
49
+ * the newest `keep`.
50
+ *
51
+ * The superseded generation is the one reads were on until the publish, and, after a rollback, the one the operator
52
+ * restored, which a pin taken since reads, and the one to roll back to if the new load is bad too. The generations a
53
+ * rollback moved away from are newer by number, so a window by number alone would keep those and delete it. The window
54
+ * a listing keeps over a row with no usable ledger comes from here, and so does a ledger's first window
55
+ * (`ledger-seed.ts`); once the row has a ledger, its order says which were made current most recently.
56
+ */
57
+ export declare function keptWindow(below: readonly number[], superseded: number | undefined, keep: number): KeptWindow;
58
+ /**
59
+ * `known`, held to the cap, in the same order. It always keeps the entries numbered in `protect`, then the ones numbered
60
+ * in `leased`, most recently current first, up to {@link MAX_KNOWN_GENERATIONS}, then the rest, most recently current
61
+ * first, while the ledger holds fewer than {@link KNOWN_FILL}. So a protected or leased entry is never dropped for room,
62
+ * and an unprotected one only when the ledger is full. "Most recently current" is the end of the list.
63
+ */
64
+ export declare function capKnown(known: readonly KnownGeneration[], protect: ReadonlySet<number>, leased?: ReadonlySet<number>): KnownGeneration[];
65
+ /** `known` with `entry` made the most recent: any entry for its generation removed, and the entry added last. */
66
+ export declare function makeCurrent(known: readonly KnownGeneration[], entry: KnownGeneration): KnownGeneration[];
67
+ /** What a publish of a generation writes into the row, and which names it pushes out of the window. */
68
+ export interface KnownAfter {
69
+ /** The ledger to write: the generation published is its last entry. */
70
+ readonly known: readonly KnownGeneration[];
71
+ /** The window as numbers to collect by name, or `undefined` to collect by listing. */
72
+ readonly list: readonly number[] | undefined;
73
+ /** The generations the window no longer keeps, ascending: the names to delete. */
74
+ readonly evict: readonly number[];
75
+ }
76
+ /**
77
+ * What a publish of `generation` (its object's footer checksum `footerCrc`) over `prev` writes into the row.
78
+ *
79
+ * The ledger is in the order the generations were made current, so the order says which were current most recently.
80
+ * That ranks the window, and the window is drawn from the entries numbered below the new generation only: collection
81
+ * can only take generations numbered below the pointer. The window is the `keep` most recently current of them (at most
82
+ * {@link KNOWN_SPAN}), so the generation the publish superseded, the ledger's last entry, comes first, and after a
83
+ * rollback the ones the operator restored outrank the ones the rollback moved away from.
84
+ *
85
+ * What it writes, in the order the entries were made current and the new generation last:
86
+ *
87
+ * - the window;
88
+ * - the entries below the new generation that `leased` names (a generation a live lease holds stays known, so it stays a
89
+ * rollback target while the lease lives), whatever `keep` says;
90
+ * - the entries numbered above the new generation, which a rollback left and a load numbered under: they are carried and
91
+ * never evicted, and take no slot of `keep`, while the ledger holds fewer than {@link KNOWN_FILL} entries (see
92
+ * {@link capKnown});
93
+ * - the new generation. An entry already numbered `generation` is a stale one (its object went outside the ledger) and
94
+ * is replaced.
95
+ *
96
+ * `evict` is the entries below the new generation that it leaves out, ascending: never a name at or above the new
97
+ * generation. With a `keep` of at most {@link MAX_KEPT_GENERATIONS} the publish collects by name, so `list` is the
98
+ * generations of the ledger below the new one; above that, or with no `keep`, `list` is `undefined` and `evict` empty, and
99
+ * a load lists. A row with a pointer and no usable ledger writes the new generation alone and lists, unless the caller
100
+ * passes a `seed` (see `ledger-seed.ts`): the window a count keeps, read from the bucket, then the new generation, and
101
+ * the publish collects by that count. A publish that clears leases is an erasure's rewrite, which deletes every older
102
+ * generation, so its ledger is the new generation alone.
103
+ */
104
+ export declare function nextKnown(prev: RegistryRecord | null, generation: number, footerCrc: number, keep: number | undefined, options?: {
105
+ readonly clearsLeases?: boolean;
106
+ readonly seed?: readonly KnownGeneration[];
107
+ readonly leased?: ReadonlySet<number>;
108
+ }): KnownAfter;
109
+ /**
110
+ * The ledger a rollback onto `target` writes over `prev`: the ledger it found with the target moved to the end, as the
111
+ * most recently current. A rollback never starts a ledger: over a row with no usable ledger it writes none, and the
112
+ * next publish seeds one. A target the ledger did not name
113
+ * is an insert, and the ledger it makes is held to the cap with the target and the generation the rollback leaves
114
+ * protected; a known target's ledger is written as read, unless another build wrote one that is past the writer's cap.
115
+ */
116
+ export declare function rollbackKnown(prev: RegistryRecord, target: KnownGeneration): readonly KnownGeneration[] | undefined;
117
+ /**
118
+ * The row's ledger with the entries numbered in `doomed` removed, which a write that is about to delete those
119
+ * generations makes first: the entries left keep their order, and the current generation is never removed (the ledger
120
+ * must end with it). Another build's ledger past the writer's cap is held to it, with the current generation protected
121
+ * and the generations the row's leases name kept. `undefined` for a row with no usable ledger, which names nothing to
122
+ * remove.
123
+ */
124
+ export declare function pruneKnown(record: RegistryRecord, doomed: ReadonlySet<number>): readonly KnownGeneration[] | undefined;
125
+ /**
126
+ * The ledger that puts a row back as it was, for the undo of a rollback: the row's own when it is usable (the
127
+ * generation it left is its last entry), held to the writer's cap when another build wrote one past it, with the
128
+ * generation the undo leaves and the one it moves from protected; `undefined` for a row that had none to put back.
129
+ */
130
+ export declare function describingKnown(record: RegistryRecord, movedTo: number): readonly KnownGeneration[] | undefined;
131
+ /**
132
+ * Whether the ledger is full: it names at least {@link KNOWN_FILL} generations, which is when a writer may have let an
133
+ * older one go (a publish vouches for at most {@link KNOWN_SPAN} below its own, and an inserting rollback drops an entry
134
+ * once 128 that no lease holds are named). Leased entries do not switch it off: a lease on an entry inside the span
135
+ * leaves the ledger as full as it was. Past the least recently current entry of a full ledger, a rollback can no longer
136
+ * tell a generation the row forgot from one it never knew, so it judges a target numbered below that entry as it did
137
+ * before the ledger existed.
138
+ */
139
+ export declare function isFullLedger(known: readonly KnownGeneration[]): boolean;
140
+ /**
141
+ * The number a target below the pointer is judged against on a full ledger: the least recently current entry that no
142
+ * lease entry of the row names (live or ended: a rollback has no clock). A leased entry stays in the ledger for its lease,
143
+ * however far the window has moved on since, so it does not mark where the ledger stopped remembering; the first entry no
144
+ * lease holds does. A ledger whose every entry is leased is judged against its first entry.
145
+ */
146
+ export declare function fallbackBound(record: RegistryRecord, known: readonly KnownGeneration[]): number;
147
+ export {};
@@ -18,8 +18,8 @@ export declare const MAX_STORED_LEASES = 256;
18
18
  */
19
19
  export declare const LEASE_CAS_ATTEMPTS: number;
20
20
  /**
21
- * Fresh writes sent after one that got no answer and left the row as it was, by the lease writers and by an erasure's
22
- * renewal of the row.
21
+ * Fresh writes sent after one that got no answer and left the row as it was, by the lease writers, by an erasure's
22
+ * renewal of the row and by a publish: four sends of the row write in all.
23
23
  */
24
24
  export declare const UNANSWERED_RESENDS = 3;
25
25
  /** The upper bound of the first of those waits, in ms; each next one doubles it. */
@@ -65,7 +65,7 @@ export declare const LEASE_ONLY_RETRIES: number;
65
65
  */
66
66
  export declare function onlyLeasesDiffer(held: RegistryRecord, now: RegistryRecord, alsoIgnore?: readonly (keyof RegistryRecord)[]): boolean;
67
67
  /** A writer's count of the lease-only changes it has waited out, and the wait. One per call of the writer. */
68
- export interface LeaseChurn {
68
+ interface LeaseChurn {
69
69
  /**
70
70
  * When `now` is `held` changed only in its leases and the bound is not spent: wait, then read the row again with
71
71
  * `reread`, and return what it found. The wait comes before the read, so the writer's next write is made against a row
@@ -124,3 +124,4 @@ export declare function takeLease(ref: SegmentRef, deps: LeaseDeps, take: LeaseT
124
124
  * races and the registry's `TransientError` for a write that stayed unanswered over an unchanged row.
125
125
  */
126
126
  export declare function releaseLease(ref: SegmentRef, deps: LeaseDeps, holder: string): Promise<void>;
127
+ export {};
@@ -0,0 +1,17 @@
1
+ import { type ReadRetry } from './retry.js';
2
+ import type { IStorageDriver, KnownGeneration, RegistryRecord, SegmentRef } from './ports.js';
3
+ /**
4
+ * Whether a publish over `row` seeds its ledger: only at a birth, over an active row that has a pointer and no usable
5
+ * ledger. A usable ledger is never seeded again, however wide it is.
6
+ */
7
+ export declare function wantsSeed(row: RegistryRecord | null): boolean;
8
+ /**
9
+ * The entries a seed names: the listed generations below the new one that a live lease holds and the window does not
10
+ * keep, then the window oldest first by number with the generation the row named as current last. `listed` is the
11
+ * listing the load's numbering made, when it made one; otherwise the bucket is listed once. Every entry's checksum is
12
+ * read from its object's footer (a 104-byte read, under the store's read retry, {@link SEED_READS} at a time), the
13
+ * superseded generation's too: when a clear summary describes it with another checksum, the row and the bucket
14
+ * disagree about that number and it is left out. A generation gone from the bucket, or whose footer fails its own
15
+ * checks, is left out; any other failed read throws, before anything is published.
16
+ */
17
+ export declare function readSeed(storage: IStorageDriver, ref: SegmentRef, row: RegistryRecord | null, generation: number, keep: number, listed: readonly number[] | undefined, leased: ReadonlySet<number>, readRetry?: ReadRetry): Promise<KnownGeneration[]>;