@cloudbitmaps/core 0.16.0 → 0.17.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.
@@ -1,4 +1,4 @@
1
- import type { IRegistryDriver, NewRegistryRecord, RegCaps, RegistryPatch, RegistryRecord, RegistryWriteOptions, SegmentRef, Token } from '../../core/ports.js';
1
+ import type { IRegistryDriver, NewRegistryRecord, ReapLegacyTombstonesOptions, ReapLegacyTombstonesResult, RegCaps, RegistryPatch, RegistryRecord, RegistryWriteOptions, SegmentRef, Token } from '../../core/ports.js';
2
2
  import type { Entropy } from '../../core/determinism.js';
3
3
  /** Defensive cap on a single registry object read from storage, before allocation (rows are tiny; ~1 KB). */
4
4
  export declare const MAX_ROW_BYTES: number;
@@ -79,6 +79,11 @@ export interface ObjectRegistryStore {
79
79
  * a live one included. Absent or `false`, the registry never calls `delete` and tombstones every row instead.
80
80
  */
81
81
  readonly conditionalDelete?: boolean;
82
+ /**
83
+ * Optional: settle {@link conditionalDelete} without a request to the backend, for a store that learns it from its
84
+ * client rather than knowing it at construction. A caller that must refuse before its first request awaits it first.
85
+ */
86
+ resolveCapabilities?(): Promise<void>;
82
87
  }
