@cloudbitmaps/core 0.9.0 → 0.11.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 +33 -16
  3. package/dist/chunk-5OMC5U7U.js +508 -0
  4. package/dist/chunk-5OMC5U7U.js.map +7 -0
  5. package/dist/core/audit.d.ts +128 -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 +68 -0
  9. package/dist/core/codec.d.ts +122 -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 +254 -0
  14. package/dist/core/crbm/crc32c.d.ts +10 -0
  15. package/dist/core/crbm/format.d.ts +114 -0
  16. package/dist/core/crbm/reader.d.ts +86 -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 +522 -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 +77 -0
  23. package/dist/core/due-index.d.ts +53 -0
  24. package/dist/core/engine.d.ts +246 -0
  25. package/dist/core/erase-id.d.ts +176 -0
  26. package/dist/core/erasure.d.ts +185 -0
  27. package/dist/core/errors.d.ts +113 -0
  28. package/dist/core/generation-gc.d.ts +56 -0
  29. package/dist/core/keys.d.ts +23 -0
  30. package/dist/core/load.d.ts +121 -0
  31. package/dist/core/lru.d.ts +69 -0
  32. package/dist/core/metrics.d.ts +136 -0
  33. package/dist/core/name-codec.d.ts +69 -0
  34. package/dist/core/pinned-storage-source.d.ts +90 -0
  35. package/dist/core/ports.d.ts +423 -0
  36. package/dist/core/reader-defaults.d.ts +18 -0
  37. package/dist/core/registry-scan.d.ts +47 -0
  38. package/dist/core/retention-sweep.d.ts +198 -0
  39. package/dist/core/retention.d.ts +67 -0
  40. package/dist/core/retry.d.ts +69 -0
  41. package/dist/core/rollback.d.ts +75 -0
  42. package/dist/core/validate.d.ts +44 -0
  43. package/dist/driver-kit.d.ts +9 -0
  44. package/dist/driver-kit.js +275 -0
  45. package/dist/driver-kit.js.map +7 -0
  46. package/dist/drivers/_shared/keys.d.ts +26 -0
  47. package/dist/drivers/_shared/object-registry-keys.d.ts +28 -0
  48. package/dist/drivers/_shared/object-registry.d.ts +89 -0
  49. package/dist/drivers/_shared/registry.d.ts +66 -0
  50. package/dist/drivers/backends.d.ts +43 -0
  51. package/dist/drivers/crypto.d.ts +38 -0
  52. package/dist/drivers/localfs/fs-util.d.ts +21 -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 +49 -0
  57. package/dist/drivers/retry/retrying-chunk-source.d.ts +59 -0
  58. package/dist/export/index.d.ts +93 -0
  59. package/dist/index.d.ts +76 -2760
  60. package/dist/index.js +3527 -3133
  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-BD22WDAN.js +0 -230
  79. package/dist/chunk-BD22WDAN.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-OZ6LAFV4.js +0 -6
  83. package/dist/chunk-OZ6LAFV4.js.map +0 -1
  84. package/dist/chunk-SNJVZ227.js +0 -35
  85. package/dist/chunk-SNJVZ227.js.map +0 -1
  86. package/dist/dynamodb/index.cjs +0 -767
  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 -4990
  99. package/dist/index.cjs.map +0 -1
  100. package/dist/index.d.cts +0 -2761
  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-CvcvJWf6.d.cts +0 -395
  114. package/dist/ports-CvcvJWf6.d.ts +0 -395
  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 -918
  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
package/NOTICE CHANGED
@@ -1,4 +1,4 @@
1
- CloudRoaring
1
+ @cloudbitmaps/core
2
2
  Copyright 2026 Sharvil Kadam
3
3
 
4
4
  This product includes software developed as an open-source project.
@@ -7,6 +7,6 @@ file. You may obtain a copy of the License at:
7
7
 
8
8
  http://www.apache.org/licenses/LICENSE-2.0
9
9
 
10
- CloudRoaring builds on the Roaring Bitmap data structure and is designed to use
11
- the CRoaring engine (via the `roaring` Node.js binding) for in-memory bit math.
12
- Those projects are independent works under their own licenses.
10
+ @cloudbitmaps/core is the codec-agnostic engine of CloudBitmaps. It contains no bitmap
11
+ codec and no cloud SDK, and has zero runtime dependencies; a flavor package supplies the
12
+ codec through the CodecInterface seam.
package/README.md CHANGED
@@ -1,23 +1,40 @@
1
1
  # @cloudbitmaps/core
