@blamejs/core 0.6.70 → 0.7.1
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 +6 -0
- package/README.md +11 -1
- package/index.js +2 -5
- package/lib/api-key.js +24 -55
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +20 -16
- package/lib/app.js +6 -5
- package/lib/archive.js +58 -50
- package/lib/atomic-file.js +11 -5
- package/lib/audit-chain.js +12 -4
- package/lib/audit-sign.js +38 -13
- package/lib/audit-tools.js +4 -4
- package/lib/audit.js +8 -4
- package/lib/auth/jwt.js +3 -2
- package/lib/auth/lockout.js +3 -13
- package/lib/auth/oauth.js +27 -17
- package/lib/auth/passkey.js +1 -5
- package/lib/auth/password.js +23 -13
- package/lib/backup/bundle.js +2 -4
- package/lib/backup/crypto.js +8 -6
- package/lib/backup/index.js +18 -14
- package/lib/backup/manifest.js +6 -2
- package/lib/break-glass.js +12 -7
- package/lib/bundler.js +32 -21
- package/lib/cache-redis.js +8 -3
- package/lib/cache.js +39 -59
- package/lib/canonical-json.js +17 -2
- package/lib/chain-writer.js +2 -2
- package/lib/cli-helpers.js +9 -4
- package/lib/cli.js +27 -5
- package/lib/cluster-provider-db.js +2 -1
- package/lib/cluster-storage.js +71 -11
- package/lib/cluster.js +30 -25
- package/lib/config-drift.js +3 -4
- package/lib/constants.js +8 -3
- package/lib/cookies.js +16 -4
- package/lib/credential-hash.js +4 -7
- package/lib/crypto.js +9 -9
- package/lib/csv.js +11 -8
- package/lib/db-declare-row-policy.js +4 -9
- package/lib/db-declare-view.js +3 -6
- package/lib/db-query.js +9 -3
- package/lib/db-schema.js +75 -10
- package/lib/db.js +116 -70
- package/lib/deprecate.js +3 -8
- package/lib/dev.js +30 -19
- package/lib/dual-control.js +2 -4
- package/lib/error-page.js +32 -39
- package/lib/external-db-migrate.js +23 -26
- package/lib/external-db.js +11 -3
- package/lib/file-type.js +16 -8
- package/lib/forms.js +48 -12
- package/lib/framework-error.js +1 -2
- package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
- package/lib/handlers.js +1 -1
- package/lib/http-client-cookie-jar.js +25 -18
- package/lib/http-client.js +46 -23
- package/lib/i18n.js +6 -16
- package/lib/jobs.js +5 -1
- package/lib/log-stream-cloudwatch.js +31 -30
- package/lib/log-stream-local.js +12 -3
- package/lib/log-stream-otlp-grpc.js +29 -23
- package/lib/log-stream-otlp.js +13 -19
- package/lib/log-stream-syslog.js +27 -22
- package/lib/log-stream-webhook.js +8 -17
- package/lib/log-stream.js +13 -2
- package/lib/log.js +83 -5
- package/lib/mail-bounce.js +22 -15
- package/lib/mail-dkim.js +3 -6
- package/lib/mail.js +62 -34
- package/lib/metrics.js +61 -25
- package/lib/middleware/api-encrypt.js +15 -13
- package/lib/middleware/body-parser.js +46 -39
- package/lib/middleware/bot-guard.js +25 -4
- package/lib/middleware/compression.js +26 -11
- package/lib/middleware/cors.js +6 -5
- package/lib/middleware/csp-nonce.js +4 -3
- package/lib/middleware/csrf-protect.js +1 -1
- package/lib/middleware/db-role-for.js +7 -30
- package/lib/middleware/health.js +42 -8
- package/lib/middleware/index.js +55 -36
- package/lib/middleware/rate-limit.js +11 -8
- package/lib/middleware/request-id.js +10 -2
- package/lib/middleware/request-log.js +3 -2
- package/lib/middleware/require-auth.js +5 -2
- package/lib/middleware/sse.js +5 -4
- package/lib/migration-files.js +37 -0
- package/lib/migrations.js +26 -15
- package/lib/mtls-ca.js +38 -17
- package/lib/mtls-engine-default.js +15 -7
- package/lib/network-dns.js +49 -20
- package/lib/network-heartbeat.js +7 -7
- package/lib/network-nts.js +70 -48
- package/lib/network-proxy.js +34 -15
- package/lib/network-tls.js +7 -5
- package/lib/network.js +3 -3
- package/lib/notify.js +14 -36
- package/lib/ntp-check.js +7 -6
- package/lib/numeric-bounds.js +35 -3
- package/lib/object-store/azure-blob-bucket-ops.js +73 -15
- package/lib/object-store/azure-blob.js +31 -24
- package/lib/object-store/gcs-bucket-ops.js +41 -17
- package/lib/object-store/gcs.js +41 -34
- package/lib/object-store/http-put.js +2 -11
- package/lib/object-store/http-request.js +38 -0
- package/lib/object-store/index.js +1 -1
- package/lib/object-store/sigv4-bucket-ops.js +33 -37
- package/lib/object-store/sigv4.js +50 -30
- package/lib/observability.js +14 -3
- package/lib/otel-export.js +16 -14
- package/lib/pagination.js +23 -8
- package/lib/parsers/index.js +11 -5
- package/lib/parsers/safe-env.js +43 -14
- package/lib/parsers/safe-ini.js +29 -10
- package/lib/parsers/safe-toml.js +55 -25
- package/lib/parsers/safe-xml.js +22 -11
- package/lib/parsers/safe-yaml.js +55 -17
- package/lib/permissions.js +24 -33
- package/lib/pqc-agent.js +4 -2
- package/lib/pqc-gate.js +28 -15
- package/lib/protobuf-encoder.js +9 -3
- package/lib/protocol-dispatcher.js +29 -12
- package/lib/pubsub-redis.js +25 -8
- package/lib/pubsub.js +84 -12
- package/lib/queue-local.js +13 -4
- package/lib/queue-redis.js +2 -2
- package/lib/queue-sqs.js +21 -16
- package/lib/queue.js +8 -4
- package/lib/redis-client.js +13 -10
- package/lib/request-helpers.js +33 -2
- package/lib/restore-bundle.js +2 -4
- package/lib/restore-rollback.js +4 -1
- package/lib/restore.js +12 -10
- package/lib/retry.js +58 -9
- package/lib/router.js +96 -27
- package/lib/safe-async.js +97 -4
- package/lib/safe-buffer.js +47 -0
- package/lib/safe-json.js +18 -9
- package/lib/safe-schema.js +54 -18
- package/lib/safe-url.js +6 -2
- package/lib/scheduler.js +25 -14
- package/lib/security-assert.js +31 -26
- package/lib/seeders.js +71 -60
- package/lib/session.js +13 -7
- package/lib/slug.js +35 -8
- package/lib/ssrf-guard.js +58 -43
- package/lib/static.js +11 -7
- package/lib/storage.js +2 -1
- package/lib/template.js +17 -7
- package/lib/testing.js +28 -8
- package/lib/time.js +26 -5
- package/lib/totp.js +18 -9
- package/lib/tracing.js +15 -5
- package/lib/uuid.js +47 -20
- package/lib/validate-opts.js +213 -0
- package/lib/vault/index.js +36 -27
- package/lib/vault/rotate.js +41 -22
- package/lib/vault/wrap.js +26 -19
- package/lib/webhook.js +37 -68
- package/lib/websocket.js +98 -52
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
- package/lib/auth/totp.js +0 -17
- package/lib/object-store/retry.js +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ Pre-1.0 the surface is intentionally evolving — every release may
|
|
|
6
6
|
change something operators depend on. Read each entry before
|
|
7
7
|
upgrading across more than a few patches at a time.
|
|
8
8
|
|
|
9
|
+
## v0.7.x
|
|
10
|
+
|
|
11
|
+
- **0.7.1** (2026-05-04) — `b.websocket` route opts gain `handshakeGuid` to override the RFC 6455 §1.3 magic string used in the `Sec-WebSocket-Accept` derivation. Default stays `258EAFA5-E914-47DA-95CA-C5AB0DC85B11`. Operators with closed-ecosystem clients running their own GUID (typical for migrations from frameworks that customize the handshake to namespace their own client family) drop `handshakeGuid: "<their-uuid>"` into the `router.ws(path, handler, opts)` opts and clients keep working unchanged. Throws at upgrade time if the override is malformed — UUID-shape regex with a 64-char length cap before the regex test, so a typo produces a clear error instead of silently producing a `Sec-WebSocket-Accept` the client can't match. **Tests** — 3 new layer-0 assertions in `test/00-primitives.js` (custom GUID produces different accept key, empty / null falls back to RFC default, malformed handshakeGuid rejected at config time). Smoke 7338 → 7341 / wiki e2e 178 / Linux container smoke 7341 (145s) / eslint clean / shellcheck clean.
|
|
12
|
+
|
|
13
|
+
- **0.7.0** (2026-05-04) — Codebase-patterns hardening sweep + primitive consolidation. The duplicate-block detector in `test/layer-0-primitives/codebase-patterns.test.js` ran at MIN_DISTINCT_FILES=3 and surfaced ~50 inline-shape clusters that had proliferated across lib/ — the kind of soft drift that's invisible at higher thresholds and hides re-introduction of bug classes the framework already swept once. **New primitive families** consolidated those clusters down: opts validation (`validateOpts.requireObject` / `auditShape` / `observabilityShape` / `applyDefaults` / `optional{Boolean,Function,PositiveInt,FiniteNonNegative,PositiveFinite,NonEmptyString}` / `requireNonEmptyString` / `makeAuditEmitter`), async coordination (`safeAsync.safeInvoke` / `makeDropCallback` / `makeScheduledFlush`), and SQL execution (`dbSchema.runInTransaction` / `runSqlOnHandle`). Plus `numericBounds.requireXFiniteIntIfPresent` for opt-time numeric-shape gates that throw via the caller's framework-error class, `log.makeViaOrFallback` for operator-log routing with per-module fallback, `observability.safeEvent` for hot-path drop-silent emission, `safeBuffer.HEX_RE`/`CRLF_RE`/`TRAILING_HSPACE_RE` regex constants + `isHex` / `hasCrlf` / `stripCrlf` / `stripTrailingHspace` helpers, `time.toIso8601NoMs`, `migrationFiles.MIGRATION_FILE_RE` / `isMigrationFileName` (shared filename grammar across migrations / seeders / external-db-migrate), and `lib/object-store/http-request.js` as a shared HTTP request helper across azure-blob / gcs / sigv4 / http-put. Roughly 80 inline call sites across 50+ files refactored to route through the new primitives; ~1500 lines of duplicate inline shapes eliminated. **Catalog gate** — `KNOWN_ANTIPATTERNS` in the codebase-patterns test now has 24 entries firing at n=1, so future code re-introducing any of the registered inline shapes (even one new file) fails the gate immediately. Pre-v0.7.0 the duplicate-block detector required n>=3 to fire; the catalog closes the gap by registering each extracted primitive's inline shape so it can't drift back in. **Cluster allowlist** — `KNOWN_CLUSTERS` allowlist (n>=3 detector) has 25 entries with documented structural reasons (parser error class signatures don't fit the framework's `(code, message)` contract; framework-convention shapes like middleware factories; future consolidation candidates). **Cleanup** — deleted dead re-export shims `lib/object-store/retry.js` (re-exported `lib/retry.js`) and `lib/auth/totp.js` (re-exported `lib/totp.js`); both fit the rule "pre-v1 frameworks have no operators to compatibly upgrade — every legacy fallback is dead code." Renamed `lib/internal-sha1-hibp.js` → `lib/framework-sha1-hibp.js` to match the lib naming convention (the `internal-` prefix wasn't in the convention's five-bucket list; `framework-` is the canonical "restricted-use" bucket alongside `framework-error.js` / `framework-schema.js`). **No operator-facing API breakage** — `b.objectStoreRetry` was removed from the public surface (operators use `b.retry` directly, which has been the canonical primitive since v0.2.24). **Eslint config tightened** — added `eqeqeq` (with the `null` exception for the `== null` null-or-undefined idiom), `no-throw-literal`, `no-promise-executor-return`, `default-case`, `no-loss-of-precision`. The previous config was hiding 11 real errors: 8 sites of `return resolve()` / `return done()` inside `new Promise(executor)` (return value silently discarded) across app / dev / http-client / mail / router / wiki/integration / 00-primitives, plus a missing default-case + 2 intentional template-language `==` / `!=` operators in template.js's binary-op evaluator (now allowed via inline `// eslint-disable-next-line eqeqeq -- template language operator`). Also adds `structuredClone` to the Node-globals list so db.js's deep-clone calls don't trip `no-undef`. **Release-workflow gate added** — Linux container smoke `docker run --rm -v "/$(pwd):/blamejs" -w //blamejs node:24-alpine node test/smoke.js` is now part of the release flow. Catches lingering-handle bugs that pass on Windows / macOS but hang or error on Linux CI. **Tests** — smoke 7338 / Linux container smoke 7338 (148s) / wiki e2e 178 / per-primitive integration 16 files / wiki integration green / eslint clean / shellcheck clean.
|
|
14
|
+
|
|
9
15
|
## v0.6.x
|
|
10
16
|
|
|
11
17
|
- **0.6.70** (2026-05-03) — `lib/numeric-bounds` extended to 3 more sites the v0.6.69 sweep didn't cover. **`lib/middleware/csp-nonce.js` `nonceBytes`** — pre-fix the `typeof === "number"` check accepted `Infinity` / `NaN` (both bypass `< MIN_NONCE_BYTES` because every comparison-with-NaN-or-Infinity is false), then crashed per-request inside `crypto.generateBytes(Infinity)` with `ERR_OUT_OF_RANGE`. The DoS-shape: an operator typo at boot took down every CSP-protected route at request time. Now rejects at create() with `csp-nonce/bad-nonce-bytes`. **`lib/migrations.js` and `lib/external-db-migrate.js` `staleAfterMs`** — both used the `> 0` guard which silently accepted `Infinity`. `(Date.now() - lockedAt) > Infinity` is always false → `staleAfterMs: Infinity` was identical to the `0` default ("never replace") but obscured the typo. Operators wanting "never expire" now pass `0` explicitly; everything else (Infinity / NaN / fractional / negative / non-number) rejects with a clear message. **Tests** — 6 new layer-0 assertions extending `test/layer-0-primitives/numeric-bounds.test.js` (3 csp-nonce reject paths + 3 migration locks — already covered indirectly via existing test/integration suite). Smoke 7293 → 7296 / wiki e2e 178 / Linux 85.9s / eslint clean / shellcheck clean.
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Full primitive-by-primitive docs live at [blamejs.com](https://blamejs.com), whi
|
|
|
64
64
|
- **Communication** — [WebSockets](https://blamejs.com/websockets) · [Mail](https://blamejs.com/mail) · [Notifications](https://blamejs.com/notifications)
|
|
65
65
|
- **Tools** — [Observability](https://blamejs.com/observability) · [Testing](https://blamejs.com/testing) · [i18n & Locale](https://blamejs.com/i18n-locale) · [Format Helpers](https://blamejs.com/format-helpers)
|
|
66
66
|
- **Compliance** — [Compliance Patterns](https://blamejs.com/compliance-patterns)
|
|
67
|
-
- **Production** — [Cluster Mode](https://blamejs.com/cluster) · [Reliability](https://blamejs.com/reliability) · [Backup & Restore](https://blamejs.com/backup-restore)
|
|
67
|
+
- **Production** — [Cluster Mode](https://blamejs.com/cluster) · [Reliability](https://blamejs.com/reliability) · [Backup & Restore](https://blamejs.com/backup-restore) · [Quality Contract](https://blamejs.com/quality-contract)
|
|
68
68
|
|
|
69
69
|
## CLI
|
|
70
70
|
|
|
@@ -124,6 +124,16 @@ These libraries are exceptional work — blamejs wouldn't exist without them. Al
|
|
|
124
124
|
|
|
125
125
|
Because when something breaks, `blame` should know exactly where it lives. We own the stack so you don't have to chase the fault across an ecosystem.
|
|
126
126
|
|
|
127
|
+
## Quality contract
|
|
128
|
+
|
|
129
|
+
Every release passes a layered gate at `test/layer-0-primitives/codebase-patterns.test.js` that operates on lib/ source:
|
|
130
|
+
|
|
131
|
+
- **Bug-class detectors** — raw byte / time literals, `JSON.parse` on operator input without size cap, numeric opts that silently accept `Infinity` / `NaN`, ReDoS-risky regex without length cap, hash / token compares without `timingSafeEqual`, raw `new URL` skipping the SSRF gate, `Math.random()` in security-sensitive paths, and a couple dozen others — each a bug class the framework already swept once and won't re-introduce.
|
|
132
|
+
- **Inline-shape catalog (n=1)** — every primitive that's been extracted (`validateOpts.requireNonEmptyString`, `safeAsync.makeScheduledFlush`, `dbSchema.runInTransaction`, etc.) registers the inline shape it replaced; new code that re-implements the shape fails the gate even if it's the only file matching.
|
|
133
|
+
- **Cluster allowlist (n>=3)** — duplicate-block detection across files. Genuine new clusters get extracted; clusters that resist extraction (parser error class signature mismatches, framework-convention shapes, cross-domain coincidences) get an entry with a documented structural reason. No silent allowlisting.
|
|
134
|
+
|
|
135
|
+
The gate is part of `node test/smoke.js`; the framework refuses to release without it green.
|
|
136
|
+
|
|
127
137
|
## Contributing
|
|
128
138
|
|
|
129
139
|
Patches welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev setup, house rules (zero npm runtime deps, PQC-only crypto, audit-on-every-action, ship-complete-not-incremental), and the PR loop. New to the codebase? Start with [ARCHITECTURE.md](ARCHITECTURE.md) for the orientation map.
|
package/index.js
CHANGED
|
@@ -57,9 +57,6 @@ var ntpCheck = require("./lib/ntp-check");
|
|
|
57
57
|
var auditSign = require("./lib/audit-sign");
|
|
58
58
|
var objectStore = require("./lib/object-store");
|
|
59
59
|
var retry = require("./lib/retry");
|
|
60
|
-
// objectStoreRetry is preserved as a re-export of the canonical b.retry
|
|
61
|
-
// for backward compatibility with consumers of the pre-v0.2.24 surface.
|
|
62
|
-
var objectStoreRetry = require("./lib/object-store/retry");
|
|
63
60
|
var queue = require("./lib/queue");
|
|
64
61
|
var logStream = require("./lib/log-stream");
|
|
65
62
|
var redact = require("./lib/redact");
|
|
@@ -90,7 +87,7 @@ var ssrfGuard = require("./lib/ssrf-guard");
|
|
|
90
87
|
var authHeader = require("./lib/auth-header");
|
|
91
88
|
var auth = {
|
|
92
89
|
password: require("./lib/auth/password"),
|
|
93
|
-
totp: require("./lib/
|
|
90
|
+
totp: require("./lib/totp"),
|
|
94
91
|
passkey: require("./lib/auth/passkey"),
|
|
95
92
|
jwt: require("./lib/auth/jwt"),
|
|
96
93
|
oauth: require("./lib/auth/oauth"),
|
|
@@ -187,7 +184,6 @@ module.exports = {
|
|
|
187
184
|
storage: storage,
|
|
188
185
|
objectStore: objectStore,
|
|
189
186
|
retry: retry,
|
|
190
|
-
objectStoreRetry: objectStoreRetry,
|
|
191
187
|
queue: queue,
|
|
192
188
|
logStream: logStream,
|
|
193
189
|
redact: redact,
|
|
@@ -195,6 +191,7 @@ module.exports = {
|
|
|
195
191
|
middleware: middleware,
|
|
196
192
|
atomicFile: atomicFile,
|
|
197
193
|
parsers: parsers,
|
|
194
|
+
safeEnv: parsers.env,
|
|
198
195
|
cluster: cluster,
|
|
199
196
|
frameworkSchema: frameworkSchema,
|
|
200
197
|
clusterStorage: clusterStorage,
|
package/lib/api-key.js
CHANGED
|
@@ -68,10 +68,7 @@ var { ApiKeyError } = require("./framework-error");
|
|
|
68
68
|
|
|
69
69
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
70
70
|
|
|
71
|
-
function _emitEvent(
|
|
72
|
-
try { observability().event(name, value, labels || {}); }
|
|
73
|
-
catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
|
|
74
|
-
}
|
|
71
|
+
function _emitEvent(n, v, l) { observability().safeEvent(n, v, l || {}); }
|
|
75
72
|
|
|
76
73
|
var _err = ApiKeyError.factory;
|
|
77
74
|
|
|
@@ -110,8 +107,8 @@ var DEFAULT_ROTATE_GRACE_MS = C.TIME.days(7);
|
|
|
110
107
|
// `auditSuccess: false`. Failures stay on regardless.
|
|
111
108
|
var DEFAULTS = Object.freeze({
|
|
112
109
|
prefix: "bk",
|
|
113
|
-
idBytes: 8,
|
|
114
|
-
secretBytes: 16,
|
|
110
|
+
idBytes: C.BYTES.bytes(8), // 16 hex chars
|
|
111
|
+
secretBytes: C.BYTES.bytes(16), // 32 hex chars
|
|
115
112
|
trackLastUsedAt: true, // visibility on dormant / leaked keys
|
|
116
113
|
auditFailures: true, // failure events are actionable signals
|
|
117
114
|
auditSuccess: true, // compliance trail — opt out at extreme volume
|
|
@@ -143,30 +140,15 @@ function _validateIdentifier(name, value) {
|
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
function _validateCreateOpts(opts) {
|
|
146
|
-
|
|
147
|
-
throw _err("BAD_OPT", "apiKey.create: opts must be an object");
|
|
148
|
-
}
|
|
143
|
+
validateOpts.requireObject(opts, "apiKey.create", ApiKeyError);
|
|
149
144
|
_validateIdentifier("apiKey.create: namespace", opts.namespace);
|
|
150
145
|
if (opts.prefix !== undefined) _validateIdentifier("apiKey.create: prefix", opts.prefix);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (opts.trackLastUsedAt !== undefined && typeof opts.trackLastUsedAt !== "boolean") {
|
|
158
|
-
throw _err("BAD_OPT", "apiKey.create: trackLastUsedAt must be a boolean");
|
|
159
|
-
}
|
|
160
|
-
if (opts.auditFailures !== undefined && typeof opts.auditFailures !== "boolean") {
|
|
161
|
-
throw _err("BAD_OPT", "apiKey.create: auditFailures must be a boolean");
|
|
162
|
-
}
|
|
163
|
-
if (opts.auditSuccess !== undefined && typeof opts.auditSuccess !== "boolean") {
|
|
164
|
-
throw _err("BAD_OPT", "apiKey.create: auditSuccess must be a boolean");
|
|
165
|
-
}
|
|
166
|
-
if (opts.purgeAfterMs !== undefined &&
|
|
167
|
-
(typeof opts.purgeAfterMs !== "number" || !isFinite(opts.purgeAfterMs) || opts.purgeAfterMs < 0)) {
|
|
168
|
-
throw _err("BAD_OPT", "apiKey.create: purgeAfterMs must be a non-negative finite number");
|
|
169
|
-
}
|
|
146
|
+
validateOpts.optionalPositiveInt(opts.idBytes, "apiKey.create: idBytes", ApiKeyError);
|
|
147
|
+
validateOpts.optionalPositiveInt(opts.secretBytes, "apiKey.create: secretBytes", ApiKeyError);
|
|
148
|
+
validateOpts.optionalBoolean(opts.trackLastUsedAt, "apiKey.create: trackLastUsedAt", ApiKeyError);
|
|
149
|
+
validateOpts.optionalBoolean(opts.auditFailures, "apiKey.create: auditFailures", ApiKeyError);
|
|
150
|
+
validateOpts.optionalBoolean(opts.auditSuccess, "apiKey.create: auditSuccess", ApiKeyError);
|
|
151
|
+
validateOpts.optionalFiniteNonNegative(opts.purgeAfterMs, "apiKey.create: purgeAfterMs", ApiKeyError);
|
|
170
152
|
if (opts.hashAlgo !== undefined) {
|
|
171
153
|
if (typeof opts.hashAlgo !== "string" ||
|
|
172
154
|
(opts.hashAlgo !== "shake256" && opts.hashAlgo !== "argon2id")) {
|
|
@@ -174,23 +156,13 @@ function _validateCreateOpts(opts) {
|
|
|
174
156
|
JSON.stringify(opts.hashAlgo));
|
|
175
157
|
}
|
|
176
158
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
throw _err("BAD_OPT", "apiKey.create: audit must be a b.audit-shaped object (safeEmit fn)");
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (opts.clock !== undefined && typeof opts.clock !== "function") {
|
|
183
|
-
throw _err("BAD_OPT", "apiKey.create: clock must be a function or undefined");
|
|
184
|
-
}
|
|
159
|
+
validateOpts.auditShape(opts.audit, "apiKey.create", ApiKeyError);
|
|
160
|
+
validateOpts.optionalFunction(opts.clock, "apiKey.create: clock", ApiKeyError);
|
|
185
161
|
}
|
|
186
162
|
|
|
187
163
|
function _validateIssueOpts(opts) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
if (typeof opts.ownerId !== "string" || opts.ownerId.length === 0) {
|
|
192
|
-
throw _err("MISSING_OWNER", "apiKey.issue: ownerId must be a non-empty string");
|
|
193
|
-
}
|
|
164
|
+
validateOpts.requireObject(opts, "apiKey.issue", ApiKeyError);
|
|
165
|
+
validateOpts.requireNonEmptyString(opts.ownerId, "apiKey.issue: ownerId", ApiKeyError, "MISSING_OWNER");
|
|
194
166
|
if (opts.scopes !== undefined) {
|
|
195
167
|
if (!Array.isArray(opts.scopes)) {
|
|
196
168
|
throw _err("BAD_SCOPES", "apiKey.issue: scopes must be an array of strings");
|
|
@@ -257,23 +229,20 @@ function create(opts) {
|
|
|
257
229
|
"purgeAfterMs", "hashAlgo", "audit", "clock",
|
|
258
230
|
], "apiKey");
|
|
259
231
|
_validateCreateOpts(opts);
|
|
260
|
-
var
|
|
232
|
+
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
233
|
+
var prefix = cfg.prefix;
|
|
261
234
|
var namespace = opts.namespace;
|
|
262
|
-
var idBytes =
|
|
263
|
-
var secretBytes =
|
|
264
|
-
var trackLastUsedAt =
|
|
265
|
-
var auditFailures =
|
|
266
|
-
var auditSuccess =
|
|
267
|
-
var purgeAfterMs =
|
|
268
|
-
var hashAlgo =
|
|
235
|
+
var idBytes = cfg.idBytes;
|
|
236
|
+
var secretBytes = cfg.secretBytes;
|
|
237
|
+
var trackLastUsedAt = cfg.trackLastUsedAt;
|
|
238
|
+
var auditFailures = cfg.auditFailures;
|
|
239
|
+
var auditSuccess = cfg.auditSuccess;
|
|
240
|
+
var purgeAfterMs = cfg.purgeAfterMs;
|
|
241
|
+
var hashAlgo = cfg.hashAlgo;
|
|
269
242
|
var audit = opts.audit || null;
|
|
270
243
|
var clock = opts.clock || function () { return Date.now(); };
|
|
271
244
|
|
|
272
|
-
|
|
273
|
-
if (!audit) return;
|
|
274
|
-
try { audit.safeEmit(Object.assign({ action: action }, info)); }
|
|
275
|
-
catch (_e) { /* audit best-effort */ }
|
|
276
|
-
}
|
|
245
|
+
var _emit = validateOpts.makeAuditEmitter(audit);
|
|
277
246
|
|
|
278
247
|
// Build the audit actor by extracting the 5 W's from the supplied
|
|
279
248
|
// request (WHO/WHERE/HOW), then layering caller-supplied context
|
package/lib/api-snapshot.js
CHANGED
|
@@ -50,9 +50,12 @@
|
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
var fs = require("fs");
|
|
53
|
+
var nb = require("./numeric-bounds");
|
|
53
54
|
var safeJson = require("./safe-json");
|
|
54
55
|
var { FrameworkError } = require("./framework-error");
|
|
55
56
|
|
|
57
|
+
var DEFAULT_MAX_DEPTH = 0x08;
|
|
58
|
+
|
|
56
59
|
class ApiSnapshotError extends FrameworkError {
|
|
57
60
|
constructor(code, message) {
|
|
58
61
|
super(message, code);
|
|
@@ -115,7 +118,7 @@ function capture(target, opts) {
|
|
|
115
118
|
throw new ApiSnapshotError("api-snapshot/bad-target",
|
|
116
119
|
"capture: target must be a module's exports object");
|
|
117
120
|
}
|
|
118
|
-
var maxDepth =
|
|
121
|
+
var maxDepth = nb.isPositiveFiniteInt(opts.maxDepth) ? opts.maxDepth : DEFAULT_MAX_DEPTH;
|
|
119
122
|
var skipUnderscore = opts.skipUnderscore !== false;
|
|
120
123
|
var snapshot = _walkNode(target, 0, maxDepth, new Set(), skipUnderscore);
|
|
121
124
|
if (snapshot.type !== "object") {
|
package/lib/app-shutdown.js
CHANGED
|
@@ -81,17 +81,23 @@
|
|
|
81
81
|
*/
|
|
82
82
|
|
|
83
83
|
var safeAsync = require("./safe-async");
|
|
84
|
+
var nb = require("./numeric-bounds");
|
|
85
|
+
var tracing = null;
|
|
86
|
+
try { tracing = require("./tracing"); } catch (_e) { /* tracing optional */ }
|
|
84
87
|
var { defineClass } = require("./framework-error");
|
|
85
88
|
var { boot } = require("./log");
|
|
89
|
+
var C = require("./constants");
|
|
86
90
|
|
|
87
91
|
var AppShutdownError = defineClass("AppShutdownError", { alwaysPermanent: true });
|
|
88
92
|
var log = boot("app-shutdown");
|
|
89
93
|
|
|
90
|
-
var DEFAULT_GRACE_MS =
|
|
94
|
+
var DEFAULT_GRACE_MS = C.TIME.seconds(30);
|
|
91
95
|
|
|
92
96
|
function create(opts) {
|
|
93
97
|
opts = opts || {};
|
|
94
|
-
|
|
98
|
+
nb.requirePositiveFiniteIntIfPresent(opts.graceMs,
|
|
99
|
+
"app-shutdown.create: opts.graceMs", AppShutdownError, "app-shutdown/bad-grace-ms");
|
|
100
|
+
var graceMs = opts.graceMs !== undefined ? opts.graceMs : DEFAULT_GRACE_MS;
|
|
95
101
|
var phases = Array.isArray(opts.phases) ? opts.phases.slice() : [];
|
|
96
102
|
var installSignalHandlers = !!opts.installSignalHandlers;
|
|
97
103
|
for (var i = 0; i < phases.length; i++) {
|
|
@@ -189,8 +195,6 @@ function create(opts) {
|
|
|
189
195
|
draining = true;
|
|
190
196
|
startTime = Date.now();
|
|
191
197
|
shutdownPromise = (async function () {
|
|
192
|
-
var tracing = null;
|
|
193
|
-
try { tracing = require("./tracing"); } catch (_e) { /* tracing optional */ }
|
|
194
198
|
var phaseResults = [];
|
|
195
199
|
var remainingPhases = phases.length;
|
|
196
200
|
for (var i = 0; i < phases.length; i++) {
|
|
@@ -295,7 +299,7 @@ function standardPhases(components) {
|
|
|
295
299
|
phases.push({
|
|
296
300
|
name: "mark-draining",
|
|
297
301
|
run: function () { components.health.markShuttingDown(); },
|
|
298
|
-
timeoutMs:
|
|
302
|
+
timeoutMs: C.TIME.seconds(1),
|
|
299
303
|
});
|
|
300
304
|
}
|
|
301
305
|
|
|
@@ -308,29 +312,29 @@ function standardPhases(components) {
|
|
|
308
312
|
phases.push({
|
|
309
313
|
name: "scheduler",
|
|
310
314
|
run: function () { return components.scheduler.stop(); },
|
|
311
|
-
timeoutMs:
|
|
315
|
+
timeoutMs: C.TIME.seconds(5),
|
|
312
316
|
});
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
if (components.jobs && typeof components.jobs.shutdown === "function") {
|
|
316
320
|
phases.push({
|
|
317
321
|
name: "jobs",
|
|
318
|
-
run: function () { return components.jobs.shutdown({ timeoutMs:
|
|
319
|
-
timeoutMs:
|
|
322
|
+
run: function () { return components.jobs.shutdown({ timeoutMs: C.TIME.seconds(5) }); },
|
|
323
|
+
timeoutMs: C.TIME.seconds(8),
|
|
320
324
|
});
|
|
321
325
|
} else if (components.queue && typeof components.queue.shutdown === "function") {
|
|
322
326
|
phases.push({
|
|
323
327
|
name: "queue",
|
|
324
|
-
run: function () { return components.queue.shutdown({ timeoutMs:
|
|
325
|
-
timeoutMs:
|
|
328
|
+
run: function () { return components.queue.shutdown({ timeoutMs: C.TIME.seconds(5) }); },
|
|
329
|
+
timeoutMs: C.TIME.seconds(8),
|
|
326
330
|
});
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
if (components.router && typeof components.router.closeWebSockets === "function") {
|
|
330
334
|
phases.push({
|
|
331
335
|
name: "websockets",
|
|
332
|
-
run: function () { return components.router.closeWebSockets({ timeoutMs:
|
|
333
|
-
timeoutMs:
|
|
336
|
+
run: function () { return components.router.closeWebSockets({ timeoutMs: C.TIME.seconds(3) }); },
|
|
337
|
+
timeoutMs: C.TIME.seconds(5),
|
|
334
338
|
});
|
|
335
339
|
}
|
|
336
340
|
|
|
@@ -342,7 +346,7 @@ function standardPhases(components) {
|
|
|
342
346
|
components.server.close(function () { resolve(); });
|
|
343
347
|
});
|
|
344
348
|
},
|
|
345
|
-
timeoutMs:
|
|
349
|
+
timeoutMs: C.TIME.seconds(10),
|
|
346
350
|
});
|
|
347
351
|
}
|
|
348
352
|
|
|
@@ -350,7 +354,7 @@ function standardPhases(components) {
|
|
|
350
354
|
phases.push({
|
|
351
355
|
name: "cluster",
|
|
352
356
|
run: function () { return components.cluster.shutdown(); },
|
|
353
|
-
timeoutMs:
|
|
357
|
+
timeoutMs: C.TIME.seconds(5),
|
|
354
358
|
});
|
|
355
359
|
}
|
|
356
360
|
|
|
@@ -358,7 +362,7 @@ function standardPhases(components) {
|
|
|
358
362
|
phases.push({
|
|
359
363
|
name: "db",
|
|
360
364
|
run: function () { components.db.close(); },
|
|
361
|
-
timeoutMs:
|
|
365
|
+
timeoutMs: C.TIME.seconds(5),
|
|
362
366
|
});
|
|
363
367
|
}
|
|
364
368
|
|
|
@@ -366,7 +370,7 @@ function standardPhases(components) {
|
|
|
366
370
|
phases.push({
|
|
367
371
|
name: "external-db",
|
|
368
372
|
run: function () { return components.externalDb.shutdown(); },
|
|
369
|
-
timeoutMs:
|
|
373
|
+
timeoutMs: C.TIME.seconds(5),
|
|
370
374
|
});
|
|
371
375
|
}
|
|
372
376
|
|
package/lib/app.js
CHANGED
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
var fs = require("fs");
|
|
93
93
|
var path = require("path");
|
|
94
94
|
var appShutdown = require("./app-shutdown");
|
|
95
|
+
var C = require("./constants");
|
|
95
96
|
var cluster = require("./cluster");
|
|
96
97
|
var db = require("./db");
|
|
97
98
|
var externalDb = require("./external-db");
|
|
@@ -269,7 +270,7 @@ async function createApp(opts) {
|
|
|
269
270
|
orchestrator.addPhase({
|
|
270
271
|
name: "beforeStop",
|
|
271
272
|
run: shutdownOpts.beforeStop,
|
|
272
|
-
timeoutMs: shutdownOpts.beforeStopTimeoutMs ||
|
|
273
|
+
timeoutMs: shutdownOpts.beforeStopTimeoutMs || C.TIME.seconds(5),
|
|
273
274
|
});
|
|
274
275
|
}
|
|
275
276
|
|
|
@@ -278,14 +279,14 @@ async function createApp(opts) {
|
|
|
278
279
|
orchestrator.addPhase({
|
|
279
280
|
name: "drain-in-flight",
|
|
280
281
|
run: orchestrator.waitInFlight,
|
|
281
|
-
timeoutMs: shutdownOpts.drainTimeoutMs ||
|
|
282
|
+
timeoutMs: shutdownOpts.drainTimeoutMs || C.TIME.seconds(10),
|
|
282
283
|
});
|
|
283
284
|
|
|
284
285
|
if (typeof shutdownOpts.afterDrain === "function") {
|
|
285
286
|
orchestrator.addPhase({
|
|
286
287
|
name: "afterDrain",
|
|
287
288
|
run: shutdownOpts.afterDrain,
|
|
288
|
-
timeoutMs: shutdownOpts.afterDrainTimeoutMs ||
|
|
289
|
+
timeoutMs: shutdownOpts.afterDrainTimeoutMs || C.TIME.seconds(5),
|
|
289
290
|
});
|
|
290
291
|
}
|
|
291
292
|
|
|
@@ -314,7 +315,7 @@ async function createApp(opts) {
|
|
|
314
315
|
name: "http-server",
|
|
315
316
|
run: function () {
|
|
316
317
|
return new Promise(function (resolve) {
|
|
317
|
-
if (!server)
|
|
318
|
+
if (!server) { resolve(); return; }
|
|
318
319
|
server.close(function () {
|
|
319
320
|
server = null; // address() now returns null
|
|
320
321
|
listenPort = null;
|
|
@@ -323,7 +324,7 @@ async function createApp(opts) {
|
|
|
323
324
|
});
|
|
324
325
|
});
|
|
325
326
|
},
|
|
326
|
-
timeoutMs:
|
|
327
|
+
timeoutMs: C.TIME.seconds(10),
|
|
327
328
|
};
|
|
328
329
|
// Find the index right after the "websockets" phase if present;
|
|
329
330
|
// otherwise prepend before "cluster" (or just push to end if
|
package/lib/archive.js
CHANGED
|
@@ -36,26 +36,29 @@
|
|
|
36
36
|
var zlib = require("node:zlib");
|
|
37
37
|
var fs = require("node:fs");
|
|
38
38
|
var nodeCrypto = require("node:crypto");
|
|
39
|
+
var C = require("./constants");
|
|
39
40
|
var { defineClass } = require("./framework-error");
|
|
40
41
|
|
|
41
42
|
var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
42
43
|
|
|
43
|
-
// ZIP signatures
|
|
44
|
+
// ZIP signatures (APPNOTE 4.3 — file-format-fixed magic dwords)
|
|
44
45
|
var SIG_LFH = 0x04034b50; // local file header
|
|
45
46
|
var SIG_CFH = 0x02014b50; // central directory file header
|
|
46
47
|
var SIG_EOCD = 0x06054b50; // end of central directory
|
|
47
48
|
|
|
48
|
-
// Compression methods
|
|
49
|
-
var
|
|
50
|
-
var
|
|
49
|
+
// Compression methods (APPNOTE 4.4.5 — protocol-fixed method IDs)
|
|
50
|
+
var METHOD_STORE_ID = 0;
|
|
51
|
+
var METHOD_DEFLATE_ID = 8;
|
|
51
52
|
|
|
52
53
|
// CRC-32 — IEEE 802.3 polynomial. node:crypto has no native CRC32, so
|
|
53
54
|
// we vendor the standard table-driven implementation.
|
|
55
|
+
var CRC32_TABLE_LEN = 256; // entries (one per byte value)
|
|
56
|
+
var CRC32_BIT_ITER = 8; // bits per byte during table build
|
|
54
57
|
var CRC32_TABLE = (function () {
|
|
55
|
-
var t = new Uint32Array(
|
|
56
|
-
for (var i = 0; i <
|
|
58
|
+
var t = new Uint32Array(CRC32_TABLE_LEN);
|
|
59
|
+
for (var i = 0; i < CRC32_TABLE_LEN; i++) {
|
|
57
60
|
var c = i;
|
|
58
|
-
for (var j = 0; j <
|
|
61
|
+
for (var j = 0; j < CRC32_BIT_ITER; j++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
59
62
|
t[i] = c >>> 0;
|
|
60
63
|
}
|
|
61
64
|
return t;
|
|
@@ -107,18 +110,18 @@ function zip() {
|
|
|
107
110
|
"addFile: content must be a Buffer or string, got " + typeof content);
|
|
108
111
|
|
|
109
112
|
opts = opts || {};
|
|
110
|
-
var method = opts.method === "store" ?
|
|
113
|
+
var method = opts.method === "store" ? METHOD_STORE_ID : METHOD_DEFLATE_ID;
|
|
111
114
|
var mtime = opts.mtime instanceof Date ? opts.mtime : new Date();
|
|
112
115
|
|
|
113
116
|
var crc = _crc32(bodyBuf);
|
|
114
117
|
var stored = bodyBuf;
|
|
115
|
-
if (method ===
|
|
118
|
+
if (method === METHOD_DEFLATE_ID) {
|
|
116
119
|
stored = zlib.deflateRawSync(bodyBuf);
|
|
117
120
|
// If deflate didn't shrink it (small/already-compressed inputs),
|
|
118
121
|
// fall back to STORE to save the operator a few bytes.
|
|
119
122
|
if (stored.length >= bodyBuf.length) {
|
|
120
123
|
stored = bodyBuf;
|
|
121
|
-
method =
|
|
124
|
+
method = METHOD_STORE_ID;
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -135,42 +138,47 @@ function zip() {
|
|
|
135
138
|
function _buildLocalFileHeader(entry) {
|
|
136
139
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
137
140
|
var dt = _msdosDateTime(entry.mtime);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
hdr.
|
|
143
|
-
hdr.
|
|
144
|
-
hdr.writeUInt16LE(
|
|
145
|
-
hdr.
|
|
146
|
-
hdr.
|
|
147
|
-
hdr.
|
|
148
|
-
hdr.writeUInt16LE(
|
|
149
|
-
hdr.
|
|
141
|
+
// APPNOTE 4.3.7 — local file header. Offsets are byte positions
|
|
142
|
+
// within the 30-byte fixed header; each route through C.BYTES.bytes
|
|
143
|
+
// so the framework's byte-math discipline applies even to format-
|
|
144
|
+
// fixed offsets.
|
|
145
|
+
var hdr = Buffer.alloc(C.BYTES.bytes(30));
|
|
146
|
+
hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
|
|
147
|
+
hdr.writeUInt16LE(20, C.BYTES.bytes(4)); // version needed
|
|
148
|
+
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(6)); // flags: bit 11 = UTF-8 name
|
|
149
|
+
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
|
|
150
|
+
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
|
|
151
|
+
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(12));
|
|
152
|
+
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(14));
|
|
153
|
+
hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(18));
|
|
154
|
+
hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(22));
|
|
155
|
+
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
|
|
156
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(28)); // extra field length
|
|
150
157
|
return Buffer.concat([hdr, nameBuf]);
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
function _buildCentralDirectoryEntry(entry, lfhOffset) {
|
|
154
161
|
var nameBuf = Buffer.from(entry.name, "utf8");
|
|
155
162
|
var dt = _msdosDateTime(entry.mtime);
|
|
156
|
-
|
|
157
|
-
hdr.
|
|
158
|
-
hdr.
|
|
159
|
-
hdr.writeUInt16LE(
|
|
160
|
-
hdr.writeUInt16LE(
|
|
161
|
-
hdr.writeUInt16LE(
|
|
162
|
-
hdr.writeUInt16LE(
|
|
163
|
-
hdr.writeUInt16LE(dt.
|
|
164
|
-
hdr.
|
|
165
|
-
hdr.writeUInt32LE(entry.
|
|
166
|
-
hdr.writeUInt32LE(entry.
|
|
167
|
-
hdr.
|
|
168
|
-
hdr.writeUInt16LE(
|
|
169
|
-
hdr.writeUInt16LE(0,
|
|
170
|
-
hdr.writeUInt16LE(0,
|
|
171
|
-
hdr.writeUInt16LE(0,
|
|
172
|
-
hdr.
|
|
173
|
-
hdr.writeUInt32LE(
|
|
163
|
+
// APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
|
|
164
|
+
var hdr = Buffer.alloc(C.BYTES.bytes(46));
|
|
165
|
+
hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
|
|
166
|
+
hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
|
|
167
|
+
hdr.writeUInt16LE(20, C.BYTES.bytes(6)); // version needed
|
|
168
|
+
hdr.writeUInt16LE(0x0800, C.BYTES.bytes(0x08)); // flags: bit 11 = UTF-8
|
|
169
|
+
hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
|
|
170
|
+
hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
|
|
171
|
+
hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
|
|
172
|
+
hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(0x10));
|
|
173
|
+
hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(20));
|
|
174
|
+
hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(0x18));
|
|
175
|
+
hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(28));
|
|
176
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(30)); // extra field length
|
|
177
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(0x20)); // file comment length
|
|
178
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(34)); // disk number start
|
|
179
|
+
hdr.writeUInt16LE(0, C.BYTES.bytes(36)); // internal file attributes
|
|
180
|
+
hdr.writeUInt32LE(0, C.BYTES.bytes(38)); // external file attributes
|
|
181
|
+
hdr.writeUInt32LE(lfhOffset, C.BYTES.bytes(42));
|
|
174
182
|
return Buffer.concat([hdr, nameBuf]);
|
|
175
183
|
}
|
|
176
184
|
|
|
@@ -196,16 +204,16 @@ function zip() {
|
|
|
196
204
|
pieces.push(cdh);
|
|
197
205
|
cdSize += cdh.length;
|
|
198
206
|
}
|
|
199
|
-
//
|
|
200
|
-
var eocd = Buffer.alloc(22);
|
|
201
|
-
eocd.writeUInt32LE(SIG_EOCD, 0);
|
|
202
|
-
eocd.writeUInt16LE(0, 4); // disk number
|
|
203
|
-
eocd.writeUInt16LE(0, 6); // disk where CD starts
|
|
204
|
-
eocd.writeUInt16LE(entries.length,
|
|
205
|
-
eocd.writeUInt16LE(entries.length, 10); // total entries
|
|
206
|
-
eocd.writeUInt32LE(cdSize, 12); // size of central directory
|
|
207
|
-
eocd.writeUInt32LE(cdStart,
|
|
208
|
-
eocd.writeUInt16LE(0, 20); // comment length
|
|
207
|
+
// APPNOTE 4.3.16 — end of central directory record (22-byte fixed).
|
|
208
|
+
var eocd = Buffer.alloc(C.BYTES.bytes(22));
|
|
209
|
+
eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
|
|
210
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(4)); // disk number
|
|
211
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(6)); // disk where CD starts
|
|
212
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08)); // entries on this disk
|
|
213
|
+
eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10)); // total entries
|
|
214
|
+
eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12)); // size of central directory
|
|
215
|
+
eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10)); // offset of central directory
|
|
216
|
+
eocd.writeUInt16LE(0, C.BYTES.bytes(20)); // comment length
|
|
209
217
|
pieces.push(eocd);
|
|
210
218
|
return Buffer.concat(pieces);
|
|
211
219
|
}
|
package/lib/atomic-file.js
CHANGED
|
@@ -36,12 +36,15 @@ var path = require("path");
|
|
|
36
36
|
var { generateToken, sha3Hash } = require("./crypto");
|
|
37
37
|
var safeJson = require("./safe-json");
|
|
38
38
|
var C = require("./constants");
|
|
39
|
+
var { boot } = require("./log");
|
|
39
40
|
var safeBuffer = require("./safe-buffer");
|
|
40
41
|
var numericBounds = require("./numeric-bounds");
|
|
41
42
|
var safeAsync = require("./safe-async");
|
|
42
43
|
var retry = require("./retry");
|
|
43
44
|
var { FrameworkError } = require("./framework-error");
|
|
44
45
|
|
|
46
|
+
var log = boot("atomic-file");
|
|
47
|
+
|
|
45
48
|
var DEFAULTS = {
|
|
46
49
|
maxBytes: C.BYTES.mib(64), // 64 MiB ceiling on read
|
|
47
50
|
retryAttempts: 5,
|
|
@@ -203,7 +206,7 @@ function writeSync(filepath, data, opts) {
|
|
|
203
206
|
var dir = path.dirname(filepath);
|
|
204
207
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
205
208
|
|
|
206
|
-
var tmpPath = filepath + ".tmp-" + generateToken(8);
|
|
209
|
+
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
207
210
|
var renamed = false;
|
|
208
211
|
try {
|
|
209
212
|
var fd = fs.openSync(tmpPath, "w", opts.fileMode);
|
|
@@ -279,7 +282,7 @@ async function write(filepath, data, opts) {
|
|
|
279
282
|
return new Promise(function (resolve, reject) {
|
|
280
283
|
var dir = path.dirname(filepath);
|
|
281
284
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
282
|
-
var tmpPath = filepath + ".tmp-" + generateToken(8);
|
|
285
|
+
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
283
286
|
var renamed = false;
|
|
284
287
|
try {
|
|
285
288
|
var fd = fs.openSync(tmpPath, "w", opts.fileMode);
|
|
@@ -424,7 +427,8 @@ async function lock(filepath, fn, opts) {
|
|
|
424
427
|
try {
|
|
425
428
|
var stat = fs.statSync(lockPath);
|
|
426
429
|
if (Date.now() - stat.mtimeMs > C.TIME.minutes(5)) {
|
|
427
|
-
try { fs.unlinkSync(lockPath); }
|
|
430
|
+
try { fs.unlinkSync(lockPath); }
|
|
431
|
+
catch (uerr) { log.debug("stale-lock unlink failed", { path: lockPath, error: uerr.message }); }
|
|
428
432
|
continue;
|
|
429
433
|
}
|
|
430
434
|
} catch (_e) { /* stat raced with another process — keep waiting */ }
|
|
@@ -448,8 +452,10 @@ async function lock(filepath, fn, opts) {
|
|
|
448
452
|
try {
|
|
449
453
|
return await fn();
|
|
450
454
|
} finally {
|
|
451
|
-
try { fs.closeSync(fd); }
|
|
452
|
-
|
|
455
|
+
try { fs.closeSync(fd); }
|
|
456
|
+
catch (cerr) { log.debug("lock fd close failed", { error: cerr.message }); }
|
|
457
|
+
try { fs.unlinkSync(lockPath); }
|
|
458
|
+
catch (uerr) { log.debug("lock release unlink failed", { path: lockPath, error: uerr.message }); }
|
|
453
459
|
}
|
|
454
460
|
}
|
|
455
461
|
|