@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.
- package/README.md +85 -0
- package/dist/{chunk-RSLVTRJY.js → chunk-IGLIZBR7.js} +982 -281
- package/dist/chunk-IGLIZBR7.js.map +7 -0
- package/dist/core/absent-operands.d.ts +8 -0
- package/dist/core/audit.d.ts +67 -4
- package/dist/core/bit-route.d.ts +2 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/bulk-load.d.ts +148 -0
- package/dist/core/cas-retry.d.ts +27 -0
- package/dist/core/check.d.ts +22 -0
- package/dist/core/chunk-checks.d.ts +6 -1
- package/dist/core/chunk-window.d.ts +11 -4
- package/dist/core/combine-expr.d.ts +1 -3
- package/dist/core/combine-feed.d.ts +2 -16
- package/dist/core/combine-many.d.ts +21 -15
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +76 -18
- package/dist/core/crbm/fingerprint.d.ts +23 -0
- package/dist/core/crbm/format.d.ts +2 -5
- package/dist/core/crbm/frame.d.ts +10 -0
- package/dist/core/crbm/plan-reads.d.ts +9 -3
- package/dist/core/crbm/reader.d.ts +51 -12
- package/dist/core/crbm/varint.d.ts +2 -1
- package/dist/core/crbm/writer.d.ts +5 -1
- package/dist/core/crbm-open.d.ts +154 -0
- package/dist/core/crbm-storage-source.d.ts +243 -490
- package/dist/core/crbm-write.d.ts +75 -0
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/engine.d.ts +239 -47
- package/dist/core/erase-id.d.ts +97 -27
- package/dist/core/erasure.d.ts +4 -3
- package/dist/core/errors.d.ts +8 -2
- package/dist/core/generation-gc.d.ts +131 -16
- package/dist/core/generation-list.d.ts +11 -0
- package/dist/core/id-window.d.ts +46 -0
- package/dist/core/invalidation-scope.d.ts +77 -0
- package/dist/core/known-generations-shape.d.ts +13 -0
- package/dist/core/known-generations.d.ts +147 -0
- package/dist/core/leases.d.ts +9 -1
- package/dist/core/ledger-seed.d.ts +17 -0
- package/dist/core/load.d.ts +17 -36
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +9 -7
- package/dist/core/order.d.ts +2 -0
- package/dist/core/pinned-storage-source.d.ts +7 -7
- package/dist/core/pointer-id.d.ts +19 -0
- package/dist/core/ports.d.ts +210 -45
- package/dist/core/publish.d.ts +207 -0
- package/dist/core/reader-defaults.d.ts +14 -3
- package/dist/core/registry-scan.d.ts +0 -2
- package/dist/core/retention-sweep.d.ts +69 -65
- package/dist/core/retention.d.ts +1 -1
- package/dist/core/retry.d.ts +12 -19
- package/dist/core/rollback.d.ts +62 -32
- package/dist/core/summary.d.ts +74 -25
- package/dist/core/token.d.ts +13 -8
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +6 -1
- package/dist/driver-kit.js +90 -10
- package/dist/driver-kit.js.map +3 -3
- package/dist/drivers/_shared/bytes.d.ts +6 -0
- package/dist/drivers/_shared/faults.d.ts +15 -0
- package/dist/drivers/_shared/object-keys.d.ts +18 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
- package/dist/drivers/_shared/object-registry.d.ts +3 -1
- package/dist/drivers/_shared/options.d.ts +19 -0
- package/dist/drivers/_shared/registry.d.ts +31 -19
- package/dist/drivers/localfs/exact-case.d.ts +0 -3
- package/dist/drivers/localfs/fs-util.d.ts +22 -0
- package/dist/drivers/localfs/registry.d.ts +10 -4
- package/dist/drivers/localfs/storage.d.ts +16 -8
- package/dist/drivers/memory.d.ts +14 -9
- package/dist/drivers/retry/retrying-chunk-source.d.ts +1 -5
- package/dist/export/index.d.ts +16 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +4303 -2938
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-RSLVTRJY.js.map +0 -7
- package/dist/core/cost.d.ts +0 -351
- package/dist/core/kept-generations.d.ts +0 -40
package/dist/core/erase-id.d.ts
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The rewrite is the same generation without one id, so it keeps everything else: the new object carries the source's
|
|
13
13
|
* metadata as it is, and the row's summary of it, built from what was written, counts one id fewer and holds the same
|
|
14
|
-
* metadata. Metadata is not scanned for the id.
|
|
15
|
-
* row's
|
|
16
|
-
* the
|
|
14
|
+
* metadata. Metadata is not scanned for the id. A source object whose metadata block was stripped is another object
|
|
15
|
+
* than the row's summary names: its size is not the one the summary records, so the fingerprint check refuses it
|
|
16
|
+
* before the erasure reads any of its ids. Only one forged to match that fingerprint gets past, and on an encrypted
|
|
17
|
+
* segment it is rewritten with the metadata of the row's sealed summary: the block's presence is not authenticated, so
|
|
18
|
+
* the authenticated copy decides.
|
|
17
19
|
*
|
|
18
20
|
* **`erased: true` is a claim about every generation of the segment, not only the one it replaced.** A rollback
|
|
19
21
|
* leaves generations above the pointer that were once current and can be made current again, so a holder can sit
|
|
@@ -48,16 +50,22 @@
|
|
|
48
50
|
* Which reason comes from **re-reading the row**, not from assuming a supersession: a moved pointer is
|
|
49
51
|
* `'superseded'`, a tombstoned row `'destroyed'`, a purged row `'absent'`, a row with no pointer
|
|
50
52
|
* `'no-generation'`. A pointer still on `from` means the object it names is genuinely gone — the forbidden
|
|
51
|
-
* `missing-storage-generation` state —
|
|
52
|
-
*
|
|
53
|
-
* registry error.
|
|
53
|
+
* `missing-storage-generation` state — or is another object than the row's summary names by its fingerprint, and that
|
|
54
|
+
* throws, because no re-run fixes either. That `NotFoundError` is the only signal of those states, so a faulting
|
|
55
|
+
* re-read rethrows it rather than replacing it with a transient-looking registry error.
|
|
54
56
|
*
|
|
55
57
|
* **A refused rewrite deletes its own object when it would outlive the winner above the pointer.** `putImmutable`
|
|
56
58
|
* commits atomically, so a rewrite whose stream throws leaves no object at all. One that completed is either
|
|
57
59
|
* below the winner's pointer, where the winner's collection takes it, or above it: the case where this call took
|
|
58
60
|
* its number after the winner's object was already in the bucket. Up there nothing collects it, and it was derived
|
|
59
61
|
* from a generation the winner has replaced, so when the winner was another erasure it still holds the id that
|
|
60
|
-
* erasure has just reported gone. That object is deleted before this call returns
|
|
62
|
+
* erasure has just reported gone. That object is deleted before this call returns, once its footer matches the
|
|
63
|
+
* rewrite's and a renewal of the row has refused every publish fenced on a row read before it, under the version the
|
|
64
|
+
* footer read reported, unless {@link DISCARD_ATTEMPTS} renewals lose to other writes (a load, a rollback, a
|
|
65
|
+
* retention write): then it is kept, and can hold the winner's id, until a later call takes it: any erasure that
|
|
66
|
+
* rewrites the segment (this one's re-run among them, while its id is still in the current generation) numbers above it
|
|
67
|
+
* and its `keep: 0` collection takes it, an erasure of the winner's id deletes it as a holder, and once loads number
|
|
68
|
+
* past it their collection takes it as it takes any orphan — see `discardRefused`.
|
|
61
69
|
*
|
|
62
70
|
* Without that fence the failure was silent and severe, and both halves were reproduced: `nextGeneration` picks a
|
|
63
71
|
* number above everything in the bucket, so a forward-only publish here always won — discarding a concurrent
|
|
@@ -68,11 +76,28 @@
|
|
|
68
76
|
* A live read still fetching from the collected generation heals forward to the rewrite, and a pin of it fails with
|
|
69
77
|
* `NotFoundError` for any chunk it has yet to read: that is the documented cost of physical deletion on return. **Do not re-load the id while erasing it**: a load that lands after this rewrite
|
|
70
78
|
* carries whatever its source held, and the library cannot know that source was meant to exclude the id.
|
|
71
|
-
* A load already in flight writes its object above the pointer before it publishes
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
79
|
+
* A load already in flight writes its object above the pointer before it publishes, or, as a first load onto a row with
|
|
80
|
+
* no pointer, before there is a pointer at all. An erasure that finds the id in that object renews the row's `pointerId`
|
|
81
|
+
* before deleting it, so the load's publish, fenced on the row it read, is refused (`published: false`) and the pointer
|
|
82
|
+
* never names a missing object (see `fence`). An erasure that finds no holder writes nothing.
|
|
83
|
+
*
|
|
84
|
+
* Before it deletes anything, a call that found a holder takes every generation it will delete out of the row's ledger
|
|
85
|
+
* of known generations, in one write made after its search (the same write as the renewal where there is one). A
|
|
86
|
+
* generation the ledger names is one a collection by a count spares, so the delete comes after the name goes: a
|
|
87
|
+
* rollback that raced the write loses its swap, and a collection that reads the row before a delete spares what the row
|
|
88
|
+
* names. That write is made only when the ledger names something to delete, and it is the
|
|
89
|
+
* one case in which an erasure of an id held only by an older generation writes the row.
|
|
90
|
+
*
|
|
91
|
+
* An object sealed under a key its row does not hold cannot be searched, and no read of the segment can open it: a
|
|
92
|
+
* first load's on a row with no pointer, whose key that load has not published; on a segment with a key, a first
|
|
93
|
+
* load's that lost the race to the one that published, or crashed, under a key it made and never stored; and on a
|
|
94
|
+
* cleartext segment with a pointer, any encrypted object, since a key is made only for a segment's first generation
|
|
95
|
+
* and a publish never adds one to a segment that has generations: a first load's that minted a key and crashed, or
|
|
96
|
+
* lost the race to a cleartext first load. It counts as
|
|
97
|
+
* a holder whatever the id: an erasure of any id that finds one deletes it, under the same renewal of the row and read
|
|
98
|
+
* before the delete, and lists it in `collected`, and a first load still in flight that wrote it is refused. Only a
|
|
99
|
+
* searched object that held the id makes the answer `erased: true`. The current generation is never one of them: one
|
|
100
|
+
* the row's key does not open throws, as every read of the segment does.
|
|
76
101
|
*/
|
|
77
102
|
import { type IAuditSink } from './audit.js';
|
|
78
103
|
import type { CodecInterface } from './codec.js';
|
|
@@ -81,6 +106,20 @@ import type { Rng } from './determinism.js';
|
|
|
81
106
|
import type { IKeystore } from './crypto.js';
|
|
82
107
|
import type { IStorageDriver, IRegistryDriver, SegmentRef } from './ports.js';
|
|
83
108
|
import { type ReadRetry } from './retry.js';
|
|
109
|
+
/**
|
|
110
|
+
* Renewals a refused rewrite's discard makes before it keeps its object: the first, and one more each time a write that
|
|
111
|
+
* is neither a write of leases alone nor another erasure's renewal of the row lands between its read of the row and its
|
|
112
|
+
* renewal. Those two are waited out, up to the limit on lease waits (`LEASE_ONLY_RETRIES`), and cost none, so the
|
|
113
|
+
* losers of one race of erasures all delete their rewrites; a write that gets no answer is sent again within the same
|
|
114
|
+
* renewal.
|
|
115
|
+
*/
|
|
116
|
+
export declare const DISCARD_ATTEMPTS = 8;
|
|
117
|
+
/**
|
|
118
|
+
* Writes a call's fence makes before it gives up on a row that keeps changing under it: each one lost to another writer
|
|
119
|
+
* (a load, a rollback, another erasure) that is not a write of the row's leases alone costs one. The call then reports
|
|
120
|
+
* `'superseded'` with nothing deleted.
|
|
121
|
+
*/
|
|
122
|
+
export declare const FENCE_ATTEMPTS = 5;
|
|
84
123
|
/** What {@link eraseIdFromSegment} needs: the objects, the pointer, the codec, and the key material if encrypted. */
|
|
85
124
|
export interface EraseIdDeps {
|
|
86
125
|
readonly storage: IStorageDriver;
|
|
@@ -98,11 +137,15 @@ export interface EraseIdDeps {
|
|
|
98
137
|
readonly keystore?: IKeystore;
|
|
99
138
|
/** When true, refuse to rewrite a **cleartext** segment (the same guard the read path and a load offer). */
|
|
100
139
|
readonly requireEncryption?: boolean;
|
|
101
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Supplying a clock that can yield makes the rewrite cooperative, as it makes a load. Its `sleep` is what a registry
|
|
142
|
+
* write that got no answer waits on before a fresh one is sent; with no `sleep`, such a write is not sent again and
|
|
143
|
+
* its `TransientError` is thrown.
|
|
144
|
+
*/
|
|
102
145
|
readonly clock?: Yielder;
|
|
103
146
|
/**
|
|
104
|
-
* The random source that spreads the waits between the
|
|
105
|
-
* source is used if it has one, and otherwise each wait is its bound.
|
|
147
|
+
* The random source that spreads the waits between the fresh compare-and-swaps of the rewrite and of the row's
|
|
148
|
+
* renewal. Absent, the read retry's source is used if it has one, and otherwise each wait is its bound.
|
|
106
149
|
*/
|
|
107
150
|
readonly rng?: Rng;
|
|
108
151
|
/** 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. */
|
|
@@ -111,7 +154,7 @@ export interface EraseIdDeps {
|
|
|
111
154
|
* The store's read retry, for the reads the rewrite makes along the way: the generation it rewrites and each of its
|
|
112
155
|
* chunks, the read-back that verifies the generation it wrote, and any other generation it checks for the id. A
|
|
113
156
|
* transient fault on one is run again under it rather than failing the erasure. Absent, each read is made once.
|
|
114
|
-
* The deletes are not retried, and the rewrite's registry write
|
|
157
|
+
* The deletes are not retried, and the rewrite's registry write and the row's renewal are settled as a load's is.
|
|
115
158
|
*/
|
|
116
159
|
readonly readRetry?: ReadRetry;
|
|
117
160
|
}
|
|
@@ -127,13 +170,20 @@ export interface EraseIdResult {
|
|
|
127
170
|
readonly erased: boolean;
|
|
128
171
|
/**
|
|
129
172
|
* Why the id was not erased, when `erased` is false. `'absent'` (no registry row), `'destroyed'` (a crypto-shred
|
|
130
|
-
* tombstone — already unreadable), `'no-generation'` (a row with no
|
|
131
|
-
*
|
|
173
|
+
* tombstone — already unreadable), `'no-generation'` (a row with no generation yet: its bucket can hold first loads'
|
|
174
|
+
* objects, none of them found to hold the id, and `collected` names any this call deleted unsearched, see below),
|
|
175
|
+
* `'not-member'` (no
|
|
176
|
+
* generation in the bucket holds the id — the common case across a fleet scan — with the same note on `collected`),
|
|
177
|
+
* or `'superseded'`.
|
|
132
178
|
*
|
|
133
179
|
* **`'superseded'` means this call did not erase the id, not that the id is still there.** Another writer — a
|
|
134
|
-
* load, another erasure, or a rollback — moved the pointer off the generation this call read
|
|
135
|
-
*
|
|
136
|
-
*
|
|
180
|
+
* load, another erasure, or a rollback — moved the pointer off the generation this call read (on a row with no
|
|
181
|
+
* pointer, any other write of the row before this call renewed it, a `setRetention` included), or replaced an
|
|
182
|
+
* object it meant to delete (another erasure deleted a holder above the pointer, or on a row with no pointer, and a
|
|
183
|
+
* load stored another object under its number, so the storage driver's conditional delete refused this call's,
|
|
184
|
+
* whether or not the pointer moved). What it was doing no longer follows from what is current: a rewrite derived
|
|
185
|
+
* from that generation is not a valid successor to the new one, and a generation it meant to delete may be the one
|
|
186
|
+
* the pointer now names, or another object than the one it read.
|
|
137
187
|
* Re-run against the new generation: if the id is still present it is erased then; if the racing writer was
|
|
138
188
|
* another erasure of the *same* id, the re-run reports `'not-member'` because it is already gone. Either way the
|
|
139
189
|
* re-run settles it, which is why it is the documented action for this reason and for no other.
|
|
@@ -151,14 +201,20 @@ export interface EraseIdResult {
|
|
|
151
201
|
readonly fromGeneration?: number;
|
|
152
202
|
/**
|
|
153
203
|
* The generation written without the id (present whenever one was written, even if `superseded`). A refused
|
|
154
|
-
* rewrite whose object sits above the winner's pointer deletes that object again before it returns
|
|
204
|
+
* rewrite whose object sits above the winner's pointer deletes that object again before it returns, when its footer
|
|
205
|
+
* matches the rewrite's and a renewal of the row refuses every publish fenced on a row read before it.
|
|
155
206
|
*/
|
|
156
207
|
readonly generation?: number;
|
|
157
208
|
/**
|
|
158
209
|
* Generations this call deleted. On a rewrite that is normally `[fromGeneration]`, plus any older orphans. When
|
|
159
|
-
* the current generation did not hold the id, it is every generation below
|
|
160
|
-
* together) and each one above the pointer that held the id; the ones above it
|
|
161
|
-
* because they are an operator's rollback targets.
|
|
210
|
+
* the current generation did not hold the id and a generation this call searched did, it is every generation below
|
|
211
|
+
* the pointer (`keep: 0` takes them together) and each one above the pointer that held the id; the ones above it
|
|
212
|
+
* that did not hold the id stay, because they are an operator's rollback targets. On a row with no pointer it is
|
|
213
|
+
* each first load's object that held the id. Wherever it found them, it is also each object sealed under a key the
|
|
214
|
+
* row does not hold, which this call cannot search and deletes whatever the id. When those are the only holders, it
|
|
215
|
+
* is only those, deleted one by one, and every generation this call searched stays: `reason` is then
|
|
216
|
+
* `'no-generation'` on a row with no pointer and `'not-member'` on one with a pointer. Empty when nothing was
|
|
217
|
+
* deleted.
|
|
162
218
|
*
|
|
163
219
|
* **It is what THIS call deleted, not the proof that the id is gone** — those differ: a concurrent collector can
|
|
164
220
|
* take a generation holding the id first, and then `erased: true` is returned without it in `collected`, because
|
|
@@ -170,7 +226,18 @@ export interface EraseIdResult {
|
|
|
170
226
|
* collection pass that could not prove the segment was still the same one (which an ordinary retirement landing
|
|
171
227
|
* mid-call is enough to cause); the same refusal on the path where the current generation did not hold the id
|
|
172
228
|
* and nothing was published at all; and a generation still holding the id when the bucket is listed at the end
|
|
173
|
-
* — one an operator rolled the pointer onto while a rewrite was collecting, say
|
|
229
|
+
* — one an operator rolled the pointer onto while a rewrite was collecting, say, or, on a row with no pointer, the
|
|
230
|
+
* object of a first load that read the row before this call renewed it and wrote it after this call listed the
|
|
231
|
+
* bucket. The row write an erasure makes before deleting a holder a load may still publish, or before a refused
|
|
232
|
+
* rewrite discards its own object, when it gets no answer that reading the row can settle, throws the registry's
|
|
233
|
+
* `TransientError`, and that object is not deleted.
|
|
234
|
+
*
|
|
235
|
+
* A rollback that lands between the call's read of the row and one of its deletes, onto the generation being deleted,
|
|
236
|
+
* leaves the pointer on an object that is gone. The call finds it at its next read of the row, or by reading the row
|
|
237
|
+
* once more after its last delete, and **throws `NotFoundError`** rather than report anything: every read of the
|
|
238
|
+
* segment fails until a rollback moves the pointer to a generation still in the bucket, and nothing restores the
|
|
239
|
+
* deleted object. When nothing left in the bucket holds the id, the call has emitted its `segment.collect` event
|
|
240
|
+
* first, so the deletion is on the record. Re-run it after that rollback.
|
|
174
241
|
*
|
|
175
242
|
* **Re-run it**, and read what the re-run says rather than assuming it finished the job. The re-run looks for
|
|
176
243
|
* the id in every generation in the bucket, not only the current one, and there are three outcomes:
|
|
@@ -192,7 +259,10 @@ export interface EraseIdResult {
|
|
|
192
259
|
* it. See the module note for the contract.
|
|
193
260
|
*
|
|
194
261
|
* Emits one `segment.rewrite` audit event at the publish (before the superseded generation is collected), so the
|
|
195
|
-
* compliance record exists the moment the generation without the id is authoritative.
|
|
262
|
+
* compliance record exists the moment the generation without the id is authoritative. An erasure that rewrites nothing,
|
|
263
|
+
* because only other generations held the id, emits one `segment.collect` once no generation holds it, and so does one
|
|
264
|
+
* that deleted only objects no read of the segment can open, which names no generation the id was found in. Either
|
|
265
|
+
* event comes before the `NotFoundError` of a rollback that moved the pointer onto a generation the call deleted.
|
|
196
266
|
*/
|
|
197
267
|
export declare function eraseIdFromSegment(ref: SegmentRef, id: number, deps: EraseIdDeps, options?: {
|
|
198
268
|
audit?: IAuditSink;
|
package/dist/core/erasure.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* operation — `eraseIdFromSegment` rewrites the generation without it.
|
|
14
14
|
*/
|
|
15
15
|
import { type IAuditSink } from './audit.js';
|
|
16
|
-
import {
|
|
16
|
+
import type { ChurnDeps } from './leases.js';
|
|
17
17
|
import type { IStorageDriver, IRegistryDriver, RegistryRecord, SegmentRef } from './ports.js';
|
|
18
18
|
export interface EraseDeps extends ChurnDeps {
|
|
19
19
|
readonly registry: IRegistryDriver;
|
|
@@ -60,7 +60,7 @@ export declare function destroySegment(ref: SegmentRef, deps: EraseDeps, options
|
|
|
60
60
|
* listing is capped at `maxScanSegments` (default {@link DEFAULT_MAX_SCAN_SEGMENTS}, 250,000), the ceiling every
|
|
61
61
|
* other fleet scan keeps. A namespace over it throws `BudgetExceededError` **before any segment is destroyed**,
|
|
62
62
|
* never part-way through; raise `maxScanSegments` when the namespace really is that large and the memory is there.
|
|
63
|
-
* A `maxScanSegments` that is not a
|
|
63
|
+
* A `maxScanSegments` that is not a safe whole number of at least 1 throws `ValidationError`, again before anything is destroyed.
|
|
64
64
|
*/
|
|
65
65
|
export declare function eraseNamespace(namespace: string, deps: EraseDeps, options: {
|
|
66
66
|
confirmNamespace: string;
|
|
@@ -195,7 +195,7 @@ export declare function dropSegment(ref: SegmentRef, deps: DropDeps, options: {
|
|
|
195
195
|
* in the shred's own write, so only a tombstone a sweep wrote ever carries a sweep's mark, which is what lets a later
|
|
196
196
|
* sweep purge it and never a crypto-shred's.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
interface SweepDrop {
|
|
199
199
|
readonly wanted: (row: RegistryRecord) => boolean;
|
|
200
200
|
readonly stamp: (retention: RegistryRecord['retention']) => RegistryRecord['retention'];
|
|
201
201
|
}
|
|
@@ -208,3 +208,4 @@ export declare function dropSegmentFor(ref: SegmentRef, deps: DropDeps, options:
|
|
|
208
208
|
dryRun?: boolean;
|
|
209
209
|
audit?: IAuditSink;
|
|
210
210
|
}, sweep: SweepDrop | undefined): Promise<DropResult | null>;
|
|
211
|
+
export {};
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -26,7 +26,11 @@ export declare class CloudRoaringError extends Error {
|
|
|
26
26
|
readonly [ERROR_BRAND]: true;
|
|
27
27
|
constructor(message: string);
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Invalid input, which the same call fails on again. Most is refused before any storage call (a bad id, segment name
|
|
31
|
+
* or option); some is found by the call itself: a read past the end of an object, or a bucket or container a cloud
|
|
32
|
+
* driver finds does not exist.
|
|
33
|
+
*/
|
|
30
34
|
export declare class ValidationError extends CloudRoaringError {
|
|
31
35
|
}
|
|
32
36
|
/** An OCC conditional write/delete lost the race — the row changed since it was read. */
|
|
@@ -148,7 +152,9 @@ export declare function isLeaseLimitError(err: unknown): err is LeaseLimitError;
|
|
|
148
152
|
export declare function isNotFoundError(err: unknown): err is NotFoundError;
|
|
149
153
|
/** Corrupt/oversized/failed-checksum bytes from a tier. */
|
|
150
154
|
export declare function isIntegrityError(err: unknown): err is IntegrityError;
|
|
151
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Invalid caller input; also the error a cloud driver raises for a bucket or container it finds does not exist.
|
|
157
|
+
*/
|
|
152
158
|
export declare function isValidationError(err: unknown): err is ValidationError;
|
|
153
159
|
/** This build or this store's wiring cannot do what was asked, though nothing is malformed. */
|
|
154
160
|
export declare function isUnsupportedError(err: unknown): err is UnsupportedError;
|
|
@@ -1,9 +1,84 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -46,7 +123,8 @@ export interface LoadNumber {
|
|
|
46
123
|
* and collection takes it once a generation above it is current. Like a listing's, the number can be one whose
|
|
47
124
|
* object was deleted (an erasure removes the generations above a rolled-back pointer that held the id), which is
|
|
48
125
|
* why nothing identifies a generation by its number alone: the reader cache and the chunk cache key on the number and
|
|
49
|
-
* the row's
|
|
126
|
+
* the row's `pointerId`, which every write that takes a number again renews, and an open of a row with a usable
|
|
127
|
+
* summary holds the object to the fingerprint the summary records; a pin keys on its object's fingerprint.
|
|
50
128
|
*/
|
|
51
129
|
export declare function nextLoadGeneration(ref: SegmentRef, deps: GenerationDeps, row: RegistryRecord | null): Promise<LoadNumber>;
|
|
52
130
|
/**
|
|
@@ -72,6 +150,11 @@ export declare const LIST_COLLECTION_CADENCE = 16;
|
|
|
72
150
|
* a pin taken while it was still active re-checks the status on every open — and nothing else in the library
|
|
73
151
|
* would ever collect them, so without this those objects are billed forever.
|
|
74
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
|
+
*
|
|
75
158
|
* **Throws {@link WriteConflictError} when it cannot act on the row it read**: the row is gone when it is re-read,
|
|
76
159
|
* after the listing or before a delete; on an active segment, the pointer has fallen below the cutoff before a
|
|
77
160
|
* delete; on a `destroyed` segment, the row's token has changed. Not to be confused with the empty array it still
|
|
@@ -96,33 +179,52 @@ export declare const LIST_COLLECTION_CADENCE = 16;
|
|
|
96
179
|
export declare function gcOrphanGenerations(ref: SegmentRef, deps: GenerationDeps, options?: {
|
|
97
180
|
keep?: number;
|
|
98
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
|
+
}>;
|
|
99
196
|
/**
|
|
100
197
|
* What makes a collection spare leased generations: the clock that says whether a lease has ended. Only a load's
|
|
101
198
|
* collection passes one, and a load with no clock passes none: it cannot tell a live lease from an ended one, so it
|
|
102
199
|
* reads none. A pass without a guard never reads the row's leases, which is what keeps erasure, shred and drop deleting
|
|
103
200
|
* what a lease names.
|
|
104
201
|
*/
|
|
105
|
-
|
|
202
|
+
interface LeaseGuard {
|
|
106
203
|
readonly now: () => number;
|
|
107
204
|
}
|
|
108
205
|
/** What a load's publish recorded in the row, as `publishGeneration` reports it. */
|
|
109
206
|
export interface PublishedKept {
|
|
110
|
-
/** The
|
|
207
|
+
/** The window the publish recorded, as numbers, or `undefined` when it recorded none (the row then does not know which are kept). */
|
|
111
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[];
|
|
112
211
|
/** The names the publish pushed out of the window, ascending. */
|
|
113
212
|
readonly evict: readonly number[];
|
|
114
213
|
/** The token of the row the publish wrote, when it is known: a write settled by reading the row has none. */
|
|
115
214
|
readonly token: Token | undefined;
|
|
116
215
|
}
|
|
117
216
|
/**
|
|
118
|
-
* Collect after a load's publish. The row records the generations the load keeps, so the pass deletes by name
|
|
119
|
-
* 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
|
|
120
219
|
* pass) only when it cannot rely on the row or must catch what a name does not:
|
|
121
220
|
*
|
|
122
|
-
* - the
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
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;
|
|
126
228
|
* - the guard found the current generation's object gone, or, for a `keep` of 1 or more when the guard took the size
|
|
127
229
|
* from the row's summary and opened nothing, one zero-byte read of it does not find it (`proveCurrent`): the
|
|
128
230
|
* window then counts a generation that is not there, so the same pass keeps what is;
|
|
@@ -134,10 +236,13 @@ export interface PublishedKept {
|
|
|
134
236
|
* below it. A load onto a `destroyed` row is refused at its publish and never reaches here; a drop that lands after
|
|
135
237
|
* the publish leaves this pass collecting the tombstone's generations, and the drop's own sweep takes the rest.
|
|
136
238
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* a
|
|
140
|
-
*
|
|
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.
|
|
141
246
|
*/
|
|
142
247
|
export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
143
248
|
generation: number;
|
|
@@ -155,6 +260,11 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
|
|
|
155
260
|
proveCurrent?: GenKey;
|
|
156
261
|
/** Spare the generations the row's live leases hold. Absent, the pass ignores leases. */
|
|
157
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;
|
|
158
268
|
}): Promise<number[]>;
|
|
159
269
|
/**
|
|
160
270
|
* Delete, with no listing, the generations a publish of `generation` pushed out of its row's window, `evict`, taken
|
|
@@ -170,9 +280,14 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
|
|
|
170
280
|
* from taking a generation that a later publish, after a rollback, put in the window. A pointer that has moved above
|
|
171
281
|
* `generation` does not stop it: a publish landing in the meantime leaves routine collection working. A fault, a
|
|
172
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`.
|
|
173
287
|
*/
|
|
174
288
|
export declare function deleteEvicted(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
175
289
|
generation: number;
|
|
176
290
|
evict: readonly number[];
|
|
177
291
|
leases?: LeaseGuard;
|
|
178
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 {};
|