@blamejs/core 0.8.43 → 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 +92 -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/budr.js
CHANGED
|
@@ -1,38 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.budr
|
|
3
|
+
* @module b.budr
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title BC/DR
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Recovery
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Backup / Disaster-Recovery RTO/RPO declaration primitive for
|
|
9
|
+
* regulated workloads (HIPAA / DORA / ISO 22301:2019 / NIST
|
|
10
|
+
* SP 800-34). Operators declare their Recovery Time Objective (max
|
|
11
|
+
* acceptable downtime) and Recovery Point Objective (max acceptable
|
|
12
|
+
* data loss) per service; the framework captures the targets in a
|
|
13
|
+
* tamper-evident audit row and exposes them via `list()` / `get()`
|
|
14
|
+
* for dashboard / regulator-export use.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* What it
|
|
15
|
-
*
|
|
16
|
+
* The framework cannot enforce end-to-end RTO/RPO — those depend on
|
|
17
|
+
* downstream backup cadence, replication topology, and restore
|
|
18
|
+
* testing the operator owns. What it does enforce is the shape of
|
|
19
|
+
* the declaration (typed targets, BCDR tier vocabulary, regulator
|
|
20
|
+
* citation list) so the auditor-facing record stays consistent
|
|
21
|
+
* across services.
|
|
16
22
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* b.budr.declare(opts) -> declaration
|
|
20
|
-
* opts:
|
|
21
|
-
* service: operator-named service identifier (string).
|
|
22
|
-
* rtoMs: Recovery Time Objective in milliseconds.
|
|
23
|
-
* rpoMs: Recovery Point Objective in milliseconds.
|
|
24
|
-
* tier: "platinum" / "gold" / "silver" / "bronze"
|
|
25
|
-
* (BCDR criticality classification — platinum
|
|
26
|
-
* most-critical).
|
|
27
|
-
* criticality: "critical" / "high" / "medium" / "low".
|
|
28
|
-
* owner: operator-named accountable owner (team / role).
|
|
29
|
-
* reviewedAt: timestamp of the most recent operator review.
|
|
30
|
-
* citations: array of regulatory citations (e.g. ["dora-art-11", "iso-22301:2019"]).
|
|
31
|
-
* audit: bool, default true.
|
|
32
|
-
*
|
|
33
|
-
* b.budr.list() -> Array<declaration>
|
|
34
|
-
*
|
|
35
|
-
* b.budr.get(service) -> declaration | null
|
|
23
|
+
* @card
|
|
24
|
+
* Backup / Disaster-Recovery RTO/RPO declaration primitive for regulated workloads (HIPAA / DORA / ISO 22301:2019 / NIST SP 800-34).
|
|
36
25
|
*/
|
|
37
26
|
|
|
38
27
|
var nb = require("./numeric-bounds");
|
|
@@ -48,6 +37,45 @@ var CRITICALITIES = ["critical", "high", "medium", "low"];
|
|
|
48
37
|
|
|
49
38
|
var declarations = new Map();
|
|
50
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @primitive b.budr.declare
|
|
42
|
+
* @signature b.budr.declare(opts)
|
|
43
|
+
* @since 0.8.0
|
|
44
|
+
* @status stable
|
|
45
|
+
* @compliance hipaa, dora, soc2
|
|
46
|
+
* @related b.budr.get, b.budr.list, b.dora.create
|
|
47
|
+
*
|
|
48
|
+
* Register a service's Recovery Time Objective + Recovery Point
|
|
49
|
+
* Objective targets. Each call replaces the previous declaration for
|
|
50
|
+
* the same `service` and emits a `budr.declared` audit row carrying
|
|
51
|
+
* the typed targets, BCDR tier, criticality, owner, and regulator
|
|
52
|
+
* citations. Throws `BudrError` on bad opts (unknown tier, missing
|
|
53
|
+
* targets, citation list not an array).
|
|
54
|
+
*
|
|
55
|
+
* @opts
|
|
56
|
+
* service: string (1..128 chars, [A-Za-z0-9._:/-]),
|
|
57
|
+
* rtoMs: number (positive finite integer milliseconds),
|
|
58
|
+
* rpoMs: number (positive finite integer milliseconds),
|
|
59
|
+
* tier: "platinum" | "gold" | "silver" | "bronze",
|
|
60
|
+
* criticality: "critical" | "high" | "medium" | "low",
|
|
61
|
+
* owner: string (team / role accountable for restore),
|
|
62
|
+
* reviewedAt: number (ms-since-epoch of last operator review),
|
|
63
|
+
* citations: Array<string> (e.g. ["dora-art-11", "iso-22301:2019"]),
|
|
64
|
+
* audit: boolean (default true; set false to skip audit emit),
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* var dec = b.budr.declare({
|
|
68
|
+
* service: "payments-gateway",
|
|
69
|
+
* rtoMs: 60 * 60 * 1000,
|
|
70
|
+
* rpoMs: 5 * 60 * 1000,
|
|
71
|
+
* tier: "platinum",
|
|
72
|
+
* criticality: "critical",
|
|
73
|
+
* owner: "team-payments",
|
|
74
|
+
* citations: ["dora-art-11", "iso-22301:2019"],
|
|
75
|
+
* });
|
|
76
|
+
* dec.tier; // → "platinum"
|
|
77
|
+
* dec.rtoMs; // → 3600000
|
|
78
|
+
*/
|
|
51
79
|
function declare(opts) {
|
|
52
80
|
if (!opts || typeof opts !== "object") {
|
|
53
81
|
throw BudrError.factory("BAD_OPTS", "budr.declare: opts required");
|
|
@@ -109,12 +137,57 @@ function declare(opts) {
|
|
|
109
137
|
return declaration;
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
/**
|
|
141
|
+
* @primitive b.budr.get
|
|
142
|
+
* @signature b.budr.get(service)
|
|
143
|
+
* @since 0.8.0
|
|
144
|
+
* @status stable
|
|
145
|
+
* @related b.budr.declare, b.budr.list
|
|
146
|
+
*
|
|
147
|
+
* Look up the registered declaration for `service`. Returns the
|
|
148
|
+
* frozen declaration object on hit, `null` on miss or non-string
|
|
149
|
+
* input. Cheap — purely an in-memory Map lookup.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* b.budr.declare({
|
|
153
|
+
* service: "core-ledger", rtoMs: 1800000, rpoMs: 60000,
|
|
154
|
+
* tier: "platinum", criticality: "critical",
|
|
155
|
+
* });
|
|
156
|
+
* var dec = b.budr.get("core-ledger");
|
|
157
|
+
* dec.rpoMs; // → 60000
|
|
158
|
+
* b.budr.get("not-registered"); // → null
|
|
159
|
+
*/
|
|
112
160
|
function get(service) {
|
|
113
161
|
if (typeof service !== "string") return null;
|
|
114
162
|
var rec = declarations.get(service);
|
|
115
163
|
return rec === undefined ? null : rec;
|
|
116
164
|
}
|
|
117
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @primitive b.budr.list
|
|
168
|
+
* @signature b.budr.list()
|
|
169
|
+
* @since 0.8.0
|
|
170
|
+
* @status stable
|
|
171
|
+
* @related b.budr.declare, b.budr.get
|
|
172
|
+
*
|
|
173
|
+
* Snapshot of every registered declaration in insertion order.
|
|
174
|
+
* Returns a fresh array — mutating it does not affect the registry.
|
|
175
|
+
* Use this to drive a dashboard table or to export the operator's
|
|
176
|
+
* BCDR posture for an auditor.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* b.budr.declare({
|
|
180
|
+
* service: "payments-gateway", rtoMs: 3600000, rpoMs: 300000,
|
|
181
|
+
* tier: "platinum", criticality: "critical",
|
|
182
|
+
* });
|
|
183
|
+
* b.budr.declare({
|
|
184
|
+
* service: "reporting", rtoMs: 14400000, rpoMs: 3600000,
|
|
185
|
+
* tier: "silver", criticality: "medium",
|
|
186
|
+
* });
|
|
187
|
+
* var all = b.budr.list();
|
|
188
|
+
* all.length; // → 2
|
|
189
|
+
* all[0].service; // → "payments-gateway"
|
|
190
|
+
*/
|
|
118
191
|
function list() {
|
|
119
192
|
return Array.from(declarations.values());
|
|
120
193
|
}
|
package/lib/bundler.js
CHANGED
|
@@ -1,80 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* bundler
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* @module b.bundler
|
|
4
|
+
* @nav Tools
|
|
5
|
+
* @title Bundler
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Optionally watches entries and rebuilds on change
|
|
7
|
+
* @intro
|
|
8
|
+
* Client-side asset bundler — produces content-hashed
|
|
9
|
+
* `dist/<name>.<hash>.<ext>` files plus a `manifest.json` mapping
|
|
10
|
+
* logical name to hashed filename. Designed to drop into a static
|
|
11
|
+
* server (`b.static`) so cache-busting lives at the filename layer
|
|
12
|
+
* and HTML can long-cache hashed paths.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
14
|
+
* No-build-step fallback: the default `engine.passthrough` reads
|
|
15
|
+
* each entry from disk verbatim, hashes it, and writes the hashed
|
|
16
|
+
* copy. Operators with no module-graph need ship their source files
|
|
17
|
+
* directly through the bundler and skip the toolchain entirely.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `engineInstance` implements:
|
|
24
|
-
*
|
|
25
|
-
* {
|
|
26
|
-
* name: string, // logged on build
|
|
27
|
-
* transform: async (entryPath, contentBuf) => {
|
|
28
|
-
* content: Buffer | string, // post-transform output
|
|
29
|
-
* sourceMap: string | Buffer | null, // optional .map sibling
|
|
30
|
-
* imports?: [string], // optional — for graph-aware watch
|
|
31
|
-
* },
|
|
32
|
-
* }
|
|
33
|
-
*
|
|
34
|
-
* The default engine is `b.bundler.engine.passthrough` — reads the file
|
|
35
|
-
* verbatim, no transform. This is what every existing `b.bundler.create`
|
|
36
|
-
* call gets without setting `engine`.
|
|
19
|
+
* Module-graph / tree-shake / minify / sourcemaps: operators supply
|
|
20
|
+
* esbuild (devDependency, never vendored) and adapt it via
|
|
21
|
+
* `engine.fromEsbuild(esbuild, opts)` — the framework owns the
|
|
22
|
+
* integration seam, the operator brings the heavy machinery. The
|
|
23
|
+
* ~10 MB esbuild-wasm blob is intentionally not vendored.
|
|
37
24
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
25
|
+
* Hashes are SHA3-512, first 16 hex chars by default (operators
|
|
26
|
+
* override via `opts.hashLen` between 4 and 64). Source maps written
|
|
27
|
+
* by an engine land as `<hashed>.<ext>.map` siblings.
|
|
41
28
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* outdir: "./public/dist",
|
|
46
|
-
* engine: b.bundler.engine.fromEsbuild(esbuild, {
|
|
47
|
-
* bundle: true,
|
|
48
|
-
* format: "esm",
|
|
49
|
-
* target: ["chrome120", "firefox120", "safari17"],
|
|
50
|
-
* minify: true,
|
|
51
|
-
* sourcemap: true,
|
|
52
|
-
* }),
|
|
53
|
-
* });
|
|
29
|
+
* Watch mode: `bundler.watch(callback)` arms `fs.watch` on each
|
|
30
|
+
* entry's directory, debounces bursts via `opts.graceMs` (default
|
|
31
|
+
* 100 ms), and rebuilds the entire entry set on change.
|
|
54
32
|
*
|
|
55
|
-
*
|
|
56
|
-
* (the wasm blob is ~10 MB — bigger than every other vendored dep
|
|
57
|
-
* combined; it would 3-5x the @blamejs/core npm tarball for a build-
|
|
58
|
-
* time tool most operators only need at deploy time anyway). Treating
|
|
59
|
-
* esbuild as an operator-supplied driver follows the same pattern as
|
|
60
|
-
* `b.externalDb` and `b.mtlsCa.create({ engine })`: the framework owns
|
|
61
|
-
* the integration seam, the operator brings the heavy machinery.
|
|
33
|
+
* Manifest format:
|
|
62
34
|
*
|
|
63
|
-
*
|
|
64
|
-
* - A bespoke bundler bundled INSIDE the framework. The engine
|
|
65
|
-
* surface IS the answer; operators wanting esbuild / rollup /
|
|
66
|
-
* swc / vite wire them through it.
|
|
35
|
+
* { "app": "app.4a8c2f1d9e3b7062.js", "styles": "styles.b29f1e7c.css" }
|
|
67
36
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
37
|
+
* Integrates with `lib/static.js`: serve `outdir` as a static
|
|
38
|
+
* directory; `b.static`'s hashed-path detection sets long-cache
|
|
39
|
+
* headers on files that look hashed, and `integrity()` reads the
|
|
40
|
+
* manifest to emit Subresource Integrity attributes.
|
|
70
41
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* Integrates with lib/static.js: serve `outdir` as a static directory;
|
|
75
|
-
* lib/static.js's hashed-path detection sets long-cache headers on
|
|
76
|
-
* files that look hashed, and integrity() reads the manifest to
|
|
77
|
-
* generate Subresource Integrity attributes.
|
|
42
|
+
* @card
|
|
43
|
+
* Client-side asset bundler — produces content-hashed `dist/<name>.<hash>.<ext>` files plus a `manifest.json` mapping logical name to hashed filename.
|
|
78
44
|
*/
|
|
79
45
|
|
|
80
46
|
var path = require("path");
|
|
@@ -224,6 +190,60 @@ function _validateEngine(eng) {
|
|
|
224
190
|
return eng;
|
|
225
191
|
}
|
|
226
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @primitive b.bundler.create
|
|
195
|
+
* @signature b.bundler.create(opts)
|
|
196
|
+
* @since 0.4.0
|
|
197
|
+
* @status stable
|
|
198
|
+
* @related b.static.serve
|
|
199
|
+
*
|
|
200
|
+
* Build a content-hashed asset pipeline for a fixed set of named
|
|
201
|
+
* entries. The returned object exposes `build()` (one-shot rebuild,
|
|
202
|
+
* resolves to `{ outputs, manifestPath, manifest, durationMs }`),
|
|
203
|
+
* `watch(callback)` (arm `fs.watch` and debounce-rebuild on change),
|
|
204
|
+
* and `close()` (drop watchers and pending timers).
|
|
205
|
+
*
|
|
206
|
+
* Throws `BundlerError` at config time on missing / malformed entries,
|
|
207
|
+
* missing `outdir`, an out-of-range `hashLen`, or an engine that does
|
|
208
|
+
* not implement `{ name, transform }`.
|
|
209
|
+
*
|
|
210
|
+
* @opts
|
|
211
|
+
* entries: { [name: string]: string }, // logical name → source path
|
|
212
|
+
* outdir: string, // dist directory (created if missing, mode 0o755)
|
|
213
|
+
* cwd: string, // resolves relative entries / outdir; defaults to process.cwd()
|
|
214
|
+
* engine: { name: string, transform: async (entryPath, contentBuf) => { content, sourceMap?, imports? } },
|
|
215
|
+
* // defaults to engine.passthrough
|
|
216
|
+
* manifest: string | false, // manifest filename ("manifest.json"), or false to skip
|
|
217
|
+
* hash: boolean, // emit <name>.<hash>.<ext>; default true
|
|
218
|
+
* hashLen: number, // hex chars in the hash, 4..64; default 16
|
|
219
|
+
* graceMs: number, // watch-mode debounce ms; default 100
|
|
220
|
+
* log: object, // structured logger ({ info, warn, error })
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* var bundler = b.bundler.create({
|
|
224
|
+
* entries: { app: "./src/app.js", styles: "./src/styles.css" },
|
|
225
|
+
* outdir: "./public/dist",
|
|
226
|
+
* hashLen: 16,
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* // bundler.build() returns a Promise resolving to:
|
|
230
|
+
* // { outputs: [...], manifest: { app: "app.<hash>.js", styles: "styles.<hash>.css" },
|
|
231
|
+
* // manifestPath: ".../public/dist/manifest.json", durationMs: <number> }
|
|
232
|
+
*
|
|
233
|
+
* // Watch mode — rebuild on edits.
|
|
234
|
+
* bundler.watch(function (err, result) {
|
|
235
|
+
* if (err) return;
|
|
236
|
+
* // result.manifest is the freshly-written name→hashed-filename map
|
|
237
|
+
* });
|
|
238
|
+
*
|
|
239
|
+
* // Operator-supplied esbuild for module-graph + tree-shake + minify.
|
|
240
|
+
* // var esbuild = require("esbuild");
|
|
241
|
+
* // var modGraph = b.bundler.create({
|
|
242
|
+
* // entries: { app: "./src/app.js" },
|
|
243
|
+
* // outdir: "./public/dist",
|
|
244
|
+
* // engine: b.bundler.engine.fromEsbuild(esbuild, { minify: true, sourcemap: true }),
|
|
245
|
+
* // });
|
|
246
|
+
*/
|
|
227
247
|
function create(opts) {
|
|
228
248
|
opts = opts || {};
|
|
229
249
|
validateOpts(opts, [
|
package/lib/cache.js
CHANGED
|
@@ -1,88 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.cache
|
|
3
|
+
* @module b.cache
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title Cache
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* maxEntries: 10000,
|
|
10
|
-
* maxBytes: C.BYTES.mib(100), // memory backend only
|
|
11
|
-
* sizeOf: function (v) { return v.byteLength; }, // optional override
|
|
12
|
-
* slidingTtl: true, // bump expiresAt on hit
|
|
13
|
-
* audit: b.audit, // optional
|
|
14
|
-
* });
|
|
15
|
-
*
|
|
16
|
-
* await cache.set("u-42", record, { ttlMs: C.TIME.minutes(10), tags: ["user:42", "session"] });
|
|
17
|
-
* var hit = await cache.get("u-42");
|
|
18
|
-
*
|
|
19
|
-
* // Memoize / read-through:
|
|
20
|
-
* var profile = await cache.wrap("u-42", function () {
|
|
21
|
-
* return db.users.findOne({ _id: "u-42" });
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* // Bulk invalidate (memory backend):
|
|
25
|
-
* await cache.invalidateTag("user:42"); // purges every entry tagged user:42
|
|
26
|
-
*
|
|
27
|
-
* Surface (returned by create):
|
|
7
|
+
* @intro
|
|
8
|
+
* LRU + TTL cache with operator-supplied namespacing, drop-silent
|
|
9
|
+
* key validation on hot-path observability, and pluggable backends
|
|
10
|
+
* that share semantics across single-process and clustered nodes.
|
|
28
11
|
*
|
|
29
|
-
*
|
|
30
|
-
* set(key, value, opts?) → void (opts: { ttlMs, tags })
|
|
31
|
-
* del(key) → boolean (existed)
|
|
32
|
-
* has(key) → boolean (does NOT bump LRU recency)
|
|
33
|
-
* clear(opts?) → number (purged) (opts: { req, context })
|
|
34
|
-
* size() → number
|
|
35
|
-
* bytes() → number (memory backend only — total stored bytes)
|
|
36
|
-
* wrap(key, fn, opts?) → fn's return value (opts: { ttlMs, singleFlight })
|
|
37
|
-
* invalidateTag(tag, opts?) → number (purged) (opts: { req, context })
|
|
38
|
-
* getTags(key) → string[] | null
|
|
39
|
-
* close() → void
|
|
12
|
+
* Three first-class backends ship in the box:
|
|
40
13
|
*
|
|
41
|
-
*
|
|
14
|
+
* - "memory" (default) — Map + LRU eviction (maxEntries) + bytes
|
|
15
|
+
* eviction (maxBytes) + periodic sweep. Single-process accuracy.
|
|
16
|
+
* - "cluster" — _blamejs_cache table via cluster-storage. One
|
|
17
|
+
* table serves every CacheInstance via "<namespace>:<key>"
|
|
18
|
+
* composite key; ON CONFLICT UPSERT for atomic set.
|
|
19
|
+
* - "redis" — cache-redis client; sliding TTL via EXPIRE; tag
|
|
20
|
+
* wipes via SCAN+DEL on a per-namespace prefix.
|
|
42
21
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
22
|
+
* A `{ get, set, del, clear, size, close }` operator-supplied
|
|
23
|
+
* object is the custom-backend escape hatch (Memcached, in-memory
|
|
24
|
+
* harnesses, anything else with the same async surface).
|
|
45
25
|
*
|
|
46
|
-
*
|
|
47
|
-
* is "<namespace>:<key>" so one table serves every CacheInstance.
|
|
48
|
-
* UPSERT via ON CONFLICT for atomic set; DELETE WHERE expiresAt
|
|
49
|
-
* for sweep. JSON-only value serialization.
|
|
26
|
+
* Hot-path validation policy:
|
|
50
27
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
28
|
+
* - create() opts → throw at boot (config-time)
|
|
29
|
+
* - key arg on get/set/del → throw at call site (programming bug)
|
|
30
|
+
* - per-call ttlMs override → throw at call site (silent footgun
|
|
31
|
+
* if accepted)
|
|
32
|
+
* - audit / observability → drop silent (hot-path sink)
|
|
33
|
+
* - method-after-close → throw BAD_STATE
|
|
53
34
|
*
|
|
54
|
-
*
|
|
35
|
+
* Security defaults that are NOT opt-in:
|
|
55
36
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* - audit / observability emit failures → drop silent (hot-path sink)
|
|
61
|
-
* - method called after close() → throw BAD_STATE at call site
|
|
37
|
+
* - auditClear: true mass purges are operator-action shaped
|
|
38
|
+
* - auditFailures: true backend errors are signal
|
|
39
|
+
* - hot-path get/set/hit/miss/eviction → observability only
|
|
40
|
+
* (the audit chain would drown at any reasonable QPS)
|
|
62
41
|
*
|
|
63
|
-
*
|
|
42
|
+
* Returned `CacheInstance` shape:
|
|
64
43
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
44
|
+
* get(key) → value | undefined
|
|
45
|
+
* set(key, value, opts?) → void (opts: { ttlMs, tags, seal })
|
|
46
|
+
* del(key) → boolean
|
|
47
|
+
* has(key) → boolean (does NOT bump LRU recency)
|
|
48
|
+
* clear(opts?) → number (opts: { req, context })
|
|
49
|
+
* size() → number
|
|
50
|
+
* bytes() → number (memory backend only)
|
|
51
|
+
* wrap(key, fn, opts?) → fn's return (opts: { ttlMs, singleFlight })
|
|
52
|
+
* invalidateTag(tag, opts?) → number (opts: { req, context })
|
|
53
|
+
* getTags(key) → string[] | null
|
|
54
|
+
* close() → void
|
|
69
55
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
56
|
+
* Stale-while-revalidate, single-flight wrap (concurrent calls
|
|
57
|
+
* collapse to one compute), tag-based bulk invalidation (memory +
|
|
58
|
+
* cluster), and cross-node invalidation via b.pubsub are all built
|
|
59
|
+
* in — operator opts in via the `staleWhileRevalidate` /
|
|
60
|
+
* `invalidationPubsub` opts.
|
|
74
61
|
*
|
|
75
|
-
*
|
|
62
|
+
* What is NOT in the box: maxBytes on the cluster backend (would
|
|
63
|
+
* require an aggregate query per set; operator prunes the shared
|
|
64
|
+
* table on their own schedule) and per-entry exact slidingTtl on
|
|
65
|
+
* the cluster backend (sliding extends by the cache's defaultTtlMs;
|
|
66
|
+
* operators with mixed-TTL writes wanting strict per-entry sliding
|
|
67
|
+
* use the memory backend or extend at the application layer).
|
|
76
68
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* operator controls cluster-table size with their own pruning if
|
|
80
|
-
* bytes pressure surfaces.
|
|
81
|
-
* - Per-entry exact slidingTtl on the cluster backend — sliding works
|
|
82
|
-
* on cluster but extends by the cache's defaultTtlMs (we don't
|
|
83
|
-
* store per-row ttl). Operators with mixed-TTL writes wanting
|
|
84
|
-
* strict per-entry sliding use the memory backend or extend at
|
|
85
|
-
* the application layer.
|
|
69
|
+
* @card
|
|
70
|
+
* LRU + TTL cache with operator-supplied namespacing, drop-silent key validation on hot-path observability, and pluggable backends that share semantics across single-process and clustered nodes.
|
|
86
71
|
*/
|
|
87
72
|
|
|
88
73
|
var cacheRedis = require("./cache-redis");
|
|
@@ -100,6 +85,15 @@ var { CacheError } = require("./framework-error");
|
|
|
100
85
|
|
|
101
86
|
var log = boot("cache");
|
|
102
87
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
88
|
+
// D-L5 — opt-in vault seal for cluster-backend cache values. Lazy so
|
|
89
|
+
// vault-not-initialized in tests with a memory cache doesn't crash
|
|
90
|
+
// at module load.
|
|
91
|
+
var vault = lazyRequire(function () { return require("./vault"); });
|
|
92
|
+
// Marker bytes that prefix sealed values in _blamejs_cache.valueJson —
|
|
93
|
+
// "blamejs:cache.sealed:" + base64-of-vault-sealed-payload. The
|
|
94
|
+
// non-prefixed cache rows pass straight through JSON.parse as before
|
|
95
|
+
// so the seal opt is a strict per-call upgrade with no migration.
|
|
96
|
+
var CACHE_SEAL_PREFIX = "blamejs:cache.sealed:";
|
|
103
97
|
|
|
104
98
|
var _err = CacheError.factory;
|
|
105
99
|
|
|
@@ -486,7 +480,17 @@ function _clusterBackend(cfg) {
|
|
|
486
480
|
[newExpires, now, _composedKey(key), now]
|
|
487
481
|
).catch(function () { /* best-effort */ });
|
|
488
482
|
}
|
|
489
|
-
|
|
483
|
+
var stored = row.valueJson;
|
|
484
|
+
// D-L5 — sealed-row decode. Sealed entries are prefixed at write
|
|
485
|
+
// time so the unseal-on-read path is a strict opt-in: rows
|
|
486
|
+
// written without seal:true continue parsing as before.
|
|
487
|
+
if (typeof stored === "string" && stored.indexOf(CACHE_SEAL_PREFIX) === 0) {
|
|
488
|
+
try {
|
|
489
|
+
var unsealed = vault().unseal(stored.substring(CACHE_SEAL_PREFIX.length));
|
|
490
|
+
return safeJson.parse(unsealed, { maxBytes: C.BYTES.mib(64) });
|
|
491
|
+
} catch (_e) { return undefined; }
|
|
492
|
+
}
|
|
493
|
+
try { return safeJson.parse(stored, { maxBytes: C.BYTES.mib(64) }); }
|
|
490
494
|
catch (_e) { return undefined; }
|
|
491
495
|
}
|
|
492
496
|
|
|
@@ -497,6 +501,13 @@ function _clusterBackend(cfg) {
|
|
|
497
501
|
// changed value, app code treats it as the original" — a subtle
|
|
498
502
|
// freshness bug that's hard to debug.
|
|
499
503
|
var json = safeJson.stringify(value);
|
|
504
|
+
// D-L5 — opt-in vault seal. When the caller passes seal: true,
|
|
505
|
+
// wrap the JSON via b.vault.seal (XChaCha20-Poly1305) before
|
|
506
|
+
// landing in _blamejs_cache.valueJson. The marker prefix is what
|
|
507
|
+
// get() looks for to know it must unseal on read.
|
|
508
|
+
if (meta && meta.seal === true) {
|
|
509
|
+
json = CACHE_SEAL_PREFIX + vault().seal(json);
|
|
510
|
+
}
|
|
500
511
|
var storedExpires = (expiresAt === Infinity) ? Number.MAX_SAFE_INTEGER : expiresAt;
|
|
501
512
|
var now = clock();
|
|
502
513
|
var ck = _composedKey(key);
|
|
@@ -713,6 +724,103 @@ function _customBackend(operatorBackend, cfg) {
|
|
|
713
724
|
|
|
714
725
|
// ---- Public create ----
|
|
715
726
|
|
|
727
|
+
/**
|
|
728
|
+
* @primitive b.cache.create
|
|
729
|
+
* @signature b.cache.create(opts)
|
|
730
|
+
* @since 0.1.0
|
|
731
|
+
* @status stable
|
|
732
|
+
* @related b.pubsub.create, b.audit
|
|
733
|
+
*
|
|
734
|
+
* Build a `CacheInstance` bound to a `namespace`. The instance owns
|
|
735
|
+
* its sweep timer, its backend connection, its single-flight inflight
|
|
736
|
+
* map, and (when `invalidationPubsub` is supplied) a pubsub
|
|
737
|
+
* subscription that mirrors `del` / `clear` / `invalidateTag` events
|
|
738
|
+
* across nodes. Multiple instances coexist — a "session.user" memory
|
|
739
|
+
* cache and a "billing.invoice" cluster cache share neither keys nor
|
|
740
|
+
* tags. `close()` releases everything.
|
|
741
|
+
*
|
|
742
|
+
* The `backend` opt picks the storage tier: `"memory"` (default,
|
|
743
|
+
* single-process LRU+TTL), `"cluster"` (shared SQL table for
|
|
744
|
+
* multi-node coherence), `"redis"` (when `redisUrl` is supplied;
|
|
745
|
+
* native EXPIRE-based TTL), or an operator-supplied object with
|
|
746
|
+
* `{ get, set, del, clear, size, close }` for any other store.
|
|
747
|
+
* Backends are interchangeable from the caller's perspective —
|
|
748
|
+
* `await cache.get(key)` returns the same shape regardless.
|
|
749
|
+
*
|
|
750
|
+
* @opts
|
|
751
|
+
* namespace: string, // required; collision domain; must not contain ':'
|
|
752
|
+
* backend: "memory" | "cluster" | "redis" | object, // default "memory"
|
|
753
|
+
* ttlMs: number | Infinity, // default C.TIME.minutes(5)
|
|
754
|
+
* maxEntries: number | Infinity, // memory backend cap; default 10000
|
|
755
|
+
* maxBytes: number | Infinity, // memory backend cap; default Infinity
|
|
756
|
+
* sizeOf: function(value) -> number, // memory bytes accounting override
|
|
757
|
+
* sweepIntervalMs: number, // expired-entry sweep cadence; default C.TIME.minutes(1); minimum 1000
|
|
758
|
+
* staleWhileRevalidate: boolean, // wrap() serves stale + refreshes in background; default false
|
|
759
|
+
* slidingTtl: boolean, // bump expiresAt on hit; default false
|
|
760
|
+
* auditFailures: boolean, // emit audit on backend errors; default true
|
|
761
|
+
* auditClear: boolean, // emit audit on clear / invalidateTag; default true
|
|
762
|
+
* audit: { emit } | b.audit, // audit sink override
|
|
763
|
+
* observability: { event } | b.observability, // metrics sink override
|
|
764
|
+
* clock: function() -> number, // Date.now() override (testing)
|
|
765
|
+
* invalidationPubsub: b.pubsub instance, // cross-node del/clear/tag mirroring
|
|
766
|
+
* redisUrl: string, // backend === "redis" only; required there
|
|
767
|
+
* redisPassword: string, // backend === "redis" only
|
|
768
|
+
* redisUsername: string, // backend === "redis" only
|
|
769
|
+
* redisTls: boolean, // backend === "redis" only
|
|
770
|
+
* redisCa: string | Buffer, // backend === "redis" only; PEM CA bundle
|
|
771
|
+
* redisServername: string, // backend === "redis" only; SNI override
|
|
772
|
+
* redisConnectTimeoutMs: number, // backend === "redis" only
|
|
773
|
+
* redisCommandTimeoutMs: number, // backend === "redis" only
|
|
774
|
+
* redisMaxReconnectAttempts: number, // backend === "redis" only
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* var b = require("@blamejs/core");
|
|
778
|
+
* var C = b.constants;
|
|
779
|
+
*
|
|
780
|
+
* // Simple set/get against the default memory backend.
|
|
781
|
+
* var sessions = b.cache.create({
|
|
782
|
+
* namespace: "session.user",
|
|
783
|
+
* ttlMs: C.TIME.minutes(5),
|
|
784
|
+
* maxEntries: 10000,
|
|
785
|
+
* });
|
|
786
|
+
* await sessions.set("u-42", { uid: "u-42", role: "admin" });
|
|
787
|
+
* var hit = await sessions.get("u-42");
|
|
788
|
+
* // → { uid: "u-42", role: "admin" }
|
|
789
|
+
*
|
|
790
|
+
* @example
|
|
791
|
+
* var b = require("@blamejs/core");
|
|
792
|
+
* var C = b.constants;
|
|
793
|
+
*
|
|
794
|
+
* // wrap() pattern with per-call TTL override + single-flight.
|
|
795
|
+
* // Concurrent callers collapse to one DB read; subsequent reads
|
|
796
|
+
* // serve from cache for 10 minutes.
|
|
797
|
+
* var profiles = b.cache.create({
|
|
798
|
+
* namespace: "billing.profile",
|
|
799
|
+
* ttlMs: C.TIME.minutes(2),
|
|
800
|
+
* });
|
|
801
|
+
* var profile = await profiles.wrap(
|
|
802
|
+
* "u-42",
|
|
803
|
+
* function () { return { uid: "u-42", plan: "pro" }; },
|
|
804
|
+
* { ttlMs: C.TIME.minutes(10) }
|
|
805
|
+
* );
|
|
806
|
+
* // → { uid: "u-42", plan: "pro" }
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* var b = require("@blamejs/core");
|
|
810
|
+
* var C = b.constants;
|
|
811
|
+
*
|
|
812
|
+
* // Cluster-shared cache: every node sees the same entries via the
|
|
813
|
+
* // _blamejs_cache table. Tag-based bulk invalidation purges across
|
|
814
|
+
* // every namespace member in one call.
|
|
815
|
+
* var inventory = b.cache.create({
|
|
816
|
+
* namespace: "catalog.item",
|
|
817
|
+
* backend: "cluster",
|
|
818
|
+
* ttlMs: C.TIME.minutes(15),
|
|
819
|
+
* });
|
|
820
|
+
* await inventory.set("sku-1001", { qty: 42 }, { tags: ["warehouse:east"] });
|
|
821
|
+
* var purged = await inventory.invalidateTag("warehouse:east");
|
|
822
|
+
* // → 1
|
|
823
|
+
*/
|
|
716
824
|
function create(opts) {
|
|
717
825
|
opts = opts || {};
|
|
718
826
|
validateOpts(opts, [
|
|
@@ -885,7 +993,19 @@ function create(opts) {
|
|
|
885
993
|
}
|
|
886
994
|
}
|
|
887
995
|
}
|
|
888
|
-
|
|
996
|
+
// D-L5 — opt-in vault seal. Strict-shape check: must be the literal
|
|
997
|
+
// boolean true, not just truthy. Backends that don't support seal
|
|
998
|
+
// (memory, custom) ignore the flag transparently; cluster backend
|
|
999
|
+
// wraps valueJson via b.vault.seal before INSERT.
|
|
1000
|
+
var seal = !!(callerOpts && callerOpts.seal === true);
|
|
1001
|
+
if (seal && backend.name !== "cluster") {
|
|
1002
|
+
throw _err("BAD_OPT",
|
|
1003
|
+
"cache.set: seal: true is only supported on the cluster backend " +
|
|
1004
|
+
"(this cache instance uses '" + (backend.name || "custom") + "'). " +
|
|
1005
|
+
"Memory-backed caches do not need seal because the value never reaches disk; " +
|
|
1006
|
+
"custom backends wrap their own at-rest encryption.");
|
|
1007
|
+
}
|
|
1008
|
+
try { await backend.set(key, value, expiresAt, { ttlMs: ttlMs, tags: tags, seal: seal }); }
|
|
889
1009
|
catch (e) {
|
|
890
1010
|
emitObs("cache.backend.failed", { namespace: namespace, op: "set" });
|
|
891
1011
|
_backendFailedAudit("set", e);
|