83
88
  export declare class ObjectStoreRegistry implements IRegistryDriver {
84
89
  private readonly store;
@@ -114,6 +119,19 @@ export declare class ObjectStoreRegistry implements IRegistryDriver {
114
119
  private observedRow;
115
120
  list(namespace?: string): AsyncIterable<RegistryRecord>;
116
121
  delete(ref: SegmentRef, expected?: Token): Promise<void>;
122
+ /**
123
+ * Remove the `deleted: true` rows with no incarnation id (the envelopes a release before 0.12 left), or count them.
124
+ * One listing, a read per key and a conditional delete per envelope, so a row that a create wrote over since it was
125
+ * read keeps its new version and the delete is refused. Refuses with {@link CapabilityError}, before any request,
126
+ * where the store's delete is not known to apply its precondition.
127
+ *
128
+ * An object that cannot be read or parsed stops the run with the error that names its key, and removes nothing
129
+ * more: it may be a newer release's row. Rows removed before the stop stay removed, and a re-run is safe.
130
+ *
131
+ * Not resumable: it lists and reads from the start on every call. A delete refused as a conflict, including one for a
132
+ * row already gone, is counted as `raced`.
133
+ */
134
+ reapLegacyTombstones(options: ReapLegacyTombstonesOptions): Promise<ReapLegacyTombstonesResult>;
117
135
  /**
118
136
  * Read + parse a registry object, returning its envelope and version fence, or null if absent.
119
137
  *
@@ -1,11 +1,8 @@
1
1
  import type { Entropy } from '../../core/determinism.js';
2
2
  import { incarnationOf } from '../../core/token.js';
3
+ import { usableKeptGens } from '../../core/kept-generations.js';
3
4
  import type { NewRegistryRecord, RegistryPatch, RegistryRecord, SegmentRef, Token } from '../../core/ports.js';
4
- /**
5
- * The fields a stored record may carry: {@link RegistryRecord}'s, and nothing else. A field no reader resolves
6
- * through is refused on read-back like any other corruption (invariant 5), rather than ignored.
7
- */
8
- export declare const RECORD_FIELDS: readonly ["namespace", "segment", "currentGen", "wrappedDeks", "keyId", "status", "retention", "residency", "summary", "createdAt", "updatedAt", "token"];
5
+ export declare const RECORD_FIELDS: readonly ["namespace", "segment", "currentGen", "wrappedDeks", "keyId", "status", "retention", "residency", "summary", "createdAt", "updatedAt", "token", "keptGens", "leases"];
9
6
  /**
10
7
  * Validate the caller-settable fields at `create`, and return the record to store: the caller's, with its summary
11
8
  * replaced by the checked, frozen copy {@link validateSummary} builds.
@@ -38,11 +35,11 @@ export interface RegistryEnvelope {
38
35
  * on a backward-incompatible change. Policy: a **higher** stamp than this build knows → `UnsupportedError`
39
36
  * (fail-closed); an **absent** or malformed stamp → `IntegrityError`, since every row this build writes has one.
40
37
  *
41
- * Schema 2 adds the record's `summary` and the incarnation-form token. Every row this build writes is stamped 2,
42
- * whatever it holds, and a build that reads only schema 1 refuses it, so a fleet cannot go back once one has been
43
- * written.
38
+ * Schema 2 added the record's `summary` and the incarnation-form token; schema 3 adds `keptGens`. Every row this
39
+ * build writes is stamped 3, whatever it holds, and a build that reads only schema 2 refuses it, so a fleet cannot go
40
+ * back once one has been written.
44
41
  */
45
- export declare const REGISTRY_SCHEMA_VERSION = 2;
42
+ export declare const REGISTRY_SCHEMA_VERSION = 3;
46
43
  /**
47
44
  * Validate a persisted registry row's `schemaVersion` (untrusted bytes, invariant 5) and return it: an absent or
48
45
  * malformed value → `IntegrityError`; a version newer than this build → `UnsupportedError` (fail-closed rather than
@@ -51,6 +48,7 @@ export declare const REGISTRY_SCHEMA_VERSION = 2;
51
48
  export declare function assertRegistrySchemaVersion(raw: unknown, ctx: string): number;
52
49
  /** Serialize a registry envelope for persistence, stamping the current {@link REGISTRY_SCHEMA_VERSION}. */
53
50
  export declare function serializeRegistryEnvelope(env: RegistryEnvelope): string;
51
+ export { usableKeptGens };
54
52
  export { incarnationOf };
55
53
  /** A fresh incarnation id from `entropy`: 128 bits as 32 lowercase hex digits. */
56
54
  export declare function drawIncarnation(entropy: Entropy): string;
@@ -64,12 +64,18 @@ export interface ExportManifest {
64
64
  */
65
65
  readonly failed: readonly ExportFailure[];
66
66
  }
67
- /** The minimal read surface the export needs from a store — a `CloudRoaring` satisfies this structurally. */
67
+ /**
68
+ * The minimal read surface the export needs from a store — a `CloudRoaring` satisfies this structurally: `segment(name,
69
+ * { namespace? })` returns a handle whose `pin()` resolves to something that iterates the ids. A segment's
70
+ * `pin()` resolves its generation once and reads only that generation's object, so one segment's export is one instant.
71
+ */
68
72
  export interface SegmentReader {
69
73
  segment(name: string, options?: {
70
74
  namespace?: string;
71
75
  }): {
72
- iterate(): AsyncIterable<number>;
76
+ pin(): Promise<{
77
+ iterate(): AsyncIterable<number>;
78
+ }>;
73
79
  };
74
80
  }
75
81
  /**
@@ -83,11 +89,17 @@ export interface SegmentReader {
83
89
  * `close()` fault is likewise recorded (never triggering `abort()` — they're mutually exclusive).
84
90
  *
85
91
  * Re-running overwrites the segments it re-exports but does **not** prune files for segments that have since
86
- * disappeared — export to a fresh directory for a clean dump. For a *current* image, run against a
87
- * freshly-constructed store: a store's storage source re-resolves each segment's generation on a short TTL, or, with
88
- * no registry, with `cache.genTtlMs: 0`, or built without a clock, only when an eviction, a sweep or an invalidation
89
- * makes it, so a long-lived store may export a view one load behind, or more. Each segment is read live, not pinned:
90
- * a publish while a long segment exports can re-resolve it part-way, and its file then holds chunks of two
91
- * generations, each whole and verified. For one generation per file, export from a quiet window.
92
+ * disappeared — export to a fresh directory for a clean dump.
93
+ *
94
+ * Each segment is pinned for its export: its generation is read from the registry when its export begins, on a warm
95
+ * store as on a cold one, and only that generation's object is read for the whole segment, so a publish during a long
96
+ * segment's export cannot put two generations in one file. A cold store makes one registry read and one tail read
97
+ * per segment; a warm store makes one registry read per segment. A pin holds nothing: if a collection or an erasure
98
+ * removes the pinned generation mid-export, that segment's export fails, recorded in `failed`, and never reads the
99
+ * newer generation. Different segments are different instants.
100
+ *
101
+ * `reader` is any object with `segment(name, { namespace? })` returning a handle with `pin()`, which resolves to
102
+ * `{ iterate(): AsyncIterable<number> }` (see {@link SegmentReader}). A reader without that shape throws a
103
+ * `ValidationError` before any file is opened.
92
104
  */
93
105
  export declare function runExport(reader: SegmentReader, registry: IRegistryDriver, sink: ExportSink, options?: ExportOptions): Promise<ExportManifest>;
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { BoundedLru } from './core/lru.js';
21
21
  export { safeMetrics } from './core/metrics.js';
22
22
  export { groundedReport } from './core/cost.js';
23
23
  export { runExport } from './export/index.js';
24
+ export type { SegmentReader } from './export/index.js';
24
25
  export { splitId } from './core/bit-route.js';
25
26
  export { mapWithConcurrency } from './core/concurrency.js';
26
27
  export { resolveBudget, resolvePerOpBudget, checkBudget, collectWithinBudget } from './core/budget.js';
@@ -32,20 +33,23 @@ export { isStorageBackend } from './core/ports.js';
32
33
  export { LocalFsStorage, MemoryStorage } from './drivers/backends.js';
33
34
  export type { LocalFsStorageOptions, MemoryStorageOptions } from './drivers/backends.js';
34
35
  export { CrbmStorageChunkSource } from './core/crbm-storage-source.js';
35
- export type { CrbmStorageChunkSourceOptions, PinnedObject } from './core/crbm-storage-source.js';
36
+ export type { CrbmStorageChunkSourceOptions, PinLease, PinnedObject, } from './core/crbm-storage-source.js';
37
+ export { LEASE_SKEW_MS, MAX_LEASE_MS, MAX_LEASES_PER_SEGMENT, releaseLease, takeLease, } from './core/leases.js';
38
+ export type { LeaseDeps, LeaseTake, TakenLease } from './core/leases.js';
36
39
  export { PinnedStorageChunkSource } from './core/pinned-storage-source.js';
37
40
  export type { PinnedAt } from './core/pinned-storage-source.js';
38
41
  export { loadSegment, loadSegmentChunks } from './core/load.js';
39
42
  export type { LoadDeps, LoadOptions, LoadGuard, LoadResult, LoadRefusal } from './core/load.js';
40
43
  export type { LoadInput, PortableBitmap } from './core/load-input.js';
44
+ export { decodeSerialized } from './core/load-input.js';
41
45
  export { listGenerations, rollbackSegment } from './core/rollback.js';
42
46
  export type { GenerationListDeps, GenerationEntry, RollbackResult } from './core/rollback.js';
43
47
  export { eraseIdFromSegment } from './core/erase-id.js';
44
48
  export type { EraseIdDeps, EraseIdResult } from './core/erase-id.js';
45
49
  export type { CodecInterface, CodecBitmap, EncodedChunk } from './core/codec.js';
46
50
  export type { Clock, Entropy, Rng } from './core/determinism.js';
47
- export type { StorageChunkSource, IStorageDriver, StorageCaps, ChunkRef, ChunkRead, ReadChunksOptions, SegmentRef, GenKey, IRegistryDriver, StorageBackend, RegistryRecord, NewRegistryRecord, RegistryPatch, RegistryStatus, RegistrySummary, RegistryWriteOptions, ClearRegistrySummary, SealedRegistrySummary, GenerationMetadata, GenerationSummary, RegCaps, GovernanceMeta, SegmentSize, } from './core/ports.js';
48
- export { CloudRoaringError, ValidationError, WriteConflictError, IntegrityError, NotFoundError, UnsupportedError, CapabilityError, TransientError, KeyUnavailableError, BudgetExceededError, isCloudRoaringError, isWriteConflictError, isTransientError, isNotFoundError, isIntegrityError, isValidationError, } from './core/errors.js';
51
+ export type { StorageChunkSource, IStorageDriver, StorageCaps, ChunkRef, ChunkRead, ReadChunksOptions, SegmentRef, GenKey, IRegistryDriver, StorageBackend, RegistryRecord, NewRegistryRecord, RegistryPatch, RegistryStatus, RegistrySummary, RegistryWriteOptions, LeaseEntry, ReapLegacyTombstonesOptions, ReapLegacyTombstonesResult, ClearRegistrySummary, SealedRegistrySummary, GenerationMetadata, GenerationSummary, RegCaps, GovernanceMeta, SegmentSize, } from './core/ports.js';
52
+ export { CloudRoaringError, ValidationError, WriteConflictError, IntegrityError, NotFoundError, UnsupportedError, CapabilityError, TransientError, KeyUnavailableError, BudgetExceededError, LeaseExpiredError, LeaseLimitError, isCloudRoaringError, isWriteConflictError, isTransientError, isNotFoundError, isIntegrityError, isValidationError, isLeaseExpiredError, isLeaseLimitError, } from './core/errors.js';
49
53
  export type { Aead, AeadSealed, IKeystore, WrappedDek, CrbmCrypto } from './core/crypto.js';
50
54
  export { aadFor } from './core/crypto.js';
51
55
  export { NodeAead, InProcessKeystore } from './drivers/crypto.js';
@@ -55,6 +59,8 @@ export type { DropDeps, DropResult, EraseDeps, DestroyResult } from './core/eras
55
59
  export { setSegmentRetention, clearSegmentRetention, getSegmentRetention, readRetentionPolicy, MIN_EXPIRES_AT_MS, } from './core/retention.js';
56
60
  export type { RetentionPolicy, RetentionDeps, SetRetentionResult } from './core/retention.js';
57
61
  export { retireExpired } from './core/retention-sweep.js';
62
+ export { reapRegistryTombstones } from './core/registry-reap.js';
63
+ export type { ReapRegistryTombstonesOptions, ReapRegistryTombstonesResult, } from './core/registry-reap.js';
58
64
  export { excludingReservedRows } from './core/registry-scan.js';
59
65
  export type { RetireExpiredOptions, RetireExpiredResult, RetireEntry, } from './core/retention-sweep.js';
60
66
  export { DEFAULT_BUDGET } from './core/budget.js';