@blamejs/core 0.8.42 → 0.8.49
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/chain-writer.js
CHANGED
|
@@ -1,50 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.chainWriter
|
|
4
|
+
* @nav Observability
|
|
5
|
+
* @title Chain Writer
|
|
4
6
|
*
|
|
5
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Race-safe append to a hash-chained log table. Both `audit_log` and
|
|
9
|
+
* `consent_log` share the same row shape — take next monotonic
|
|
10
|
+
* counter, read previous row's `rowHash`, seal the logical row via
|
|
11
|
+
* field-crypto, materialize null entries for every hashable column
|
|
12
|
+
* so canonicalization sees the same key set at write-time and
|
|
13
|
+
* verify-time, compute `rowHash` over the sealed content, INSERT
|
|
14
|
+
* with `prevHash` / `rowHash` / `nonce` / `fencingToken`.
|
|
6
15
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 5. Compute rowHash over the sealed content (excluding chain bookkeeping)
|
|
14
|
-
* 6. INSERT with prevHash / rowHash / nonce / fencingToken
|
|
16
|
+
* The chain-writer extracts that pattern so every consumer gets the
|
|
17
|
+
* same race protection. Each instance owns a per-chain Mutex
|
|
18
|
+
* serializing read-prev → compute-hash → insert (without it,
|
|
19
|
+
* concurrent appends hash against the same prev-tip and fork the
|
|
20
|
+
* chain), plus a Once initializing the in-process counter from
|
|
21
|
+
* `MAX(monotonicCounter)` on first use.
|
|
15
22
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* Writes route through the cluster-storage dispatcher so the same
|
|
24
|
+
* chain definition works on single-node SQLite and on cluster-mode
|
|
25
|
+
* external Postgres. `cluster.requireLeader()` runs before the
|
|
26
|
+
* mutex; followers reject with `NotLeaderError`. Table names are
|
|
27
|
+
* restricted to the `ALLOWED_CHAIN_TABLES` allowlist so a misconfig
|
|
28
|
+
* can't point a writer at a non-chain table and corrupt the chain
|
|
29
|
+
* semantics.
|
|
23
30
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* - The hashable column list (for canonicalization)
|
|
28
|
-
* - A Mutex serializing the chain (read-prev → compute-hash → insert)
|
|
29
|
-
* - A Once initializing the in-process counter from MAX(monotonicCounter)
|
|
31
|
+
* Operators usually don't construct chain-writers directly — `b.audit`
|
|
32
|
+
* and `b.consent` each construct one at module load. Direct use is
|
|
33
|
+
* for new chain-backed tables registered in `ALLOWED_CHAIN_TABLES`.
|
|
30
34
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Public API:
|
|
35
|
-
*
|
|
36
|
-
* chainWriter.create({
|
|
37
|
-
* table: "audit_log" | "consent_log" | …,
|
|
38
|
-
* columnsForInsert: [string], order matters; INSERT uses this
|
|
39
|
-
* hashableColumns: [string], for canonicalize null-fill
|
|
40
|
-
* validateAction: function (event) optional; throws on invalid input
|
|
41
|
-
* })
|
|
42
|
-
*
|
|
43
|
-
* writer.append(logical) async; returns { rowHash, prevHash, …logical }
|
|
44
|
-
* writer._resetForTest() re-initializes counter + mutex
|
|
45
|
-
*
|
|
46
|
-
* Operators usually don't construct chain-writers directly; audit and
|
|
47
|
-
* consent each construct one at module load.
|
|
35
|
+
* @card
|
|
36
|
+
* Race-safe append to a hash-chained log table.
|
|
48
37
|
*/
|
|
49
38
|
|
|
50
39
|
var { generateToken, generateBytes } = require("./crypto");
|
|
@@ -74,6 +63,42 @@ class ChainWriterError extends FrameworkError {
|
|
|
74
63
|
}
|
|
75
64
|
}
|
|
76
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @primitive b.chainWriter.create
|
|
68
|
+
* @signature b.chainWriter.create(opts)
|
|
69
|
+
* @since 0.8.48
|
|
70
|
+
* @status stable
|
|
71
|
+
* @related b.audit, b.consent, b.auditChain
|
|
72
|
+
*
|
|
73
|
+
* Build a chain-writer bound to a single hash-chained table. Returns
|
|
74
|
+
* `{ table, append, _resetForTest, _getMutexForTest }`. `append(logical)`
|
|
75
|
+
* is the public surface — async, leader-gated, mutex-serialized; on
|
|
76
|
+
* success it returns the logical row decorated with the computed
|
|
77
|
+
* `rowHash` and `prevHash`.
|
|
78
|
+
*
|
|
79
|
+
* @opts
|
|
80
|
+
* table: string, // one of ALLOWED_CHAIN_TABLES (audit_log | consent_log)
|
|
81
|
+
* columnsForInsert: string[], // INSERT column order (every name is identifier-validated)
|
|
82
|
+
* hashableColumns: string[], // columns that participate in the rowHash canonicalization
|
|
83
|
+
* validateInput: Function, // optional; (logical) → throws on invalid shape
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* var writer = b.chainWriter.create({
|
|
87
|
+
* table: "audit_log",
|
|
88
|
+
* columnsForInsert: ["_id", "monotonicCounter", "recordedAt",
|
|
89
|
+
* "action", "outcome",
|
|
90
|
+
* "prevHash", "rowHash", "nonce", "fencingToken"],
|
|
91
|
+
* hashableColumns: ["_id", "monotonicCounter", "recordedAt",
|
|
92
|
+
* "action", "outcome"],
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* var row = await writer.append({
|
|
96
|
+
* action: "user.login",
|
|
97
|
+
* outcome: "success",
|
|
98
|
+
* });
|
|
99
|
+
* row.rowHash; // → "<hex sha3-512 digest>"
|
|
100
|
+
* row.prevHash; // → "<previous tip rowHash, or zero-hash on first row>"
|
|
101
|
+
*/
|
|
77
102
|
function create(opts) {
|
|
78
103
|
if (!opts || !opts.table || !Array.isArray(opts.columnsForInsert) ||
|
|
79
104
|
!Array.isArray(opts.hashableColumns)) {
|
package/lib/circuit-breaker.js
CHANGED
|
@@ -1,46 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.circuitBreaker
|
|
3
|
+
* @module b.circuitBreaker
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title Circuit Breaker
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Top-level circuit-breaker primitive. Re-exports the CircuitBreaker
|
|
9
|
+
* class otherwise reachable as `b.retry.CircuitBreaker`, plus a
|
|
10
|
+
* `create(opts)` factory matching every other framework primitive's
|
|
11
|
+
* `create()` shape. The implementation lives in `lib/retry.js` so the
|
|
12
|
+
* retry classifier and the breaker share the `isRetryable` /
|
|
13
|
+
* observability emit conventions; this module is the operator-facing
|
|
14
|
+
* surface so callers don't have to know retry is the breaker's home.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* State machine: `closed` (normal flow; failures count up to
|
|
17
|
+
* `failureThreshold`), `open` (every call fast-fails for `cooldownMs`),
|
|
18
|
+
* `half` (first probe closes the breaker on success or re-opens it
|
|
19
|
+
* on failure). Intended for per-target use — one instance per
|
|
20
|
+
* upstream service. Sharing a breaker across unrelated targets
|
|
21
|
+
* defeats the failure-threshold semantic.
|
|
18
22
|
*
|
|
23
|
+
* @card
|
|
24
|
+
* Top-level circuit-breaker primitive.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
var retry = require("./retry");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @primitive b.circuitBreaker.create
|
|
31
|
+
* @signature b.circuitBreaker.create(opts)
|
|
32
|
+
* @since 0.8.48
|
|
33
|
+
* @status stable
|
|
34
|
+
* @related b.retry, b.httpClient
|
|
35
|
+
*
|
|
36
|
+
* Build a circuit-breaker. Returns a CircuitBreaker instance with
|
|
37
|
+
* `wrap(fn)` (executes `fn` if the breaker is closed; throws RetryError
|
|
38
|
+
* with `code: "retry/circuit-open"` when open), `state()`, `reset()`,
|
|
39
|
+
* and `onStateChange(handler)` listener registration. Pass-through
|
|
40
|
+
* factory: identical instance shape to `b.retry.CircuitBreaker`, with
|
|
41
|
+
* the framework's `create(opts)` vocabulary.
|
|
42
|
+
*
|
|
43
|
+
* @opts
|
|
44
|
+
* name: string, // identifier used in audit + state-change events
|
|
45
|
+
* failureThreshold: number, // failures in the closed state before opening
|
|
46
|
+
* cooldownMs: number, // milliseconds the breaker stays open before probing
|
|
47
|
+
* successThreshold: number, // probe successes required to close from half-open
|
|
48
|
+
* audit: Object, // optional b.audit instance for state-change emission
|
|
49
|
+
* onStateChange: Function, // ({ name, from, to, at }) → void
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
19
52
|
* var cb = b.circuitBreaker.create({
|
|
20
|
-
* name:
|
|
21
|
-
* failureThreshold:
|
|
22
|
-
* cooldownMs:
|
|
23
|
-
* successThreshold:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* // event = { name, from, to, at }
|
|
27
|
-
* log("breaker " + event.name + " " + event.from + " -> " + event.to);
|
|
53
|
+
* name: "upstream-billing",
|
|
54
|
+
* failureThreshold: 5,
|
|
55
|
+
* cooldownMs: 30000,
|
|
56
|
+
* successThreshold: 2,
|
|
57
|
+
* onStateChange: function (e) {
|
|
58
|
+
* // e = { name, from: "closed", to: "open", at: <ms> }
|
|
28
59
|
* },
|
|
29
60
|
* });
|
|
30
|
-
* await cb.wrap(function () { return upstream.callRiskyOp(); });
|
|
31
61
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
62
|
+
* var result = await cb.wrap(async function () {
|
|
63
|
+
* return { ok: true, value: 42 };
|
|
64
|
+
* });
|
|
65
|
+
* result.value; // → 42
|
|
35
66
|
*/
|
|
36
|
-
|
|
37
|
-
var retry = require("./retry");
|
|
38
|
-
|
|
39
|
-
// Pass-through factory — operators get the same instance shape as
|
|
40
|
-
// b.retry.CircuitBreaker but with the framework's `create(opts)`
|
|
41
|
-
// vocabulary. The breaker class is unchanged; this is a thin
|
|
42
|
-
// surface re-export so b.circuitBreaker is operator-discoverable
|
|
43
|
-
// alongside b.retry.
|
|
44
67
|
function create(opts) {
|
|
45
68
|
return new retry.CircuitBreaker(opts || {});
|
|
46
69
|
}
|
package/lib/cli-helpers.js
CHANGED
|
@@ -1,50 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* @module b.cliHelpers
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title CLI Helpers
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Shared shape for blamejs CLI subcommands AND for operators
|
|
9
|
+
* writing their own one-shot CLI scripts on top of the framework.
|
|
10
|
+
* Three patterns recur across every CLI command (`migrate`, `seed`,
|
|
11
|
+
* `audit`, `vault`, `backup`, `api-key`): bootstrap a headless
|
|
12
|
+
* `b.createApp` instance from `--data-dir` and shut it down cleanly;
|
|
13
|
+
* report success / error / usage with a consistent
|
|
14
|
+
* `blamejs <verb> <sub>: <message>` prefix on stderr plus canonical
|
|
15
|
+
* exit codes (0 ok, 1 runtime failure, 2 arg error); resolve a
|
|
16
|
+
* passphrase from a `--<flag>` or env var into the Buffer shape
|
|
17
|
+
* the underlying crypto primitives accept.
|
|
9
18
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* exit codes (0 ok, 1 runtime failure, 2 arg error).
|
|
15
|
-
* 3. Resolve a passphrase from `--<flag>` or an env var, encode to
|
|
16
|
-
* the Buffer the underlying crypto primitive needs.
|
|
19
|
+
* The reporter writes through whichever stream `ctx.stdout` /
|
|
20
|
+
* `ctx.stderr` point at — `process.stdout` / `process.stderr` in
|
|
21
|
+
* production, captured stream stubs in tests — so the same handler
|
|
22
|
+
* is testable without spawning a child process.
|
|
17
23
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* var report = cli.makeReporter(ctx, "blamejs my-tool issue");
|
|
21
|
-
* if (!args.flags["data-dir"]) return report.usage(MY_USAGE);
|
|
22
|
-
*
|
|
23
|
-
* var pp = cli.resolvePassphrase(args, ctx, {
|
|
24
|
-
* flag: "passphrase",
|
|
25
|
-
* envVar: "MY_TOOL_PASSPHRASE",
|
|
26
|
-
* });
|
|
27
|
-
* if (!pp) return report.error("--passphrase or MY_TOOL_PASSPHRASE is required", 2);
|
|
28
|
-
*
|
|
29
|
-
* var booted;
|
|
30
|
-
* try {
|
|
31
|
-
* booted = await cli.bootApp({
|
|
32
|
-
* dataDir: args.flags["data-dir"],
|
|
33
|
-
* vaultMode: "wrapped",
|
|
34
|
-
* env: ctx.env,
|
|
35
|
-
* });
|
|
36
|
-
* // do the op against booted.b.X / booted.app.db / etc.
|
|
37
|
-
* return report.ok("done");
|
|
38
|
-
* } catch (e) {
|
|
39
|
-
* return report.error(e.message);
|
|
40
|
-
* } finally {
|
|
41
|
-
* if (booted) await booted.app.shutdown();
|
|
42
|
-
* }
|
|
43
|
-
*
|
|
44
|
-
* The reporter writes through whichever stream `ctx.stdout` / `ctx.stderr`
|
|
45
|
-
* point at — `process.stdout` / `process.stderr` in production, captured
|
|
46
|
-
* stream stubs in tests — so the same handler is testable without
|
|
47
|
-
* spawning a child process.
|
|
24
|
+
* @card
|
|
25
|
+
* Shared shape for blamejs CLI subcommands AND for operators writing their own one-shot CLI scripts on top of the framework.
|
|
48
26
|
*/
|
|
49
27
|
|
|
50
28
|
var lazyRequire = require("./lazy-require");
|
|
@@ -66,15 +44,26 @@ function _writeLine(stream, line) {
|
|
|
66
44
|
}
|
|
67
45
|
|
|
68
46
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
47
|
+
* @primitive b.cliHelpers.makeReporter
|
|
48
|
+
* @signature b.cliHelpers.makeReporter(ctx, prefix)
|
|
49
|
+
* @since 0.8.0
|
|
50
|
+
* @status stable
|
|
51
|
+
* @related b.cliHelpers.resolvePassphrase, b.cliHelpers.bootApp
|
|
71
52
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
53
|
+
* Build a reporter bound to a CLI context (`ctx.stdout` /
|
|
54
|
+
* `ctx.stderr`) and a verb prefix. Every stderr message produced by
|
|
55
|
+
* `.error` / `.usage` gets the prefix. Methods return canonical
|
|
56
|
+
* Unix exit codes — `0` for `ok` / `helpStdout`, `1` for `error`
|
|
57
|
+
* (override via second arg), `2` for `usage` (argument-error
|
|
58
|
+
* convention).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* var ctx = { stdout: process.stdout, stderr: process.stderr };
|
|
62
|
+
* var report = b.cliHelpers.makeReporter(ctx, "blamejs vault seal");
|
|
63
|
+
* var ok = report.ok("sealed: /data/vault"); // → 0
|
|
64
|
+
* var fail = report.error("decrypt failed"); // → 1
|
|
65
|
+
* var arg = report.error("missing --data-dir", 2); // → 2
|
|
66
|
+
* var help = report.usage("Usage: blamejs vault ..."); // → 2
|
|
78
67
|
*/
|
|
79
68
|
function makeReporter(ctx, prefix) {
|
|
80
69
|
if (!ctx || typeof ctx !== "object") {
|
|
@@ -111,15 +100,35 @@ function makeReporter(ctx, prefix) {
|
|
|
111
100
|
// ---- Passphrase resolution -----------------------------------------------
|
|
112
101
|
|
|
113
102
|
/**
|
|
114
|
-
*
|
|
115
|
-
* (
|
|
103
|
+
* @primitive b.cliHelpers.resolvePassphrase
|
|
104
|
+
* @signature b.cliHelpers.resolvePassphrase(args, ctx, opts)
|
|
105
|
+
* @since 0.8.0
|
|
106
|
+
* @status stable
|
|
107
|
+
* @related b.cliHelpers.makeReporter, b.cliHelpers.bootApp
|
|
108
|
+
*
|
|
109
|
+
* Resolve a passphrase from a CLI flag (preferred) or an env var
|
|
110
|
+
* (fallback) into a UTF-8 Buffer — the shape vault / crypto
|
|
111
|
+
* primitives accept. Returns `null` when neither source produced a
|
|
112
|
+
* non-empty string; the caller decides whether absence is a hard
|
|
113
|
+
* error (vault seal) or a soft default (plaintext-mode dev data dir).
|
|
116
114
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
115
|
+
* @opts
|
|
116
|
+
* flag: string (CLI flag name, e.g. "passphrase" reads args.flags.passphrase),
|
|
117
|
+
* envVar: string (env var fallback, e.g. "BLAMEJS_VAULT_PASSPHRASE"),
|
|
119
118
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
119
|
+
* @example
|
|
120
|
+
* var args = { flags: { passphrase: "hunter2" } };
|
|
121
|
+
* var ctx = { env: { BLAMEJS_VAULT_PASSPHRASE: "envval" } };
|
|
122
|
+
* var pp = b.cliHelpers.resolvePassphrase(args, ctx, {
|
|
123
|
+
* flag: "passphrase",
|
|
124
|
+
* envVar: "BLAMEJS_VAULT_PASSPHRASE",
|
|
125
|
+
* });
|
|
126
|
+
* pp.toString("utf8"); // → "hunter2" (flag wins over env)
|
|
127
|
+
*
|
|
128
|
+
* var none = b.cliHelpers.resolvePassphrase({ flags: {} }, { env: {} }, {
|
|
129
|
+
* flag: "passphrase", envVar: "BLAMEJS_VAULT_PASSPHRASE",
|
|
130
|
+
* });
|
|
131
|
+
* none; // → null
|
|
123
132
|
*/
|
|
124
133
|
function resolvePassphrase(args, ctx, opts) {
|
|
125
134
|
opts = opts || {};
|
|
@@ -143,26 +152,48 @@ function resolvePassphrase(args, ctx, opts) {
|
|
|
143
152
|
// ---- Headless app bootstrap ----------------------------------------------
|
|
144
153
|
|
|
145
154
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
155
|
+
* @primitive b.cliHelpers.bootApp
|
|
156
|
+
* @signature b.cliHelpers.bootApp(opts)
|
|
157
|
+
* @since 0.8.0
|
|
158
|
+
* @status stable
|
|
159
|
+
* @related b.cliHelpers.makeReporter, b.cliHelpers.resolvePassphrase
|
|
150
160
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
161
|
+
* Boot a headless `b.createApp` instance from a data dir so a CLI
|
|
162
|
+
* script (framework subcommand or operator-written tool) can operate
|
|
163
|
+
* against the same vault + DB + audit chain the live app uses, with
|
|
164
|
+
* no HTTP listener attached. Returns `{ b, app }` where `b` is the
|
|
165
|
+
* framework module and `app` is the headless instance — caller MUST
|
|
166
|
+
* `await booted.app.shutdown()` in a `finally` so SQLite file handles
|
|
167
|
+
* and the cluster lease release.
|
|
168
|
+
*
|
|
169
|
+
* The default DB at-rest mode is `plain` because CLI runs are
|
|
170
|
+
* short-lived ops that never serve requests; encrypted-at-rest needs
|
|
171
|
+
* a tmpfs handle that wouldn't survive CLI exit anyway. Operators
|
|
172
|
+
* running against a production data dir whose DB is encrypted-at-rest
|
|
173
|
+
* pass `dbAtRest: "encrypted"` and ensure `BLAMEJS_TMPDIR` is set.
|
|
158
174
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
175
|
+
* @opts
|
|
176
|
+
* dataDir: string (filesystem path to the data dir; required),
|
|
177
|
+
* vaultMode: "wrapped" | "plaintext" (default "wrapped" — wrapped
|
|
178
|
+
* reads BLAMEJS_VAULT_PASSPHRASE from `opts.env`),
|
|
179
|
+
* dbAtRest: "plain" | "encrypted" (default "plain"),
|
|
180
|
+
* env: object (env-var bag; default process.env),
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* async function run() {
|
|
184
|
+
* var booted;
|
|
185
|
+
* try {
|
|
186
|
+
* booted = await b.cliHelpers.bootApp({
|
|
187
|
+
* dataDir: "./data",
|
|
188
|
+
* vaultMode: "plaintext",
|
|
189
|
+
* env: process.env,
|
|
190
|
+
* });
|
|
191
|
+
* var rows = await booted.app.db.all("SELECT count(*) AS n FROM _blamejs_audit_log");
|
|
192
|
+
* return rows[0].n;
|
|
193
|
+
* } finally {
|
|
194
|
+
* if (booted) await booted.app.shutdown();
|
|
195
|
+
* }
|
|
196
|
+
* }
|
|
166
197
|
*/
|
|
167
198
|
async function bootApp(opts) {
|
|
168
199
|
opts = opts || {};
|
package/lib/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ var fs = require("node:fs");
|
|
|
38
38
|
var os = require("node:os");
|
|
39
39
|
var path = require("path");
|
|
40
40
|
var apiSnapshot = require("./api-snapshot");
|
|
41
|
+
var argParser = require("./arg-parser");
|
|
41
42
|
var auditChain = require("./audit-chain");
|
|
42
43
|
var auditTools = require("./audit-tools");
|
|
43
44
|
var backup = require("./backup");
|
|
@@ -75,37 +76,12 @@ function _writeLine(stream, line) {
|
|
|
75
76
|
|
|
76
77
|
// Minimal argv parser: positional args + flag map. Supports both
|
|
77
78
|
// `--flag value` and `--flag=value`. Single-dash forms (-v) treated
|
|
78
|
-
// as long aliases to keep the surface predictable.
|
|
79
|
+
// as long aliases to keep the surface predictable. Routed through the
|
|
80
|
+
// reusable b.argParser.parseRaw primitive — every subcommand's hand-
|
|
81
|
+
// written flag validation continues to read the same { pos, flags }
|
|
82
|
+
// shape the cli has always exposed.
|
|
79
83
|
function _parseArgs(argv) {
|
|
80
|
-
|
|
81
|
-
var flags = {};
|
|
82
|
-
for (var i = 0; i < argv.length; i++) {
|
|
83
|
-
var tok = argv[i];
|
|
84
|
-
if (tok === "--") {
|
|
85
|
-
for (var j = i + 1; j < argv.length; j++) pos.push(argv[j]);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
if (tok.indexOf("--") === 0) {
|
|
89
|
-
var name = tok.slice(2);
|
|
90
|
-
var eq = name.indexOf("=");
|
|
91
|
-
var val;
|
|
92
|
-
if (eq !== -1) {
|
|
93
|
-
val = name.slice(eq + 1);
|
|
94
|
-
name = name.slice(0, eq);
|
|
95
|
-
} else if (i + 1 < argv.length && argv[i + 1].indexOf("--") !== 0) {
|
|
96
|
-
val = argv[++i];
|
|
97
|
-
} else {
|
|
98
|
-
val = true; // boolean flag
|
|
99
|
-
}
|
|
100
|
-
flags[name] = val;
|
|
101
|
-
} else if (tok.indexOf("-") === 0 && tok.length === 2) {
|
|
102
|
-
// -v, -h
|
|
103
|
-
flags[tok.slice(1)] = true;
|
|
104
|
-
} else {
|
|
105
|
-
pos.push(tok);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { pos: pos, flags: flags };
|
|
84
|
+
return argParser.parseRaw(argv);
|
|
109
85
|
}
|
|
110
86
|
|
|
111
87
|
function _resolvePath(p, cwd) {
|
package/lib/cloud-events.js
CHANGED
|
@@ -1,41 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.cloudEvents
|
|
4
|
+
* @nav Communication
|
|
5
|
+
* @title CloudEvents
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* CNCF
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Produce and consume webhook / pubsub / queue payloads in the
|
|
9
|
+
* framework-neutral CNCF CloudEvents v1.0 schema
|
|
10
|
+
* (cloudevents.io/spec/v1.0). The spec is adopted by AWS
|
|
11
|
+
* EventBridge, Azure Event Grid, Google Eventarc, Knative, Datadog,
|
|
12
|
+
* and the wider CNCF ecosystem — wrapping outbound events at
|
|
13
|
+
* `b.webhook` / `b.pubsub` / `b.queue` boundaries lets operators
|
|
14
|
+
* interop with these consumers without each consumer learning a
|
|
15
|
+
* bespoke shape.
|
|
10
16
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* // time: "2026-05-06T...",
|
|
23
|
-
* // subject: "order/o-1234",
|
|
24
|
-
* // datacontenttype: "application/json",
|
|
25
|
-
* // data: { id: "o-1234", total: 4250 },
|
|
26
|
-
* // }
|
|
27
|
-
*
|
|
28
|
-
* var ce = b.cloudEvents.parse(envelope); // throws on shape violation
|
|
29
|
-
*
|
|
30
|
-
* Spec compliance — REQUIRED attributes (CloudEvents §3.1):
|
|
31
|
-
* id, source, specversion, type
|
|
17
|
+
* `wrap` produces a structured-mode envelope from operator-supplied
|
|
18
|
+
* `source` / `type` / `subject` / `data` (and optional `extensions`),
|
|
19
|
+
* auto-filling `id` (UUID v4) and `time` (ISO 8601). Buffer payloads
|
|
20
|
+
* are routed to the `data_base64` field with a default
|
|
21
|
+
* `application/octet-stream` content-type; non-Buffer payloads land
|
|
22
|
+
* in `data` with `application/json`. `parse` validates a received
|
|
23
|
+
* envelope against the §3.1 required-attribute set, refuses unknown
|
|
24
|
+
* `specversion` values and the illegal `data` + `data_base64`
|
|
25
|
+
* simultaneous form, decodes base64-mode payloads back to a Buffer,
|
|
26
|
+
* and surfaces operator-defined extension attributes separately so
|
|
27
|
+
* consumers can route on them without grepping the envelope.
|
|
32
28
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
29
|
+
* Extension-attribute names follow the §3.1 naming rules
|
|
30
|
+
* (lowercase ASCII alnum, 1..20 chars). Names that collide with a
|
|
31
|
+
* spec attribute are refused.
|
|
35
32
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* + digits, length 1–20).
|
|
33
|
+
* @card
|
|
34
|
+
* Produce and consume webhook / pubsub / queue payloads in the framework-neutral CNCF CloudEvents v1.0 schema (cloudevents.io/spec/v1.0).
|
|
39
35
|
*/
|
|
40
36
|
|
|
41
37
|
var nodeCrypto = require("crypto");
|
|
@@ -74,6 +70,47 @@ function _genId() {
|
|
|
74
70
|
|
|
75
71
|
// ---- wrap ----
|
|
76
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @primitive b.cloudEvents.wrap
|
|
75
|
+
* @signature b.cloudEvents.wrap(opts)
|
|
76
|
+
* @since 0.7.45
|
|
77
|
+
* @status stable
|
|
78
|
+
* @related b.cloudEvents.parse, b.webhook.signer, b.pubsub.create
|
|
79
|
+
*
|
|
80
|
+
* Produces a CloudEvents v1.0 structured-mode envelope from
|
|
81
|
+
* `opts.source` + `opts.type` (the only required inputs). `id` is
|
|
82
|
+
* auto-filled with a UUID v4 when absent; `time` is auto-filled with
|
|
83
|
+
* the current ISO 8601 timestamp. Buffer `data` is base64-encoded
|
|
84
|
+
* into `data_base64` with `application/octet-stream`; non-Buffer
|
|
85
|
+
* `data` lands in the `data` attribute with `application/json`.
|
|
86
|
+
* Extension keys must match `[a-z0-9]{1,20}` and must not collide
|
|
87
|
+
* with a spec attribute — both refusals throw `CloudEventsError` at
|
|
88
|
+
* config time.
|
|
89
|
+
*
|
|
90
|
+
* @opts
|
|
91
|
+
* {
|
|
92
|
+
* source: string, // required; URI-reference per §3.1
|
|
93
|
+
* type: string, // required; reverse-DNS recommended
|
|
94
|
+
* id?: string, // default UUID v4
|
|
95
|
+
* time?: string, // default new Date().toISOString()
|
|
96
|
+
* subject?: string,
|
|
97
|
+
* datacontenttype?: string, // auto: application/json | application/octet-stream
|
|
98
|
+
* dataschema?: string, // URI of payload schema
|
|
99
|
+
* data?: object|Buffer, // Buffer routes to data_base64
|
|
100
|
+
* extensions?: object // keys [a-z0-9]{1,20}, no spec collisions
|
|
101
|
+
* }
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* var b = require("blamejs").create();
|
|
105
|
+
* var ce = b.cloudEvents.wrap({
|
|
106
|
+
* source: "/services/orders",
|
|
107
|
+
* type: "com.example.order.created",
|
|
108
|
+
* subject: "order/o-1234",
|
|
109
|
+
* data: { id: "o-1234", total: 4250 }
|
|
110
|
+
* });
|
|
111
|
+
* ce.specversion;
|
|
112
|
+
* // → "1.0"
|
|
113
|
+
*/
|
|
77
114
|
function wrap(opts) {
|
|
78
115
|
validateOpts.requireObject(opts, "cloudEvents.wrap", CloudEventsError);
|
|
79
116
|
validateOpts.requireNonEmptyString(opts.source,
|
|
@@ -140,6 +177,36 @@ function wrap(opts) {
|
|
|
140
177
|
|
|
141
178
|
// ---- parse ----
|
|
142
179
|
|
|
180
|
+
/**
|
|
181
|
+
* @primitive b.cloudEvents.parse
|
|
182
|
+
* @signature b.cloudEvents.parse(envelope)
|
|
183
|
+
* @since 0.7.45
|
|
184
|
+
* @status stable
|
|
185
|
+
* @related b.cloudEvents.wrap, b.webhook.verifier
|
|
186
|
+
*
|
|
187
|
+
* Validates a received CloudEvents v1.0 envelope and returns a
|
|
188
|
+
* normalized record `{ specversion, id, source, type, time, subject,
|
|
189
|
+
* datacontenttype, dataschema, data, extensions }`. Throws
|
|
190
|
+
* `CloudEventsError` for missing required attributes (§3.1),
|
|
191
|
+
* unsupported `specversion`, the illegal simultaneous `data` +
|
|
192
|
+
* `data_base64` form (§3.1.1), and base64-decoding failures.
|
|
193
|
+
* Buffer-mode payloads (`data_base64`) are decoded back to a
|
|
194
|
+
* `Buffer`; operator-defined extension attributes are surfaced under
|
|
195
|
+
* `.extensions` so routing can branch on them without scanning the
|
|
196
|
+
* envelope.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* var b = require("blamejs").create();
|
|
200
|
+
* var record = b.cloudEvents.parse({
|
|
201
|
+
* specversion: "1.0",
|
|
202
|
+
* id: "evt-1",
|
|
203
|
+
* source: "/services/orders",
|
|
204
|
+
* type: "com.example.order.created",
|
|
205
|
+
* data: { id: "o-1234", total: 4250 }
|
|
206
|
+
* });
|
|
207
|
+
* record.type;
|
|
208
|
+
* // → "com.example.order.created"
|
|
209
|
+
*/
|
|
143
210
|
function parse(envelope) {
|
|
144
211
|
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
145
212
|
throw new CloudEventsError("cloud-events/bad-envelope",
|