@blamejs/core 0.9.12 → 0.9.15
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 +3 -0
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +10 -7
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/audit.js +29 -17
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +24 -9
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/config-drift.js +15 -15
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +3 -3
- package/lib/crypto.js +145 -0
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +35 -35
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dsr.js +22 -15
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +2 -2
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/inbox.js +21 -15
- package/lib/keychain.js +9 -9
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +4 -4
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +249 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +16 -16
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +250 -0
- package/lib/migrations.js +3 -3
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +13 -13
- package/lib/notify.js +3 -3
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-agent.js +116 -26
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +18 -18
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/retry.js +50 -0
- package/lib/router.js +13 -13
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +4 -4
- package/lib/self-update-standalone-verifier.js +280 -0
- package/lib/self-update.js +32 -26
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +7 -7
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +70 -66
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/watcher.js +23 -23
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +4 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/queue-local.js
CHANGED
|
@@ -35,7 +35,7 @@ var C = require("./constants");
|
|
|
35
35
|
var { generateToken } = require("./crypto");
|
|
36
36
|
var cryptoField = require("./crypto-field");
|
|
37
37
|
var lazyRequire = require("./lazy-require");
|
|
38
|
-
var
|
|
38
|
+
var numericBounds = require("./numeric-bounds");
|
|
39
39
|
var safeJson = require("./safe-json");
|
|
40
40
|
var scheduler = require("./scheduler");
|
|
41
41
|
var { QueueError } = require("./framework-error");
|
|
@@ -388,10 +388,10 @@ function create(_config) {
|
|
|
388
388
|
opts = opts || {};
|
|
389
389
|
var limit = 100;
|
|
390
390
|
if (opts.limit !== undefined) {
|
|
391
|
-
if (!
|
|
391
|
+
if (!numericBounds.isPositiveFiniteInt(opts.limit)) {
|
|
392
392
|
throw new QueueError("queue/bad-opt",
|
|
393
393
|
"queue.dlqList: limit must be a positive finite integer; got " +
|
|
394
|
-
|
|
394
|
+
numericBounds.shape(opts.limit), true);
|
|
395
395
|
}
|
|
396
396
|
limit = opts.limit;
|
|
397
397
|
}
|
package/lib/queue.js
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*/
|
|
44
44
|
var C = require("./constants");
|
|
45
45
|
var clusterStorage = require("./cluster-storage");
|
|
46
|
-
var
|
|
46
|
+
var bCrypto = require("./crypto");
|
|
47
47
|
var lazyRequire = require("./lazy-require");
|
|
48
48
|
var { boot } = require("./log");
|
|
49
49
|
var numericChecks = require("./numeric-checks");
|
|
@@ -877,7 +877,7 @@ function enqueueFlow(spec) {
|
|
|
877
877
|
return Promise.reject(e);
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
-
var flowId = "flow-" +
|
|
880
|
+
var flowId = "flow-" + bCrypto.generateToken(C.BYTES.bytes(8));
|
|
881
881
|
|
|
882
882
|
return observability.tap("queue.enqueueFlow",
|
|
883
883
|
{ queueName: spec.queueName, flowId: flowId, childCount: spec.children.length },
|
package/lib/redis-client.js
CHANGED
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* callers can branch on transport vs server-side errors.
|
|
26
26
|
*/
|
|
27
27
|
var net = require("node:net");
|
|
28
|
-
var
|
|
29
|
-
var
|
|
28
|
+
var nodeTls = require("node:tls");
|
|
29
|
+
var nodeUrl = require("node:url");
|
|
30
30
|
var C = require("./constants");
|
|
31
31
|
var safeAsync = require("./safe-async");
|
|
32
32
|
var validateOpts = require("./validate-opts");
|
|
@@ -319,7 +319,7 @@ function create(opts) {
|
|
|
319
319
|
var tlsConnectOpts = { host: host, port: port };
|
|
320
320
|
if (servername) tlsConnectOpts.servername = servername;
|
|
321
321
|
if (caBundle) tlsConnectOpts.ca = caBundle;
|
|
322
|
-
sock =
|
|
322
|
+
sock = nodeTls.connect(tlsConnectOpts, onOk);
|
|
323
323
|
} else {
|
|
324
324
|
sock = net.connect({ host: host, port: port }, onOk);
|
|
325
325
|
}
|
|
@@ -457,7 +457,7 @@ function create(opts) {
|
|
|
457
457
|
// Empty-username + non-empty password is the legacy single-arg AUTH form.
|
|
458
458
|
function _parseRedisUrl(s) {
|
|
459
459
|
var u;
|
|
460
|
-
try { u = new
|
|
460
|
+
try { u = new nodeUrl.URL(s); }
|
|
461
461
|
catch (e) {
|
|
462
462
|
throw _err("BAD_URL", "redis url parse failed: " + ((e && e.message) || String(e)));
|
|
463
463
|
}
|
package/lib/restore-bundle.js
CHANGED
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
* Backup-bundle reader — verify the manifest signature, list bundle contents without decrypting, and cherry-pick a restore subset to a staging directory the caller atomically swaps into place.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
var
|
|
49
|
-
var
|
|
48
|
+
var nodeFs = require("fs");
|
|
49
|
+
var nodePath = require("path");
|
|
50
50
|
var atomicFile = require("./atomic-file");
|
|
51
51
|
var backupCrypto = require("./backup/crypto");
|
|
52
52
|
var backupManifest = require("./backup/manifest");
|
|
@@ -65,7 +65,7 @@ function _cleanupStaging(stagingDir) {
|
|
|
65
65
|
// Best-effort recursive remove — if cleanup fails, surface that to
|
|
66
66
|
// the caller via stderr but never override the original error
|
|
67
67
|
// we're already throwing.
|
|
68
|
-
try {
|
|
68
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); }
|
|
69
69
|
catch (_e) { /* best-effort */ }
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -123,15 +123,15 @@ function _cleanupStaging(stagingDir) {
|
|
|
123
123
|
async function extract(opts) {
|
|
124
124
|
var t0 = Date.now();
|
|
125
125
|
opts = opts || {};
|
|
126
|
-
if (typeof opts.bundleDir !== "string" || !
|
|
126
|
+
if (typeof opts.bundleDir !== "string" || !nodeFs.existsSync(opts.bundleDir)) {
|
|
127
127
|
throw new RestoreBundleError("restore-bundle/no-bundle",
|
|
128
128
|
"extract: opts.bundleDir is required and must exist");
|
|
129
129
|
}
|
|
130
130
|
validateOpts.requireNonEmptyString(opts.stagingDir, "extract: opts.stagingDir", RestoreBundleError, "restore-bundle/no-staging");
|
|
131
|
-
if (
|
|
131
|
+
if (nodeFs.existsSync(opts.stagingDir)) {
|
|
132
132
|
throw new RestoreBundleError("restore-bundle/staging-exists",
|
|
133
133
|
"extract: stagingDir already exists: " + opts.stagingDir +
|
|
134
|
-
" (refusing to merge into existing directory — pick a fresh
|
|
134
|
+
" (refusing to merge into existing directory — pick a fresh nodePath)");
|
|
135
135
|
}
|
|
136
136
|
if (!Buffer.isBuffer(opts.passphrase) && typeof opts.passphrase !== "string") {
|
|
137
137
|
throw new RestoreBundleError("restore-bundle/no-passphrase",
|
|
@@ -145,14 +145,14 @@ async function extract(opts) {
|
|
|
145
145
|
|
|
146
146
|
// 1. Read + parse + validate manifest
|
|
147
147
|
_emit(progress, { phase: "read_manifest" });
|
|
148
|
-
var manifestPath =
|
|
149
|
-
if (!
|
|
148
|
+
var manifestPath = nodePath.join(bundleDir, "manifest.json");
|
|
149
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
150
150
|
throw new RestoreBundleError("restore-bundle/missing-manifest",
|
|
151
151
|
"extract: bundleDir has no manifest.json — bundle is incomplete or not a blamejs backup");
|
|
152
152
|
}
|
|
153
153
|
var manifest;
|
|
154
154
|
try {
|
|
155
|
-
manifest = backupManifest.parse(
|
|
155
|
+
manifest = backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
156
156
|
} catch (e) {
|
|
157
157
|
if (e && e.isBackupManifestError) throw e;
|
|
158
158
|
throw new RestoreBundleError("restore-bundle/bad-manifest",
|
|
@@ -215,13 +215,13 @@ async function extract(opts) {
|
|
|
215
215
|
continue;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
var blobPath =
|
|
219
|
-
if (!
|
|
218
|
+
var blobPath = nodePath.join(bundleDir, entry.encryptedPath);
|
|
219
|
+
if (!nodeFs.existsSync(blobPath)) {
|
|
220
220
|
throw new RestoreBundleError("restore-bundle/missing-blob",
|
|
221
221
|
"extract: manifest references '" + entry.encryptedPath +
|
|
222
222
|
"' but the bundle has no such file");
|
|
223
223
|
}
|
|
224
|
-
var blob =
|
|
224
|
+
var blob = nodeFs.readFileSync(blobPath);
|
|
225
225
|
if (blob.length !== entry.encryptedSize) {
|
|
226
226
|
throw new RestoreBundleError("restore-bundle/size-mismatch",
|
|
227
227
|
"extract: blob '" + entry.encryptedPath + "' has size " + blob.length +
|
|
@@ -257,8 +257,8 @@ async function extract(opts) {
|
|
|
257
257
|
" — bundle is corrupted or manifest tampered");
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
var destPath =
|
|
261
|
-
atomicFile.ensureDir(
|
|
260
|
+
var destPath = nodePath.join(stagingDir, entry.relativePath);
|
|
261
|
+
atomicFile.ensureDir(nodePath.dirname(destPath));
|
|
262
262
|
atomicFile.writeSync(destPath, plaintext, { fileMode: 0o600 });
|
|
263
263
|
|
|
264
264
|
fileCount++;
|
|
@@ -321,16 +321,16 @@ async function extract(opts) {
|
|
|
321
321
|
*/
|
|
322
322
|
function inspect(opts) {
|
|
323
323
|
opts = opts || {};
|
|
324
|
-
if (typeof opts.bundleDir !== "string" || !
|
|
324
|
+
if (typeof opts.bundleDir !== "string" || !nodeFs.existsSync(opts.bundleDir)) {
|
|
325
325
|
throw new RestoreBundleError("restore-bundle/no-bundle",
|
|
326
326
|
"inspect: opts.bundleDir is required and must exist");
|
|
327
327
|
}
|
|
328
|
-
var manifestPath =
|
|
329
|
-
if (!
|
|
328
|
+
var manifestPath = nodePath.join(opts.bundleDir, "manifest.json");
|
|
329
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
330
330
|
throw new RestoreBundleError("restore-bundle/missing-manifest",
|
|
331
331
|
"inspect: bundleDir has no manifest.json");
|
|
332
332
|
}
|
|
333
|
-
return backupManifest.parse(
|
|
333
|
+
return backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
module.exports = {
|
package/lib/restore-rollback.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @intro
|
|
8
8
|
* Backup-restore safety net — atomic dataDir swap with a versioned
|
|
9
|
-
* rollback
|
|
9
|
+
* rollback nodePath. The primitive `b.restore` calls to put a
|
|
10
10
|
* freshly-decrypted bundle into place: filesystem rename is atomic
|
|
11
11
|
* on POSIX (and on Windows when nothing has the dir open), so the
|
|
12
12
|
* swap either fully completes or the previous `dataDir` is
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
* corrupting state.
|
|
40
40
|
*
|
|
41
41
|
* @card
|
|
42
|
-
* Backup-restore safety net — atomic dataDir swap with a versioned rollback
|
|
42
|
+
* Backup-restore safety net — atomic dataDir swap with a versioned rollback nodePath.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var
|
|
46
|
-
var
|
|
45
|
+
var nodeFs = require("fs");
|
|
46
|
+
var nodePath = require("path");
|
|
47
47
|
var atomicFile = require("./atomic-file");
|
|
48
48
|
var C = require("./constants");
|
|
49
|
-
var
|
|
49
|
+
var numericBounds = require("./numeric-bounds");
|
|
50
50
|
var safeJson = require("./safe-json");
|
|
51
51
|
var { defineClass } = require("./framework-error");
|
|
52
52
|
|
|
@@ -94,7 +94,7 @@ function _resolveRollbackRoot(opts) {
|
|
|
94
94
|
*/
|
|
95
95
|
function swap(opts) {
|
|
96
96
|
opts = opts || {};
|
|
97
|
-
if (typeof opts.stagingDir !== "string" || !
|
|
97
|
+
if (typeof opts.stagingDir !== "string" || !nodeFs.existsSync(opts.stagingDir)) {
|
|
98
98
|
throw new RestoreRollbackError("restore-rollback/no-staging",
|
|
99
99
|
"swap: opts.stagingDir is required and must exist");
|
|
100
100
|
}
|
|
@@ -106,20 +106,20 @@ function swap(opts) {
|
|
|
106
106
|
atomicFile.ensureDir(rollbackRoot);
|
|
107
107
|
|
|
108
108
|
var swappedAt = atomicFile.pathTimestamp();
|
|
109
|
-
var rollbackPath =
|
|
110
|
-
var markerPath =
|
|
109
|
+
var rollbackPath = nodePath.join(rollbackRoot, swappedAt);
|
|
110
|
+
var markerPath = nodePath.join(rollbackRoot, swappedAt + ".marker.json");
|
|
111
111
|
|
|
112
|
-
if (
|
|
112
|
+
if (nodeFs.existsSync(rollbackPath) || nodeFs.existsSync(markerPath)) {
|
|
113
113
|
throw new RestoreRollbackError("restore-rollback/collision",
|
|
114
114
|
"swap: a rollback at " + rollbackPath + " already exists — refusing to overwrite");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
var hadDataDir =
|
|
117
|
+
var hadDataDir = nodeFs.existsSync(opts.dataDir);
|
|
118
118
|
|
|
119
|
-
// Step 1: rename current dataDir → rollback
|
|
119
|
+
// Step 1: rename current dataDir → rollback nodePath. Skipped on first
|
|
120
120
|
// restore (no existing dataDir).
|
|
121
121
|
if (hadDataDir) {
|
|
122
|
-
try {
|
|
122
|
+
try { nodeFs.renameSync(opts.dataDir, rollbackPath); }
|
|
123
123
|
catch (e) {
|
|
124
124
|
throw new RestoreRollbackError("restore-rollback/rename-existing-failed",
|
|
125
125
|
"swap: could not move existing dataDir to rollback: " + ((e && e.message) || String(e)));
|
|
@@ -127,11 +127,11 @@ function swap(opts) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Step 2: rename staging → dataDir
|
|
130
|
-
try {
|
|
130
|
+
try { nodeFs.renameSync(opts.stagingDir, opts.dataDir); }
|
|
131
131
|
catch (e) {
|
|
132
132
|
// Step 2 failed — try to undo step 1 so the operator's dataDir is back
|
|
133
133
|
if (hadDataDir) {
|
|
134
|
-
try {
|
|
134
|
+
try { nodeFs.renameSync(rollbackPath, opts.dataDir); }
|
|
135
135
|
catch (_e) { /* dataDir is now in rollbackPath; operator must recover manually */ }
|
|
136
136
|
}
|
|
137
137
|
throw new RestoreRollbackError("restore-rollback/rename-staging-failed",
|
|
@@ -148,7 +148,7 @@ function swap(opts) {
|
|
|
148
148
|
operator: opts.marker || null,
|
|
149
149
|
};
|
|
150
150
|
try {
|
|
151
|
-
|
|
151
|
+
nodeFs.writeFileSync(markerPath, JSON.stringify(marker, null, 2) + "\n", { mode: 0o600 });
|
|
152
152
|
} catch (_e) { /* marker write is best-effort */ }
|
|
153
153
|
|
|
154
154
|
return {
|
|
@@ -193,19 +193,19 @@ async function rollback(opts) {
|
|
|
193
193
|
throw new RestoreRollbackError("restore-rollback/no-datadir",
|
|
194
194
|
"rollback: opts.dataDir is required");
|
|
195
195
|
}
|
|
196
|
-
if (typeof opts.rollbackPath !== "string" || !
|
|
196
|
+
if (typeof opts.rollbackPath !== "string" || !nodeFs.existsSync(opts.rollbackPath)) {
|
|
197
197
|
throw new RestoreRollbackError("restore-rollback/no-rollback",
|
|
198
198
|
"rollback: opts.rollbackPath is required and must exist");
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Move the current dataDir aside (so the rollback's rename target is empty)
|
|
202
202
|
var discardedAt = null;
|
|
203
|
-
if (
|
|
203
|
+
if (nodeFs.existsSync(opts.dataDir)) {
|
|
204
204
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
205
205
|
atomicFile.ensureDir(rollbackRoot);
|
|
206
206
|
discardedAt = atomicFile.pathTimestamp();
|
|
207
|
-
var discardedPath =
|
|
208
|
-
try {
|
|
207
|
+
var discardedPath = nodePath.join(rollbackRoot, "discarded-" + discardedAt);
|
|
208
|
+
try { nodeFs.renameSync(opts.dataDir, discardedPath); }
|
|
209
209
|
catch (e) {
|
|
210
210
|
throw new RestoreRollbackError("restore-rollback/rename-existing-failed",
|
|
211
211
|
"rollback: could not move current dataDir aside: " + ((e && e.message) || String(e)));
|
|
@@ -214,7 +214,7 @@ async function rollback(opts) {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// Rename the rollback dir back into dataDir's place
|
|
217
|
-
try {
|
|
217
|
+
try { nodeFs.renameSync(opts.rollbackPath, opts.dataDir); }
|
|
218
218
|
catch (e) {
|
|
219
219
|
throw new RestoreRollbackError("restore-rollback/rollback-rename-failed",
|
|
220
220
|
"rollback: could not move rollback into dataDir: " + ((e && e.message) || String(e)) +
|
|
@@ -223,7 +223,7 @@ async function rollback(opts) {
|
|
|
223
223
|
|
|
224
224
|
// Best-effort: clean up the marker file alongside the rollback path
|
|
225
225
|
var markerPath = opts.rollbackPath + ".marker.json";
|
|
226
|
-
try { if (
|
|
226
|
+
try { if (nodeFs.existsSync(markerPath)) nodeFs.unlinkSync(markerPath); }
|
|
227
227
|
catch (_e) { /* marker cleanup is best-effort */ }
|
|
228
228
|
|
|
229
229
|
return {
|
|
@@ -258,22 +258,22 @@ async function rollback(opts) {
|
|
|
258
258
|
function list(opts) {
|
|
259
259
|
opts = opts || {};
|
|
260
260
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
261
|
-
if (!
|
|
262
|
-
var entries =
|
|
261
|
+
if (!nodeFs.existsSync(rollbackRoot)) return [];
|
|
262
|
+
var entries = nodeFs.readdirSync(rollbackRoot, { withFileTypes: true });
|
|
263
263
|
var out = [];
|
|
264
264
|
for (var i = 0; i < entries.length; i++) {
|
|
265
265
|
if (!entries[i].isDirectory()) continue;
|
|
266
266
|
var name = entries[i].name;
|
|
267
267
|
if (name.indexOf("discarded-") === 0) continue; // discarded dirs aren't restore points
|
|
268
|
-
var p =
|
|
268
|
+
var p = nodePath.join(rollbackRoot, name);
|
|
269
269
|
var markerPath = p + ".marker.json";
|
|
270
270
|
var marker = null;
|
|
271
|
-
if (
|
|
272
|
-
try { marker = safeJson.parse(
|
|
271
|
+
if (nodeFs.existsSync(markerPath)) {
|
|
272
|
+
try { marker = safeJson.parse(nodeFs.readFileSync(markerPath, "utf8"), { maxBytes: C.BYTES.kib(64) }); }
|
|
273
273
|
catch (_e) { marker = null; }
|
|
274
274
|
}
|
|
275
275
|
var stat;
|
|
276
|
-
try { stat =
|
|
276
|
+
try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
|
|
277
277
|
out.push({
|
|
278
278
|
rollbackPath: p,
|
|
279
279
|
swappedAt: (marker && marker.swappedAt) || stat.mtime.toISOString(),
|
|
@@ -311,18 +311,18 @@ function list(opts) {
|
|
|
311
311
|
*/
|
|
312
312
|
function purge(opts) {
|
|
313
313
|
opts = opts || {};
|
|
314
|
-
|
|
314
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.keep,
|
|
315
315
|
"restore-rollback.purge: opts.keep", RestoreRollbackError, "restore-rollback/bad-keep");
|
|
316
316
|
var keep = opts.keep !== undefined ? opts.keep : 0;
|
|
317
317
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
318
|
-
if (!
|
|
318
|
+
if (!nodeFs.existsSync(rollbackRoot)) return { kept: keep, deleted: [] };
|
|
319
319
|
// Always sweep "discarded-*" dirs — they're never restore points
|
|
320
|
-
var entries =
|
|
320
|
+
var entries = nodeFs.readdirSync(rollbackRoot, { withFileTypes: true });
|
|
321
321
|
var deleted = [];
|
|
322
322
|
for (var i = 0; i < entries.length; i++) {
|
|
323
323
|
if (entries[i].isDirectory() && entries[i].name.indexOf("discarded-") === 0) {
|
|
324
|
-
var p =
|
|
325
|
-
try {
|
|
324
|
+
var p = nodePath.join(rollbackRoot, entries[i].name);
|
|
325
|
+
try { nodeFs.rmSync(p, { recursive: true, force: true }); deleted.push(p); }
|
|
326
326
|
catch (_e) { /* best-effort */ }
|
|
327
327
|
}
|
|
328
328
|
}
|
|
@@ -333,9 +333,9 @@ function purge(opts) {
|
|
|
333
333
|
for (var j = 0; j < toDelete.length; j++) {
|
|
334
334
|
var rbPath = toDelete[j].rollbackPath;
|
|
335
335
|
var mkPath = rbPath + ".marker.json";
|
|
336
|
-
try {
|
|
336
|
+
try { nodeFs.rmSync(rbPath, { recursive: true, force: true }); deleted.push(rbPath); }
|
|
337
337
|
catch (_e) { /* best-effort */ }
|
|
338
|
-
try { if (
|
|
338
|
+
try { if (nodeFs.existsSync(mkPath)) nodeFs.unlinkSync(mkPath); }
|
|
339
339
|
catch (_e) { /* best-effort */ }
|
|
340
340
|
}
|
|
341
341
|
return { kept: keep, deleted: deleted };
|
package/lib/restore.js
CHANGED
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
* manual recovery)
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
var
|
|
54
|
+
var nodeFs = require("fs");
|
|
55
55
|
var os = require("os");
|
|
56
|
-
var
|
|
56
|
+
var nodePath = require("path");
|
|
57
57
|
var C = require("./constants");
|
|
58
|
-
var
|
|
58
|
+
var bCrypto = require("./crypto");
|
|
59
59
|
var numericChecks = require("./numeric-checks");
|
|
60
60
|
var restoreBundle = require("./restore-bundle");
|
|
61
61
|
var restoreRollback = require("./restore-rollback");
|
|
@@ -128,11 +128,11 @@ function create(opts) {
|
|
|
128
128
|
while (stack.length > 0) {
|
|
129
129
|
var current = stack.pop();
|
|
130
130
|
var entries;
|
|
131
|
-
try { entries =
|
|
131
|
+
try { entries = nodeFs.readdirSync(current, { withFileTypes: true }); }
|
|
132
132
|
catch (_e) { continue; }
|
|
133
133
|
for (var i = 0; i < entries.length; i++) {
|
|
134
134
|
var entry = entries[i];
|
|
135
|
-
var full =
|
|
135
|
+
var full = nodePath.join(current, entry.name);
|
|
136
136
|
if (entry.isDirectory()) {
|
|
137
137
|
stack.push(full);
|
|
138
138
|
} else if (entry.isFile()) {
|
|
@@ -141,7 +141,7 @@ function create(opts) {
|
|
|
141
141
|
return { tooManyFiles: true, fileCount: fileCount };
|
|
142
142
|
}
|
|
143
143
|
try {
|
|
144
|
-
totalBytes +=
|
|
144
|
+
totalBytes += nodeFs.statSync(full).size;
|
|
145
145
|
if (totalBytes > maxPulledBytes) {
|
|
146
146
|
return { tooManyBytes: true, totalBytes: totalBytes };
|
|
147
147
|
}
|
|
@@ -200,8 +200,8 @@ function create(opts) {
|
|
|
200
200
|
"inspect: bundle '" + bundleId + "' not in storage");
|
|
201
201
|
}
|
|
202
202
|
await _preflightBundleSize(bundleId);
|
|
203
|
-
var pullDir =
|
|
204
|
-
"blamejs-restore-inspect-" +
|
|
203
|
+
var pullDir = nodePath.join(os.tmpdir(),
|
|
204
|
+
"blamejs-restore-inspect-" + bCrypto.generateToken(4));
|
|
205
205
|
try {
|
|
206
206
|
await storage.readBundle(bundleId, pullDir);
|
|
207
207
|
var pulled = _walkPullDirFootprint(pullDir);
|
|
@@ -217,7 +217,7 @@ function create(opts) {
|
|
|
217
217
|
}
|
|
218
218
|
return restoreBundle.inspect({ bundleDir: pullDir });
|
|
219
219
|
} finally {
|
|
220
|
-
try {
|
|
220
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -234,13 +234,13 @@ function create(opts) {
|
|
|
234
234
|
"run: bundle '" + bundleId + "' not in storage");
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
var pullId =
|
|
238
|
-
var pullDir =
|
|
239
|
-
var stagingDir =
|
|
237
|
+
var pullId = bCrypto.generateToken(4);
|
|
238
|
+
var pullDir = nodePath.join(os.tmpdir(), "blamejs-restore-pull-" + pullId);
|
|
239
|
+
var stagingDir = nodePath.join(os.tmpdir(), "blamejs-restore-staging-" + pullId);
|
|
240
240
|
|
|
241
241
|
function _cleanupTmp() {
|
|
242
|
-
try {
|
|
243
|
-
try {
|
|
242
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
243
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// 1. Pull bundle out of storage
|
|
@@ -319,7 +319,7 @@ function create(opts) {
|
|
|
319
319
|
} catch (e) {
|
|
320
320
|
// Pull dir is safe to clean (the source bundle is in storage);
|
|
321
321
|
// staging stays for manual recovery.
|
|
322
|
-
try {
|
|
322
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
323
323
|
_emitAudit("restore.failure",
|
|
324
324
|
{ bundleId: bundleId, reason: "swap: " + ((e && e.message) || String(e)) },
|
|
325
325
|
"failure");
|
|
@@ -331,7 +331,7 @@ function create(opts) {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
// 4. Clean up the pull dir (source bundle still in storage)
|
|
334
|
-
try {
|
|
334
|
+
try { nodeFs.rmSync(pullDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
335
335
|
|
|
336
336
|
var summary = {
|
|
337
337
|
bundleId: bundleId,
|
package/lib/retry.js
CHANGED
|
@@ -445,8 +445,58 @@ class CircuitBreaker {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
/**
|
|
449
|
+
* @primitive b.retry.withBreaker
|
|
450
|
+
* @signature b.retry.withBreaker(fn, opts)
|
|
451
|
+
* @since 0.9.13
|
|
452
|
+
* @status stable
|
|
453
|
+
* @related b.retry.withRetry, b.circuitBreaker.create
|
|
454
|
+
*
|
|
455
|
+
* Compose `withRetry` + a CircuitBreaker so one retry-loop invocation
|
|
456
|
+
* counts as exactly one breaker call. The breaker observes the
|
|
457
|
+
* eventual outcome of the retry loop (success or final failure), not
|
|
458
|
+
* each intermediate retry attempt — otherwise every retried call
|
|
459
|
+
* inflates the breaker's failure counter and the breaker opens far
|
|
460
|
+
* sooner than intended.
|
|
461
|
+
*
|
|
462
|
+
* Every downstream consumer that wants this composition writes
|
|
463
|
+
* `breaker.wrap(() => b.retry.withRetry(fn, retryOpts))` by hand;
|
|
464
|
+
* this primitive captures the pattern so the convention is uniform.
|
|
465
|
+
*
|
|
466
|
+
* @opts
|
|
467
|
+
* retry: Object, // forwarded to withRetry — same options
|
|
468
|
+
* breaker: CircuitBreaker, // existing breaker instance (required)
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* var cb = b.circuitBreaker.create({ name: "upstream-billing", failureThreshold: 5 });
|
|
472
|
+
* var result = await b.retry.withBreaker(async function () {
|
|
473
|
+
* return await fetch("https://billing.example.com/v1/charges");
|
|
474
|
+
* }, {
|
|
475
|
+
* retry: { maxAttempts: 3, baseDelayMs: 500 },
|
|
476
|
+
* breaker: cb,
|
|
477
|
+
* });
|
|
478
|
+
*/
|
|
479
|
+
function withBreaker(fn, opts) {
|
|
480
|
+
opts = opts || {};
|
|
481
|
+
if (typeof fn !== "function") {
|
|
482
|
+
throw new TypeError("retry.withBreaker: fn must be a function, got " + typeof fn);
|
|
483
|
+
}
|
|
484
|
+
if (!opts.breaker || typeof opts.breaker.wrap !== "function") {
|
|
485
|
+
throw new TypeError("retry.withBreaker: opts.breaker must be a CircuitBreaker instance (with .wrap(fn))");
|
|
486
|
+
}
|
|
487
|
+
// breaker.wrap counts ONE breaker call regardless of how many retry
|
|
488
|
+
// attempts withRetry makes internally. The breaker only sees the
|
|
489
|
+
// final outcome — success closes it (after enough probes in
|
|
490
|
+
// half-open), or final failure opens it after `failureThreshold`
|
|
491
|
+
// exhausted-retry-loop calls.
|
|
492
|
+
return opts.breaker.wrap(function () {
|
|
493
|
+
return withRetry(fn, opts.retry || {});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
448
497
|
module.exports = {
|
|
449
498
|
withRetry: withRetry,
|
|
499
|
+
withBreaker: withBreaker,
|
|
450
500
|
isRetryable: isRetryable,
|
|
451
501
|
backoffDelay: backoffDelay,
|
|
452
502
|
CircuitBreaker: CircuitBreaker,
|
package/lib/router.js
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
var http = require("http");
|
|
36
36
|
var http2 = require("http2");
|
|
37
|
-
var
|
|
38
|
-
var
|
|
37
|
+
var nodeFs = require("fs");
|
|
38
|
+
var nodePath = require("path");
|
|
39
39
|
var C = require("./constants");
|
|
40
40
|
var requestHelpers = require("./request-helpers");
|
|
41
41
|
var lazyRequire = require("./lazy-require");
|
|
@@ -296,8 +296,8 @@ class Router {
|
|
|
296
296
|
this.routes = [];
|
|
297
297
|
this.middleware = [];
|
|
298
298
|
// WebSocket routes are kept separate from HTTP routes — they're
|
|
299
|
-
// matched on the upgrade / Extended CONNECT
|
|
300
|
-
// verb. Map<
|
|
299
|
+
// matched on the upgrade / Extended CONNECT nodePath, not on a method
|
|
300
|
+
// verb. Map<nodePath, { handler, opts }>.
|
|
301
301
|
this._wsRoutes = new Map();
|
|
302
302
|
// Active WebSocket connections opened through router.ws(). Tracked
|
|
303
303
|
// so router.closeWebSockets() can do a clean rolling-shutdown.
|
|
@@ -407,7 +407,7 @@ class Router {
|
|
|
407
407
|
// process.exit(0);
|
|
408
408
|
//
|
|
409
409
|
// Tests use the same primitive in teardown — no parallel cleanup
|
|
410
|
-
//
|
|
410
|
+
// nodePath, no h1-upgrade detached-socket workaround.
|
|
411
411
|
async closeWebSockets(opts) {
|
|
412
412
|
opts = opts || {};
|
|
413
413
|
var timeoutMs = typeof opts.timeoutMs === "number" ? opts.timeoutMs : C.TIME.seconds(5);
|
|
@@ -603,7 +603,7 @@ class Router {
|
|
|
603
603
|
|
|
604
604
|
// ---- WebSocket route registration ----
|
|
605
605
|
//
|
|
606
|
-
// ws(
|
|
606
|
+
// ws(nodePath, handler, opts?)
|
|
607
607
|
// path — exact match. Path-param patterns aren't supported on
|
|
608
608
|
// upgrade requests; operators that need dynamic paths
|
|
609
609
|
// register one ws route per stable shape.
|
|
@@ -1089,7 +1089,7 @@ class Router {
|
|
|
1089
1089
|
// server's emitter list clean for HTTP-only deployments.
|
|
1090
1090
|
if (self._wsRoutes.size > 0) {
|
|
1091
1091
|
// h1 upgrade event — fires for "Upgrade: websocket" from h1
|
|
1092
|
-
// clients. Routes by
|
|
1092
|
+
// clients. Routes by nodePath; refuses with 426 in h2-only mode.
|
|
1093
1093
|
server.on("upgrade", function (req, socket, head) {
|
|
1094
1094
|
var pathname = String(req.url || "/").split("?")[0];
|
|
1095
1095
|
var route = self._wsRoutes.get(pathname);
|
|
@@ -1201,18 +1201,18 @@ class Router {
|
|
|
1201
1201
|
*/
|
|
1202
1202
|
// Static file serving middleware
|
|
1203
1203
|
function serveStatic(dir) {
|
|
1204
|
-
var root =
|
|
1204
|
+
var root = nodePath.resolve(dir);
|
|
1205
1205
|
return (req, res, next) => {
|
|
1206
1206
|
if (req.method !== "GET") return next();
|
|
1207
1207
|
var rel = req.pathname;
|
|
1208
1208
|
if (rel.includes("\0")) return next();
|
|
1209
|
-
var filePath =
|
|
1209
|
+
var filePath = nodePath.resolve(nodePath.join(root, rel));
|
|
1210
1210
|
if (!filePath.startsWith(root)) return next();
|
|
1211
|
-
if (!
|
|
1211
|
+
if (!nodeFs.existsSync(filePath) || nodeFs.statSync(filePath).isDirectory()) return next();
|
|
1212
1212
|
|
|
1213
|
-
var ext =
|
|
1213
|
+
var ext = nodePath.extname(filePath).toLowerCase();
|
|
1214
1214
|
var mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
1215
|
-
var stat =
|
|
1215
|
+
var stat = nodeFs.statSync(filePath);
|
|
1216
1216
|
var hasVersion = req.url && req.url.includes("?v=");
|
|
1217
1217
|
var cacheControl = hasVersion
|
|
1218
1218
|
? "public, max-age=31536000, immutable"
|
|
@@ -1222,7 +1222,7 @@ function serveStatic(dir) {
|
|
|
1222
1222
|
"Content-Length": stat.size,
|
|
1223
1223
|
"Cache-Control": cacheControl,
|
|
1224
1224
|
});
|
|
1225
|
-
|
|
1225
|
+
nodeFs.createReadStream(filePath).pipe(res);
|
|
1226
1226
|
};
|
|
1227
1227
|
}
|
|
1228
1228
|
|
package/lib/sandbox.js
CHANGED
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
* arbitrary source from the public internet.
|
|
79
79
|
*/
|
|
80
80
|
|
|
81
|
-
var
|
|
81
|
+
var nodePath = require("path");
|
|
82
82
|
var lazyRequire = require("./lazy-require");
|
|
83
83
|
var validateOpts = require("./validate-opts");
|
|
84
84
|
var numericBounds = require("./numeric-bounds");
|
|
85
|
-
var
|
|
85
|
+
var C = require("./constants");
|
|
86
86
|
var { SandboxError } = require("./framework-error");
|
|
87
87
|
|
|
88
88
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
@@ -110,14 +110,14 @@ var ALWAYS_AVAILABLE = Object.freeze([
|
|
|
110
110
|
"Promise", "Error", "TypeError", "RangeError", "RegExp",
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
|
-
var WORKER_PATH =
|
|
113
|
+
var WORKER_PATH = nodePath.resolve(__dirname, "sandbox-worker.js");
|
|
114
114
|
|
|
115
115
|
// Default caps. Sourced from C.* helpers so the unit lives at the call site.
|
|
116
116
|
var DEFAULT_TIMEOUT_MS = 250;
|
|
117
|
-
var MAX_TIMEOUT_MS =
|
|
118
|
-
var DEFAULT_MAX_BYTES =
|
|
119
|
-
var MAX_MAX_BYTES =
|
|
120
|
-
var MIN_MAX_BYTES =
|
|
117
|
+
var MAX_TIMEOUT_MS = C.TIME.seconds(10);
|
|
118
|
+
var DEFAULT_MAX_BYTES = C.BYTES.mib(64);
|
|
119
|
+
var MAX_MAX_BYTES = C.BYTES.gib(1);
|
|
120
|
+
var MIN_MAX_BYTES = C.BYTES.mib(4);
|
|
121
121
|
|
|
122
122
|
function _validateAllowed(allowed) {
|
|
123
123
|
if (allowed === undefined || allowed === null) return [];
|
|
@@ -217,7 +217,7 @@ function run(opts) {
|
|
|
217
217
|
// floor so the worker can boot. Round each cap down to a MiB integer.
|
|
218
218
|
// Floors / caps are quanta of MiB chosen to fit a small embedded
|
|
219
219
|
// worker; passed straight to v8's resourceLimits.
|
|
220
|
-
var oneMib =
|
|
220
|
+
var oneMib = C.BYTES.mib(1);
|
|
221
221
|
// The MiB-unit caps below are integers passed directly to v8's
|
|
222
222
|
// resourceLimits (already typed in MiB by the v8 API), not byte
|
|
223
223
|
// counts - the constants helpers don't apply.
|