@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.
Files changed (132) hide show
  1. package/NOTICE +4 -4
  2. package/README.md +28 -6
  3. package/dist/chunk-BUPDGZRR.js +461 -0
  4. package/dist/chunk-BUPDGZRR.js.map +7 -0
  5. package/dist/core/audit.d.ts +127 -0
  6. package/dist/core/bit-route.d.ts +21 -0
  7. package/dist/core/blob.d.ts +34 -0
  8. package/dist/core/budget.d.ts +66 -0
  9. package/dist/core/codec.d.ts +114 -0
  10. package/dist/core/concurrency.d.ts +15 -0
  11. package/dist/core/consistency.d.ts +73 -0
  12. package/dist/core/cooperative.d.ts +78 -0
  13. package/dist/core/cost.d.ts +102 -0
  14. package/dist/core/crbm/crc32c.d.ts +10 -0
  15. package/dist/core/crbm/format.d.ts +116 -0
  16. package/dist/core/crbm/reader.d.ts +69 -0
  17. package/dist/core/crbm/varint.d.ts +13 -0
  18. package/dist/core/crbm/writer.d.ts +39 -0
  19. package/dist/core/crbm-storage-source.d.ts +432 -0
  20. package/dist/core/crypto.d.ts +80 -0
  21. package/dist/core/determinism.d.ts +42 -0
  22. package/dist/core/discover.d.ts +75 -0
  23. package/dist/core/due-index.d.ts +55 -0
  24. package/dist/core/engine.d.ts +204 -0
  25. package/dist/core/erase-id.d.ts +141 -0
  26. package/dist/core/erasure.d.ts +179 -0
  27. package/dist/core/errors.d.ts +120 -0
  28. package/dist/core/generation-gc.d.ts +52 -0
  29. package/dist/core/keys.d.ts +23 -0
  30. package/dist/core/load.d.ts +102 -0
  31. package/dist/core/lru.d.ts +69 -0
  32. package/dist/core/metrics.d.ts +132 -0
  33. package/dist/core/name-codec.d.ts +79 -0
  34. package/dist/core/pinned-storage-source.d.ts +69 -0
  35. package/dist/core/ports.d.ts +340 -0
  36. package/dist/core/registry-scan.d.ts +38 -0
  37. package/dist/core/retention-sweep.d.ts +190 -0
  38. package/dist/core/retention.d.ts +67 -0
  39. package/dist/core/retry.d.ts +65 -0
  40. package/dist/core/rollback.d.ts +71 -0
  41. package/dist/core/validate.d.ts +20 -0
  42. package/dist/driver-kit.d.ts +10 -0
  43. package/dist/driver-kit.js +314 -0
  44. package/dist/driver-kit.js.map +7 -0
  45. package/dist/drivers/_shared/aws-errors.d.ts +18 -0
  46. package/dist/drivers/_shared/keys.d.ts +27 -0
  47. package/dist/drivers/_shared/object-registry-keys.d.ts +28 -0
  48. package/dist/drivers/_shared/object-registry.d.ts +84 -0
  49. package/dist/drivers/_shared/registry.d.ts +62 -0
  50. package/dist/drivers/backends.d.ts +49 -0
  51. package/dist/drivers/crypto.d.ts +38 -0
  52. package/dist/drivers/localfs/fs-util.d.ts +20 -0
  53. package/dist/drivers/localfs/paths.d.ts +33 -0
  54. package/dist/drivers/localfs/registry.d.ts +28 -0
  55. package/dist/drivers/localfs/storage.d.ts +19 -0
  56. package/dist/drivers/memory.d.ts +57 -0
  57. package/dist/drivers/retry/retrying-drivers.d.ts +90 -0
  58. package/dist/export/index.d.ts +92 -0
  59. package/dist/index.d.ts +85 -2534
  60. package/dist/index.js +3076 -2978
  61. package/dist/index.js.map +7 -1
  62. package/dist/testing/fuzz-core.d.ts +14 -0
  63. package/package.json +19 -115
  64. package/dist/azure/index.cjs +0 -368
  65. package/dist/azure/index.cjs.map +0 -1
  66. package/dist/azure/index.d.cts +0 -48
  67. package/dist/azure/index.d.ts +0 -48
  68. package/dist/azure/index.js +0 -304
  69. package/dist/azure/index.js.map +0 -1
  70. package/dist/cassandra/index.cjs +0 -265
  71. package/dist/cassandra/index.cjs.map +0 -1
  72. package/dist/cassandra/index.d.cts +0 -52
  73. package/dist/cassandra/index.d.ts +0 -52
  74. package/dist/cassandra/index.js +0 -204
  75. package/dist/cassandra/index.js.map +0 -1
  76. package/dist/chunk-7LMLYSVJ.js +0 -43
  77. package/dist/chunk-7LMLYSVJ.js.map +0 -1
  78. package/dist/chunk-L54FL5DW.js +0 -203
  79. package/dist/chunk-L54FL5DW.js.map +0 -1
  80. package/dist/chunk-NUIDEEFZ.js +0 -91
  81. package/dist/chunk-NUIDEEFZ.js.map +0 -1
  82. package/dist/chunk-SNJVZ227.js +0 -35
  83. package/dist/chunk-SNJVZ227.js.map +0 -1
  84. package/dist/chunk-YEHMG6W2.js +0 -6
  85. package/dist/chunk-YEHMG6W2.js.map +0 -1
  86. package/dist/dynamodb/index.cjs +0 -740
  87. package/dist/dynamodb/index.cjs.map +0 -1
  88. package/dist/dynamodb/index.d.cts +0 -106
  89. package/dist/dynamodb/index.d.ts +0 -106
  90. package/dist/dynamodb/index.js +0 -469
  91. package/dist/dynamodb/index.js.map +0 -1
  92. package/dist/gcs/index.cjs +0 -343
  93. package/dist/gcs/index.cjs.map +0 -1
  94. package/dist/gcs/index.d.cts +0 -46
  95. package/dist/gcs/index.d.ts +0 -46
  96. package/dist/gcs/index.js +0 -279
  97. package/dist/gcs/index.js.map +0 -1
  98. package/dist/index.cjs +0 -4608
  99. package/dist/index.cjs.map +0 -1
  100. package/dist/index.d.cts +0 -2535
  101. package/dist/mongodb/index.cjs +0 -260
  102. package/dist/mongodb/index.cjs.map +0 -1
  103. package/dist/mongodb/index.d.cts +0 -45
  104. package/dist/mongodb/index.d.ts +0 -45
  105. package/dist/mongodb/index.js +0 -199
  106. package/dist/mongodb/index.js.map +0 -1
  107. package/dist/mysql/index.cjs +0 -281
  108. package/dist/mysql/index.cjs.map +0 -1
  109. package/dist/mysql/index.d.cts +0 -56
  110. package/dist/mysql/index.d.ts +0 -56
  111. package/dist/mysql/index.js +0 -211
  112. package/dist/mysql/index.js.map +0 -1
  113. package/dist/ports-5BQTGKze.d.cts +0 -356
  114. package/dist/ports-5BQTGKze.d.ts +0 -356
  115. package/dist/postgres/index.cjs +0 -273
  116. package/dist/postgres/index.cjs.map +0 -1
  117. package/dist/postgres/index.d.cts +0 -54
  118. package/dist/postgres/index.d.ts +0 -54
  119. package/dist/postgres/index.js +0 -203
  120. package/dist/postgres/index.js.map +0 -1
  121. package/dist/redis/index.cjs +0 -257
  122. package/dist/redis/index.cjs.map +0 -1
  123. package/dist/redis/index.d.cts +0 -42
  124. package/dist/redis/index.d.ts +0 -42
  125. package/dist/redis/index.js +0 -197
  126. package/dist/redis/index.js.map +0 -1
  127. package/dist/s3/index.cjs +0 -891
  128. package/dist/s3/index.cjs.map +0 -1
  129. package/dist/s3/index.d.cts +0 -112
  130. package/dist/s3/index.d.ts +0 -112
  131. package/dist/s3/index.js +0 -555
  132. package/dist/s3/index.js.map +0 -1
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Bridges the `.crbm` archive format to the engine's Storage seam.
3
+ *
4
+ * `CrbmStorageChunkSource` implements the {@link StorageChunkSource} the engine reads through, over an
5
+ * {@link IStorageDriver}: it resolves a segment's current generation, opens its {@link CrbmReader} once, and serves
6
+ * per-chunk payloads. `writeCrbmGeneration` / `writeCrbmGenerationStream` are the write primitives (a generation
7
+ * built from bitmaps, in memory or streamed), `bulkLoadCrbmGeneration` is the load path over them, and
8
+ * `publishGeneration` is the forward-only pointer advance every write ends with.
9
+ */
10
+ import { type IAuditSink } from './audit.js';
11
+ import type { Yielder } from './cooperative.js';
12
+ import type { Clock } from './determinism.js';
13
+ import type { CrbmCrypto, IKeystore, WrappedDek } from './crypto.js';
14
+ import type { ChunkRef, StorageChunkSource, GenKey, IStorageDriver, IRegistryDriver, SegmentRef, SegmentSize, Token } from './ports.js';
15
+ import { CrbmReader } from './crbm/reader.js';
16
+ import type { CrbmReaderOptions } from './crbm/reader.js';
17
+ import type { CodecBitmap, CodecInterface } from './codec.js';
18
+ export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
19
+ /**
20
+ * Optional {@link IRegistryDriver}. When provided, the current generation is resolved via the registry's
21
+ * authoritative `currentGen` (one cheap strong read) instead of a `list` scan of every generation — the
22
+ * retirement of that scan. When absent, the source falls back to the `list`-scan (so the
23
+ * in-memory / simple setups keep working with no registry). The resolved generation is cached and
24
+ * **re-resolved on a short TTL** ({@link currentGenTtlMs}, needs a {@link clock}) so a long-lived source
25
+ * observes a load's new generation within the TTL instead of pinning one generation forever.
26
+ */
27
+ readonly registry?: IRegistryDriver;
28
+ /**
29
+ * Optional {@link IKeystore} for reading **encrypted** segments. When a segment's registry record
30
+ * carries wrapped DEK(s), the source unwraps the DEK via this keystore and decrypts each chunk/index. Reading
31
+ * an encrypted segment without a keystore throws {@link KeyUnavailableError}; cleartext segments ignore it.
32
+ * Requires a `registry` (that's where the wrapped DEKs live).
33
+ */
34
+ readonly keystore?: IKeystore;
35
+ /**
36
+ * Enforce that every segment read is encrypted. When true, resolving a **cleartext** segment (no
37
+ * wrapped DEKs) throws {@link KeyUnavailableError} — a guard against silently reading a segment that should
38
+ * have been encrypted. Off by default (encryption is opt-in).
39
+ */
40
+ readonly requireEncryption?: boolean;
41
+ /**
42
+ * Time source for the current-generation TTL refresh — the determinism seam; `core/` never reads
43
+ * ambient time. Refresh needs **both** a clock and a `registry`; without either the source **pins** the
44
+ * first-resolved generation for its lifetime (the behaviour before the registry was wired in). The `CloudRoaring` facade passes its
45
+ * clock automatically, so wiring a `registry` is enough to get live cross-generation invalidation.
46
+ */
47
+ readonly clock?: Pick<Clock, 'now'>;
48
+ /**
49
+ * How long (ms) a resolved `currentGen` is trusted before the next read re-resolves it (default 2000) — the
50
+ * bound on read staleness after a load publishes: a reader may serve the prior generation for up to this long,
51
+ * then converges. Lazy (checked on read — no timer); ≤ one cheap registry read per segment per window, and a
52
+ * new {@link CrbmReader} is opened only when the generation actually changed. `0` (or no clock) ⇒ pin forever.
53
+ */
54
+ readonly currentGenTtlMs?: number;
55
+ /**
56
+ * Hard ceiling on how many segments' readers (each holding a fully-parsed `.crbm` index) are cached at once
57
+ * (default 1024) — a memory bound for a long-running server. Past it, the least-recently-used
58
+ * segment's reader is evicted; the next read of an evicted segment re-opens it (one cheap tail GET, since
59
+ * generations are immutable). Raise it for a big cache working set of small segments.
60
+ */
61
+ readonly maxOpenSegments?: number;
62
+ /**
63
+ * Aggregate byte ceiling on the parsed `.crbm` indices resident in the reader cache (default 64 MiB) — the
64
+ * **second half of that memory bound**. `maxOpenSegments` alone bounds by *count*, but a wide/dense segment's
65
+ * parsed index can be several MB, so 1024 wide indices could pin ~GBs and blow a small heap (e.g. a 128 MB
66
+ * Lambda) while the count is nominally "in bounds". This caps the summed {@link CrbmReader.retainedIndexBytes}
67
+ * across cached readers; the least-recently-used reader is evicted once the total would exceed it — whichever
68
+ * of the count/byte bounds binds first. Lower it for memory-tight deployments with wide segments; a single
69
+ * segment whose index alone exceeds the budget is still cached (it can't be shrunk) but nothing else alongside.
70
+ */
71
+ readonly maxOpenIndexBytes?: number;
72
+ }
73
+ export declare class CrbmStorageChunkSource implements StorageChunkSource {
74
+ private readonly driver;
75
+ /**
76
+ * One resolved reader per segment, re-resolved on a short TTL ({@link CrbmStorageChunkSourceOptions.currentGenTtlMs},
77
+ * needs a clock). Within the TTL a segment's Storage bytes are treated as an immutable snapshot; when the TTL
78
+ * elapses the next read cheaply re-resolves `currentGen` and, only if it advanced (a load published),
79
+ * opens the new generation — so a long-lived source observes new generations within the TTL rather than
80
+ * pinning one forever. The engine pairs this with a **generation-keyed** cache so a bump never
81
+ * serves a stale decoded chunk. Without a clock or a registry the source pins the first generation for its
82
+ * lifetime (the behaviour before the registry was wired in). A segment with no generation yet is not memoized, so it's re-checked until
83
+ * one exists. **Bounded** by a {@link BoundedLru} ({@link CrbmStorageChunkSourceOptions.maxOpenSegments}, default
84
+ * 1024): past the ceiling the least-recently-used segment's reader (and its parsed index) is evicted — the
85
+ * steady-state memory bound; re-opening an evicted segment is one cheap tail GET.
86
+ */
87
+ private readonly snapshots;
88
+ private readonly registry;
89
+ private readonly keystore;
90
+ private readonly requireEncryption;
91
+ private readonly readerOptions;
92
+ private readonly clock;
93
+ private readonly currentGenTtlMs;
94
+ constructor(driver: IStorageDriver, options?: CrbmStorageChunkSourceOptions);
95
+ /** The current resolved reader for a segment, refreshing on the TTL. Cheap within the TTL window. */
96
+ private resolvedReader;
97
+ /** TTL elapsed: cheaply re-resolve `currentGen`; reopen only if it actually changed, else reuse the prior reader. */
98
+ private computeRefreshedReader;
99
+ /**
100
+ * Memoize a reader-promise as the segment's snapshot at `now()`, forgetting it later if it resolves to
101
+ * null / throws (identity-guarded, so a stale cleanup never clobbers a fresher snapshot). Installing the
102
+ * (pending) promise **synchronously** is what lets concurrent callers coalesce onto one in-flight resolve.
103
+ */
104
+ private install;
105
+ private now;
106
+ private expired;
107
+ /**
108
+ * The segment's current generation number — the engine keys its chunk cache by this so a generation bump
109
+ * is observed instead of serving a stale decoded chunk. Served from the (TTL-refreshed)
110
+ * snapshot, so no extra backend read within the TTL window. `null` if the segment has no committed generation.
111
+ *
112
+ * Heals a swept generation exactly like {@link withFreshSnapshot} — the engine calls this **once per op**,
113
+ * before any chunk fetch, so an unhealed miss here fails the whole operation rather than one chunk. It is
114
+ * spelled out rather than delegated because this is the hottest call in the library: once per operand of
115
+ * every `has`/`count`/`iterate`/`intersect`, and almost always served from the cached snapshot with no
116
+ * backend call at all. Routing it through the generic helper cost ~115 ns/op on that path (a second async
117
+ * frame, a per-call closure, and an `await` on a plain number) for a race that fires only during a
118
+ * concurrent sweep. One retry, then propagate — same contract, same eviction rule.
119
+ */
120
+ currentGeneration(ref: SegmentRef): Promise<number | null>;
121
+ /**
122
+ * Forget everything derived from `ref`: the resolved snapshot and the open reader behind it (and with the
123
+ * reader, the DEK it unwrapped at open time). The next read resolves `currentGen` from the registry again.
124
+ *
125
+ * Needed because the TTL refresh only ever answers "has the pointer moved?", and every *destructive* verb —
126
+ * an erasure that deletes the generation holding the bit, a `dropSegment`, a crypto-shred, a retirement —
127
+ * leaves the pointer's answer unchanged from this source's point of view while making the snapshot wrong.
128
+ * Until this is called the source keeps serving that snapshot with no backend read at all, so nothing on the
129
+ * storage side can close the window; with `cache.genTtlMs: 0` or no clock it never closes.
130
+ */
131
+ invalidate(ref: SegmentRef): void;
132
+ /**
133
+ * Whether the segment is a registered name at all. With a registry that is exactly "a row exists" — including
134
+ * a row with no generation yet (minted by `setRetention` before the first load) and a `destroyed` tombstone,
135
+ * both of which are names somebody deliberately created. Without a registry there is no registration to
136
+ * consult, so the honest answer is whether the bucket holds any generation of it.
137
+ *
138
+ * Deliberately NOT served from the snapshot memo: the memo resolves to `null` for both states this exists to
139
+ * tell apart.
140
+ */
141
+ /**
142
+ * `<generation>` for a registry-less source (no row, so no incarnation to confuse), or
143
+ * `<generation>:<row token>` with one. The token moves on every row write, so an unrelated write (a
144
+ * `setRetention`) costs the segment's decoded chunks once — bounded, and on an admin path. Exactness in the
145
+ * direction that matters: two incarnations can never share a version string.
146
+ */
147
+ currentVersion(ref: SegmentRef): Promise<string | null>;
148
+ /**
149
+ * Resolve the segment **once** and report what a pin should hold: the generation, and the version that
150
+ * identifies those exact bytes. `null` when the segment resolves to no generation — there is nothing to pin,
151
+ * and a caller must treat that as "this handle reads empty", not "pinning is unsupported here".
152
+ */
153
+ pinGeneration(ref: SegmentRef): Promise<{
154
+ generation: number;
155
+ version: string;
156
+ } | null>;
157
+ /**
158
+ * A reader for one **specific** generation, memoized in the same bounded LRU as the live snapshots under a
159
+ * generation-qualified key.
160
+ *
161
+ * Sharing the LRU is the point, not an implementation detail. A pinned reader held in a private field is
162
+ * outside the memory ceiling the library advertises — measured at 10.34 MiB per live pin, 32 pins holding
163
+ * 111.7 MiB against an 8 MiB configured bound. A pinned *generation number* has no such problem: the
164
+ * generation is immutable, so eviction is harmless and re-opening at the same number reproduces the same
165
+ * bytes. It also removes a memoized-rejection bug by construction — `install` forgets a promise that rejects,
166
+ * where a hand-rolled `this.reader ??= open()` cached the rejection for the life of the handle and made a pin
167
+ * the one read path with no resilience.
168
+ *
169
+ * **`status` is re-checked here**, on every open rather than once at pin time. A pin taken before a
170
+ * crypto-shred must not keep unwrapping a DEK the shred destroyed; a destroyed segment resolves no generation
171
+ * for anyone, pinned or not.
172
+ */
173
+ private readerAt;
174
+ private openAt;
175
+ /** The row's current key wrappings, for opening a pinned generation. */
176
+ private deksFor;
177
+ /** Read one chunk of a specific generation — the pinned read path. */
178
+ getChunkAt(ref: ChunkRef, generation: number): Promise<Uint8Array | null>;
179
+ /** Chunk keys of a specific generation — the pinned shape read. */
180
+ listChunkKeysAt(ref: SegmentRef, generation: number): Promise<number[]>;
181
+ /** Per-chunk cardinalities of a specific generation — powers a pinned `count()` with no payload reads. */
182
+ cardinalitiesAt(ref: SegmentRef, generation: number): Promise<ReadonlyMap<number, number> | null>;
183
+ /** Grounded size of a specific generation. */
184
+ sizeOfAt(ref: SegmentRef, generation: number): Promise<SegmentSize | null>;
185
+ exists(ref: SegmentRef): Promise<boolean>;
186
+ /**
187
+ * Evict a snapshot we just failed to read from — matched by its reader *promise*, so a concurrent call's
188
+ * fresher snapshot is never clobbered. The identity guard is the whole point: `install` replaces the entry
189
+ * wholesale, so comparing anything else would drop a good snapshot on the floor.
190
+ */
191
+ private dropStale;
192
+ private resolveLatest;
193
+ /** Open a {@link CrbmReader} for an already-resolved generation target (decrypting if the segment is encrypted). */
194
+ private openForTarget;
195
+ /**
196
+ * The current generation + its DEK wrappings: the registry's authoritative record (one strong read), or a
197
+ * `list` scan for the max generation when there's no registry (a registry-less source can only read cleartext
198
+ * — there's nowhere a wrapped DEK could live).
199
+ */
200
+ private resolveTarget;
201
+ /** Build the per-generation decryption context for an encrypted segment (undefined for cleartext). */
202
+ private cryptoForRead;
203
+ getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
204
+ listChunkKeys(ref: SegmentRef): Promise<number[]>;
205
+ sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
206
+ cardinalities(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;
207
+ /**
208
+ * Run `read` against the pinned snapshot, healing the torn-read window generation GC can open: if the
209
+ * generation we resolved was superseded *and* swept (the grace window elapsed), the Storage driver throws
210
+ * {@link NotFoundError}. Surfacing that as a query failure would turn a benign, recoverable race into a
211
+ * caller-visible error, so instead we drop the stale snapshot,
212
+ * re-resolve `currentGen`, and retry once — the read then serves the newer (committed, immutable) generation,
213
+ * a monotonic move forward — within one incarnation of the row. A name that was retired and re-created is a
214
+ * different segment and can resolve to a LOWER generation, which is why the snapshot carries the row's token
215
+ * as its lineage rather than trusting the generation number (invariant 1).
216
+ *
217
+ * **Resolution and open are inside the retry, not before it.** Resolving `currentGen` and opening that
218
+ * generation's object are two backend round trips with a gap between them, so the object can be swept after
219
+ * the registry named it and before its tail is read — and that miss surfaces from awaiting the snapshot, not
220
+ * from `read`. Awaiting outside the retry left the *first* attempt unhealed on the one path GC actually
221
+ * races: `keep: 0`, which every id erasure passes, sweeps microseconds after the publish.
222
+ *
223
+ * A *second* NotFound is pathological (GC outrunning resolution) and propagates rather than fabricating an
224
+ * absent answer — never return a wrong result. `ifGone` is returned when the segment has no committed
225
+ * generation to serve: storage is empty, or the row was dropped or crypto-shredded, in which case reading empty
226
+ * is the documented outcome rather than a failure.
227
+ *
228
+ * **Bounded to exactly two resolve-and-open round trips.** That is a cost contract, not a detail: the retry
229
+ * now re-reads the *registry*, so an unbounded one would hammer the shared, throttle-prone resource in a
230
+ * tight loop on a segment whose pointer names an object that is permanently absent — and an N-way
231
+ * `intersect` would do it N times. Gated in `tests/core/storage-source-heal-open.test.ts` by counting calls.
232
+ */
233
+ private withFreshSnapshot;
234
+ }
235
+ /**
236
+ * Write one immutable generation from in-memory bitmaps (the seed / bulk-load primitive). Chunks are
237
+ * sorted ascending and empty bitmaps skipped (empty chunks are never stored). Returns the driver's
238
+ * `{ size, sha256 }` for the written object. Pass `options.crypto` to AES-256-GCM-encrypt the generation
239
+ * (built from the segment's DEK, with associated data bound to `(segment, generation)`).
240
+ */
241
+ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey, chunks: Iterable<{
242
+ chunkKey: number;
243
+ bitmap: CodecBitmap;
244
+ }>, options?: {
245
+ crypto?: CrbmCrypto;
246
+ clock?: Yielder;
247
+ }): Promise<{
248
+ size: number;
249
+ sha256: string;
250
+ }>;
251
+ /** What {@link writeCrbmGenerationStream} wrote: the driver's `{ size, sha256 }` + a tally of the generation. */
252
+ export interface StreamWriteResult {
253
+ readonly size: number;
254
+ readonly sha256: string;
255
+ /** Non-empty chunk keys written, in ascending order (for the post-write verify, since the stream is consumed). */
256
+ readonly chunkKeys: number[];
257
+ /** Total ids written. */
258
+ readonly cardinality: number;
259
+ }
260
+ /**
261
+ * Streaming variant of {@link writeCrbmGeneration} for a **constant-memory** rewrite (the erasure rewrite, a
262
+ * sorted load): consumes an **already-ascending** async stream of `{ chunkKey, bitmap }`, feeding each to the
263
+ * codec and freeing it, instead of materializing the whole generation. Paired with a streaming storage sink (S3
264
+ * multipart / LocalFs temp file), peak memory is ~one chunk + one part. Returns a tally so the caller can verify
265
+ * the re-opened object without re-iterating the (now-consumed) stream. Input **must** be ascending by `chunkKey`
266
+ * — the codec rejects an out-of-order chunk; empty bitmaps are skipped.
267
+ */
268
+ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: GenKey, chunks: AsyncIterable<{
269
+ chunkKey: number;
270
+ bitmap: CodecBitmap;
271
+ }>, options?: {
272
+ crypto?: CrbmCrypto;
273
+ clock?: Yielder;
274
+ }): Promise<StreamWriteResult>;
275
+ /**
276
+ * Point a segment's registry `currentGen` at `key.generation` — the publish step that makes a freshly-written
277
+ * generation the authoritative latest (so registry-aware readers see it). **Forward-only and idempotent:**
278
+ * if the registry has no row it creates one; if it's already at/ahead of `key.generation` it's a no-op (an
279
+ * out-of-order/duplicate publish never regresses the pointer); otherwise it advances via compare-and-swap,
280
+ * retrying a few times under contention. Separated from the Storage write so callers can publish atomically
281
+ * after the immutable object is durable (write-then-publish).
282
+ *
283
+ * **Throws** rather than returning, in two cases, because each would otherwise create a state no reader can
284
+ * recover from: a `destroyed` (crypto-shredded) row, where advancing the pointer would name an object encrypted
285
+ * under a shredded key; and an advance carrying **new** `wrappedDeks`, which would either strand that key or make
286
+ * the row advertise encryption over cleartext generations. Contention that never clears throws
287
+ * {@link WriteConflictError} after the retries.
288
+ *
289
+ * **`expectFrom` turns forward-only into read-modify-write.** Forward-only is the right rule for a writer whose
290
+ * content does not depend on what was current — a load computes its ids upstream, so publishing over a newer
291
+ * generation loses nothing the loader knew about. It is the WRONG rule for a writer that *derived* its content
292
+ * from a particular generation: the erasure rewrite streams generation `from` and clears one bit, so publishing
293
+ * it over a newer generation silently discards whatever that generation added. Passing `expectFrom` makes the
294
+ * publish land only while the pointer is still exactly there, and return `false` otherwise, so the caller can
295
+ * report `superseded` and re-derive. The compare-and-swap itself carries the token read in the same iteration,
296
+ * so the check and the write see the same row.
297
+ *
298
+ * **`expectToken` fences the LINEAGE, and a derived writer needs both.** A generation number identifies a
299
+ * generation only within one incarnation of a name: `nextGeneration` restarts at `0` once the row is purged and
300
+ * the bucket empty, so a name that is retired and re-created has a *different* segment wearing the *same*
301
+ * `currentGen`. `expectFrom` alone matched it — and an erasure rewrite then published one incarnation's content
302
+ * over another's, deleted the live objects with its `keep: 0` collection, and returned `erased: true`. The row's
303
+ * OCC token is the identity that survives this: {@link IRegistryDriver.delete} tombstones rather than unlinks,
304
+ * so "a later `create` still gets a fresh, greater token" and no token is ever reused across incarnations.
305
+ *
306
+ * Pass the token read alongside `expectFrom` and the publish lands only on the same row it was derived from.
307
+ * The check is deliberately **conservative**: the token also changes on writes that are not supersessions at
308
+ * all (a `setRetention`, a due-index reindex), so such a write makes a derived publish report `superseded` and
309
+ * the caller re-derive. That costs a re-run on a rare, unrelated write; the alternative costs a segment.
310
+ */
311
+ export declare function publishGeneration(registry: IRegistryDriver, key: GenKey, options?: {
312
+ wrappedDeks?: readonly WrappedDek[];
313
+ expectFrom?: number;
314
+ expectToken?: Token;
315
+ /**
316
+ * Publish only while the segment still has **no registry row**.
317
+ *
318
+ * The fence for a caller whose decision rests on the row being ABSENT. `expectFrom` and `expectToken`
319
+ * cannot express it: both compare against a value read from a row, so when there was no row there is
320
+ * nothing to compare and both are simply omitted — leaving the publish a bare forward-only advance that
321
+ * happily lands over whatever appeared in the meantime.
322
+ *
323
+ * That gap was a silent wipe, not a theoretical one. A guarded load into a segment that did not exist yet
324
+ * read "no row", so its empty/`minRetained` bounds had nothing to judge and passed vacuously; a
325
+ * concurrent writer then created the row and published a thousand ids; and the guarded load published an
326
+ * EMPTY generation over them, reporting `published: true` with no reason. Reproduced through both
327
+ * `loadSegment` and the `*Into` verbs, which hit it far more often because materialising into a
328
+ * destination that does not exist yet is the ordinary first run of a pipeline.
329
+ */
330
+ expectAbsent?: boolean;
331
+ }): Promise<boolean>;
332
+ /** What a bulk-load wrote — the driver's `{ size, sha256 }` plus a summary of the built generation. */
333
+ export interface BulkLoadResult {
334
+ /** Bytes written to the Storage object. */
335
+ readonly size: number;
336
+ /** The driver's content hash of the object. */
337
+ readonly sha256: string;
338
+ /** Distinct non-empty chunks written (≤ 65536). */
339
+ readonly chunkCount: number;
340
+ /**
341
+ * The DEK freshly minted for this generation, wrapped — present only when the segment is encrypted AND this
342
+ * call minted one (an existing segment reuses its key, which is already on the row).
343
+ *
344
+ * Returned so a caller that deliberately does **not** pass `registry` here — writing now and publishing later,
345
+ * which is how a load applies a guard while the old generation is still authoritative — can carry the key onto
346
+ * its own `publishGeneration`. Without it that publish stores no DEK and the generation it makes current is
347
+ * unreadable: the bytes are encrypted with a key nothing recorded.
348
+ */
349
+ readonly wrappedDeks?: readonly WrappedDek[];
350
+ /** Total distinct ids in the generation (post-dedup). */
351
+ readonly cardinality: number;
352
+ /**
353
+ * Whether this generation is now the segment's **current** one — `undefined` when no `registry` was wired, so
354
+ * there is no pointer and no publish step to report on.
355
+ *
356
+ * `false` means the object is durable but a **concurrent writer published a higher generation first**, so this
357
+ * one is an orphan that no reader will ever resolve. The write succeeded and the *load* did not: whatever this
358
+ * call was asked to make the segment contain, the segment does not contain. That is not a detail a caller can
359
+ * be left to infer — a `*Into` materialisation used to report the generation it wrote as "the destination's
360
+ * new current generation" on exactly this path, which is a plain untruth — so the flag is on the result and the
361
+ * verbs that promise a published generation check it.
362
+ */
363
+ readonly becameCurrent?: boolean;
364
+ }
365
+ /**
366
+ * **Bulk-load** a whole immutable generation from a flat id source — the batch "seed/rebuild" entry point.
367
+ * Unlike {@link writeCrbmGeneration} (which takes pre-grouped bitmaps), this
368
+ * consumes an arbitrary, **unsorted** stream of u32 ids (sync or async) and routes each into its chunk's
369
+ * Roaring bitmap as it goes. The input is consumed lazily and ids dedup on insert, so memory is bounded by
370
+ * the **built generation** — the in-memory Roaring representation of the *distinct* set (≤ one bitmap per
371
+ * non-empty chunk) — not by the input length: you can stream a billion duplicate-heavy ids and hold only the
372
+ * distinct result. Note this is `O(distinct set)`, **not** window-bounded like {@link SegmentEngine.intersect}:
373
+ * bulk-load holds the whole generation in RAM, which suits batch seed/rebuild jobs but not an
374
+ * unbounded-cardinality stream (a segment larger than RAM needs external-merge / pre-sorted input — later phase).
375
+ *
376
+ * Each id must be an integer in `[0, 2³²)` ({@link splitId} throws {@link ValidationError} otherwise) — the
377
+ * source is consumed lazily, so a bad id aborts mid-stream without writing a partial object (the driver
378
+ * commits only after the callback resolves). An empty source writes a valid empty generation.
379
+ *
380
+ * Writes a fresh full snapshot of a segment. The caller picks the generation number in `key` — `nextGeneration`
381
+ * computes the right one from the registry and the bucket — and re-using an existing generation throws
382
+ * {@link WriteConflictError} (write-once). Without a registry a `StorageChunkSource` serves the **highest**
383
+ * generation present, so a too-high number silently shadows real data; with one, `publishGeneration` decides.
384
+ */
385
+ export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenKey, ids: Iterable<number> | AsyncIterable<number>, options?: {
386
+ registry?: IRegistryDriver;
387
+ keystore?: IKeystore;
388
+ requireEncryption?: boolean;
389
+ audit?: IAuditSink;
390
+ /** Bitmap codec. Optional in the type; a **flavor** package binds it ({@link requireCodec}). */
391
+ codec?: CodecInterface;
392
+ /**
393
+ * Injected clock. Supplying one makes bulk-load **cooperative**: it yields the event loop periodically so a
394
+ * long load does not stall everything else on the process. Without it the load still completes, just
395
+ * without yielding — which is the pre-existing behaviour, kept so this is purely additive.
396
+ *
397
+ * `@cloudbitmaps/roaring` supplies a real clock by default, so flavor users get cooperative behaviour with
398
+ * no wiring. `core/` cannot default it: it is timer-free by lint, which is exactly why waiting goes through
399
+ * this seam rather than `setTimeout`.
400
+ */
401
+ clock?: Clock;
402
+ /**
403
+ * Write the object but do **not** advance the pointer (default: publish when a `registry` is wired).
404
+ *
405
+ * For a caller that has to inspect what it wrote before deciding whether it should become current — a
406
+ * guarded {@link loadSegment} is the one in-tree case — because the check is only meaningful while the old
407
+ * generation is still authoritative. The `registry` is still required for an encrypted segment and still
408
+ * consulted: it is where an existing segment's DEK lives, and reusing that key is not optional. The wrapped
409
+ * DEK comes back on the result so the deferred publish can store it.
410
+ *
411
+ * A caller that defers the publish owns what it wrote: an unpublished object sits ABOVE `currentGen`, where
412
+ * generation collection deliberately never looks, so nothing else will ever reclaim it.
413
+ */
414
+ publish?: boolean;
415
+ }): Promise<BulkLoadResult>;
416
+ /**
417
+ * Open a {@link CrbmReader} on one generation over the storage driver's range/tail reads (decrypting if `crypto`).
418
+ * The reader the write paths use to re-read what they wrote, and the erasure rewrite uses to stream the old
419
+ * generation; the engine's read path goes through {@link CrbmStorageChunkSource} instead, which caches these.
420
+ */
421
+ export declare function openGenerationReader(storage: IStorageDriver, key: GenKey, crypto: CrbmCrypto | undefined, options?: Omit<CrbmReaderOptions, 'crypto'>): Promise<CrbmReader>;
422
+ /**
423
+ * Re-open a freshly written generation and assert it round-trips exactly what was streamed into it: the same
424
+ * per-chunk key set *and* the same total cardinality (on top of the codec's own per-chunk CRC + footer checks).
425
+ * `expected` is the streaming writer's tally (the stream is consumed, so it can't be re-iterated) — the key-set
426
+ * comparison catches a dropped/extra chunk that a cardinality-only check could miss when two errors cancel out.
427
+ * Throws {@link IntegrityError}: the object is on disk but must not be published.
428
+ */
429
+ export declare function verifyGeneration(storage: IStorageDriver, key: GenKey, expected: {
430
+ readonly chunkKeys: readonly number[];
431
+ readonly cardinality: number;
432
+ }, crypto: CrbmCrypto | undefined): Promise<void>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Encryption seams — the **pure** interfaces `core/` depends on so the codec can encrypt/decrypt without
3
+ * importing `node:crypto` (lint-banned here, like `Clock`/`Rng`/drivers). Concrete AES-256-GCM lives outside
4
+ * `core/` (`src/drivers/crypto.ts`) and is injected. The envelope scheme: a per-segment DEK wrapped under an
5
+ * operator-held KEK, so discarding the DEK crypto-shreds the segment. Per-chunk AEAD over the `.crbm` payloads + index; a per-segment **DEK** wrapped (envelope
6
+ * encryption) under one or more **KEK**s by an {@link IKeystore}; the wrapped DEK(s) live in the registry, so
7
+ * crypto-shred = delete them.
8
+ */
9
+ /** One AEAD result: a fresh per-message nonce, the ciphertext, and the authentication tag. */
10
+ export interface AeadSealed {
11
+ readonly nonce: Uint8Array;
12
+ readonly ciphertext: Uint8Array;
13
+ readonly tag: Uint8Array;
14
+ }
15
+ /**
16
+ * Authenticated encryption bound to a single key (a segment's DEK). `aad` (associated data) is authenticated
17
+ * but not encrypted — the codec passes each chunk's `(namespace, segment, generation, chunkKey)` identity
18
+ * (the library derives it; an `Aead` implementation receives it), so a chunk's ciphertext can't be silently
19
+ * relocated to another segment/generation/chunk or
20
+ * the index, even by someone who can rewrite Storage objects. The seam is symmetric and self-framing-agnostic:
21
+ * the caller decides where the nonce/tag go (inline with a chunk, or in the footer for the index).
22
+ */
23
+ export interface Aead {
24
+ /** Encrypt `plaintext`, authenticating `aad`. Returns a fresh nonce + ciphertext + tag. */
25
+ seal(plaintext: Uint8Array, aad: Uint8Array): AeadSealed;
26
+ /**
27
+ * Decrypt + verify. Throws {@link IntegrityError} on **any** mismatch (wrong key, tampered ciphertext/tag,
28
+ * or `aad` that doesn't match what was sealed) — never returns wrong-but-plausible plaintext.
29
+ */
30
+ open(sealed: AeadSealed, aad: Uint8Array): Uint8Array;
31
+ }
32
+ /**
33
+ * A DEK wrapped under one KEK. A segment stores a **list** of these (one per KEK it was wrapped under — e.g.
34
+ * an active KEK plus an offline recovery KEK), so losing one KEK isn't fatal and KEK rotation needs no
35
+ * data re-encryption. `keyId` names which KEK; `wrapped` is the opaque wrapped-key blob (base64). Carries no
36
+ * plaintext key material — safe to store in the registry next to the (encrypted) data.
37
+ */
38
+ export interface WrappedDek {
39
+ readonly keyId: string;
40
+ readonly wrapped: string;
41
+ }
42
+ /**
43
+ * Envelope key management — the injected seam between the codec and however the operator holds key material
44
+ * (in-process BYOK by default; KMS/Vault as optional adapters later). Async so a remote keystore (KMS) fits
45
+ * the same interface. Never exposes raw key bytes to `core/`: it returns an {@link Aead} bound to the DEK.
46
+ */
47
+ export interface IKeystore {
48
+ /**
49
+ * Mint a fresh per-segment DEK, wrapped under the active KEK (and any recovery KEK). Returns the wrapping(s)
50
+ * to persist in the registry plus an {@link Aead} to encrypt this segment's chunks with.
51
+ */
52
+ createDek(): Promise<{
53
+ wrapped: WrappedDek[];
54
+ aead: Aead;
55
+ }>;
56
+ /**
57
+ * Reconstruct the {@link Aead} from a segment's stored wrappings by unwrapping with any KEK currently held.
58
+ * Throws {@link KeyUnavailableError} if it holds none of the referenced KEKs (lost key, or a shredded
59
+ * segment whose wrappings were deleted).
60
+ */
61
+ openDek(wrapped: readonly WrappedDek[]): Promise<Aead>;
62
+ }
63
+ /**
64
+ * What the encrypting codec needs: the {@link Aead} for this segment's DEK plus a way to build the AAD for a
65
+ * given scope (`'index'` or a chunkKey). Built per (segment, generation) by the storage-source bridge so the
66
+ * codec itself stays segment-agnostic.
67
+ */
68
+ export interface CrbmCrypto {
69
+ readonly aead: Aead;
70
+ aadFor(scope: number | 'index'): Uint8Array;
71
+ }
72
+ /**
73
+ * Build the AEAD associated-data binding a chunk (or the index) to its exact location:
74
+ * `v1 ‖ len(namespace) ‖ namespace ‖ len(segment) ‖ segment ‖ generation ‖ scope ‖ chunkKey`. Length-prefixed
75
+ * so distinct `(namespace, segment)` pairs can never collide. Pure (no crypto) — just the authenticated label.
76
+ */
77
+ export declare function aadFor(ref: {
78
+ readonly namespace?: string;
79
+ readonly segment: string;
80
+ }, generation: number, scope: number | 'index'): Uint8Array;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The determinism seam.
3
+ *
4
+ * `core/` is a pure function of its inputs + injected dependencies — it never reaches for
5
+ * ambient time or randomness. Production wiring supplies real implementations; the deterministic
6
+ * tests supply controlled, seeded ones, so any run can be replayed exactly from a seed.
7
+ *
8
+ * These are interfaces only: the seam the whole library depends on for reproducibility. Every module that
9
+ * needs time, randomness or a yield takes one of these rather than reaching for a global.
10
+ */
11
+ /** Injected time source. `core/` must never call `Date.now()` directly. */
12
+ export interface Clock {
13
+ /** Milliseconds since the Unix epoch. */
14
+ now(): number;
15
+ /**
16
+ * Resolve after at least `ms` have elapsed. The retry/backoff path's only way to wait — `core/` must
17
+ * never reach for `setTimeout` directly (it's timer-free, lint-enforced). Production wiring supplies a
18
+ * `setTimeout`-backed implementation; a test supplies a virtual-time one so a backoff schedule is
19
+ * replayable. `ms <= 0` resolves on a microtask without scheduling a timer.
20
+ */
21
+ sleep(ms: number): Promise<void>;
22
+ /**
23
+ * Hand the event loop back once, without asking for any elapsed time.
24
+ *
25
+ * This is **not** `sleep(0)`, and the difference is the reason the member exists. `sleep(0)` resolves on a
26
+ * microtask, and microtasks drain before the loop advances a phase — so awaiting it inside a CPU-bound loop
27
+ * yields to nothing at all, and a co-resident server stays blocked for the loop's full duration. A real yield
28
+ * needs a macrotask. `sleep(1)` is one, but it buys the relief at ~1 ms of dead wall-clock per yield.
29
+ *
30
+ * **Optional**, so every `Clock` written before this member still satisfies the interface. Callers must
31
+ * therefore degrade rather than assume: `clock.yieldNow?.() ?? clock.sleep(1)` — correct on any clock, cheap
32
+ * on one that implements this. `core/` cannot supply it itself (it is timer-free, lint-enforced); production
33
+ * wiring backs it with `setImmediate`, and a test can make it a no-op so virtual time is not perturbed
34
+ * by what is purely a scheduling courtesy.
35
+ */
36
+ yieldNow?(): Promise<void>;
37
+ }
38
+ /** Injected randomness. `core/` must never call `Math.random()` directly. Seedable for simulation. */
39
+ export interface Rng {
40
+ /** A float in the half-open interval [0, 1). */
41
+ next(): number;
42
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `segmentExists` and `listSegments` — ask the registry what is there.
3
+ *
4
+ * The registry has always known which segments exist; it is what `checkConsistency`, the retention sweep and
5
+ * `exportSegments` each enumerate. It was simply never exposed, so a user asking "do I already have this?" had
6
+ * to infer it: `count()` returns `0` for a segment that was never loaded AND for one loaded with no ids, and
7
+ * `generations()` costs a bucket listing to answer a question the pointer alone settles.
8
+ *
9
+ * Inferring it is the expensive kind of wrong. The alternative a user reaches for is keeping their own list of
10
+ * segment names alongside the store — a second source of truth, updated by hand, drifting from the first the
11
+ * moment a load fails halfway. The registry is already the list; these two functions read it.
12
+ */
13
+ import type { IRegistryDriver, RegistryRecord, SegmentRef } from './ports.js';
14
+ /** One segment the registry knows about. */
15
+ export interface SegmentInfo extends SegmentRef {
16
+ /**
17
+ * The generation the **pointer names** — not what a read would resolve. On a `destroyed` tombstone this is
18
+ * still a number while reads answer empty, so read it together with {@link SegmentInfo.status} rather than
19
+ * alone. `null` means the row exists with no Storage generation yet.
20
+ */
21
+ readonly currentGen: number | null;
22
+ /**
23
+ * `destroyed` is a tombstone: the row stays so sweeps can still find it, and the generations are gone — on an
24
+ * encrypted segment the DEK wrappings went with them, which is what makes the bytes unrecoverable rather than
25
+ * merely deleted.
26
+ */
27
+ readonly status: RegistryRecord['status'];
28
+ }
29
+ /**
30
+ * Whether this segment's pointer resolves a generation — the question behind "does it already exist?".
31
+ *
32
+ * One registry point read, on every backend. Deliberately narrower than "is there a row": it is **false** for a
33
+ * row that exists with `currentGen: null` (a retention policy recorded ahead of the first load) and for a
34
+ * `destroyed` tombstone, because a read answers empty in both. The predicate mirrors generation resolution
35
+ * clause for clause, which is what keeps the two from disagreeing.
36
+ *
37
+ * It is NOT the same question as `count() > 0`. A segment loaded with no ids exists and counts zero — the
38
+ * distinction between "never loaded" and "loaded, and genuinely empty" is the one `count()` cannot make and
39
+ * the main reason this function is worth having.
40
+ *
41
+ * Two states answer `true` where a read still gives you nothing, and neither is this call's job to detect: a
42
+ * **torn restore** (`missing-storage-generation` — a live pointer whose object was deleted) makes reads *throw*
43
+ * `NotFoundError` rather than answer empty, and `runConsistencyCheck` is the call that looks for it; and a
44
+ * handle carrying an expired `expiresAt` reads as empty by a rule that lives on the handle, not on the row.
45
+ *
46
+ * Not a lock. A segment can be created or dropped between this call and whatever you do next; if the answer
47
+ * has to hold, use the fences that exist for it — `load`'s guard, or `expectFrom`/`expectToken` on a publish.
48
+ */
49
+ export declare function segmentExists(ref: SegmentRef, registry: IRegistryDriver): Promise<boolean>;
50
+ /**
51
+ * Every segment the registry holds, streamed — optionally scoped to one namespace.
52
+ *
53
+ * **This is an admin/discovery call, not a request-path one.** It is the registry's own enumeration — a paged
54
+ * LIST over the `registry/` prefix — so cost grows with the fleet, not with what you are looking for. The
55
+ * fleet-wide sweeps that *drain* it bound it (`maxScanSegments`); this one streams, so the bound is yours.
56
+ *
57
+ * **Scoping to a namespace narrows the LIST prefix**, so it really is the difference between reading one
58
+ * tenant and reading all of them.
59
+ *
60
+ * Streams, so a large fleet need not be held at once, and stopping the iteration stops the scan — **with one
61
+ * exception that matters**: a driver that buffers its enumeration defeats both properties, and
62
+ * `RetryingRegistryDriver` does exactly that (it must, to retry a `list` as a unit). Wrapped in it, the whole
63
+ * scan is paid for and resident before the first row reaches you. On the four native drivers the guarantee
64
+ * holds; on S3 the granularity is a page, so one LIST page and its in-flight row reads complete regardless.
65
+ *
66
+ * Yields `destroyed` tombstones, and rows whose `currentGen` is `null`, because hiding either would make this
67
+ * disagree with the registry it reports on — a filtered enumeration that looks complete is how a retention
68
+ * sweep ends up permanently skipping rows nobody can see. Filter on `status`/`currentGen` yourself, or use
69
+ * {@link segmentExists} for the "does the pointer resolve" question. Internal bookkeeping rows are the one
70
+ * exclusion, and only on an unscoped scan: they are not segments, and a caller naming their namespace still
71
+ * sees them.
72
+ */
73
+ export declare function listSegments(registry: IRegistryDriver, options?: {
74
+ namespace?: string;
75
+ }): AsyncIterable<SegmentInfo>;