2
2
 
3
- The **codec-agnostic cloud engine** behind the [CloudBitmaps](https://github.com/cloudbitmaps/cloudbitmaps) family:
4
- tiered storage (HOT RAM → WARM key/value deltas → COLD immutable `.crbm` objects), serverless chunk-skipping
5
- intersection, the segment registry, crash-safe 2-phase-commit compaction, encryption-at-rest + crypto-shred,
6
- segment lifecycle (disposal, and a per-segment retention policy with the sweep that enforces it), and
7
- **every storage driver** (S3 · GCS · Azure Blob · DynamoDB · PostgreSQL · Redis · MongoDB · Cassandra/ScyllaDB ·
8
- MySQL/MariaDB, each on its own subpath with the backend SDK as an optional peer dependency).
9
-
10
- ## You probably want a flavor, not this package
11
-
12
- This package holds no bitmap codec — that lives in a *flavor* package which depends on this one and supplies it
13
- through the `CodecInterface` seam. Install the flavor; `@cloudbitmaps/core` arrives **transitively**:
3
+ **Most people do not install this package.** It is the engine underneath [CloudBitmaps](https://github.com/cloudbitmaps/cloudbitmaps), and it arrives on its own
4
+ when you install the two packages you want. Install those instead:
14
5
 
15
6
  ```bash
16
- npm i @cloudbitmaps/roaring # the roaring flavor (flagship)
7
+ pnpm add @cloudbitmaps/roaring @cloudbitmaps/s3 # the store, and the storage you have (or /gcs, or /azure-blob)
17
8
  ```
18
9
 
19
- Depend on `@cloudbitmaps/core` directly only to **author a flavor or a driver**. It has **zero runtime
20
- dependencies** of its own.
10
+ On pnpm 10 and later, allow the one build script first, or the package throws at `import` while the install exits 0. Put this in your `package.json`: `{ "pnpm": { "onlyBuiltDependencies": ["roaring"] } }`. npm and pnpm 9 need nothing extra. [Details](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#cannot-find-module-buildreleaseroaringnode-after-a-successful-install).
11
+
12
+ Depend on `@cloudbitmaps/core` directly only to write a flavor or a driver.
13
+
14
+ > **ESM-only, Node ≥ 22.12.** Use `import`; for `require()`, Jest and TypeScript, see
15
+ > [CommonJS, Jest and TypeScript](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#commonjs-jest-and-typescript).
16
+
17
+ > Pre-1.0: the API and the on-disk format can still change. These docs describe `main`, ahead of the npm release.
18
+
19
+ ## What it is
20
+
21
+ The codec-agnostic cloud engine: a bounded in-memory cache over immutable `.crbm` objects, chunk-skipping
22
+ intersection, the segment registry, the write-once load-and-publish path, generation collection, encryption at rest
23
+ and crypto-shred, erasure by rewrite, retention, and the in-memory and local-filesystem backends. It has **zero runtime
24
+ dependencies and no cloud SDK**. The cloud backends are their own packages, built against
25
+ `@cloudbitmaps/core/driver-kit`, the declared contract a driver depends on. A third-party driver can use it too.
26
+
27
+ Its main entry is the flavor-author kit: the read engine, the standalone forms of the store's methods (`loadSegment`,
28
+ `dropSegment`, `retireExpired` and the rest), and the retry and budget internals. `@cloudbitmaps/roaring` re-exports,
29
+ by name, what an application uses.
30
+
31
+ ## Documentation
32
+
33
+ - [API reference](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/api-reference.md), including the
34
+ [driver kit](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/api-reference.md#driver-kit--what-you-need-to-implement-a-driver)
35
+ - [Getting started](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md)
36
+ - [Changelog](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/CHANGELOG.md)
37
+
38
+ ## License
21
39
 
22
- Full docs, guides, and the design corpus live in the
23
- [repository](https://github.com/cloudbitmaps/cloudbitmaps). Licensed Apache-2.0.
40
+ Apache-2.0
@@ -0,0 +1,508 @@
1
+ // src/core/errors.ts
2
+ var ERROR_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.error");
3
+ var TRANSIENT_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.error.transient");
4
+ var CloudRoaringError = class extends Error {
5
+ /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
6
+ [ERROR_BRAND] = true;
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = new.target.name;
10
+ }
11
+ };
12
+ var ValidationError = class extends CloudRoaringError {
13
+ };
14
+ var WriteConflictError = class extends CloudRoaringError {
15
+ };
16
+ var IntegrityError = class extends CloudRoaringError {
17
+ };
18
+ var NotFoundError = class extends CloudRoaringError {
19
+ };
20
+ var UnsupportedError = class extends CloudRoaringError {
21
+ };
22
+ var CapabilityError = class extends CloudRoaringError {
23
+ };
24
+ var BudgetExceededError = class extends CloudRoaringError {
25
+ };
26
+ var KeyUnavailableError = class extends CloudRoaringError {
27
+ };
28
+ var TransientError = class extends CloudRoaringError {
29
+ /** A second brand so a transient fault is classifiable cross-bundle. */
30
+ [TRANSIENT_BRAND] = true;
31
+ constructor(message, options) {
32
+ super(message);
33
+ if (options && "cause" in options) this.cause = options.cause;
34
+ }
35
+ };
36
+ function hasBrand(err, brand) {
37
+ return typeof err === "object" && err !== null && err[brand] === true;
38
+ }
39
+ function isCloudRoaringError(err) {
40
+ return hasBrand(err, ERROR_BRAND);
41
+ }
42
+ function isWriteConflictError(err) {
43
+ return isCloudRoaringError(err) && err.name === "WriteConflictError";
44
+ }
45
+ function isTransientError(err) {
46
+ return hasBrand(err, TRANSIENT_BRAND);
47
+ }
48
+ function isNotFoundError(err) {
49
+ return isCloudRoaringError(err) && err.name === "NotFoundError";
50
+ }
51
+ function isIntegrityError(err) {
52
+ return isCloudRoaringError(err) && err.name === "IntegrityError";
53
+ }
54
+ function isValidationError(err) {
55
+ return isCloudRoaringError(err) && err.name === "ValidationError";
56
+ }
57
+
58
+ // src/core/name-codec.ts
59
+ var KEY_SAFE = /[A-Za-z0-9._:-]/;
60
+ var PATH_SAFE = /[A-Za-z0-9._-]/;
61
+ var UTF8 = new TextEncoder();
62
+ function escapeChar(ch) {
63
+ let out = "";
64
+ for (const byte of UTF8.encode(ch)) {
65
+ out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
66
+ }
67
+ return out;
68
+ }
69
+ function encodeWith(name, safe) {
70
+ let out = "";
71
+ for (const ch of name) {
72
+ out += ch !== "%" && safe.test(ch) ? ch : escapeChar(ch);
73
+ }
74
+ return out.startsWith("_") ? `%5F${out.slice(1)}` : out;
75
+ }
76
+ function decodePercent(encoded) {
77
+ const bytes = [];
78
+ for (let i = 0; i < encoded.length; ) {
79
+ if (encoded[i] === "%" && i + 2 < encoded.length + 1) {
80
+ const hex = encoded.slice(i + 1, i + 3);
81
+ if (!/^[0-9A-Fa-f]{2}$/.test(hex)) return encoded;
82
+ bytes.push(parseInt(hex, 16));
83
+ i += 3;
84
+ } else {
85
+ for (const b of new TextEncoder().encode(encoded[i])) bytes.push(b);
86
+ i += 1;
87
+ }
88
+ }
89
+ return new TextDecoder("utf-8", { fatal: false, ignoreBOM: true }).decode(new Uint8Array(bytes));
90
+ }
91
+ function encodeNameForKey(name) {
92
+ return defuseDotComponent(encodeWith(name, KEY_SAFE));
93
+ }
94
+ function defuseDotComponent(encoded) {
95
+ return encoded === "." || encoded === ".." ? encoded.replaceAll(".", "%2E") : encoded;
96
+ }
97
+ function decodeNameFromKey(encoded) {
98
+ return decodePercent(encoded);
99
+ }
100
+ function encodeNameForPath(name) {
101
+ let encoded = encodeWith(name, PATH_SAFE);
102
+ const dotted = defuseDotComponent(encoded);
103
+ if (dotted !== encoded) return dotted;
104
+ const stem = encoded.split(".")[0] ?? "";
105
+ if (/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(stem)) {
106
+ encoded = escapeChar(encoded[0]) + encoded.slice(1);
107
+ }
108
+ if (encoded.endsWith(".")) encoded = `${encoded.slice(0, -1)}%2E`;
109
+ return encoded;
110
+ }
111
+ function decodeNameFromPath(encoded) {
112
+ return decodePercent(encoded);
113
+ }
114
+
115
+ // src/core/validate.ts
116
+ var CHUNK_KEY_MAX = 65535;
117
+ var MAX_NAME_LENGTH = 256;
118
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
119
+ function encodedNameLength(value) {
120
+ return Math.max(encodeNameForKey(value).length, encodeNameForPath(value).length);
121
+ }
122
+ function validatePart(value, field) {
123
+ if (typeof value !== "string") {
124
+ throw new ValidationError(`${field} must be a string; got ${typeof value}`);
125
+ }
126
+ if (value.length === 0) {
127
+ throw new ValidationError(`${field} must not be empty`);
128
+ }
129
+ if (value.length > MAX_NAME_LENGTH) {
130
+ throw new ValidationError(
131
+ `${field} is too long: ${value.length} characters (limit ${MAX_NAME_LENGTH} once encoded for a storage key)`
132
+ );
133
+ }
134
+ if (LONE_SURROGATE.test(value)) {
135
+ throw new ValidationError(
136
+ `${field} contains an unpaired surrogate, so it has no UTF-8 encoding and cannot be stored. This is usually a string sliced through an astral character (an emoji, say) \u2014 slice by code point instead.`
137
+ );
138
+ }
139
+ const encoded = encodedNameLength(value);
140
+ if (encoded > MAX_NAME_LENGTH) {
141
+ throw new ValidationError(
142
+ `${field} is too long: ${encoded} characters once encoded for storage (limit ${MAX_NAME_LENGTH}). The name itself is ${value.length} characters \u2014 encoding expands anything outside [A-Za-z0-9._-], so a name of mostly non-ASCII text, or one full of colons, reaches the limit sooner than its length suggests.`
143
+ );
144
+ }
145
+ }
146
+ function validateSegmentRef(ref) {
147
+ validatePart(ref.segment, "segment");
148
+ if (ref.namespace !== void 0) validatePart(ref.namespace, "namespace");
149
+ }
150
+ var RESERVED_NAMESPACE_PREFIX = "cbm.due.";
151
+ function isReservedNamespace(namespace) {
152
+ return namespace !== void 0 && namespace.startsWith(RESERVED_NAMESPACE_PREFIX);
153
+ }
154
+ function validateUserNamespace(namespace) {
155
+ validatePart(namespace, "namespace");
156
+ if (isReservedNamespace(namespace)) {
157
+ throw new ValidationError(
158
+ `namespace "${namespace}" is reserved: names starting with "${RESERVED_NAMESPACE_PREFIX}" hold the library's own bookkeeping rows, which every fleet-wide scan skips, so a segment there would be invisible to an erasure, a consistency check, an export and a retention sweep. Choose another namespace.`
159
+ );
160
+ }
161
+ }
162
+ function validateUserRef(ref) {
163
+ validatePart(ref.segment, "segment");
164
+ if (ref.namespace !== void 0) validateUserNamespace(ref.namespace);
165
+ }
166
+ function validateChunkRef(ref) {
167
+ validateSegmentRef(ref);
168
+ if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {
169
+ throw new ValidationError(
170
+ `chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]; got ${ref.chunkKey}`
171
+ );
172
+ }
173
+ }
174
+
175
+ // src/core/concurrency.ts
176
+ async function mapWithConcurrency(items, limit, fn) {
177
+ if (!Number.isInteger(limit) || limit < 1) {
178
+ throw new ValidationError(`concurrency limit must be a positive integer; got ${limit}`);
179
+ }
180
+ const results = new Array(items.length);
181
+ let next = 0;
182
+ let failed = false;
183
+ let firstError;
184
+ const worker = async () => {
185
+ while (!failed) {
186
+ const i = next++;
187
+ if (i >= items.length) return;
188
+ try {
189
+ results[i] = await fn(items[i], i);
190
+ } catch (err) {
191
+ if (!failed) {
192
+ failed = true;
193
+ firstError = err;
194
+ }
195
+ return;
196
+ }
197
+ }
198
+ };
199
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
200
+ await Promise.all(workers);
201
+ if (failed) throw firstError;
202
+ return results;
203
+ }
204
+
205
+ // src/core/ports.ts
206
+ var STORAGE_BACKEND_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.storage-backend");
207
+ var STORAGE_BACKEND = STORAGE_BACKEND_BRAND;
208
+ function brandAsBackend(target) {
209
+ if (target === null || typeof target !== "object") {
210
+ throw new ValidationError("brandAsBackend needs `{ storage, registry }`");
211
+ }
212
+ const { storage, registry } = target;
213
+ if (storage === null || typeof storage !== "object" || typeof storage.putImmutable !== "function") {
214
+ throw new ValidationError(
215
+ "brandAsBackend: `storage` must be an IStorageDriver (it has no `putImmutable`)"
216
+ );
217
+ }
218
+ if (registry === null || typeof registry !== "object" || typeof registry.compareAndSwap !== "function") {
219
+ throw new ValidationError(
220
+ "brandAsBackend: `registry` must be an IRegistryDriver (it has no `compareAndSwap`)"
221
+ );
222
+ }
223
+ try {
224
+ Object.defineProperty(target, STORAGE_BACKEND_BRAND, {
225
+ value: true,
226
+ enumerable: false,
227
+ writable: false,
228
+ configurable: false
229
+ });
230
+ } catch {
231
+ throw new ValidationError(
232
+ "brandAsBackend: cannot brand a frozen or non-extensible object \u2014 pass a plain object or a class instance"
233
+ );
234
+ }
235
+ return target;
236
+ }
237
+ function isStorageBackend(value) {
238
+ return typeof value === "object" && value !== null && value[STORAGE_BACKEND_BRAND] === true;
239
+ }
240
+
241
+ // src/drivers/_shared/keys.ts
242
+ var DEFAULT_NAMESPACE = "_default";
243
+ function namespaceKeyPart(namespace) {
244
+ return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForKey(namespace);
245
+ }
246
+ function namespacePathPart(namespace) {
247
+ return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
248
+ }
249
+
250
+ // src/drivers/_shared/registry.ts
251
+ var STATUSES = ["active", "destroyed"];
252
+ var RECORD_FIELDS = [
253
+ "namespace",
254
+ "segment",
255
+ "currentGen",
256
+ "wrappedDeks",
257
+ "keyId",
258
+ "status",
259
+ "retention",
260
+ "residency",
261
+ "createdAt",
262
+ "updatedAt",
263
+ "token"
264
+ ];
265
+ var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
266
+ var MAX_GOVERNANCE_BYTES = 64 * 1024;
267
+ var MAX_WRAPPED_DEKS = 8;
268
+ var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
269
+ function validateGeneration(gen) {
270
+ if (gen === null) return;
271
+ if (!Number.isInteger(gen) || gen < 0) {
272
+ throw new ValidationError(
273
+ `currentGen must be a non-negative integer or null (no Storage generation yet); got ${gen}`
274
+ );
275
+ }
276
+ }
277
+ function validatePatchGeneration(patch) {
278
+ if (!("currentGen" in patch)) return;
279
+ if (patch.currentGen === void 0) {
280
+ throw new ValidationError(
281
+ `currentGen was present in the patch but undefined. Pass \`null\` to clear the pointer (the segment has no Storage generation), or omit the key to leave it unchanged \u2014 an undefined value is refused because it would un-publish the segment's Storage data.`
282
+ );
283
+ }
284
+ validateGeneration(patch.currentGen);
285
+ }
286
+ function validateStatus(status) {
287
+ if (!STATUSES.includes(status)) {
288
+ throw new ValidationError(`status must be one of ${STATUSES.join("/")}; got ${status}`);
289
+ }
290
+ }
291
+ function validateGovernance(meta, field) {
292
+ if (meta === void 0) return;
293
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
294
+ throw new ValidationError(
295
+ `${field} must be a plain object (got ${meta === null ? "null" : typeof meta})`
296
+ );
297
+ }
298
+ let json;
299
+ try {
300
+ json = JSON.stringify(meta);
301
+ } catch {
302
+ throw new ValidationError(`${field} must be JSON-serializable`);
303
+ }
304
+ if (json.length > MAX_GOVERNANCE_BYTES) {
305
+ throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
306
+ }
307
+ }
308
+ function validateWrappedDeks(value, isStored) {
309
+ if (value === void 0) return;
310
+ const fail = (msg) => {
311
+ throw isStored ? new IntegrityError(msg) : new ValidationError(msg);
312
+ };
313
+ if (!Array.isArray(value)) fail("wrappedDeks must be an array");
314
+ const list = value;
315
+ if (list.length === 0) fail("wrappedDeks must be non-empty when present");
316
+ if (list.length > MAX_WRAPPED_DEKS)
317
+ fail(`wrappedDeks has ${list.length} entries, cap ${MAX_WRAPPED_DEKS}`);
318
+ for (const w of list) {
319
+ if (w === null || typeof w !== "object") fail("wrappedDeks entry must be an object");
320
+ const { keyId, wrapped } = w;
321
+ if (typeof keyId !== "string" || keyId.length === 0)
322
+ fail("wrappedDeks entry needs a non-empty keyId");
323
+ if (typeof wrapped !== "string" || wrapped.length === 0)
324
+ fail("wrappedDeks entry needs a non-empty wrapped blob");
325
+ if (wrapped.length > MAX_WRAPPED_DEK_BYTES)
326
+ fail(`wrappedDeks entry is ${wrapped.length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);
327
+ }
328
+ }
329
+ function validateNewRegistryRecord(rec) {
330
+ validateGeneration(rec.currentGen);
331
+ if (rec.status !== void 0) validateStatus(rec.status);
332
+ validateWrappedDeks(rec.wrappedDeks, false);
333
+ validateGovernance(rec.retention, "retention");
334
+ validateGovernance(rec.residency, "residency");
335
+ }
336
+ function validateRegistryPatch(patch) {
337
+ validatePatchGeneration(patch);
338
+ if (patch.status !== void 0) validateStatus(patch.status);
339
+ if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
340
+ if ("retention" in patch) validateGovernance(patch.retention, "retention");
341
+ if ("residency" in patch) validateGovernance(patch.residency, "residency");
342
+ }
343
+ var REGISTRY_SCHEMA_VERSION = 1;
344
+ function assertRegistrySchemaVersion(raw, ctx) {
345
+ if (raw === void 0) {
346
+ throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
347
+ }
348
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
349
+ throw new IntegrityError(`registry row has a malformed schemaVersion (${String(raw)}): ${ctx}`);
350
+ }
351
+ if (raw > REGISTRY_SCHEMA_VERSION) {
352
+ throw new UnsupportedError(
353
+ `registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`
354
+ );
355
+ }
356
+ }
357
+ function serializeRegistryEnvelope(env) {
358
+ return JSON.stringify({
359
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
360
+ deleted: env.deleted,
361
+ record: env.record
362
+ });
363
+ }
364
+ function registryCounterOf(record) {
365
+ if (!/^(0|[1-9]\d*)$/.test(record.token)) {
366
+ throw new IntegrityError(`registry row token is not a canonical counter: ${record.token}`);
367
+ }
368
+ const n = Number(record.token);
369
+ if (!Number.isSafeInteger(n)) {
370
+ throw new IntegrityError(`registry row token is out of safe-integer range: ${record.token}`);
371
+ }
372
+ return n;
373
+ }
374
+ function parseRegistryEnvelope(text, ctx) {
375
+ let parsed;
376
+ try {
377
+ parsed = JSON.parse(text);
378
+ } catch {
379
+ throw new IntegrityError(`registry row is not valid JSON: ${ctx}`);
380
+ }
381
+ if (parsed === null || typeof parsed !== "object") {
382
+ throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
383
+ }
384
+ const env = parsed;
385
+ assertRegistrySchemaVersion(env.schemaVersion, ctx);
386
+ const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
387
+ if (extra.length > 0) {
388
+ throw new IntegrityError(
389
+ `registry row has fields its envelope does not declare (${extra.join(", ")}): ${ctx}`
390
+ );
391
+ }
392
+ if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
393
+ throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
394
+ }
395
+ const r = env.record;
396
+ assertStoredRecordShape(r, ctx);
397
+ if (typeof r.token !== "string") {
398
+ throw new IntegrityError(`registry row is missing its token: ${ctx}`);
399
+ }
400
+ return { deleted: env.deleted, record: env.record };
401
+ }
402
+ function recordFromNew(ref, rec, now, token) {
403
+ return {
404
+ namespace: ref.namespace,
405
+ segment: ref.segment,
406
+ currentGen: rec.currentGen,
407
+ wrappedDeks: rec.wrappedDeks,
408
+ keyId: rec.keyId,
409
+ status: rec.status ?? "active",
410
+ retention: rec.retention,
411
+ residency: rec.residency,
412
+ createdAt: now,
413
+ updatedAt: now,
414
+ token
415
+ };
416
+ }
417
+ function assertStoredRecordShape(r, ctx) {
418
+ if (typeof r.segment !== "string" || r.namespace !== void 0 && typeof r.namespace !== "string" || r.currentGen !== null && typeof r.currentGen !== "number" || typeof r.status !== "string" || typeof r.createdAt !== "number" || typeof r.updatedAt !== "number") {
419
+ throw new IntegrityError(`registry record is missing required fields: ${ctx}`);
420
+ }
421
+ if (r.currentGen !== null && (!Number.isInteger(r.currentGen) || r.currentGen < 0)) {
422
+ throw new IntegrityError(`registry record has an invalid currentGen (${r.currentGen}): ${ctx}`);
423
+ }
424
+ if (!STATUSES.includes(r.status)) {
425
+ throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);
426
+ }
427
+ const extra = Object.keys(r).filter((k) => !RECORD_FIELDS.includes(k));
428
+ if (extra.length > 0) {
429
+ throw new IntegrityError(
430
+ `registry record has fields it does not declare (${extra.join(", ")}): ${ctx}`
431
+ );
432
+ }
433
+ if (r.keyId !== void 0 && typeof r.keyId !== "string") {
434
+ throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
435
+ }
436
+ validateWrappedDeks(r.wrappedDeks, true);
437
+ for (const field of ["retention", "residency"]) {
438
+ const meta = r[field];
439
+ if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
440
+ throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
441
+ }
442
+ }
443
+ }
444
+ function applyRegistryPatch(prev, patch, now, token) {
445
+ return {
446
+ namespace: prev.namespace,
447
+ segment: prev.segment,
448
+ // `'currentGen' in patch`, NOT `patch.currentGen ?? prev.currentGen`. `null` is a legal, meaningful value
449
+ // here (no Storage generation yet), and `??` treats it as absent — so the nullish form would silently ignore a
450
+ // patch that clears the pointer and leave the old generation in place. The same reason `wrappedDeks` and
451
+ // `keyId` below use presence: any field whose null is a *value* cannot be merged with `??`.
452
+ currentGen: "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen,
453
+ wrappedDeks: "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks,
454
+ keyId: "keyId" in patch ? patch.keyId : prev.keyId,
455
+ status: patch.status ?? prev.status,
456
+ retention: "retention" in patch ? patch.retention : prev.retention,
457
+ residency: "residency" in patch ? patch.residency : prev.residency,
458
+ createdAt: prev.createdAt,
459
+ updatedAt: now,
460
+ token
461
+ };
462
+ }
463
+
464
+ export {
465
+ CloudRoaringError,
466
+ ValidationError,
467
+ WriteConflictError,
468
+ IntegrityError,
469
+ NotFoundError,
470
+ UnsupportedError,
471
+ CapabilityError,
472
+ BudgetExceededError,
473
+ KeyUnavailableError,
474
+ TransientError,
475
+ isCloudRoaringError,
476
+ isWriteConflictError,
477
+ isTransientError,
478
+ isNotFoundError,
479
+ isIntegrityError,
480
+ isValidationError,
481
+ encodeNameForKey,
482
+ decodeNameFromKey,
483
+ encodeNameForPath,
484
+ decodeNameFromPath,
485
+ MAX_NAME_LENGTH,
486
+ encodedNameLength,
487
+ validateSegmentRef,
488
+ RESERVED_NAMESPACE_PREFIX,
489
+ isReservedNamespace,
490
+ validateUserNamespace,
491
+ validateUserRef,
492
+ validateChunkRef,
493
+ mapWithConcurrency,
494
+ STORAGE_BACKEND,
495
+ brandAsBackend,
496
+ isStorageBackend,
497
+ validateNewRegistryRecord,
498
+ validateRegistryPatch,
499
+ serializeRegistryEnvelope,
500
+ registryCounterOf,
501
+ parseRegistryEnvelope,
502
+ recordFromNew,
503
+ applyRegistryPatch,
504
+ DEFAULT_NAMESPACE,
505
+ namespaceKeyPart,
506
+ namespacePathPart
507
+ };
508
+ //# sourceMappingURL=chunk-5OMC5U7U.js.map