@cloudbitmaps/core 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/chunk-7GT632UF.js +641 -0
- package/dist/chunk-7GT632UF.js.map +7 -0
- package/dist/chunk-CGILDXGP.js +73 -0
- package/dist/chunk-CGILDXGP.js.map +7 -0
- package/dist/chunk-GKHERVVQ.js +121 -0
- package/dist/chunk-GKHERVVQ.js.map +7 -0
- package/dist/{chunk-OSU7W2Y3.js → chunk-MVUD4R72.js} +200 -756
- package/dist/chunk-MVUD4R72.js.map +7 -0
- package/dist/core/audit.d.ts +53 -15
- package/dist/core/bit-route.d.ts +2 -0
- package/dist/core/blob.d.ts +4 -0
- package/dist/core/budget.d.ts +8 -3
- package/dist/core/check.d.ts +49 -0
- package/dist/core/combine-many.d.ts +11 -0
- package/dist/core/consistency.d.ts +11 -2
- package/dist/core/cooperative.d.ts +7 -0
- package/dist/core/crbm-storage-source.d.ts +7 -4
- package/dist/core/crypto.d.ts +5 -0
- package/dist/core/discover.d.ts +5 -3
- package/dist/core/engine.d.ts +4 -4
- package/dist/core/erase-id.d.ts +35 -12
- package/dist/core/erasure.d.ts +61 -27
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/leases.d.ts +2 -0
- package/dist/core/load-input.d.ts +48 -0
- package/dist/core/load.d.ts +23 -2
- package/dist/core/metrics.d.ts +21 -0
- package/dist/core/pinned-storage-source.d.ts +27 -8
- package/dist/core/pointer-id.d.ts +9 -0
- package/dist/core/ports.d.ts +100 -7
- package/dist/core/preflight.d.ts +80 -0
- package/dist/core/retention-sweep.d.ts +60 -15
- package/dist/core/retention.d.ts +17 -6
- package/dist/core/retry.d.ts +1 -2
- package/dist/core/rollback.d.ts +22 -12
- package/dist/core/validate.d.ts +33 -2
- package/dist/driver-kit.d.ts +3 -3
- package/dist/driver-kit.js +61 -20
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +24 -1
- package/dist/drivers/_shared/options.d.ts +15 -7
- package/dist/drivers/backends.d.ts +17 -2
- package/dist/drivers/crypto.d.ts +17 -2
- package/dist/export/index.d.ts +16 -2
- package/dist/index.d.ts +10 -9
- package/dist/index.js +1743 -1701
- package/dist/index.js.map +4 -4
- package/dist/node.d.ts +14 -0
- package/dist/node.js +1064 -0
- package/dist/node.js.map +7 -0
- package/package.json +11 -2
- package/dist/chunk-OSU7W2Y3.js.map +0 -7
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ when you install the two packages you want. Install those instead:
|
|
|
7
7
|
pnpm add @cloudbitmaps/roaring @cloudbitmaps/s3 # the store, and the storage you have (or /gcs, or /azure-blob)
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Install the same version of every `@cloudbitmaps/*` package: they release together, and each pins the exact version of `core` it was built with.
|
|
11
|
+
|
|
10
12
|
`roaring` needs its one install script to fetch its native binary, and newer clients run a dependency's script only if you allow it. Allow it before you install:
|
|
11
13
|
|
|
12
14
|
- **npm 12:** add `{ "allowScripts": { "roaring": true } }` to your `package.json`. Without it the install exits 0 and the package throws at `import`. npm 11 runs the script but warns until you allow it the same way.
|
|
@@ -18,7 +20,7 @@ pnpm add @cloudbitmaps/roaring @cloudbitmaps/s3 # the store, and the storage y
|
|
|
18
20
|
|
|
19
21
|
Depend on `@cloudbitmaps/core` directly only to write a driver, through `@cloudbitmaps/core/driver-kit`.
|
|
20
22
|
|
|
21
|
-
> **The main entry of this package
|
|
23
|
+
> **The main entry of this package, and `@cloudbitmaps/core/node`, are internal.** The main entry is the engine `@cloudbitmaps/roaring` and the driver packages are built on, and `/node` holds the backends and keystore that need a Node builtin. Both are outside the stability promise: they can change in any minor release, and a change to either gets no CHANGELOG Breaking entry. Install `@cloudbitmaps/roaring` and a driver. The public entry here is `@cloudbitmaps/core/driver-kit`, the contract for writing a driver.
|
|
22
24
|
|
|
23
25
|
> **ESM-only, Node ≥ 22.12.** Use `import`; for `require()`, Jest and TypeScript, see
|
|
24
26
|
> [CommonJS, Jest and TypeScript](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#commonjs-jest-and-typescript).
|
|
@@ -29,7 +31,7 @@ Depend on `@cloudbitmaps/core` directly only to write a driver, through `@cloudb
|
|
|
29
31
|
|
|
30
32
|
The codec-agnostic cloud engine: a bounded in-memory cache over immutable `.crbm` objects, chunk-skipping
|
|
31
33
|
intersection, the segment registry, the write-once load-and-publish path, generation collection, encryption at rest
|
|
32
|
-
and crypto-shred, erasure by rewrite, retention, and the in-memory and local-filesystem backends. It has **zero runtime
|
|
34
|
+
and crypto-shred, erasure by rewrite, retention, and the in-memory and local-filesystem backends (exported from `@cloudbitmaps/core/node`, so the main entry imports no Node builtin). It has **zero runtime
|
|
33
35
|
dependencies and no cloud SDK**. The cloud backends are their own packages, built against
|
|
34
36
|
`@cloudbitmaps/core/driver-kit`, the declared contract a driver depends on. A third-party driver can use it too.
|
|
35
37
|
|
|
@@ -50,7 +52,8 @@ The `.crbm` storage path lives in `src/core/`, one module per concern. Arrows re
|
|
|
50
52
|
publish.ts crbm-write.ts crbm-open.ts <---- crbm-storage-source.ts read path
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
`publish.ts`, `crbm-write.ts` and `crbm-open.ts` import none of the others.
|
|
55
|
+
`publish.ts`, `crbm-write.ts` and `crbm-open.ts` import none of the others. `bulk-load.ts` also imports
|
|
56
|
+
`sorted-chunks.ts`, which cuts a sorted load's ids into chunks and imports none of them. Outside this group, `load.ts` imports
|
|
54
57
|
`bulk-load.ts`, `publish.ts` and `crbm-open.ts`; `erase-id.ts` imports `publish.ts`, `crbm-write.ts` and
|
|
55
58
|
`crbm-open.ts`; `rollback.ts` and `consistency.ts` import `crbm-open.ts`. `combine-many.ts`, `pinned-storage-source.ts`
|
|
56
59
|
(types only) and the package entry import `crbm-storage-source.ts`.
|
|
@@ -58,8 +61,9 @@ The `.crbm` storage path lives in `src/core/`, one module per concern. Arrows re
|
|
|
58
61
|
| File | Holds |
|
|
59
62
|
|---|---|
|
|
60
63
|
| `crbm-write.ts` | writing one immutable generation from in-memory bitmaps, from a stream or from already-encoded chunks |
|
|
64
|
+
| `sorted-chunks.ts` | the chunks of a sorted load (`sorted: true`): ascending ids cut into one chunk at a time as they are read, each checked (a 32-bit id, not lower than the one before; a repeat skipped), the ids read once, and the first error they raise kept for the load to throw |
|
|
61
65
|
| `publish.ts` | the forward-only pointer advance every write ends with, the lease patch, the refusal to point a keyed row at a cleartext generation, and the settling of a registry write that got no answer |
|
|
62
|
-
| `bulk-load.ts` | the loader `load()` is built on, with its read-ahead: ids, a decoded bitmap or a combine's chunks in, made into chunks (ids bucketed, a bitmap split by its codec where the codec can, a combine's chunks checked), one generation written, and published when a registry is wired and the caller does not defer it; `load()` defers it and publishes through `publish.ts` after its guard |
|
|
66
|
+
| `bulk-load.ts` | the loader `load()` is built on, with its read-ahead: ids, a decoded bitmap or a combine's chunks in, made into chunks (ids bucketed, a bitmap split by its codec where the codec can, a combine's chunks checked), one generation written, and published when a registry is wired and the caller does not defer it; `load()` defers it and publishes through `publish.ts` after its guard. Sorted ids are written through the streaming writer of `crbm-write.ts` while they are read, after the row, the number and the key are settled |
|
|
63
67
|
| `known-generations.ts` | the row's ledger, `knownGens`: what a usable one is, the window a load collects by name, the cap, and the ledger a publish and a rollback write |
|
|
64
68
|
| `ledger-seed.ts` | the first window of a ledger over a row that has none, read from the bucket |
|
|
65
69
|
| `crbm-open.ts` | opening a stored generation without the cache: the blob reader over a driver object, the checked and noting opens, the identity checks that tie an open to the object it read, generation verification, and the rollback target open |
|
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INCARNATION_TOKEN,
|
|
3
|
+
IntegrityError,
|
|
4
|
+
MAX_KNOWN_GENERATIONS,
|
|
5
|
+
MAX_LEASES_PER_SEGMENT,
|
|
6
|
+
MAX_METADATA_BYTES,
|
|
7
|
+
MAX_STORED_KNOWN_GENERATIONS,
|
|
8
|
+
MAX_STORED_LEASES,
|
|
9
|
+
MAX_SUMMARY_CARDINALITY,
|
|
10
|
+
UnsupportedError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
canonicalMetadataJson,
|
|
13
|
+
encodeNameForKey,
|
|
14
|
+
encodeNameForPath,
|
|
15
|
+
fingerprintParts,
|
|
16
|
+
knownGensShapeFault,
|
|
17
|
+
refuseUnknownOptions,
|
|
18
|
+
renewsPointer,
|
|
19
|
+
requirePositiveInteger,
|
|
20
|
+
shown,
|
|
21
|
+
tailBytesFit
|
|
22
|
+
} from "./chunk-MVUD4R72.js";
|
|
23
|
+
|
|
24
|
+
// src/drivers/_shared/options.ts
|
|
25
|
+
function checkNow(name, now) {
|
|
26
|
+
if (now !== void 0 && typeof now !== "function") {
|
|
27
|
+
throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function requirePositiveInt(name, value) {
|
|
31
|
+
return value === void 0 ? void 0 : requirePositiveInteger(value, name);
|
|
32
|
+
}
|
|
33
|
+
var DELETE_OPTION_KEYS = ["ifVersion"];
|
|
34
|
+
var WRITE_OPTION_KEYS = ["held", "onToken"];
|
|
35
|
+
function checkDeleteOptions(options) {
|
|
36
|
+
if (options === void 0) return void 0;
|
|
37
|
+
refuseUnknownOptions("delete options", options, DELETE_OPTION_KEYS);
|
|
38
|
+
const { ifVersion } = options;
|
|
39
|
+
if (ifVersion !== void 0 && typeof ifVersion !== "string") {
|
|
40
|
+
throw new ValidationError(
|
|
41
|
+
`delete options: \`ifVersion\` must be a string; got ${shown(ifVersion)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return ifVersion;
|
|
45
|
+
}
|
|
46
|
+
function checkRegistryWriteOptions(options) {
|
|
47
|
+
if (options === void 0) return;
|
|
48
|
+
refuseUnknownOptions("write options", options, WRITE_OPTION_KEYS);
|
|
49
|
+
const { held, onToken } = options;
|
|
50
|
+
if (held !== void 0 && held !== null && typeof held !== "object") {
|
|
51
|
+
throw new ValidationError(
|
|
52
|
+
`write options: \`held\` must be a registry record or null; got ${shown(held)}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (onToken !== void 0 && typeof onToken !== "function") {
|
|
56
|
+
throw new ValidationError(
|
|
57
|
+
`write options: \`onToken\` must be a function; got ${shown(onToken)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/drivers/_shared/keys.ts
|
|
63
|
+
var DEFAULT_NAMESPACE = "_default";
|
|
64
|
+
function namespaceKeyPart(namespace) {
|
|
65
|
+
return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForKey(namespace);
|
|
66
|
+
}
|
|
67
|
+
function namespacePathPart(namespace) {
|
|
68
|
+
return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/drivers/_shared/registry.ts
|
|
72
|
+
var STATUSES = ["active", "destroyed"];
|
|
73
|
+
var RECORD_FIELDS = [
|
|
74
|
+
"namespace",
|
|
75
|
+
"segment",
|
|
76
|
+
"currentGen",
|
|
77
|
+
"wrappedDeks",
|
|
78
|
+
"keyId",
|
|
79
|
+
"status",
|
|
80
|
+
"retention",
|
|
81
|
+
"residency",
|
|
82
|
+
"summary",
|
|
83
|
+
"createdAt",
|
|
84
|
+
"updatedAt",
|
|
85
|
+
"token",
|
|
86
|
+
"knownGens",
|
|
87
|
+
"leases",
|
|
88
|
+
"pointerId"
|
|
89
|
+
];
|
|
90
|
+
var DRIVER_MANAGED = ["token", "pointerId"];
|
|
91
|
+
var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
|
|
92
|
+
var MAX_GOVERNANCE_BYTES = 64 * 1024;
|
|
93
|
+
var MAX_WRAPPED_DEKS = 8;
|
|
94
|
+
var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
|
|
95
|
+
var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
|
|
96
|
+
var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
|
|
97
|
+
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
98
|
+
function validateGeneration(gen) {
|
|
99
|
+
if (gen === null) return;
|
|
100
|
+
if (!Number.isSafeInteger(gen) || gen < 0) {
|
|
101
|
+
throw new ValidationError(
|
|
102
|
+
`currentGen must be a non-negative safe integer or null (no Storage generation yet); got ${gen}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function validatePatchGeneration(patch) {
|
|
107
|
+
if (!("currentGen" in patch)) return;
|
|
108
|
+
if (patch.currentGen === void 0) {
|
|
109
|
+
throw new ValidationError(
|
|
110
|
+
`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.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
validateGeneration(patch.currentGen);
|
|
114
|
+
}
|
|
115
|
+
function validateStatus(status) {
|
|
116
|
+
if (!STATUSES.includes(status)) {
|
|
117
|
+
throw new ValidationError(`status must be one of ${STATUSES.join("/")}; got ${status}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function validateGovernance(meta, field) {
|
|
121
|
+
if (meta === void 0) return;
|
|
122
|
+
if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
|
|
123
|
+
throw new ValidationError(
|
|
124
|
+
`${field} must be a plain object (got ${meta === null ? "null" : typeof meta})`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
let json;
|
|
128
|
+
try {
|
|
129
|
+
json = JSON.stringify(meta);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new ValidationError(`${field} must be JSON-serializable`);
|
|
132
|
+
}
|
|
133
|
+
if (json.length > MAX_GOVERNANCE_BYTES) {
|
|
134
|
+
throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function validateWrappedDeks(value, origin) {
|
|
138
|
+
if (value === void 0) return;
|
|
139
|
+
const fail = (msg) => {
|
|
140
|
+
throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
|
|
141
|
+
};
|
|
142
|
+
if (!Array.isArray(value)) fail("wrappedDeks must be an array");
|
|
143
|
+
const list = value;
|
|
144
|
+
if (list.length === 0) fail("wrappedDeks must be non-empty when present");
|
|
145
|
+
if (list.length > MAX_WRAPPED_DEKS)
|
|
146
|
+
fail(`wrappedDeks has ${list.length} entries, cap ${MAX_WRAPPED_DEKS}`);
|
|
147
|
+
for (const w of list) {
|
|
148
|
+
if (w === null || typeof w !== "object") fail("wrappedDeks entry must be an object");
|
|
149
|
+
const { keyId, wrapped } = w;
|
|
150
|
+
if (typeof keyId !== "string" || keyId.length === 0)
|
|
151
|
+
fail("wrappedDeks entry needs a non-empty keyId");
|
|
152
|
+
if (typeof wrapped !== "string" || wrapped.length === 0)
|
|
153
|
+
fail("wrappedDeks entry needs a non-empty wrapped blob");
|
|
154
|
+
if (wrapped.length > MAX_WRAPPED_DEK_BYTES)
|
|
155
|
+
fail(`wrappedDeks entry is ${wrapped.length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
159
|
+
function base64PadBitsAreZero(text) {
|
|
160
|
+
const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
161
|
+
if (pad === 0) return true;
|
|
162
|
+
const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
|
|
163
|
+
return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
|
|
164
|
+
}
|
|
165
|
+
function validateSummary(value, origin, ctx) {
|
|
166
|
+
if (value === void 0) return void 0;
|
|
167
|
+
const fail = (msg) => {
|
|
168
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
169
|
+
throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
|
|
170
|
+
};
|
|
171
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
172
|
+
fail(
|
|
173
|
+
`must be an object (got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value})`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const s = value;
|
|
177
|
+
const keys = Object.keys(s);
|
|
178
|
+
const isSealed = keys.includes("sealed");
|
|
179
|
+
const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
|
|
180
|
+
const extra = keys.filter((k) => !allowed.includes(k));
|
|
181
|
+
if (extra.length > 0) {
|
|
182
|
+
fail(
|
|
183
|
+
`has fields its ${isSealed ? "sealed" : "clear"} shape does not declare (${extra.join(", ")})`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const generation = s.generation;
|
|
187
|
+
if (!Number.isSafeInteger(generation) || generation < 0) {
|
|
188
|
+
fail(`generation must be a non-negative safe integer (got ${String(generation)})`);
|
|
189
|
+
}
|
|
190
|
+
if (isSealed) {
|
|
191
|
+
const sealed = s.sealed;
|
|
192
|
+
if (typeof sealed !== "string" || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {
|
|
193
|
+
fail("sealed must be canonical, padded standard base64");
|
|
194
|
+
}
|
|
195
|
+
const text = sealed;
|
|
196
|
+
const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
|
|
197
|
+
if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
|
|
198
|
+
fail(
|
|
199
|
+
`sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count, fingerprint and tail size, a tag, and metadata up to its cap)`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return Object.freeze({ generation, sealed: text });
|
|
203
|
+
}
|
|
204
|
+
const n = s.cardinality;
|
|
205
|
+
if (!Number.isInteger(n) || n < 0 || n > MAX_SUMMARY_CARDINALITY) {
|
|
206
|
+
fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
|
|
207
|
+
}
|
|
208
|
+
const fingerprint = s.fingerprint;
|
|
209
|
+
const parts = fingerprintParts(fingerprint);
|
|
210
|
+
if (parts === void 0) {
|
|
211
|
+
fail(
|
|
212
|
+
`fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown2(JSON.stringify(fingerprint))})`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const tailBytes = s.tailBytes;
|
|
216
|
+
if (!tailBytesFit(tailBytes, parts.size)) {
|
|
217
|
+
fail(
|
|
218
|
+
`tailBytes must be an integer from the footer's size to the object's size less its preamble, and at most the cap of one open's tail read (got ${shown2(JSON.stringify(tailBytes))} for an object of ${parts.size} bytes)`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (s.metadata === void 0) {
|
|
222
|
+
return Object.freeze({
|
|
223
|
+
generation,
|
|
224
|
+
cardinality: n,
|
|
225
|
+
fingerprint,
|
|
226
|
+
tailBytes
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const canonical = canonicalMetadataJson(s.metadata, fail);
|
|
230
|
+
if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
|
|
231
|
+
if (origin === "stored") return s;
|
|
232
|
+
const metadata = Object.freeze(JSON.parse(canonical));
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
generation,
|
|
235
|
+
cardinality: n,
|
|
236
|
+
fingerprint,
|
|
237
|
+
tailBytes,
|
|
238
|
+
metadata
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function validateSummaryNames(summary, currentGen) {
|
|
242
|
+
if (summary !== void 0 && summary.generation !== currentGen) {
|
|
243
|
+
throw new ValidationError(
|
|
244
|
+
`summary describes generation ${summary.generation}, but the row points at ${String(currentGen)}`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function summaryAgreesWithKeys(summary, wrappedDeks) {
|
|
249
|
+
return "sealed" in summary === (wrappedDeks !== void 0);
|
|
250
|
+
}
|
|
251
|
+
function validateSummaryKeys(summary, wrappedDeks) {
|
|
252
|
+
if (summary !== void 0 && !summaryAgreesWithKeys(summary, wrappedDeks)) {
|
|
253
|
+
throw new ValidationError(
|
|
254
|
+
wrappedDeks === void 0 ? "summary is sealed, but the row has no wrapped keys to open it with" : "summary is in the clear, but the row is encrypted: an encrypted segment's summary must be sealed"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function validateKnownGens(value, origin, ctx) {
|
|
259
|
+
if (value === void 0) return void 0;
|
|
260
|
+
const fail = (msg) => {
|
|
261
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
262
|
+
throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
|
|
263
|
+
};
|
|
264
|
+
const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
|
|
265
|
+
const fault = knownGensShapeFault(value, max, shown2);
|
|
266
|
+
if (fault !== void 0) fail(fault);
|
|
267
|
+
const out = [];
|
|
268
|
+
for (const entry of value) {
|
|
269
|
+
out.push(
|
|
270
|
+
origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return origin === "stored" ? out : Object.freeze(out);
|
|
274
|
+
}
|
|
275
|
+
var HOLDER = /^[0-9a-f]{16}$/;
|
|
276
|
+
var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
|
|
277
|
+
function validateLeases(value, origin, ctx) {
|
|
278
|
+
if (value === void 0) return void 0;
|
|
279
|
+
const fail = (msg) => {
|
|
280
|
+
const where = ctx === void 0 ? "" : `: ${ctx}`;
|
|
281
|
+
throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
|
|
282
|
+
};
|
|
283
|
+
if (!Array.isArray(value)) fail("must be an array");
|
|
284
|
+
const list = value;
|
|
285
|
+
const max = MAX_STORED_LEASES;
|
|
286
|
+
if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
|
|
287
|
+
const seen = /* @__PURE__ */ new Set();
|
|
288
|
+
const out = [];
|
|
289
|
+
for (const e of list) {
|
|
290
|
+
if (typeof e !== "object" || e === null || Array.isArray(e)) fail("an entry must be an object");
|
|
291
|
+
const entry = e;
|
|
292
|
+
const extra = Object.keys(entry).filter((k) => !LEASE_ENTRY_KEYS.includes(k));
|
|
293
|
+
if (extra.length > 0) fail(`an entry carries an undeclared field (${extra.join(", ")})`);
|
|
294
|
+
const { holder, generation, until } = entry;
|
|
295
|
+
if (typeof holder !== "string" || !HOLDER.test(holder)) {
|
|
296
|
+
fail("a holder must be 16 lowercase hex digits");
|
|
297
|
+
}
|
|
298
|
+
if (seen.has(holder)) fail("holders must be unique");
|
|
299
|
+
seen.add(holder);
|
|
300
|
+
for (const [name, n] of [
|
|
301
|
+
["generation", generation],
|
|
302
|
+
["until", until]
|
|
303
|
+
]) {
|
|
304
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
305
|
+
fail(`${name} must be a non-negative safe integer (got ${String(n)})`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
out.push(
|
|
309
|
+
origin === "stored" ? entry : Object.freeze({
|
|
310
|
+
holder,
|
|
311
|
+
generation,
|
|
312
|
+
until
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (origin === "stored") return out;
|
|
317
|
+
return out.length === 0 ? void 0 : Object.freeze(out);
|
|
318
|
+
}
|
|
319
|
+
function validateLeasesPointer(leases, currentGen) {
|
|
320
|
+
if (leases !== void 0 && leases.length > 0 && currentGen === null) {
|
|
321
|
+
throw new ValidationError("leases: a row with no pointer has no generation to lease");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function validateKnownGensFit(knownGens, currentGen, summary) {
|
|
325
|
+
if (knownGens === void 0) return;
|
|
326
|
+
if (currentGen === null) {
|
|
327
|
+
throw new ValidationError("knownGens: a row with no pointer has no generation to know");
|
|
328
|
+
}
|
|
329
|
+
const last = knownGens[knownGens.length - 1];
|
|
330
|
+
if (last === void 0 || last.generation !== currentGen) {
|
|
331
|
+
throw new ValidationError(
|
|
332
|
+
`knownGens must end with the row's pointer ${currentGen}, in the order the generations were made current (its last entry is ${last === void 0 ? "missing" : last.generation})`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
|
|
336
|
+
throw new ValidationError(
|
|
337
|
+
`knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function refuseDriverManaged(write, what) {
|
|
342
|
+
const named = DRIVER_MANAGED.filter((field) => field in write);
|
|
343
|
+
if (named.length > 0) {
|
|
344
|
+
throw new ValidationError(
|
|
345
|
+
`${what} names ${named.join(" and ")}, which the registry sets on every write and a caller never does`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function validateKeyId(keyId) {
|
|
350
|
+
if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
|
|
351
|
+
throw new ValidationError("keyId must be a non-empty string, or absent");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function validateNewRegistryRecord(rec) {
|
|
355
|
+
refuseDriverManaged(rec, "a new record");
|
|
356
|
+
validateKeyId(rec.keyId);
|
|
357
|
+
validateGeneration(rec.currentGen);
|
|
358
|
+
if (rec.status !== void 0) validateStatus(rec.status);
|
|
359
|
+
validateWrappedDeks(rec.wrappedDeks, "caller");
|
|
360
|
+
validateGovernance(rec.retention, "retention");
|
|
361
|
+
validateGovernance(rec.residency, "residency");
|
|
362
|
+
const knownGens = validateKnownGens(rec.knownGens, "caller");
|
|
363
|
+
const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
|
|
364
|
+
const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
|
|
365
|
+
validateKnownGensFit(knownGens, rec.currentGen, summary);
|
|
366
|
+
if (!("summary" in rec)) return withKnown;
|
|
367
|
+
validateSummaryNames(summary, rec.currentGen);
|
|
368
|
+
validateSummaryKeys(summary, rec.wrappedDeks);
|
|
369
|
+
return { ...withKnown, summary };
|
|
370
|
+
}
|
|
371
|
+
function validateRegistryPatch(patch) {
|
|
372
|
+
refuseDriverManaged(patch, "a patch");
|
|
373
|
+
if ("keyId" in patch) validateKeyId(patch.keyId);
|
|
374
|
+
validatePatchGeneration(patch);
|
|
375
|
+
if (patch.status !== void 0) validateStatus(patch.status);
|
|
376
|
+
if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
|
|
377
|
+
if ("retention" in patch) validateGovernance(patch.retention, "retention");
|
|
378
|
+
if ("residency" in patch) validateGovernance(patch.residency, "residency");
|
|
379
|
+
const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
|
|
380
|
+
const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
|
|
381
|
+
if (!("summary" in patch)) return withLeases;
|
|
382
|
+
return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
|
|
383
|
+
}
|
|
384
|
+
var REGISTRY_SCHEMA_VERSION = 5;
|
|
385
|
+
function assertRegistrySchemaVersion(raw, ctx) {
|
|
386
|
+
if (raw === void 0) {
|
|
387
|
+
throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
|
|
388
|
+
}
|
|
389
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
|
|
390
|
+
throw new IntegrityError(`registry row has a malformed schemaVersion (${shown2(raw)}): ${ctx}`);
|
|
391
|
+
}
|
|
392
|
+
if (raw < REGISTRY_SCHEMA_VERSION) {
|
|
393
|
+
throw new UnsupportedError(
|
|
394
|
+
`registry row schemaVersion ${raw} is one this build does not read: it reads schema ${REGISTRY_SCHEMA_VERSION} only. Export the segments with the release that wrote the row and load them with this one into a new prefix (steps: https://github.com/cloudbitmaps/cloudbitmaps/blob/main/CHANGELOG.md, the Breaking entry on registry schema 5): ${ctx}`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (raw > REGISTRY_SCHEMA_VERSION) {
|
|
398
|
+
throw new UnsupportedError(
|
|
399
|
+
`registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return raw;
|
|
403
|
+
}
|
|
404
|
+
function serializeRegistryEnvelope(env) {
|
|
405
|
+
return JSON.stringify({
|
|
406
|
+
schemaVersion: REGISTRY_SCHEMA_VERSION,
|
|
407
|
+
deleted: env.deleted,
|
|
408
|
+
record: env.record
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
var INCARNATION_BYTES = 16;
|
|
412
|
+
var WRITE_BYTES = 8;
|
|
413
|
+
function tokenParts(token, ctx, what = "token") {
|
|
414
|
+
const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
|
|
415
|
+
if (match === null) {
|
|
416
|
+
throw new IntegrityError(
|
|
417
|
+
`registry row ${what} is not one a shipped registry writes (${shown2(JSON.stringify(token))}): ${ctx}`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const counter = Number(match[2]);
|
|
421
|
+
if (!Number.isSafeInteger(counter)) {
|
|
422
|
+
throw new IntegrityError(`registry row ${what}'s counter is out of safe-integer range: ${ctx}`);
|
|
423
|
+
}
|
|
424
|
+
return { incarnation: match[1], counter };
|
|
425
|
+
}
|
|
426
|
+
function checkPointerId(pointerId, token, ctx) {
|
|
427
|
+
const parts = tokenParts(pointerId, ctx, "pointerId");
|
|
428
|
+
if (parts.incarnation !== token.incarnation) {
|
|
429
|
+
throw new IntegrityError(
|
|
430
|
+
`registry row pointerId names another incarnation than its token: ${ctx}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
if (parts.counter > token.counter) {
|
|
434
|
+
throw new IntegrityError(`registry row pointerId is a write after its token: ${ctx}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function drawHex(entropy, bytes) {
|
|
438
|
+
const drawn = entropy(bytes);
|
|
439
|
+
if (!(drawn instanceof Uint8Array) || drawn.length !== bytes) {
|
|
440
|
+
throw new ValidationError(
|
|
441
|
+
`the registry's entropy source must return ${bytes} bytes as a Uint8Array`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
let hex = "";
|
|
445
|
+
for (const b of drawn) hex += b.toString(16).padStart(2, "0");
|
|
446
|
+
return hex;
|
|
447
|
+
}
|
|
448
|
+
function drawIncarnation(entropy) {
|
|
449
|
+
return drawHex(entropy, INCARNATION_BYTES);
|
|
450
|
+
}
|
|
451
|
+
function drawWrite(entropy) {
|
|
452
|
+
return drawHex(entropy, WRITE_BYTES);
|
|
453
|
+
}
|
|
454
|
+
function nextRegistryToken(record, entropy) {
|
|
455
|
+
const { incarnation, counter } = tokenParts(record.token, record.segment);
|
|
456
|
+
return `${incarnation}.${counter + 1}.${drawWrite(entropy)}`;
|
|
457
|
+
}
|
|
458
|
+
function newIncarnationToken(entropy, tombstone) {
|
|
459
|
+
const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
|
|
460
|
+
return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
|
|
461
|
+
}
|
|
462
|
+
function shown2(v) {
|
|
463
|
+
const text = String(v);
|
|
464
|
+
return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
|
|
465
|
+
}
|
|
466
|
+
function parseRegistryEnvelope(text, ctx, expected) {
|
|
467
|
+
let parsed;
|
|
468
|
+
try {
|
|
469
|
+
parsed = JSON.parse(text);
|
|
470
|
+
} catch {
|
|
471
|
+
throw new IntegrityError(`registry row is not valid JSON: ${ctx}`);
|
|
472
|
+
}
|
|
473
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
474
|
+
throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
|
|
475
|
+
}
|
|
476
|
+
const env = parsed;
|
|
477
|
+
assertRegistrySchemaVersion(env.schemaVersion, ctx);
|
|
478
|
+
const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
|
|
479
|
+
if (extra.length > 0) {
|
|
480
|
+
throw new IntegrityError(
|
|
481
|
+
`registry row has fields its envelope does not declare (${shown2(extra.join(", "))}): ${ctx}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
|
|
485
|
+
throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);
|
|
486
|
+
}
|
|
487
|
+
const r = env.record;
|
|
488
|
+
assertStoredRecordShape(r, ctx);
|
|
489
|
+
if (typeof r.token !== "string") {
|
|
490
|
+
throw new IntegrityError(`registry row is missing its token: ${ctx}`);
|
|
491
|
+
}
|
|
492
|
+
checkPointerId(r.pointerId, tokenParts(r.token, ctx), ctx);
|
|
493
|
+
if (expected !== void 0 && (r.segment !== expected.segment || (r.namespace ?? void 0) !== (expected.namespace ?? void 0))) {
|
|
494
|
+
throw new IntegrityError(
|
|
495
|
+
`registry row names another segment than the one it is stored for: ${ctx}`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return { deleted: env.deleted, record: env.record };
|
|
499
|
+
}
|
|
500
|
+
function recordFromNew(ref, rec, now, token) {
|
|
501
|
+
return {
|
|
502
|
+
namespace: ref.namespace,
|
|
503
|
+
segment: ref.segment,
|
|
504
|
+
currentGen: rec.currentGen,
|
|
505
|
+
wrappedDeks: rec.wrappedDeks,
|
|
506
|
+
keyId: rec.keyId,
|
|
507
|
+
status: rec.status ?? "active",
|
|
508
|
+
retention: rec.retention,
|
|
509
|
+
residency: rec.residency,
|
|
510
|
+
summary: rec.summary,
|
|
511
|
+
knownGens: rec.knownGens,
|
|
512
|
+
// A new row has no holder: a lease is written only against a row that already exists.
|
|
513
|
+
leases: void 0,
|
|
514
|
+
createdAt: now,
|
|
515
|
+
updatedAt: now,
|
|
516
|
+
token,
|
|
517
|
+
pointerId: token
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function assertStoredRecordShape(r, ctx) {
|
|
521
|
+
if (typeof r.segment !== "string" || typeof r.pointerId !== "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") {
|
|
522
|
+
throw new IntegrityError(`registry record is missing required fields: ${ctx}`);
|
|
523
|
+
}
|
|
524
|
+
if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
|
|
525
|
+
throw new IntegrityError(
|
|
526
|
+
`registry record has an invalid currentGen (${shown2(r.currentGen)}): ${ctx}`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (!STATUSES.includes(r.status)) {
|
|
530
|
+
throw new IntegrityError(`registry record has an unknown status (${shown2(r.status)}): ${ctx}`);
|
|
531
|
+
}
|
|
532
|
+
const declared = RECORD_FIELDS;
|
|
533
|
+
const extra = Object.keys(r).filter((k) => !declared.includes(k));
|
|
534
|
+
if (extra.length > 0) {
|
|
535
|
+
throw new IntegrityError(
|
|
536
|
+
`registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown2(extra.join(", "))}): ${ctx}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (r.keyId !== void 0 && typeof r.keyId !== "string") {
|
|
540
|
+
throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
|
|
541
|
+
}
|
|
542
|
+
validateWrappedDeks(r.wrappedDeks, "stored");
|
|
543
|
+
for (const field of ["retention", "residency"]) {
|
|
544
|
+
const meta = r[field];
|
|
545
|
+
if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
|
|
546
|
+
throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
validateSummary(r.summary, "stored", ctx);
|
|
550
|
+
validateKnownGens(r.knownGens, "stored", ctx);
|
|
551
|
+
validateLeases(r.leases, "stored", ctx);
|
|
552
|
+
}
|
|
553
|
+
function applyRegistryPatch(prev, patch, now, token) {
|
|
554
|
+
const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
|
|
555
|
+
const wrappedDeks = "wrappedDeks" in patch ? patch.wrappedDeks : prev.wrappedDeks;
|
|
556
|
+
let summary;
|
|
557
|
+
if ("summary" in patch) {
|
|
558
|
+
validateSummaryNames(patch.summary, currentGen);
|
|
559
|
+
validateSummaryKeys(patch.summary, wrappedDeks);
|
|
560
|
+
summary = patch.summary;
|
|
561
|
+
} else {
|
|
562
|
+
const kept = currentGen === prev.currentGen ? prev.summary : void 0;
|
|
563
|
+
summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
|
|
564
|
+
}
|
|
565
|
+
let knownGens;
|
|
566
|
+
if ("knownGens" in patch) {
|
|
567
|
+
validateKnownGensFit(patch.knownGens, currentGen, summary);
|
|
568
|
+
knownGens = patch.knownGens;
|
|
569
|
+
} else {
|
|
570
|
+
knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
|
|
571
|
+
}
|
|
572
|
+
let leases;
|
|
573
|
+
if ("leases" in patch) {
|
|
574
|
+
leases = patch.leases;
|
|
575
|
+
validateLeasesPointer(leases, currentGen);
|
|
576
|
+
const most = Math.max(MAX_LEASES_PER_SEGMENT, prev.leases?.length ?? 0);
|
|
577
|
+
if (leases !== void 0 && leases.length > most) {
|
|
578
|
+
throw new ValidationError(`leases: has ${leases.length} entries, cap ${most}`);
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
leases = prev.leases;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
namespace: prev.namespace,
|
|
585
|
+
segment: prev.segment,
|
|
586
|
+
currentGen,
|
|
587
|
+
wrappedDeks,
|
|
588
|
+
keyId: "keyId" in patch ? patch.keyId : prev.keyId,
|
|
589
|
+
status: patch.status ?? prev.status,
|
|
590
|
+
retention: "retention" in patch ? patch.retention : prev.retention,
|
|
591
|
+
residency: "residency" in patch ? patch.residency : prev.residency,
|
|
592
|
+
summary,
|
|
593
|
+
knownGens,
|
|
594
|
+
leases,
|
|
595
|
+
createdAt: prev.createdAt,
|
|
596
|
+
updatedAt: now,
|
|
597
|
+
token,
|
|
598
|
+
pointerId: renewsPointer(patch) ? token : prev.pointerId
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/drivers/_shared/entropy.ts
|
|
603
|
+
function webCrypto() {
|
|
604
|
+
const c = globalThis.crypto;
|
|
605
|
+
return c !== void 0 && typeof c.getRandomValues === "function" ? c : void 0;
|
|
606
|
+
}
|
|
607
|
+
var webCryptoEntropy = (length) => {
|
|
608
|
+
const c = webCrypto();
|
|
609
|
+
if (c === void 0) {
|
|
610
|
+
throw new UnsupportedError(
|
|
611
|
+
"this runtime has no Web Crypto (crypto.getRandomValues), which a registry needs for the token of every row it writes: run on a runtime that has it"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
return c.getRandomValues(new Uint8Array(length));
|
|
615
|
+
};
|
|
616
|
+
function entropyIsAvailable(entropy) {
|
|
617
|
+
return entropy !== webCryptoEntropy || webCrypto() !== void 0;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export {
|
|
621
|
+
checkNow,
|
|
622
|
+
requirePositiveInt,
|
|
623
|
+
checkDeleteOptions,
|
|
624
|
+
checkRegistryWriteOptions,
|
|
625
|
+
validateNewRegistryRecord,
|
|
626
|
+
validateRegistryPatch,
|
|
627
|
+
serializeRegistryEnvelope,
|
|
628
|
+
drawIncarnation,
|
|
629
|
+
drawWrite,
|
|
630
|
+
nextRegistryToken,
|
|
631
|
+
newIncarnationToken,
|
|
632
|
+
parseRegistryEnvelope,
|
|
633
|
+
recordFromNew,
|
|
634
|
+
applyRegistryPatch,
|
|
635
|
+
webCryptoEntropy,
|
|
636
|
+
entropyIsAvailable,
|
|
637
|
+
DEFAULT_NAMESPACE,
|
|
638
|
+
namespaceKeyPart,
|
|
639
|
+
namespacePathPart
|
|
640
|
+
};
|
|
641
|
+
//# sourceMappingURL=chunk-7GT632UF.js.map
|