@blamejs/core 0.8.43 → 0.8.50
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/CHANGELOG.md +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/backup/index.js
CHANGED
|
@@ -1,62 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.backup
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav Production
|
|
6
|
+
* @title Backup
|
|
4
7
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* @intro
|
|
9
|
+
* PQC-encrypted backup bundles — sealed columns + audit chain +
|
|
10
|
+
* keyring. SLH-DSA signature on every bundle, kid pinning, restore
|
|
11
|
+
* validates signature against operator-pinned public key.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* { relativePath: "db.enc", kind: "raw", required: true },
|
|
16
|
-
* { relativePath: "db.key.enc", kind: "raw", required: true },
|
|
17
|
-
* { relativePath: "vault.key", kind: "raw", required: false },
|
|
18
|
-
* { relativePath: "ca.key.sealed",kind: "vault-sealed", required: false },
|
|
19
|
-
* ],
|
|
20
|
-
* vaultKeyJson: function () { return fs.readFileSync('./data/vault.key','utf8'); },
|
|
21
|
-
* retention: { keep: 7 }, // keep latest 7; older purged after run
|
|
22
|
-
* audit: true,
|
|
23
|
-
* scheduler: b.scheduler, // optional; needed for backup.schedule()
|
|
24
|
-
* });
|
|
13
|
+
* The namespace wires `b.backupBundle.create` (encrypt + emit a bundle
|
|
14
|
+
* directory) to a pluggable storage backend, plus retention policy +
|
|
15
|
+
* audit emission. Ships with a local-filesystem backend
|
|
16
|
+
* (`b.backup.localStorage`); S3 or any custom backend drops in through
|
|
17
|
+
* the same interface.
|
|
25
18
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* {
|
|
42
|
-
* async writeBundle(bundleId, sourceDir) copy sourceDir contents under bundleId
|
|
43
|
-
* async readBundle(bundleId, destDir) copy bundle out to destDir
|
|
44
|
-
* async listBundles() → [{ bundleId, createdAt, size }]
|
|
45
|
-
* async deleteBundle(bundleId)
|
|
46
|
-
* async hasBundle(bundleId) → boolean
|
|
47
|
-
* }
|
|
19
|
+
* Storage backend contract:
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* async writeBundle(bundleId, sourceDir),
|
|
23
|
+
* async readBundle(bundleId, destDir),
|
|
24
|
+
* async listBundles(), // → [{ bundleId, createdAt, size }]
|
|
25
|
+
* async deleteBundle(bundleId),
|
|
26
|
+
* async hasBundle(bundleId),
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* `vaultKeyJson` can be a string (the operator has the JSON in hand)
|
|
30
|
+
* or a function returning a string (or async returning a string) — the
|
|
31
|
+
* framework calls it each backup so a long-running app doesn't pin
|
|
32
|
+
* the vault key in memory between runs.
|
|
48
33
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
34
|
+
* Bundle IDs are filesystem-safe timestamps with millisecond precision
|
|
35
|
+
* plus a 4-byte random suffix: `2026-04-27T14-00-00-123Z-a8f30b21`.
|
|
36
|
+
* Colons + dots in standard ISO-8601 are replaced with dashes so the
|
|
37
|
+
* id works as a directory name on every platform (Windows reserves
|
|
38
|
+
* `:` for drive letters). String sort still gives chronological order.
|
|
39
|
+
*
|
|
40
|
+
* Posture-enforced encryption: HIPAA / PCI-DSS postures refuse a
|
|
41
|
+
* pipeline created with `encrypt: false`. Posture-enforced residency:
|
|
42
|
+
* gdpr / uk-gdpr / dpdp / pipl-cn / lgpd-br / appi-jp / pdpa-sg refuse
|
|
43
|
+
* a destination tag that doesn't match the live DB residency unless
|
|
44
|
+
* the operator passes `allowCrossBorder: true` with a documented
|
|
45
|
+
* `legalBasis`.
|
|
46
|
+
*
|
|
47
|
+
* @card
|
|
48
|
+
* PQC-encrypted backup bundles — sealed columns + audit chain + keyring.
|
|
60
49
|
*/
|
|
61
50
|
|
|
62
51
|
var fs = require("fs");
|
|
@@ -65,10 +54,12 @@ var path = require("path");
|
|
|
65
54
|
var crypto = require("../crypto");
|
|
66
55
|
var atomicFile = require("../atomic-file");
|
|
67
56
|
var backupBundle = require("./bundle");
|
|
57
|
+
var backupManifest = require("./manifest");
|
|
68
58
|
var lazyRequire = require("../lazy-require");
|
|
69
59
|
var validateOpts = require("../validate-opts");
|
|
70
60
|
var numericBounds = require("../numeric-bounds");
|
|
71
61
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
62
|
+
var compliance = lazyRequire(function () { return require("../compliance"); });
|
|
72
63
|
// lazyRequire ../db so backup stays a leaf module operators can use
|
|
73
64
|
// without the rest of the framework's DB chain loaded in the same
|
|
74
65
|
// module graph (CLI tools, stand-alone backup runners). The db()
|
|
@@ -78,6 +69,15 @@ var { defineClass } = require("../framework-error");
|
|
|
78
69
|
|
|
79
70
|
var BackupError = defineClass("BackupError");
|
|
80
71
|
|
|
72
|
+
// Postures whose published controls require backup encryption. PCI
|
|
73
|
+
// DSS 4.0.1 Req 9.4.1.b ("backups are protected with strong cryptography
|
|
74
|
+
// and encrypted") and HIPAA §164.310(d)(2)(iv) ("create a retrievable,
|
|
75
|
+
// exact copy of ePHI" — encryption strongly implied by §164.312(a)(2)
|
|
76
|
+
// (iv) addressable encryption standard).
|
|
77
|
+
var BACKUP_ENCRYPTION_REQUIRED_POSTURES = Object.freeze([
|
|
78
|
+
"hipaa", "pci-dss",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
81
|
// "2026-04-27T14-00-00-123Z-a8f30b21" — atomicFile.pathTimestamp() form
|
|
82
82
|
// (ISO with ':'+'.' replaced by '-') plus a random suffix.
|
|
83
83
|
var BUNDLE_ID_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z-[0-9a-f]{8}$/;
|
|
@@ -109,6 +109,37 @@ function _dirSize(p) {
|
|
|
109
109
|
|
|
110
110
|
// ---- Local filesystem storage backend (the default) ----
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @primitive b.backup.localStorage
|
|
114
|
+
* @signature b.backup.localStorage(opts)
|
|
115
|
+
* @since 0.4.0
|
|
116
|
+
* @status stable
|
|
117
|
+
* @related b.backup.create
|
|
118
|
+
*
|
|
119
|
+
* Local-filesystem storage backend implementing the
|
|
120
|
+
* `{ writeBundle, readBundle, listBundles, deleteBundle, hasBundle }`
|
|
121
|
+
* contract. Bundles land as directories named by bundle id under
|
|
122
|
+
* `opts.root`. Newest-first ordering is enforced by reverse
|
|
123
|
+
* lexicographic sort on the timestamp-prefixed bundle id.
|
|
124
|
+
*
|
|
125
|
+
* Operators pointing at S3 / GCS / Azure Blob / a tape gateway pass a
|
|
126
|
+
* custom backend matching the same shape; the engine never touches the
|
|
127
|
+
* filesystem directly.
|
|
128
|
+
*
|
|
129
|
+
* @opts
|
|
130
|
+
* root: string, // required; directory under which bundle dirs land
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* var fs = require("node:fs");
|
|
134
|
+
* var path = require("node:path");
|
|
135
|
+
* var os = require("node:os");
|
|
136
|
+
* var root = fs.mkdtempSync(path.join(os.tmpdir(), "backup-root-"));
|
|
137
|
+
*
|
|
138
|
+
* var storage = b.backup.localStorage({ root: root });
|
|
139
|
+
* storage.name; // → "local"
|
|
140
|
+
* typeof storage.writeBundle; // → "function"
|
|
141
|
+
* typeof storage.listBundles; // → "function"
|
|
142
|
+
*/
|
|
112
143
|
function localStorage(opts) {
|
|
113
144
|
opts = opts || {};
|
|
114
145
|
validateOpts.requireNonEmptyString(opts.root, "localStorage: opts.root", BackupError, "backup/no-storage-root");
|
|
@@ -210,6 +241,71 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
210
241
|
"opts.vaultKeyJson is required (string or function returning a string)");
|
|
211
242
|
}
|
|
212
243
|
|
|
244
|
+
/**
|
|
245
|
+
* @primitive b.backup.create
|
|
246
|
+
* @signature b.backup.create(opts)
|
|
247
|
+
* @since 0.4.0
|
|
248
|
+
* @status stable
|
|
249
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, dora
|
|
250
|
+
* @related b.backup.localStorage, b.backup.recommendedFiles, b.backup.verifyManifestSignature, b.backupBundle.create
|
|
251
|
+
*
|
|
252
|
+
* Build a backup engine bound to a data directory, a storage backend,
|
|
253
|
+
* the operator's passphrase, and an include list. Returns an object
|
|
254
|
+
* with `run` / `list` / `delete` / `read` / `purgeOlder` / `schedule` /
|
|
255
|
+
* `scheduleTest` plus the wired `storage` reference.
|
|
256
|
+
*
|
|
257
|
+
* Each `run()` produces a fresh bundle id (`<iso-timestamp>-<8 hex>`),
|
|
258
|
+
* stages encryption to a process-private tmpdir, writes through
|
|
259
|
+
* `storage.writeBundle`, sweeps tmpdir, then applies retention. Audit
|
|
260
|
+
* events `backup.success` / `backup.failure` / `backup.retention.swept`
|
|
261
|
+
* land on `b.audit` when `opts.audit !== false`.
|
|
262
|
+
*
|
|
263
|
+
* Posture gates fire at `create()` time, not `run()` time — so a
|
|
264
|
+
* misconfigured pipeline refuses to construct rather than producing
|
|
265
|
+
* one good bundle and then failing the next.
|
|
266
|
+
*
|
|
267
|
+
* @opts
|
|
268
|
+
* dataDir: string, // required; must exist on disk
|
|
269
|
+
* storage: StorageBackend, // required; localStorage() or custom
|
|
270
|
+
* passphrase: Buffer | string, // required; KEK for per-file Argon2id wrap
|
|
271
|
+
* files: Array<{ relativePath, kind, required }>,
|
|
272
|
+
* vaultKeyJson: string | () => string | Promise<string>,
|
|
273
|
+
* retention: { keep: number }, // optional; sweep older bundles after run()
|
|
274
|
+
* audit: boolean, // default true
|
|
275
|
+
* scheduler: b.scheduler, // required for schedule() / scheduleTest()
|
|
276
|
+
* flushBeforeBackup: false | () => void | Promise<void>,
|
|
277
|
+
* requireFlush: boolean, // default false
|
|
278
|
+
* encrypt: boolean, // default true; refused under hipaa / pci-dss
|
|
279
|
+
* residencyTag: string | null, // e.g. "EU"; checked against b.db.getDataResidency()
|
|
280
|
+
* allowCrossBorder: boolean, // explicit override for residency mismatch
|
|
281
|
+
* legalBasis: string, // recorded in audit chain when allowCrossBorder
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* var fs = require("node:fs");
|
|
285
|
+
* var path = require("node:path");
|
|
286
|
+
* var os = require("node:os");
|
|
287
|
+
*
|
|
288
|
+
* var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "backup-data-"));
|
|
289
|
+
* var root = fs.mkdtempSync(path.join(os.tmpdir(), "backup-root-"));
|
|
290
|
+
* fs.writeFileSync(path.join(dataDir, "db.enc"), Buffer.from([1, 2, 3]));
|
|
291
|
+
* fs.writeFileSync(path.join(dataDir, "db.key.enc"), Buffer.from([4, 5, 6]));
|
|
292
|
+
*
|
|
293
|
+
* var engine = b.backup.create({
|
|
294
|
+
* dataDir: dataDir,
|
|
295
|
+
* storage: b.backup.localStorage({ root: root }),
|
|
296
|
+
* passphrase: Buffer.from("operator backup passphrase"),
|
|
297
|
+
* files: [
|
|
298
|
+
* { relativePath: "db.enc", kind: "raw", required: true },
|
|
299
|
+
* { relativePath: "db.key.enc", kind: "raw", required: true },
|
|
300
|
+
* ],
|
|
301
|
+
* vaultKeyJson: '{"version":1,"kid":"k1"}',
|
|
302
|
+
* retention: { keep: 7 },
|
|
303
|
+
* });
|
|
304
|
+
*
|
|
305
|
+
* typeof engine.run; // → "function"
|
|
306
|
+
* typeof engine.list; // → "function"
|
|
307
|
+
* typeof engine.purgeOlder; // → "function"
|
|
308
|
+
*/
|
|
213
309
|
function create(opts) {
|
|
214
310
|
opts = opts || {};
|
|
215
311
|
if (typeof opts.dataDir !== "string" || !fs.existsSync(opts.dataDir)) {
|
|
@@ -230,6 +326,85 @@ function create(opts) {
|
|
|
230
326
|
"create: opts.vaultKeyJson is required (string or function returning string)");
|
|
231
327
|
}
|
|
232
328
|
|
|
329
|
+
// Posture-enforced backup encryption (F-BUDR-4). HIPAA / PCI-DSS
|
|
330
|
+
// operators MUST keep encryption on. The framework's backup pipeline
|
|
331
|
+
// is encrypted-by-default — passphrase + per-file XChaCha20-Poly1305
|
|
332
|
+
// — but operators in third-party storage backends sometimes pass
|
|
333
|
+
// `encrypt: false` on bespoke backends. Refuse boot under regulated
|
|
334
|
+
// postures. Permits explicit `opts.allowUnencrypted: true` only when
|
|
335
|
+
// a documented compensating control is present (offline tape vault
|
|
336
|
+
// with physical custody, separate KMS-encrypted bucket, etc.) — and
|
|
337
|
+
// even then the framework refuses unless paired with the operator's
|
|
338
|
+
// posture explicitly acknowledging the deviation.
|
|
339
|
+
var posture = null;
|
|
340
|
+
try { posture = compliance().current(); }
|
|
341
|
+
catch (_e) { /* compliance optional at backup-create time */ }
|
|
342
|
+
if (posture && BACKUP_ENCRYPTION_REQUIRED_POSTURES.indexOf(posture) !== -1) {
|
|
343
|
+
if (opts.encrypt === false) {
|
|
344
|
+
throw new BackupError("backup/encryption-required",
|
|
345
|
+
"backup.create: posture='" + posture + "' requires backup encryption " +
|
|
346
|
+
"(HIPAA §164.310(d)(2)(iv) / PCI DSS 4.0.1 Req 9.4.1.b). " +
|
|
347
|
+
"Refusing to create an unencrypted backup pipeline.");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// F-CBT-3 — backup destination residency posture. EU-tagged primary
|
|
352
|
+
// backing up to a US-region destination is a GDPR Article 46
|
|
353
|
+
// cross-border transfer; without an explicit operator opt-in the
|
|
354
|
+
// framework refuses to create the pipeline under gdpr / dpdp /
|
|
355
|
+
// pipl-cn / uk-gdpr / lgpd-br / appi-jp / pdpa-sg postures.
|
|
356
|
+
//
|
|
357
|
+
// b.backup.create({
|
|
358
|
+
// ...,
|
|
359
|
+
// residencyTag: "EU", // matches your DB residency
|
|
360
|
+
// allowCrossBorder: true, // explicit override
|
|
361
|
+
// legalBasis: "EU SCCs 2021/914", // recorded in audit chain
|
|
362
|
+
// });
|
|
363
|
+
var BACKUP_RESIDENCY_REGULATED_POSTURES = ["gdpr", "uk-gdpr", "dpdp", "pipl-cn",
|
|
364
|
+
"lgpd-br", "appi-jp", "pdpa-sg"];
|
|
365
|
+
var backupResidencyTag = opts.residencyTag || null;
|
|
366
|
+
if (opts.residencyTag !== undefined && opts.residencyTag !== null &&
|
|
367
|
+
(typeof opts.residencyTag !== "string" || opts.residencyTag.length === 0)) {
|
|
368
|
+
throw new BackupError("backup/bad-residency-tag",
|
|
369
|
+
"backup.create: opts.residencyTag must be a non-empty string or null");
|
|
370
|
+
}
|
|
371
|
+
if (posture && BACKUP_RESIDENCY_REGULATED_POSTURES.indexOf(posture) !== -1) {
|
|
372
|
+
var dbResidency = null;
|
|
373
|
+
try {
|
|
374
|
+
var dbModuleR = dbModuleLazy();
|
|
375
|
+
dbResidency = (dbModuleR && typeof dbModuleR.getDataResidency === "function")
|
|
376
|
+
? dbModuleR.getDataResidency() : null;
|
|
377
|
+
} catch (_e) { dbResidency = null; }
|
|
378
|
+
var dbTag = (dbResidency && dbResidency.region) || null;
|
|
379
|
+
if (dbTag && backupResidencyTag &&
|
|
380
|
+
dbTag !== backupResidencyTag &&
|
|
381
|
+
backupResidencyTag !== "unrestricted" &&
|
|
382
|
+
dbTag !== "unrestricted") {
|
|
383
|
+
if (!opts.allowCrossBorder) {
|
|
384
|
+
throw new BackupError("backup/residency-mismatch",
|
|
385
|
+
"backup.create: db residency '" + dbTag +
|
|
386
|
+
"' but backup destination residencyTag '" + backupResidencyTag +
|
|
387
|
+
"' under '" + posture + "' posture. This is a cross-border data " +
|
|
388
|
+
"transfer (GDPR Art 46 / DPDP / PIPL category). Pass " +
|
|
389
|
+
"allowCrossBorder: true with a documented legalBasis to suppress.");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (!backupResidencyTag) {
|
|
393
|
+
// Under regulated posture an undeclared backup residency is a
|
|
394
|
+
// smell — emit warning, don't refuse (operators with single-
|
|
395
|
+
// region S3 buckets that match the DB region are the common
|
|
396
|
+
// case and shouldn't be blocked).
|
|
397
|
+
try {
|
|
398
|
+
audit().safeEmit({
|
|
399
|
+
action: "backup.residency_undeclared",
|
|
400
|
+
outcome: "success",
|
|
401
|
+
metadata: { severity: "warning", posture: posture, dbResidency: dbTag,
|
|
402
|
+
recommendation: "declare opts.residencyTag matching the DB residency tag" },
|
|
403
|
+
});
|
|
404
|
+
} catch (_e) { /* drop-silent */ }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
233
408
|
var dataDir = opts.dataDir;
|
|
234
409
|
var storage = opts.storage;
|
|
235
410
|
var passphrase = opts.passphrase;
|
|
@@ -440,6 +615,140 @@ function create(opts) {
|
|
|
440
615
|
return { name: name, instance: schedInstance };
|
|
441
616
|
}
|
|
442
617
|
|
|
618
|
+
// scheduleTest — periodic restore-and-verify drill required by HIPAA
|
|
619
|
+
// §164.308(a)(7)(ii)(D) ("testing and revision procedures"). The
|
|
620
|
+
// framework picks the latest backup, restores it to the operator-
|
|
621
|
+
// supplied directory, runs the operator's verify callback, and emits
|
|
622
|
+
// backup.test.passed / backup.test.failed in the audit chain.
|
|
623
|
+
//
|
|
624
|
+
// await b.backup.scheduleTest({
|
|
625
|
+
// cron: "0 3 * * 0", // weekly at 03:00 Sunday
|
|
626
|
+
// restoreTo: "/var/backup-test/staging",
|
|
627
|
+
// verify: async function ({ outDir, manifest }) {
|
|
628
|
+
// // operator confirms key files restored, returns truthy on
|
|
629
|
+
// // success or throws on failure.
|
|
630
|
+
// },
|
|
631
|
+
// notify: async function ({ outcome, reason, manifest }) { /* page operator */ },
|
|
632
|
+
// posture: "hipaa",
|
|
633
|
+
// });
|
|
634
|
+
function scheduleTest(testOpts) {
|
|
635
|
+
if (!scheduler || typeof scheduler.create !== "function") {
|
|
636
|
+
throw new BackupError("backup/no-scheduler",
|
|
637
|
+
"scheduleTest: opts.scheduler must be wired at create() to use scheduleTest()");
|
|
638
|
+
}
|
|
639
|
+
testOpts = testOpts || {};
|
|
640
|
+
if (typeof testOpts.cron !== "string" || testOpts.cron.length === 0) {
|
|
641
|
+
throw new BackupError("backup/bad-test-schedule",
|
|
642
|
+
"scheduleTest: opts.cron is required");
|
|
643
|
+
}
|
|
644
|
+
if (typeof testOpts.restoreTo !== "string" || testOpts.restoreTo.length === 0) {
|
|
645
|
+
throw new BackupError("backup/bad-test-restore-to",
|
|
646
|
+
"scheduleTest: opts.restoreTo is required (operator-controlled staging dir)");
|
|
647
|
+
}
|
|
648
|
+
if (typeof testOpts.verify !== "function") {
|
|
649
|
+
throw new BackupError("backup/bad-test-verify",
|
|
650
|
+
"scheduleTest: opts.verify must be an async function — operator " +
|
|
651
|
+
"supplies the per-deployment verification (file exists, schema " +
|
|
652
|
+
"matches, audit chain verifies, etc.)");
|
|
653
|
+
}
|
|
654
|
+
var name = testOpts.name || "blamejs.backup.test";
|
|
655
|
+
var schedInstance = scheduler.create({ audit: auditOn });
|
|
656
|
+
schedInstance.schedule({
|
|
657
|
+
name: name,
|
|
658
|
+
cron: testOpts.cron,
|
|
659
|
+
timezone: testOpts.timezone,
|
|
660
|
+
run: async function () {
|
|
661
|
+
var startedAt = Date.now();
|
|
662
|
+
var bundles = [];
|
|
663
|
+
try { bundles = await storage.listBundles(); }
|
|
664
|
+
catch (e) {
|
|
665
|
+
_emitAudit("backup.test.failed",
|
|
666
|
+
{ reason: "listBundles failed: " + ((e && e.message) || String(e)) },
|
|
667
|
+
"failure");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!bundles || bundles.length === 0) {
|
|
671
|
+
_emitAudit("backup.test.failed",
|
|
672
|
+
{ reason: "no bundles in storage to test against" },
|
|
673
|
+
"failure");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// Newest bundle (storage.listBundles returns newest first).
|
|
677
|
+
var bundleId = bundles[0].bundleId;
|
|
678
|
+
var stagingDir = path.join(testOpts.restoreTo,
|
|
679
|
+
"test-" + bundleId.replace(/[:.]/g, "-"));
|
|
680
|
+
// Refuse to overwrite an existing dir — operators get a fresh
|
|
681
|
+
// restore every drill.
|
|
682
|
+
if (fs.existsSync(stagingDir)) {
|
|
683
|
+
_emitAudit("backup.test.failed",
|
|
684
|
+
{ bundleId: bundleId, reason: "stagingDir already exists: " + stagingDir },
|
|
685
|
+
"failure");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
var manifestPath, manifest, sigVerification;
|
|
689
|
+
try {
|
|
690
|
+
await storage.readBundle(bundleId, stagingDir);
|
|
691
|
+
manifestPath = path.join(stagingDir, "manifest.json");
|
|
692
|
+
if (!fs.existsSync(manifestPath)) {
|
|
693
|
+
throw new BackupError("backup/test-no-manifest",
|
|
694
|
+
"manifest.json missing under restored bundle " + bundleId);
|
|
695
|
+
}
|
|
696
|
+
manifest = backupManifest.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
697
|
+
// Verify the manifest signature so a tampered backup test
|
|
698
|
+
// surfaces here, not as a regulator finding later.
|
|
699
|
+
sigVerification = backupManifest.verifySignature(manifest, {
|
|
700
|
+
expectedFingerprint: testOpts.expectedFingerprint || undefined,
|
|
701
|
+
});
|
|
702
|
+
if (!sigVerification.ok) {
|
|
703
|
+
throw new BackupError("backup/test-bad-signature",
|
|
704
|
+
"manifest signature invalid: " + sigVerification.reason);
|
|
705
|
+
}
|
|
706
|
+
// Hand off to operator verify hook
|
|
707
|
+
await testOpts.verify({
|
|
708
|
+
outDir: stagingDir,
|
|
709
|
+
manifest: manifest,
|
|
710
|
+
bundleId: bundleId,
|
|
711
|
+
sigFingerprint: sigVerification.fingerprint,
|
|
712
|
+
});
|
|
713
|
+
_emitAudit("backup.test.passed", {
|
|
714
|
+
bundleId: bundleId,
|
|
715
|
+
posture: testOpts.posture || posture || null,
|
|
716
|
+
fingerprint: sigVerification.fingerprint,
|
|
717
|
+
durationMs: Date.now() - startedAt,
|
|
718
|
+
}, "success");
|
|
719
|
+
if (typeof testOpts.notify === "function") {
|
|
720
|
+
try { await testOpts.notify({ outcome: "success", bundleId: bundleId, manifest: manifest }); }
|
|
721
|
+
catch (_e) { /* notify hook is best-effort */ }
|
|
722
|
+
}
|
|
723
|
+
} catch (e) {
|
|
724
|
+
_emitAudit("backup.test.failed", {
|
|
725
|
+
bundleId: bundleId,
|
|
726
|
+
posture: testOpts.posture || posture || null,
|
|
727
|
+
reason: (e && e.message) || String(e),
|
|
728
|
+
durationMs: Date.now() - startedAt,
|
|
729
|
+
}, "failure");
|
|
730
|
+
if (typeof testOpts.notify === "function") {
|
|
731
|
+
try {
|
|
732
|
+
await testOpts.notify({
|
|
733
|
+
outcome: "failure",
|
|
734
|
+
bundleId: bundleId,
|
|
735
|
+
reason: (e && e.message) || String(e),
|
|
736
|
+
});
|
|
737
|
+
} catch (_e) { /* notify hook is best-effort */ }
|
|
738
|
+
}
|
|
739
|
+
} finally {
|
|
740
|
+
// Best-effort cleanup so the staging dir doesn't accumulate
|
|
741
|
+
// across drills.
|
|
742
|
+
if (testOpts.cleanup !== false) {
|
|
743
|
+
try { fs.rmSync(stagingDir, { recursive: true, force: true }); }
|
|
744
|
+
catch (_e) { /* tmpdir cleanup best-effort */ }
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
return { name: name, instance: schedInstance };
|
|
750
|
+
}
|
|
751
|
+
|
|
443
752
|
return {
|
|
444
753
|
run: run,
|
|
445
754
|
list: list,
|
|
@@ -447,26 +756,116 @@ function create(opts) {
|
|
|
447
756
|
read: read,
|
|
448
757
|
purgeOlder: purgeOlder,
|
|
449
758
|
schedule: schedule,
|
|
759
|
+
scheduleTest: scheduleTest,
|
|
450
760
|
storage: storage,
|
|
451
761
|
};
|
|
452
762
|
}
|
|
453
763
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
764
|
+
/**
|
|
765
|
+
* @primitive b.backup.verifyManifestSignature
|
|
766
|
+
* @signature b.backup.verifyManifestSignature(target, opts)
|
|
767
|
+
* @since 0.7.30
|
|
768
|
+
* @status stable
|
|
769
|
+
* @compliance hipaa, pci-dss, soc2
|
|
770
|
+
* @related b.backup.create, b.backupManifest.verifySignature
|
|
771
|
+
*
|
|
772
|
+
* Read the manifest from a restored bundle directory (or accept a
|
|
773
|
+
* pre-parsed manifest object) and verify its SLH-DSA audit-sign
|
|
774
|
+
* signature. Operator-facing wrapper around
|
|
775
|
+
* `b.backupManifest.verifySignature` that handles the on-disk fetch
|
|
776
|
+
* + JCS parse, so a regulator-facing restore drill is a single call.
|
|
777
|
+
*
|
|
778
|
+
* Returns `{ ok, fingerprint?, reason? }`. Throws `BackupError` only
|
|
779
|
+
* for missing / unreadable / unparseable manifests — a bad signature
|
|
780
|
+
* returns `{ ok: false, reason }` so the caller can branch on the
|
|
781
|
+
* verdict without a try/catch.
|
|
782
|
+
*
|
|
783
|
+
* Pass `opts.expectedFingerprint` to pin the signing key; the
|
|
784
|
+
* verification rejects any signature that validates against a
|
|
785
|
+
* different key, even if the math checks out. That's the kid-pinning
|
|
786
|
+
* the restore drill leans on.
|
|
787
|
+
*
|
|
788
|
+
* @opts
|
|
789
|
+
* expectedFingerprint: string, // optional; SHA3-512 fingerprint to pin
|
|
790
|
+
*
|
|
791
|
+
* @example
|
|
792
|
+
* var fs = require("node:fs");
|
|
793
|
+
* var path = require("node:path");
|
|
794
|
+
* var os = require("node:os");
|
|
795
|
+
*
|
|
796
|
+
* var bundleDir = fs.mkdtempSync(path.join(os.tmpdir(), "verify-bundle-"));
|
|
797
|
+
* try {
|
|
798
|
+
* b.backup.verifyManifestSignature(bundleDir);
|
|
799
|
+
* } catch (e) {
|
|
800
|
+
* e.code; // → "backup/no-manifest"
|
|
801
|
+
* }
|
|
802
|
+
*/
|
|
803
|
+
function verifyManifestSignature(target, opts) {
|
|
804
|
+
opts = opts || {};
|
|
805
|
+
var manifest;
|
|
806
|
+
if (typeof target === "string") {
|
|
807
|
+
var manifestPath = path.join(target, "manifest.json");
|
|
808
|
+
if (!fs.existsSync(manifestPath)) {
|
|
809
|
+
throw new BackupError("backup/no-manifest",
|
|
810
|
+
"verifyManifestSignature: manifest.json missing at " + manifestPath);
|
|
811
|
+
}
|
|
812
|
+
try { manifest = backupManifest.parse(fs.readFileSync(manifestPath, "utf8")); }
|
|
813
|
+
catch (e) {
|
|
814
|
+
throw new BackupError("backup/bad-manifest",
|
|
815
|
+
"verifyManifestSignature: parse failed: " + ((e && e.message) || String(e)));
|
|
816
|
+
}
|
|
817
|
+
} else if (target && typeof target === "object" && target.manifest) {
|
|
818
|
+
manifest = target.manifest;
|
|
819
|
+
} else if (target && typeof target === "object" &&
|
|
820
|
+
typeof target.version === "number") {
|
|
821
|
+
manifest = target;
|
|
822
|
+
} else {
|
|
823
|
+
throw new BackupError("backup/bad-target",
|
|
824
|
+
"verifyManifestSignature: target must be a bundle dir path, " +
|
|
825
|
+
"{ manifest } object, or a parsed manifest object");
|
|
826
|
+
}
|
|
827
|
+
return backupManifest.verifySignature(manifest, opts);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* @primitive b.backup.recommendedFiles
|
|
832
|
+
* @signature b.backup.recommendedFiles(opts)
|
|
833
|
+
* @since 0.4.0
|
|
834
|
+
* @status stable
|
|
835
|
+
* @related b.backup.create, b.db.getMode, b.vault.getMode
|
|
836
|
+
*
|
|
837
|
+
* Return the framework-default include list for a given DB at-rest
|
|
838
|
+
* mode + vault wrap mode. Operators with the standard layout pass the
|
|
839
|
+
* result straight to `b.backup.create({ files })`; operators with
|
|
840
|
+
* custom data files (additional sealed keys, OIDC provider material,
|
|
841
|
+
* application-specific keystores) append their own entries.
|
|
842
|
+
*
|
|
843
|
+
* The list adapts to mode:
|
|
844
|
+
* - plain DB → the live SQLite file (default name `blamejs.db`)
|
|
845
|
+
* - encrypted DB → `db.enc` + `db.key.enc` (envelope + sealed DEK)
|
|
846
|
+
* - plaintext vault → `vault.key`
|
|
847
|
+
* - wrapped vault → `vault.key.sealed`
|
|
848
|
+
*
|
|
849
|
+
* The audit-signing key is always included (sealed in `wrapped` mode)
|
|
850
|
+
* so a restored deployment can verify its own audit chain.
|
|
851
|
+
*
|
|
852
|
+
* @opts
|
|
853
|
+
* atRest: "plain" | "encrypted", // default "encrypted"
|
|
854
|
+
* vaultMode: "plaintext" | "wrapped", // default "wrapped"
|
|
855
|
+
* dbName: string, // default "blamejs.db"
|
|
856
|
+
* additionalSealed: Array<string>, // operator-supplied sealed-file paths
|
|
857
|
+
*
|
|
858
|
+
* @example
|
|
859
|
+
* var files = b.backup.recommendedFiles({
|
|
860
|
+
* atRest: "encrypted",
|
|
861
|
+
* vaultMode: "wrapped",
|
|
862
|
+
* additionalSealed: ["ca.key.sealed", "tls/privkey.pem.sealed"],
|
|
863
|
+
* });
|
|
864
|
+
*
|
|
865
|
+
* files[0].relativePath; // → "db.enc"
|
|
866
|
+
* files[1].relativePath; // → "db.key.enc"
|
|
867
|
+
* files[2].relativePath; // → "vault.key.sealed"
|
|
868
|
+
*/
|
|
470
869
|
function recommendedFiles(opts) {
|
|
471
870
|
opts = opts || {};
|
|
472
871
|
var atRest = opts.atRest || "encrypted";
|
|
@@ -507,20 +906,42 @@ function recommendedFiles(opts) {
|
|
|
507
906
|
return files;
|
|
508
907
|
}
|
|
509
908
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
909
|
+
/**
|
|
910
|
+
* @primitive b.backup.runInWorker
|
|
911
|
+
* @signature b.backup.runInWorker(opts)
|
|
912
|
+
* @since 0.8.41
|
|
913
|
+
* @status stable
|
|
914
|
+
* @related b.backup.create
|
|
915
|
+
*
|
|
916
|
+
* Execute a backup or restore inside a `node:worker_threads` worker
|
|
917
|
+
* so the heavy-CPU Argon2id + XChaCha20-Poly1305 + SHA3-512 walk
|
|
918
|
+
* doesn't block the request loop. Returns a Promise resolving with
|
|
919
|
+
* the worker's posted message, or rejecting with the worker's error,
|
|
920
|
+
* a non-zero exit, or the operator's `timeoutMs`.
|
|
921
|
+
*
|
|
922
|
+
* The worker script is supplied by the operator — responsibility for
|
|
923
|
+
* thread-safe storage adapters stays with the operator; this helper
|
|
924
|
+
* is the dispatch + lifecycle glue. The framework rejects with
|
|
925
|
+
* `backup/no-worker-threads` when `node:worker_threads` is
|
|
926
|
+
* unavailable (sandboxed runtimes, stripped Node builds).
|
|
927
|
+
*
|
|
928
|
+
* @opts
|
|
929
|
+
* workerScript: string, // required; absolute path to the worker module
|
|
930
|
+
* args: object, // optional; passed as workerData to the worker
|
|
931
|
+
* timeoutMs: number, // optional; positive finite int, terminates worker on miss
|
|
932
|
+
*
|
|
933
|
+
* @example
|
|
934
|
+
* var path = require("node:path");
|
|
935
|
+
*
|
|
936
|
+
* b.backup.runInWorker({
|
|
937
|
+
* workerScript: path.resolve("/does/not/exist/worker.js"),
|
|
938
|
+
* args: { mode: "full" },
|
|
939
|
+
* timeoutMs: 60000,
|
|
940
|
+
* }).catch(function (err) {
|
|
941
|
+
* // worker failed to load — error surfaces as a rejected promise
|
|
942
|
+
* typeof err.message; // → "string"
|
|
943
|
+
* });
|
|
944
|
+
*/
|
|
524
945
|
function runInWorker(opts) {
|
|
525
946
|
opts = opts || {};
|
|
526
947
|
try {
|
|
@@ -569,10 +990,12 @@ function runInWorker(opts) {
|
|
|
569
990
|
}
|
|
570
991
|
|
|
571
992
|
module.exports = {
|
|
572
|
-
create:
|
|
573
|
-
localStorage:
|
|
574
|
-
recommendedFiles:
|
|
575
|
-
runInWorker:
|
|
576
|
-
|
|
577
|
-
|
|
993
|
+
create: create,
|
|
994
|
+
localStorage: localStorage,
|
|
995
|
+
recommendedFiles: recommendedFiles,
|
|
996
|
+
runInWorker: runInWorker,
|
|
997
|
+
verifyManifestSignature: verifyManifestSignature,
|
|
998
|
+
BACKUP_ENCRYPTION_REQUIRED_POSTURES: BACKUP_ENCRYPTION_REQUIRED_POSTURES,
|
|
999
|
+
BackupError: BackupError,
|
|
1000
|
+
BUNDLE_ID_RE: BUNDLE_ID_RE,
|
|
578
1001
|
};
|