@cloudbitmaps/core 0.8.2 → 0.10.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/NOTICE +4 -4
- package/README.md +28 -6
- package/dist/chunk-BUPDGZRR.js +461 -0
- package/dist/chunk-BUPDGZRR.js.map +7 -0
- package/dist/core/audit.d.ts +127 -0
- package/dist/core/bit-route.d.ts +21 -0
- package/dist/core/blob.d.ts +34 -0
- package/dist/core/budget.d.ts +66 -0
- package/dist/core/codec.d.ts +114 -0
- package/dist/core/concurrency.d.ts +15 -0
- package/dist/core/consistency.d.ts +73 -0
- package/dist/core/cooperative.d.ts +78 -0
- package/dist/core/cost.d.ts +102 -0
- package/dist/core/crbm/crc32c.d.ts +10 -0
- package/dist/core/crbm/format.d.ts +116 -0
- package/dist/core/crbm/reader.d.ts +69 -0
- package/dist/core/crbm/varint.d.ts +13 -0
- package/dist/core/crbm/writer.d.ts +39 -0
- package/dist/core/crbm-storage-source.d.ts +432 -0
- package/dist/core/crypto.d.ts +80 -0
- package/dist/core/determinism.d.ts +42 -0
- package/dist/core/discover.d.ts +75 -0
- package/dist/core/due-index.d.ts +55 -0
- package/dist/core/engine.d.ts +204 -0
- package/dist/core/erase-id.d.ts +141 -0
- package/dist/core/erasure.d.ts +179 -0
- package/dist/core/errors.d.ts +120 -0
- package/dist/core/generation-gc.d.ts +52 -0
- package/dist/core/keys.d.ts +23 -0
- package/dist/core/load.d.ts +102 -0
- package/dist/core/lru.d.ts +69 -0
- package/dist/core/metrics.d.ts +132 -0
- package/dist/core/name-codec.d.ts +79 -0
- package/dist/core/pinned-storage-source.d.ts +69 -0
- package/dist/core/ports.d.ts +340 -0
- package/dist/core/registry-scan.d.ts +38 -0
- package/dist/core/retention-sweep.d.ts +190 -0
- package/dist/core/retention.d.ts +67 -0
- package/dist/core/retry.d.ts +65 -0
- package/dist/core/rollback.d.ts +71 -0
- package/dist/core/validate.d.ts +20 -0
- package/dist/driver-kit.d.ts +10 -0
- package/dist/driver-kit.js +314 -0
- package/dist/driver-kit.js.map +7 -0
- package/dist/drivers/_shared/aws-errors.d.ts +18 -0
- package/dist/drivers/_shared/keys.d.ts +27 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +28 -0
- package/dist/drivers/_shared/object-registry.d.ts +84 -0
- package/dist/drivers/_shared/registry.d.ts +62 -0
- package/dist/drivers/backends.d.ts +49 -0
- package/dist/drivers/crypto.d.ts +38 -0
- package/dist/drivers/localfs/fs-util.d.ts +20 -0
- package/dist/drivers/localfs/paths.d.ts +33 -0
- package/dist/drivers/localfs/registry.d.ts +28 -0
- package/dist/drivers/localfs/storage.d.ts +19 -0
- package/dist/drivers/memory.d.ts +57 -0
- package/dist/drivers/retry/retrying-drivers.d.ts +90 -0
- package/dist/export/index.d.ts +92 -0
- package/dist/index.d.ts +85 -2534
- package/dist/index.js +3076 -2978
- package/dist/index.js.map +7 -1
- package/dist/testing/fuzz-core.d.ts +14 -0
- package/package.json +19 -115
- package/dist/azure/index.cjs +0 -368
- package/dist/azure/index.cjs.map +0 -1
- package/dist/azure/index.d.cts +0 -48
- package/dist/azure/index.d.ts +0 -48
- package/dist/azure/index.js +0 -304
- package/dist/azure/index.js.map +0 -1
- package/dist/cassandra/index.cjs +0 -265
- package/dist/cassandra/index.cjs.map +0 -1
- package/dist/cassandra/index.d.cts +0 -52
- package/dist/cassandra/index.d.ts +0 -52
- package/dist/cassandra/index.js +0 -204
- package/dist/cassandra/index.js.map +0 -1
- package/dist/chunk-7LMLYSVJ.js +0 -43
- package/dist/chunk-7LMLYSVJ.js.map +0 -1
- package/dist/chunk-L54FL5DW.js +0 -203
- package/dist/chunk-L54FL5DW.js.map +0 -1
- package/dist/chunk-NUIDEEFZ.js +0 -91
- package/dist/chunk-NUIDEEFZ.js.map +0 -1
- package/dist/chunk-SNJVZ227.js +0 -35
- package/dist/chunk-SNJVZ227.js.map +0 -1
- package/dist/chunk-YEHMG6W2.js +0 -6
- package/dist/chunk-YEHMG6W2.js.map +0 -1
- package/dist/dynamodb/index.cjs +0 -740
- package/dist/dynamodb/index.cjs.map +0 -1
- package/dist/dynamodb/index.d.cts +0 -106
- package/dist/dynamodb/index.d.ts +0 -106
- package/dist/dynamodb/index.js +0 -469
- package/dist/dynamodb/index.js.map +0 -1
- package/dist/gcs/index.cjs +0 -343
- package/dist/gcs/index.cjs.map +0 -1
- package/dist/gcs/index.d.cts +0 -46
- package/dist/gcs/index.d.ts +0 -46
- package/dist/gcs/index.js +0 -279
- package/dist/gcs/index.js.map +0 -1
- package/dist/index.cjs +0 -4608
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2535
- package/dist/mongodb/index.cjs +0 -260
- package/dist/mongodb/index.cjs.map +0 -1
- package/dist/mongodb/index.d.cts +0 -45
- package/dist/mongodb/index.d.ts +0 -45
- package/dist/mongodb/index.js +0 -199
- package/dist/mongodb/index.js.map +0 -1
- package/dist/mysql/index.cjs +0 -281
- package/dist/mysql/index.cjs.map +0 -1
- package/dist/mysql/index.d.cts +0 -56
- package/dist/mysql/index.d.ts +0 -56
- package/dist/mysql/index.js +0 -211
- package/dist/mysql/index.js.map +0 -1
- package/dist/ports-5BQTGKze.d.cts +0 -356
- package/dist/ports-5BQTGKze.d.ts +0 -356
- package/dist/postgres/index.cjs +0 -273
- package/dist/postgres/index.cjs.map +0 -1
- package/dist/postgres/index.d.cts +0 -54
- package/dist/postgres/index.d.ts +0 -54
- package/dist/postgres/index.js +0 -203
- package/dist/postgres/index.js.map +0 -1
- package/dist/redis/index.cjs +0 -257
- package/dist/redis/index.cjs.map +0 -1
- package/dist/redis/index.d.cts +0 -42
- package/dist/redis/index.d.ts +0 -42
- package/dist/redis/index.js +0 -197
- package/dist/redis/index.js.map +0 -1
- package/dist/s3/index.cjs +0 -891
- package/dist/s3/index.cjs.map +0 -1
- package/dist/s3/index.d.cts +0 -112
- package/dist/s3/index.d.ts +0 -112
- package/dist/s3/index.js +0 -555
- package/dist/s3/index.js.map +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { IRegistryDriver, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, SegmentRef, Token } from '../../core/ports.js';
|
|
2
|
+
/** Defensive cap on a single registry object read from storage, before allocation (rows are tiny; ~1 KB). */
|
|
3
|
+
export declare const MAX_ROW_BYTES: number;
|
|
4
|
+
/**
|
|
5
|
+
* Raised by a store when the version it pinned for a read vanished before it could fetch the bytes —
|
|
6
|
+
* a concurrent writer overwrote or deleted the object in between.
|
|
7
|
+
*
|
|
8
|
+
* This is deliberately **neither** of the two things it superficially resembles. It is not absence: the row
|
|
9
|
+
* is very likely still live, just one version further on. And it is not a write conflict: the caller may be
|
|
10
|
+
* a read-only `get()`, which has nothing to conflict with. Both of those mistakes were made — one per cloud —
|
|
11
|
+
* before this type existed, which is exactly why the signal is now part of the port rather than left to each
|
|
12
|
+
* store's judgement. {@link ObjectStoreRegistry} answers it the only way that is correct for every caller:
|
|
13
|
+
* it reads again.
|
|
14
|
+
*
|
|
15
|
+
* Stores whose read is atomic (S3 serves bytes and `ETag` from one `GetObject`) can never raise it.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ObjectVersionRaced extends Error {
|
|
18
|
+
constructor(objectKey: string);
|
|
19
|
+
}
|
|
20
|
+
/** One object as read, with the opaque version that fences a later conditional write. */
|
|
21
|
+
export interface ObjectRow {
|
|
22
|
+
readonly bytes: Uint8Array;
|
|
23
|
+
/** The store's version fence — an S3/Azure ETag, a GCS generation. Opaque to this class. */
|
|
24
|
+
readonly version: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The three I/O calls an object store must provide to host a registry. Everything else — the OCC counter,
|
|
28
|
+
* tombstones, the delete retry, key parsing — is implemented once above.
|
|
29
|
+
*/
|
|
30
|
+
export interface ObjectRegistryStore {
|
|
31
|
+
/** A label for error messages, e.g. `GCS` or `Azure Blob`. */
|
|
32
|
+
readonly label: string;
|
|
33
|
+
/**
|
|
34
|
+
* Read one object, or `null` when it is absent.
|
|
35
|
+
*
|
|
36
|
+
* **`null` means the object does not exist** — nothing weaker. A store that reads in two round trips
|
|
37
|
+
* (metadata for the version fence, then the bytes pinned to it) can find that version already gone; it
|
|
38
|
+
* MUST report that with {@link ObjectVersionRaced} rather than `null` or {@link WriteConflictError}, and
|
|
39
|
+
* the caller re-reads. Retryable faults throw {@link TransientError}.
|
|
40
|
+
*
|
|
41
|
+
* `bytes` and `version` MUST describe the **same** observation of the object: `version` is the fence a
|
|
42
|
+
* later conditional write is conditioned on, so a pair drawn from two different versions would either
|
|
43
|
+
* fail a write that should have succeeded or, worse, pass one that should not.
|
|
44
|
+
*/
|
|
45
|
+
read(key: string): Promise<ObjectRow | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Write one object under a precondition: `'absent'` means create-only, a version means
|
|
48
|
+
* compare-and-swap against exactly that version. A lost race MUST throw {@link WriteConflictError}.
|
|
49
|
+
*/
|
|
50
|
+
write(key: string, body: Uint8Array, expect: 'absent' | {
|
|
51
|
+
version: string;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
/** Every object key under `prefix`, paginated internally. */
|
|
54
|
+
listKeys(prefix: string): AsyncIterable<string>;
|
|
55
|
+
}
|
|
56
|
+
export declare class ObjectStoreRegistry implements IRegistryDriver {
|
|
57
|
+
private readonly store;
|
|
58
|
+
private readonly prefix;
|
|
59
|
+
private readonly now;
|
|
60
|
+
constructor(store: ObjectRegistryStore, prefix: string | undefined, now: () => number);
|
|
61
|
+
capabilities(): RegCaps;
|
|
62
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
63
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
64
|
+
token: Token;
|
|
65
|
+
}>;
|
|
66
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
67
|
+
token: Token;
|
|
68
|
+
}>;
|
|
69
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
70
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Read + parse a registry object, returning its envelope and version fence, or null if absent.
|
|
73
|
+
*
|
|
74
|
+
* A store that pins a version to read consistently ({@link ObjectVersionRaced}) loses that pin whenever a
|
|
75
|
+
* concurrent writer lands mid-read. Re-reading is the whole answer — the next read simply observes the
|
|
76
|
+
* newer version — and it is bounded so a row under permanent write saturation fails typed instead of
|
|
77
|
+
* spinning. Only a store racing itself gets here; S3 never does.
|
|
78
|
+
*/
|
|
79
|
+
private readRow;
|
|
80
|
+
/** {@link ObjectRegistryStore.read}, retrying the bounded number of times a mid-read overwrite allows. */
|
|
81
|
+
private readRaced;
|
|
82
|
+
/** Conditional write of an envelope. The store maps a lost precondition to {@link WriteConflictError}. */
|
|
83
|
+
private putRow;
|
|
84
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NewRegistryRecord, RegistryPatch, RegistryRecord, SegmentRef, Token } from '../../core/ports.js';
|
|
2
|
+
/** Validate the caller-settable fields at `create`. */
|
|
3
|
+
export declare function validateNewRegistryRecord(rec: NewRegistryRecord): void;
|
|
4
|
+
/** Validate the caller-settable fields in a `compareAndSwap` patch. */
|
|
5
|
+
export declare function validateRegistryPatch(patch: RegistryPatch): void;
|
|
6
|
+
/**
|
|
7
|
+
* The persisted envelope for a registry row: the record plus a tombstone flag. A **deleted** row keeps its
|
|
8
|
+
* record (with an advanced counter) rather than being removed, so the monotonic token survives a
|
|
9
|
+
* delete→recreate — ABA-safety. Shared by the persistent drivers (LocalFs file, S3 object).
|
|
10
|
+
*
|
|
11
|
+
* `schemaVersion` is a wire-only concern — it stamps the persisted bytes, not the in-memory domain object —
|
|
12
|
+
* so it lives on the serialize/parse boundary ({@link serializeRegistryEnvelope} /
|
|
13
|
+
* {@link parseRegistryEnvelope}), never on this interface.
|
|
14
|
+
*/
|
|
15
|
+
export interface RegistryEnvelope {
|
|
16
|
+
readonly deleted: boolean;
|
|
17
|
+
readonly record: RegistryRecord;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Current registry-row schema version — a pre-1.0 format-freeze prerequisite. Every persistent
|
|
21
|
+
* registry driver — every one of which persists the `{ deleted, record }` envelope — stamps its rows
|
|
22
|
+
* with this so a reader can fail-closed on a future, incompatible layout instead of misparsing it. Bump only
|
|
23
|
+
* on a backward-incompatible change. Policy: a **higher** stamp than this build knows → `UnsupportedError`
|
|
24
|
+
* (fail-closed); an **absent** stamp → legacy pre-freeze row, tolerated as v1.
|
|
25
|
+
*/
|
|
26
|
+
export declare const REGISTRY_SCHEMA_VERSION = 1;
|
|
27
|
+
/**
|
|
28
|
+
* Validate a persisted registry row's `schemaVersion` (untrusted bytes, invariant 5): absent → legacy v1
|
|
29
|
+
* (tolerated); a malformed value → `IntegrityError`; a version newer than this build → `UnsupportedError`
|
|
30
|
+
* (fail-closed rather than misread a format we don't understand).
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertRegistrySchemaVersion(raw: unknown, ctx: string): void;
|
|
33
|
+
/** Serialize a registry envelope for persistence, stamping the current {@link REGISTRY_SCHEMA_VERSION}. */
|
|
34
|
+
export declare function serializeRegistryEnvelope(env: RegistryEnvelope): string;
|
|
35
|
+
/**
|
|
36
|
+
* The OCC counter encoded in a record's token. A published row always has a **canonical decimal** token
|
|
37
|
+
* (`String(counter)`), so anything else (`"1e3"`, `"0x10"`, `" 5 "`, `""`, a >2^53 value) is corruption /
|
|
38
|
+
* tampering — reject it (invariant 5) rather than let `Number()` coerce it and silently break monotonicity.
|
|
39
|
+
*/
|
|
40
|
+
export declare function registryCounterOf(record: RegistryRecord): number;
|
|
41
|
+
/**
|
|
42
|
+
* Parse + structurally validate a persisted `{ deleted, record }` envelope from stored bytes. A published row
|
|
43
|
+
* is always whole (atomic write), so a parse failure or a missing/mistyped field means corruption/tampering —
|
|
44
|
+
* fail fast (invariant 5), never silently report "absent". `ctx` names the source (path/key) for the message.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseRegistryEnvelope(text: string, ctx: string): RegistryEnvelope;
|
|
47
|
+
/** Build a full record from a {@link NewRegistryRecord} plus identity, audit timestamps, and an OCC token. */
|
|
48
|
+
export declare function recordFromNew(ref: SegmentRef, rec: NewRegistryRecord, now: number, token: Token): RegistryRecord;
|
|
49
|
+
/**
|
|
50
|
+
* Fail-fast structural check on a record read back from a persistent tier (untrusted bytes, invariant 5):
|
|
51
|
+
* a published row is always whole, so a missing/mistyped required field means corruption/tampering — reject
|
|
52
|
+
* it rather than silently report "absent". `token` is checked by the caller (its source differs per backend:
|
|
53
|
+
* an in-body field vs a derived OCC counter).
|
|
54
|
+
*/
|
|
55
|
+
export declare function assertStoredRecordShape(r: Record<string, unknown>, ctx: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Apply a patch to an existing record, returning a new one with a fresh `updatedAt` + `token` (identity and
|
|
58
|
+
* `createdAt` are preserved). Optional fields use `'k' in patch` so a patch can *clear* them (set to
|
|
59
|
+
* `undefined`, e.g. dropping `keyId` on crypto-shred); required fields use `??` (they always have a value).
|
|
60
|
+
* Fields this build does not know (an older row's compaction bookkeeping) are not carried over.
|
|
61
|
+
*/
|
|
62
|
+
export declare function applyRegistryPatch(prev: RegistryRecord, patch: RegistryPatch, now: number, token: Token): RegistryRecord;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { STORAGE_BACKEND } from '../core/ports.js';
|
|
2
|
+
import type { StorageBackend } from '../core/ports.js';
|
|
3
|
+
import { MemoryRegistryDriver, MemoryStorageDriver } from './memory.js';
|
|
4
|
+
import { LocalFsStorageDriver } from './localfs/storage.js';
|
|
5
|
+
import { LocalFsRegistryDriver } from './localfs/registry.js';
|
|
6
|
+
export interface MemoryStorageOptions {
|
|
7
|
+
/** Injected clock for the registry's `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
8
|
+
readonly now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* An in-process backend — nothing to install, nothing to clean up, and it disappears with the process.
|
|
12
|
+
*
|
|
13
|
+
* This is the one to reach for in tests and in the first five minutes. It is **not** a cache in front of
|
|
14
|
+
* anything: it is the whole store, held in RAM, so a restart is a data loss.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MemoryStorage implements StorageBackend {
|
|
17
|
+
/** Cross-bundle brand, stamped non-enumerably in the constructor so a spread cannot carry it. */
|
|
18
|
+
readonly [STORAGE_BACKEND]: true;
|
|
19
|
+
readonly storage: MemoryStorageDriver;
|
|
20
|
+
readonly registry: MemoryRegistryDriver;
|
|
21
|
+
constructor(options?: MemoryStorageOptions);
|
|
22
|
+
}
|
|
23
|
+
export interface LocalFsStorageOptions {
|
|
24
|
+
/** Injected clock for the registry's `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
25
|
+
readonly now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A local-filesystem backend rooted at one directory: generations under `<root>/storage`, pointers under
|
|
29
|
+
* `<root>/registry`.
|
|
30
|
+
*
|
|
31
|
+
* The layout is stated here rather than by the caller, which is the point — the two halves used to be two
|
|
32
|
+
* paths a caller wrote out separately, and nothing stopped them naming different roots. It is also the layout
|
|
33
|
+
* the `export-segments` CLI expects, so a store built this way can be ejected without being told where to look.
|
|
34
|
+
*
|
|
35
|
+
* **It refuses a store written before the tier was renamed**, because the alternative is much worse than an
|
|
36
|
+
* error. Such a store keeps its generations in `<root>/cold`, where this class does not look, so the registry
|
|
37
|
+
* half resolves a pointer the storage half cannot satisfy — and the store reports
|
|
38
|
+
* `missing-storage-generation`, which is the signature of a **torn restore**. The runbook's remedies for that
|
|
39
|
+
* include rolling `currentGen` back, which is destructive and would be applied to a store that was never
|
|
40
|
+
* damaged. One `existsSync` at wiring time is a cheap price for not sending someone down that path.
|
|
41
|
+
*/
|
|
42
|
+
export declare class LocalFsStorage implements StorageBackend {
|
|
43
|
+
readonly root: string;
|
|
44
|
+
/** Cross-bundle brand, stamped non-enumerably in the constructor so a spread cannot carry it. */
|
|
45
|
+
readonly [STORAGE_BACKEND]: true;
|
|
46
|
+
readonly storage: LocalFsStorageDriver;
|
|
47
|
+
readonly registry: LocalFsRegistryDriver;
|
|
48
|
+
constructor(root: string, options?: LocalFsStorageOptions);
|
|
49
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Aead, AeadSealed, IKeystore, WrappedDek } from '../core/crypto.js';
|
|
2
|
+
/** AES-256-GCM AEAD bound to one 32-byte key. A fresh CSPRNG 96-bit nonce per `seal`; `aad` is authenticated,
|
|
3
|
+
* never stored. Nonce-reuse safety: the random-96-bit birthday bound is ~q²/2⁹⁷, negligible below ~2³² seals
|
|
4
|
+
* under one key. A per-segment DEK is **reused across all generations** (every load and rewrite encrypts under it), so
|
|
5
|
+
* seal count is cumulative over the segment's life — a segment approaching ~2³² lifetime chunk-seals should be
|
|
6
|
+
* re-seeded (a fresh segment ⇒ fresh DEK). Far beyond any realistic workload; noted for completeness. */
|
|
7
|
+
export declare class NodeAead implements Aead {
|
|
8
|
+
private readonly key;
|
|
9
|
+
constructor(key: Uint8Array);
|
|
10
|
+
seal(plaintext: Uint8Array, aad: Uint8Array): AeadSealed;
|
|
11
|
+
open(sealed: AeadSealed, aad: Uint8Array): Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Options for {@link InProcessKeystore}. `keys` maps a `keyId` → its 32-byte KEK; `activeKeyId` names the KEK
|
|
15
|
+
* new DEKs are wrapped under; an optional `recoveryKeyId` additionally wraps each new DEK under a second
|
|
16
|
+
* (e.g. offline) KEK, so losing the active KEK isn't fatal. Rotating the active KEK needs no data
|
|
17
|
+
* re-encryption: add the new KEK, point `activeKeyId` at it, keep the old one to unwrap existing segments.
|
|
18
|
+
*/
|
|
19
|
+
export interface InProcessKeystoreOptions {
|
|
20
|
+
readonly keys: Readonly<Record<string, Uint8Array>>;
|
|
21
|
+
readonly activeKeyId: string;
|
|
22
|
+
readonly recoveryKeyId?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Dependency-free BYOK keystore: envelope-wraps a fresh random per-segment DEK under the active (and optional
|
|
26
|
+
* recovery) KEK using `node:crypto`. No external service. The raw DEK never leaves a returned {@link NodeAead}.
|
|
27
|
+
*/
|
|
28
|
+
export declare class InProcessKeystore implements IKeystore {
|
|
29
|
+
private readonly keys;
|
|
30
|
+
private readonly activeKeyId;
|
|
31
|
+
private readonly recoveryKeyId;
|
|
32
|
+
constructor(options: InProcessKeystoreOptions);
|
|
33
|
+
createDek(): Promise<{
|
|
34
|
+
wrapped: WrappedDek[];
|
|
35
|
+
aead: Aead;
|
|
36
|
+
}>;
|
|
37
|
+
openDek(wrapped: readonly WrappedDek[]): Promise<Aead>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** True when `err` is a Node system error with the given `code` (e.g. `'ENOENT'`, `'EEXIST'`). */
|
|
2
|
+
export declare const isCode: (err: unknown, code: string) => boolean;
|
|
3
|
+
export declare const isFsTransient: (err: unknown) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Reclassify a transient filesystem fault as a retryable {@link TransientError} (so the retry decorator can
|
|
6
|
+
* ride it out); everything else — including the driver's own typed errors — propagates unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export declare const mapFsError: (err: unknown) => unknown;
|
|
9
|
+
/**
|
|
10
|
+
* `O_NOFOLLOW` where the platform supports it (absent on Windows → 0). OR it into an `open` flag set so a
|
|
11
|
+
* symlink **at the final path component** is refused rather than followed outside the storage root.
|
|
12
|
+
* Containment of symlinked *directory* components assumes the root itself is not attacker-writable.
|
|
13
|
+
*/
|
|
14
|
+
export declare const O_NOFOLLOW: number;
|
|
15
|
+
/**
|
|
16
|
+
* Best-effort parent-directory fsync, so a just-published `rename`/`link` survives a crash. Without it the
|
|
17
|
+
* directory entry can still be in the page cache when power goes: the generation's bytes are durable and
|
|
18
|
+
* the name pointing at them is not, which is the one way a published generation can come back missing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fsyncDir(dir: string): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { GenKey, SegmentRef } from '../../core/ports.js';
|
|
2
|
+
/** Directory holding all of a namespace's segment objects. */
|
|
3
|
+
export declare function segmentsDir(root: string, ref: SegmentRef): string;
|
|
4
|
+
/** Absolute path of one `.crbm` generation object. */
|
|
5
|
+
export declare function storageObjectPath(root: string, key: GenKey): string;
|
|
6
|
+
/** Filename pattern for a segment's generations: `<segment>.<gen>.crbm`. */
|
|
7
|
+
export declare function storageObjectFilename(segment: string, generation: number): string;
|
|
8
|
+
/** Parse a generation number out of a `<segment>.<gen>.crbm` filename, or `null` if it doesn't match. */
|
|
9
|
+
export declare function parseGeneration(segment: string, filename: string): number | null;
|
|
10
|
+
/** Directory holding a namespace's registry rows (one file per segment). */
|
|
11
|
+
export declare function registryDir(root: string, namespace: string | undefined): string;
|
|
12
|
+
/** Absolute path of one segment's registry row file: `<ns>/registry/<segment>.reg`. */
|
|
13
|
+
export declare function registryRowPath(root: string, ref: SegmentRef): string;
|
|
14
|
+
/**
|
|
15
|
+
* Parse a segment name out of a `<segment>.reg` filename, or `null` if it doesn't match — including any
|
|
16
|
+
* file whose stem isn't a valid segment name. A strict parser means a stray or planted `.reg` file makes
|
|
17
|
+
* `list()` *skip* it, never abort the whole enumeration on a boundary throw.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseRegistryRow(filename: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a namespace out of a directory name under the storage root, or `null` if it was not written by us.
|
|
22
|
+
*
|
|
23
|
+
* The inverse of the `namespacePathPart(ns)` that {@link segmentsDir} and {@link registryDir}
|
|
24
|
+
* write. A fleet-wide scan enumerates these directories, so getting it wrong does not fail one segment — it
|
|
25
|
+
* aborts the whole enumeration, taking the consistency check, the retention sweep and subject erasure with it.
|
|
26
|
+
*
|
|
27
|
+
* Like {@link parseRegistryRow} this requires the encoding to **round-trip**, so a planted literal `tenant:acme`
|
|
28
|
+
* directory sitting next to the driver's own `tenant%3Aacme` is skipped rather than reported as a namespace
|
|
29
|
+
* whose rows then resolve to the other directory. `_default` maps back to "no namespace".
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseNamespaceDir(entry: string): {
|
|
32
|
+
namespace: string | undefined;
|
|
33
|
+
} | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IRegistryDriver, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, SegmentRef, Token } from '../../core/ports.js';
|
|
2
|
+
export interface LocalFsRegistryDriverOptions {
|
|
3
|
+
/** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
4
|
+
readonly now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
export declare class LocalFsRegistryDriver implements IRegistryDriver {
|
|
7
|
+
private readonly root;
|
|
8
|
+
/** Per-row promise chain — serializes read-modify-write so an in-process CAS never loses an update. */
|
|
9
|
+
private readonly chain;
|
|
10
|
+
private readonly now;
|
|
11
|
+
constructor(root: string, options?: LocalFsRegistryDriverOptions);
|
|
12
|
+
capabilities(): RegCaps;
|
|
13
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
14
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
15
|
+
token: Token;
|
|
16
|
+
}>;
|
|
17
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
18
|
+
token: Token;
|
|
19
|
+
}>;
|
|
20
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
21
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
22
|
+
/** Namespaces to scan: just the one requested, or every namespace dir under the root. */
|
|
23
|
+
private namespaceDirs;
|
|
24
|
+
private readRow;
|
|
25
|
+
private writeRow;
|
|
26
|
+
/** Serialize callbacks for a row path so read-modify-write is atomic in-process. */
|
|
27
|
+
private withRowLock;
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BlobSink } from '../../core/blob.js';
|
|
2
|
+
import type { StorageCaps, GenKey, IStorageDriver, SegmentRef } from '../../core/ports.js';
|
|
3
|
+
export declare class LocalFsStorageDriver implements IStorageDriver {
|
|
4
|
+
private readonly root;
|
|
5
|
+
constructor(root: string);
|
|
6
|
+
capabilities(): StorageCaps;
|
|
7
|
+
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
8
|
+
size: number;
|
|
9
|
+
sha256: string;
|
|
10
|
+
}>;
|
|
11
|
+
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
12
|
+
getTail(key: GenKey, maxBytes: number): Promise<{
|
|
13
|
+
bytes: Uint8Array;
|
|
14
|
+
size: number;
|
|
15
|
+
}>;
|
|
16
|
+
delete(key: GenKey): Promise<void>;
|
|
17
|
+
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
18
|
+
private openRead;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BlobSink } from '../core/blob.js';
|
|
2
|
+
import type { ChunkRef, StorageCaps, StorageChunkSource, GenKey, IStorageDriver, IRegistryDriver, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, SegmentRef, SegmentSize, Token } from '../core/ports.js';
|
|
3
|
+
export declare class MemoryStorageChunkSource implements StorageChunkSource {
|
|
4
|
+
private readonly chunks;
|
|
5
|
+
getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
|
|
6
|
+
listChunkKeys(ref: SegmentRef): Promise<number[]>;
|
|
7
|
+
sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
|
|
8
|
+
/** Test/seed helper — populate immutable Storage bytes for a chunk directly (bypassing the `.crbm` format). */
|
|
9
|
+
seed(ref: ChunkRef, bytes: Uint8Array): void;
|
|
10
|
+
}
|
|
11
|
+
export interface MemoryRegistryDriverOptions {
|
|
12
|
+
/** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now` (drivers may use ambient time). */
|
|
13
|
+
readonly now?: () => number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* In-memory {@link IRegistryDriver} — one record per segment under OCC. The token is a single global,
|
|
17
|
+
* monotonic, never-reused counter (so a record recreated after `delete` always gets a strictly-greater
|
|
18
|
+
* token → ABA-safe even though `delete` removes the row physically).
|
|
19
|
+
*/
|
|
20
|
+
export declare class MemoryRegistryDriver implements IRegistryDriver {
|
|
21
|
+
private readonly rows;
|
|
22
|
+
private readonly now;
|
|
23
|
+
private seq;
|
|
24
|
+
constructor(options?: MemoryRegistryDriverOptions);
|
|
25
|
+
private nextToken;
|
|
26
|
+
capabilities(): RegCaps;
|
|
27
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
28
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
29
|
+
token: Token;
|
|
30
|
+
}>;
|
|
31
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
32
|
+
token: Token;
|
|
33
|
+
}>;
|
|
34
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
35
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* In-memory {@link IStorageDriver} — write-once immutable generation objects as opaque bytes. A "dumb byte
|
|
39
|
+
* mover" (understands neither roaring nor `.crbm`), so it's a faithful storage backend for tests and a zero-setup
|
|
40
|
+
* local storage tier. Mirrors the LocalFs/S3 contract (write-once, range/tail reads).
|
|
41
|
+
*/
|
|
42
|
+
export declare class MemoryStorageDriver implements IStorageDriver {
|
|
43
|
+
private readonly objects;
|
|
44
|
+
capabilities(): StorageCaps;
|
|
45
|
+
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
46
|
+
size: number;
|
|
47
|
+
sha256: string;
|
|
48
|
+
}>;
|
|
49
|
+
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
50
|
+
getTail(key: GenKey, maxBytes: number): Promise<{
|
|
51
|
+
bytes: Uint8Array;
|
|
52
|
+
size: number;
|
|
53
|
+
}>;
|
|
54
|
+
delete(key: GenKey): Promise<void>;
|
|
55
|
+
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
56
|
+
private require;
|
|
57
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry decorators.
|
|
3
|
+
*
|
|
4
|
+
* Transparent wrappers that add bounded, jittered retry of **transient** faults to any driver, using the one
|
|
5
|
+
* shared `core/retry` primitive — so every backend (S3, GCS, Azure Blob, LocalFS, …) inherits the same
|
|
6
|
+
* policy instead of each rolling its own. Pure composition over the port interfaces (no SDK, no I/O of their
|
|
7
|
+
* own); the wrapped driver is responsible for *classifying* its transient faults (raising {@link TransientError});
|
|
8
|
+
* these decorators decide *whether and when* to retry.
|
|
9
|
+
*
|
|
10
|
+
* **Streaming methods buffer**, deliberately: a partially-consumed async iterator cannot be resumed mid-stream
|
|
11
|
+
* (it would re-yield earlier items), so `RetryingStorageDriver.list` and `RetryingRegistryDriver.list` collect the
|
|
12
|
+
* whole enumeration inside `withRetry` and re-run it from the start on a fault. Both are discovery scans over a
|
|
13
|
+
* generation or namespace listing, where the result set is small and whole-scan retry is worth the memory.
|
|
14
|
+
*
|
|
15
|
+
* Point methods are retried in place, with nothing to buffer.
|
|
16
|
+
*
|
|
17
|
+
* What is **not** retried here: {@link WriteConflictError} (OCC — the publish loop owns that; a blind replay
|
|
18
|
+
* would re-apply against a stale token), and every deterministic error
|
|
19
|
+
* (`ValidationError`/`IntegrityError`/`NotFoundError`/…). Default classifier: {@link isTransientError}.
|
|
20
|
+
*/
|
|
21
|
+
import type { Clock, Rng } from '../../core/determinism.js';
|
|
22
|
+
import type { RetryPolicy } from '../../core/retry.js';
|
|
23
|
+
import type { ChunkRef, StorageCaps, StorageChunkSource, GenKey, IStorageDriver, IRegistryDriver, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, SegmentRef, SegmentSize, Token } from '../../core/ports.js';
|
|
24
|
+
import type { BlobSink } from '../../core/blob.js';
|
|
25
|
+
export interface RetryingOptions {
|
|
26
|
+
readonly clock: Clock;
|
|
27
|
+
readonly rng: Rng;
|
|
28
|
+
/** Defaults to {@link DEFAULT_RETRY_POLICY}. */
|
|
29
|
+
readonly policy?: RetryPolicy;
|
|
30
|
+
/** Override which errors are retryable. Default: {@link isTransientError} (any `TransientError`). */
|
|
31
|
+
readonly isRetryable?: (err: unknown) => boolean;
|
|
32
|
+
/** Observability hook fired before each backoff wait. */
|
|
33
|
+
readonly onRetry?: (info: {
|
|
34
|
+
attempt: number;
|
|
35
|
+
delayMs: number;
|
|
36
|
+
err: unknown;
|
|
37
|
+
}) => void;
|
|
38
|
+
}
|
|
39
|
+
/** Wrap a storage chunk source so its reads retry transient faults. */
|
|
40
|
+
export declare class RetryingStorageChunkSource implements StorageChunkSource {
|
|
41
|
+
private readonly inner;
|
|
42
|
+
private readonly policy;
|
|
43
|
+
private readonly deps;
|
|
44
|
+
/** Present only when the inner source supports it — so capability detection stays honest. */
|
|
45
|
+
readonly sizeOf?: (ref: SegmentRef) => Promise<SegmentSize | null>;
|
|
46
|
+
readonly cardinalities?: (ref: SegmentRef) => Promise<ReadonlyMap<number, number> | null>;
|
|
47
|
+
readonly currentGeneration?: (ref: SegmentRef) => Promise<number | null>;
|
|
48
|
+
readonly invalidate?: (ref: SegmentRef) => void;
|
|
49
|
+
readonly exists?: (ref: SegmentRef) => Promise<boolean>;
|
|
50
|
+
readonly currentVersion?: (ref: SegmentRef) => Promise<string | null>;
|
|
51
|
+
constructor(inner: StorageChunkSource, opts: RetryingOptions);
|
|
52
|
+
getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
|
|
53
|
+
listChunkKeys(ref: SegmentRef): Promise<number[]>;
|
|
54
|
+
}
|
|
55
|
+
/** Wrap a storage driver so its byte operations retry transient faults. */
|
|
56
|
+
export declare class RetryingStorageDriver implements IStorageDriver {
|
|
57
|
+
private readonly inner;
|
|
58
|
+
private readonly policy;
|
|
59
|
+
private readonly deps;
|
|
60
|
+
constructor(inner: IStorageDriver, opts: RetryingOptions);
|
|
61
|
+
capabilities(): StorageCaps;
|
|
62
|
+
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
63
|
+
size: number;
|
|
64
|
+
sha256: string;
|
|
65
|
+
}>;
|
|
66
|
+
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
67
|
+
getTail(key: GenKey, maxBytes: number): Promise<{
|
|
68
|
+
bytes: Uint8Array;
|
|
69
|
+
size: number;
|
|
70
|
+
}>;
|
|
71
|
+
delete(key: GenKey): Promise<void>;
|
|
72
|
+
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
73
|
+
}
|
|
74
|
+
/** Wrap a registry driver so its calls retry transient faults. CAS conflicts are not retried (caller-owned). */
|
|
75
|
+
export declare class RetryingRegistryDriver implements IRegistryDriver {
|
|
76
|
+
private readonly inner;
|
|
77
|
+
private readonly policy;
|
|
78
|
+
private readonly deps;
|
|
79
|
+
constructor(inner: IRegistryDriver, opts: RetryingOptions);
|
|
80
|
+
capabilities(): RegCaps;
|
|
81
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
82
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
83
|
+
token: Token;
|
|
84
|
+
}>;
|
|
85
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
86
|
+
token: Token;
|
|
87
|
+
}>;
|
|
88
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
89
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
90
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CodecInterface } from '../core/codec.js';
|
|
2
|
+
import type { IRegistryDriver, SegmentRef } from '../core/ports.js';
|
|
3
|
+
/** Output format for {@link runExport} / `CloudRoaring.exportSegments`. */
|
|
4
|
+
export type ExportFormat = 'roaring' | 'ndjson';
|
|
5
|
+
/** A writer for one segment's output — inject a filesystem file, an S3 upload, stdout, or a test buffer. */
|
|
6
|
+
export interface ExportWriter {
|
|
7
|
+
/** Append bytes (called one or more times; for `ndjson`, batched line chunks; for `roaring`, once). */
|
|
8
|
+
write(bytes: Uint8Array): Promise<void> | void;
|
|
9
|
+
/** **Commit** the output — called once, only on success (all bytes written). Finalize/flush/rename here. */
|
|
10
|
+
close(): Promise<void> | void;
|
|
11
|
+
/**
|
|
12
|
+
* **Discard** a partial output — called instead of `close()` if a fault occurs mid-segment, so a truncated file
|
|
13
|
+
* never masquerades as complete (e.g. delete the temp file). Optional; sinks with nothing to undo can omit it.
|
|
14
|
+
*/
|
|
15
|
+
abort?(): Promise<void> | void;
|
|
16
|
+
}
|
|
17
|
+
/** A destination the export writes each segment to. `ext` is `.roaring` or `.ndjson`. */
|
|
18
|
+
export interface ExportSink {
|
|
19
|
+
open(ref: SegmentRef, ext: string): Promise<ExportWriter> | ExportWriter;
|
|
20
|
+
}
|
|
21
|
+
export interface ExportOptions {
|
|
22
|
+
/** Output format; defaults to `'roaring'`. */
|
|
23
|
+
readonly format?: ExportFormat;
|
|
24
|
+
/**
|
|
25
|
+
* Bitmap codec used to build the exported bitmap for the `'roaring'` format. Optional in the
|
|
26
|
+
* type so this stays call-compatible public API; a **flavor** package binds it (see `requireCodec`). Not needed
|
|
27
|
+
* for `'ndjson'`, which streams plain ids.
|
|
28
|
+
*/
|
|
29
|
+
readonly codec?: CodecInterface;
|
|
30
|
+
/** Scope the export to one namespace; omit to export every registered segment. */
|
|
31
|
+
readonly namespace?: string;
|
|
32
|
+
/** `ndjson` flush threshold in ~bytes (ids are ASCII, so ≈ chars); defaults to 64 KiB. */
|
|
33
|
+
readonly ndjsonBatchBytes?: number;
|
|
34
|
+
}
|
|
35
|
+
/** Per-segment result recorded in the {@link ExportManifest}. */
|
|
36
|
+
export interface ExportedSegment {
|
|
37
|
+
readonly segment: string;
|
|
38
|
+
readonly namespace?: string;
|
|
39
|
+
/** Ids written (the segment's effective cardinality at export time). */
|
|
40
|
+
readonly count: number;
|
|
41
|
+
/** Bytes written to the sink for this segment. */
|
|
42
|
+
readonly bytes: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A segment that could **not** be exported — recorded rather than aborting the whole run (fault isolation), so a
|
|
46
|
+
* single corrupt/undecryptable segment never blocks the rest of the dump.
|
|
47
|
+
*/
|
|
48
|
+
export interface ExportFailure {
|
|
49
|
+
readonly segment: string;
|
|
50
|
+
readonly namespace?: string;
|
|
51
|
+
/** Why it failed (an error message) — e.g. a corrupt storage object, or an encrypted segment with no keystore. */
|
|
52
|
+
readonly error: string;
|
|
53
|
+
}
|
|
54
|
+
/** What {@link runExport} produced — a self-describing summary the CLI persists as `manifest.json`. */
|
|
55
|
+
export interface ExportManifest {
|
|
56
|
+
/** Schema version of this manifest shape (currently `1`) — insurance for evolving the format post-1.0. */
|
|
57
|
+
readonly version: number;
|
|
58
|
+
readonly format: ExportFormat;
|
|
59
|
+
readonly totalSegments: number;
|
|
60
|
+
readonly totalIds: number;
|
|
61
|
+
readonly segments: readonly ExportedSegment[];
|
|
62
|
+
/**
|
|
63
|
+
* Segments that could not be read (empty when everything succeeded). Its presence in the manifest is the point:
|
|
64
|
+
* "a manifest exists" ⇒ the run finished, **not** that every segment succeeded — always check this list.
|
|
65
|
+
*/
|
|
66
|
+
readonly failed: readonly ExportFailure[];
|
|
67
|
+
}
|
|
68
|
+
/** The minimal read surface the export needs from a store — a `CloudRoaring` satisfies this structurally. */
|
|
69
|
+
export interface SegmentReader {
|
|
70
|
+
segment(name: string, options?: {
|
|
71
|
+
namespace?: string;
|
|
72
|
+
}): {
|
|
73
|
+
iterate(): AsyncIterable<number>;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Export every registered segment's current generation through `sink`. Enumerates via `registry`, reads via
|
|
78
|
+
* `reader` (the store). Returns a manifest. `CloudRoaring.exportSegments` is the
|
|
79
|
+
* public entry — it calls this with its own store + registry (so enumeration and the read path provably share one
|
|
80
|
+
* registry).
|
|
81
|
+
*
|
|
82
|
+
* **Fault isolation:** a per-segment read/write fault aborts that segment's partial (so a truncated file never
|
|
83
|
+
* looks complete), records it in `failed`, and the run continues — one bad segment never blocks the rest. A
|
|
84
|
+
* `close()` fault is likewise recorded (never triggering `abort()` — they're mutually exclusive).
|
|
85
|
+
*
|
|
86
|
+
* Re-running overwrites the segments it re-exports but does **not** prune files for segments that have since
|
|
87
|
+
* disappeared — export to a fresh directory for a clean dump. For a *current* image, run against a
|
|
88
|
+
* freshly-constructed store: a store's storage source re-resolves each segment's generation on a short TTL (or pins
|
|
89
|
+
* it for its lifetime when built without a clock or registry), so a long-lived store may export a view one load
|
|
90
|
+
* behind.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runExport(reader: SegmentReader, registry: IRegistryDriver, sink: ExportSink, options?: ExportOptions): Promise<ExportManifest>;
|