@blamejs/core 0.4.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/LICENSE +201 -0
  3. package/LTS-CALENDAR.md +29 -0
  4. package/MIGRATING.md +7 -0
  5. package/NOTICE +59 -0
  6. package/README.md +100 -0
  7. package/bin/blamejs.js +13 -0
  8. package/index.js +253 -0
  9. package/lib/api-key.js +705 -0
  10. package/lib/api-snapshot.js +335 -0
  11. package/lib/app-shutdown.js +381 -0
  12. package/lib/app.js +364 -0
  13. package/lib/atomic-file.js +525 -0
  14. package/lib/audit-chain.js +168 -0
  15. package/lib/audit-sign.js +319 -0
  16. package/lib/audit-tools.js +682 -0
  17. package/lib/audit.js +753 -0
  18. package/lib/auth/jwt.js +280 -0
  19. package/lib/auth/oauth.js +691 -0
  20. package/lib/auth/passkey.js +185 -0
  21. package/lib/auth/password.js +139 -0
  22. package/lib/auth/totp.js +17 -0
  23. package/lib/auth-header.js +81 -0
  24. package/lib/backup/bundle.js +219 -0
  25. package/lib/backup/crypto.js +174 -0
  26. package/lib/backup/index.js +490 -0
  27. package/lib/backup/manifest.js +275 -0
  28. package/lib/bundler.js +295 -0
  29. package/lib/cache.js +819 -0
  30. package/lib/chain-writer.js +234 -0
  31. package/lib/cli-helpers.js +201 -0
  32. package/lib/cli.js +1377 -0
  33. package/lib/cluster-provider-db.js +245 -0
  34. package/lib/cluster-storage.js +166 -0
  35. package/lib/cluster.js +691 -0
  36. package/lib/consent.js +222 -0
  37. package/lib/constants.js +186 -0
  38. package/lib/cookies.js +293 -0
  39. package/lib/credential-hash.js +303 -0
  40. package/lib/crypto-field.js +159 -0
  41. package/lib/crypto.js +250 -0
  42. package/lib/db-query.js +297 -0
  43. package/lib/db-schema.js +250 -0
  44. package/lib/db.js +1054 -0
  45. package/lib/deprecate.js +226 -0
  46. package/lib/dev.js +324 -0
  47. package/lib/error-page.js +424 -0
  48. package/lib/events.js +135 -0
  49. package/lib/external-db.js +422 -0
  50. package/lib/forms.js +378 -0
  51. package/lib/framework-error.js +189 -0
  52. package/lib/framework-schema.js +604 -0
  53. package/lib/handlers.js +350 -0
  54. package/lib/html-balance.js +227 -0
  55. package/lib/http-client.js +615 -0
  56. package/lib/i18n.js +780 -0
  57. package/lib/jobs.js +181 -0
  58. package/lib/lazy-require.js +48 -0
  59. package/lib/log-stream-local.js +137 -0
  60. package/lib/log-stream-webhook.js +170 -0
  61. package/lib/log-stream.js +211 -0
  62. package/lib/log.js +355 -0
  63. package/lib/mail-bounce.js +507 -0
  64. package/lib/mail.js +701 -0
  65. package/lib/metrics.js +647 -0
  66. package/lib/middleware/api-encrypt.js +553 -0
  67. package/lib/middleware/attach-user.js +156 -0
  68. package/lib/middleware/body-parser.js +883 -0
  69. package/lib/middleware/bot-guard.js +148 -0
  70. package/lib/middleware/compression.js +436 -0
  71. package/lib/middleware/cors.js +236 -0
  72. package/lib/middleware/csp-nonce.js +332 -0
  73. package/lib/middleware/csrf-protect.js +275 -0
  74. package/lib/middleware/error-handler.js +46 -0
  75. package/lib/middleware/health.js +358 -0
  76. package/lib/middleware/index.js +52 -0
  77. package/lib/middleware/rate-limit.js +319 -0
  78. package/lib/middleware/request-id.js +53 -0
  79. package/lib/middleware/require-auth.js +95 -0
  80. package/lib/middleware/security-headers.js +91 -0
  81. package/lib/migrations.js +353 -0
  82. package/lib/mtls-ca.js +333 -0
  83. package/lib/mtls-engine-default.js +285 -0
  84. package/lib/nonce-store.js +177 -0
  85. package/lib/notify.js +643 -0
  86. package/lib/ntp-check.js +178 -0
  87. package/lib/object-store/azure-blob.js +467 -0
  88. package/lib/object-store/gcs.js +469 -0
  89. package/lib/object-store/http-put.js +153 -0
  90. package/lib/object-store/index.js +140 -0
  91. package/lib/object-store/local.js +163 -0
  92. package/lib/object-store/retry.js +15 -0
  93. package/lib/object-store/sigv4.js +535 -0
  94. package/lib/observability.js +114 -0
  95. package/lib/pagination.js +371 -0
  96. package/lib/parsers/index.js +64 -0
  97. package/lib/parsers/safe-csv.js +224 -0
  98. package/lib/parsers/safe-env.js +614 -0
  99. package/lib/parsers/safe-toml.js +745 -0
  100. package/lib/parsers/safe-xml.js +379 -0
  101. package/lib/parsers/safe-yaml.js +977 -0
  102. package/lib/permissions.js +430 -0
  103. package/lib/pqc-agent.js +85 -0
  104. package/lib/pqc-gate.js +266 -0
  105. package/lib/protocol-dispatcher.js +144 -0
  106. package/lib/queue-local.js +327 -0
  107. package/lib/queue.js +430 -0
  108. package/lib/redact.js +192 -0
  109. package/lib/render.js +193 -0
  110. package/lib/request-helpers.js +178 -0
  111. package/lib/restore-bundle.js +239 -0
  112. package/lib/restore-rollback.js +254 -0
  113. package/lib/restore.js +301 -0
  114. package/lib/retry.js +329 -0
  115. package/lib/router.js +437 -0
  116. package/lib/safe-async.js +520 -0
  117. package/lib/safe-buffer.js +162 -0
  118. package/lib/safe-json.js +532 -0
  119. package/lib/safe-schema.js +1176 -0
  120. package/lib/safe-sql.js +157 -0
  121. package/lib/safe-url.js +109 -0
  122. package/lib/scheduler.js +680 -0
  123. package/lib/seeders.js +622 -0
  124. package/lib/session.js +304 -0
  125. package/lib/slug.js +243 -0
  126. package/lib/static.js +268 -0
  127. package/lib/storage.js +470 -0
  128. package/lib/subject.js +281 -0
  129. package/lib/template.js +781 -0
  130. package/lib/testing.js +621 -0
  131. package/lib/totp.js +285 -0
  132. package/lib/tracing.js +484 -0
  133. package/lib/validate-opts.js +56 -0
  134. package/lib/vault/index.js +299 -0
  135. package/lib/vault/passphrase-ops.js +311 -0
  136. package/lib/vault/passphrase-source.js +198 -0
  137. package/lib/vault/rotate.js +761 -0
  138. package/lib/vault/wrap.js +289 -0
  139. package/lib/vendor/MANIFEST.json +84 -0
  140. package/lib/vendor/argon2/argon2.cjs +466 -0
  141. package/lib/vendor/argon2/argon2.d.cts +62 -0
  142. package/lib/vendor/argon2/package.json +1 -0
  143. package/lib/vendor/argon2/prebuilds/darwin-arm64/argon2.armv8.glibc.node +0 -0
  144. package/lib/vendor/argon2/prebuilds/darwin-x64/argon2.glibc.node +0 -0
  145. package/lib/vendor/argon2/prebuilds/freebsd-arm64/argon2.armv8.glibc.node +0 -0
  146. package/lib/vendor/argon2/prebuilds/freebsd-x64/argon2.glibc.node +0 -0
  147. package/lib/vendor/argon2/prebuilds/linux-arm/argon2.armv7.glibc.node +0 -0
  148. package/lib/vendor/argon2/prebuilds/linux-arm/argon2.armv7.musl.node +0 -0
  149. package/lib/vendor/argon2/prebuilds/linux-arm64/argon2.armv8.glibc.node +0 -0
  150. package/lib/vendor/argon2/prebuilds/linux-arm64/argon2.armv8.musl.node +0 -0
  151. package/lib/vendor/argon2/prebuilds/linux-x64/argon2.glibc.node +0 -0
  152. package/lib/vendor/argon2/prebuilds/linux-x64/argon2.musl.node +0 -0
  153. package/lib/vendor/argon2/prebuilds/win32-x64/argon2.glibc.node +0 -0
  154. package/lib/vendor/noble-ciphers.cjs +9 -0
  155. package/lib/vendor/pki.cjs +181 -0
  156. package/lib/vendor/simplewebauthn-server.cjs +328 -0
  157. package/lib/webhook.js +632 -0
  158. package/lib/websocket-channels.js +413 -0
  159. package/lib/websocket.js +833 -0
  160. package/package.json +39 -0
package/lib/static.js ADDED
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ /**
3
+ * Static asset serving — middleware factory + SRI integrity helper.
4
+ *
5
+ * The middleware maps URL → file under `root`, with the same path-
6
+ * containment posture as lib/template (no `..`, no `\0`, resolved path
7
+ * must stay under root). Files outside root or symlinks pointing out
8
+ * are refused.
9
+ *
10
+ * Cache-Control posture:
11
+ *
12
+ * - URLs matching opts.hashedPathPattern (default: a hex/base32 ≥8
13
+ * character segment surrounded by dots — `.abc123ef.` style) get
14
+ * `Cache-Control: public, max-age=31536000, immutable` per the
15
+ * roadmap's verification gate. The hash-in-URL convention pins
16
+ * the served bytes to the URL forever; immutable lets browsers
17
+ * skip revalidation entirely.
18
+ * - Other URLs get `Cache-Control: public, max-age=<defaultMaxAge>`
19
+ * (default 3600 = 1h). Operators tune via opts.defaultMaxAge.
20
+ *
21
+ * ETag is the first 27 chars of SHA-384(file content), base64. 162
22
+ * bits of collision resistance — overkill but cheap; same hash powers
23
+ * the SRI integrity helper so we compute it once per file.
24
+ *
25
+ * 304 Not Modified: emitted when If-None-Match matches the ETag.
26
+ *
27
+ * HEAD: returns same headers as GET, no body.
28
+ *
29
+ * Range requests are NOT supported in v1. Operators serving video/audio
30
+ * put a CDN in front. Compression (gzip/br) likewise — out of scope here.
31
+ *
32
+ * MIME types: minimal built-in table. Override via opts.contentTypes.
33
+ *
34
+ * Index files: a request resolving to a directory tries opts.indexFile
35
+ * (default "index.html"); operators disable with opts.indexFile = null.
36
+ *
37
+ * Public API:
38
+ *
39
+ * staticServe.create({ root, mountPath?, hashedPathPattern?,
40
+ * defaultMaxAge?, indexFile?, contentTypes? })
41
+ * → (req, res, next) middleware
42
+ *
43
+ * await staticServe.integrity(filePath)
44
+ * → "sha384-<base64>" suitable for an HTML integrity= attribute.
45
+ * Cached per file across calls; invalidated on mtime change.
46
+ */
47
+ var fs = require("fs");
48
+ var fsp = require("fs/promises");
49
+ var path = require("path");
50
+ var nodeCrypto = require("crypto");
51
+ var validateOpts = require("./validate-opts");
52
+
53
+ var DEFAULT_HASHED_PATTERN = /\.[a-fA-F0-9]{8,}\./;
54
+ var DEFAULT_INDEX_FILE = "index.html";
55
+ var DEFAULT_MAX_AGE_SEC = 3600; // 1 hour for non-hashed paths
56
+ var IMMUTABLE_MAX_AGE_SEC = 31536000; // 1 year for hashed paths
57
+
58
+ // Minimal MIME table. Operators with exotic types pass opts.contentTypes
59
+ // to override. The framework deliberately doesn't bring a 200-entry
60
+ // mime-db dependency — most servers serve a handful of types.
61
+ var DEFAULT_CONTENT_TYPES = {
62
+ ".html": "text/html; charset=utf-8",
63
+ ".htm": "text/html; charset=utf-8",
64
+ ".css": "text/css; charset=utf-8",
65
+ ".js": "application/javascript; charset=utf-8",
66
+ ".mjs": "application/javascript; charset=utf-8",
67
+ ".json": "application/json; charset=utf-8",
68
+ ".map": "application/json; charset=utf-8",
69
+ ".txt": "text/plain; charset=utf-8",
70
+ ".md": "text/markdown; charset=utf-8",
71
+ ".xml": "application/xml; charset=utf-8",
72
+ ".svg": "image/svg+xml",
73
+ ".png": "image/png",
74
+ ".jpg": "image/jpeg",
75
+ ".jpeg": "image/jpeg",
76
+ ".gif": "image/gif",
77
+ ".webp": "image/webp",
78
+ ".avif": "image/avif",
79
+ ".ico": "image/x-icon",
80
+ ".woff": "font/woff",
81
+ ".woff2": "font/woff2",
82
+ ".ttf": "font/ttf",
83
+ ".otf": "font/otf",
84
+ ".pdf": "application/pdf",
85
+ ".wasm": "application/wasm",
86
+ ".webmanifest": "application/manifest+json",
87
+ };
88
+
89
+ // ---- Module-level metadata cache (for both middleware ETag and the
90
+ // standalone integrity() helper). Keyed by absolute file path; entries
91
+ // invalidated on mtime change.
92
+
93
+ var _cache = new Map();
94
+
95
+ async function _readMeta(absPath) {
96
+ var stat;
97
+ try { stat = await fsp.stat(absPath); }
98
+ catch (_e) { return null; }
99
+ if (!stat.isFile()) return null;
100
+
101
+ var cached = _cache.get(absPath);
102
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
103
+ return cached;
104
+ }
105
+
106
+ // Stream the file through SHA-384. fs.readFileSync is simpler but
107
+ // would balloon RSS for large assets — operators shouldn't be
108
+ // constrained on what they can serve.
109
+ var hash = nodeCrypto.createHash("sha384");
110
+ await new Promise(function (resolve, reject) {
111
+ var s = fs.createReadStream(absPath);
112
+ s.on("data", function (chunk) { hash.update(chunk); });
113
+ s.on("end", resolve);
114
+ s.on("error", reject);
115
+ });
116
+ var digest = hash.digest("base64");
117
+
118
+ var entry = {
119
+ mtimeMs: stat.mtimeMs,
120
+ size: stat.size,
121
+ etag: '"' + digest.slice(0, 27) + '"', // 162-bit ETag
122
+ integrity: "sha384-" + digest,
123
+ absPath: absPath,
124
+ };
125
+ _cache.set(absPath, entry);
126
+ return entry;
127
+ }
128
+
129
+ function _resolveSafe(root, requestedPath) {
130
+ if (typeof requestedPath !== "string" || requestedPath.length === 0) return null;
131
+ if (requestedPath.indexOf("\0") !== -1) return null;
132
+ // path.resolve handles ".." normalization; we then check containment.
133
+ var resolved = path.resolve(root, "." + requestedPath);
134
+ var rootResolved = path.resolve(root);
135
+ if (resolved !== rootResolved &&
136
+ !resolved.startsWith(rootResolved + path.sep)) return null;
137
+ return resolved;
138
+ }
139
+
140
+ function _contentTypeFor(filePath, table) {
141
+ var ext = path.extname(filePath).toLowerCase();
142
+ return (table && table[ext]) || DEFAULT_CONTENT_TYPES[ext] || "application/octet-stream";
143
+ }
144
+
145
+ function _writeNotModified(res, etag, cacheControl) {
146
+ res.writeHead(304, {
147
+ "ETag": etag,
148
+ "Cache-Control": cacheControl,
149
+ });
150
+ res.end();
151
+ }
152
+
153
+ function _writeNotFound(res) {
154
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8", "Content-Length": 9 });
155
+ res.end("Not Found");
156
+ }
157
+
158
+ // ---- Public: integrity() ----
159
+
160
+ async function integrity(absPath) {
161
+ if (typeof absPath !== "string" || absPath.length === 0) {
162
+ throw new Error("staticServe.integrity: absPath must be a non-empty string");
163
+ }
164
+ var meta = await _readMeta(path.resolve(absPath));
165
+ if (!meta) throw new Error("staticServe.integrity: file not found: " + absPath);
166
+ return meta.integrity;
167
+ }
168
+
169
+ // ---- Public: create() ----
170
+
171
+ function create(opts) {
172
+ opts = opts || {};
173
+ validateOpts(opts, [
174
+ "root", "mountPath", "hashedPathPattern",
175
+ "indexFile", "defaultMaxAge", "contentTypes",
176
+ ], "b.staticServe");
177
+ if (!opts.root) throw new Error("staticServe.create({ root }) is required");
178
+ if (!fs.existsSync(opts.root)) {
179
+ throw new Error("staticServe.create: root does not exist: " + opts.root);
180
+ }
181
+ var root = path.resolve(opts.root);
182
+ var mountPath = opts.mountPath || ""; // strip from req URL before lookup
183
+ var hashedPattern = opts.hashedPathPattern || DEFAULT_HASHED_PATTERN;
184
+ var indexFile = opts.indexFile === null ? null : (opts.indexFile || DEFAULT_INDEX_FILE);
185
+ var defaultMaxAge = typeof opts.defaultMaxAge === "number" ? opts.defaultMaxAge : DEFAULT_MAX_AGE_SEC;
186
+ var contentTypes = opts.contentTypes || null;
187
+
188
+ function _cacheControlFor(urlPath) {
189
+ if (hashedPattern.test(urlPath)) {
190
+ return "public, max-age=" + IMMUTABLE_MAX_AGE_SEC + ", immutable";
191
+ }
192
+ return "public, max-age=" + defaultMaxAge;
193
+ }
194
+
195
+ return async function staticServe(req, res, next) {
196
+ if (req.method !== "GET" && req.method !== "HEAD") return next();
197
+
198
+ // Strip query string + mount path before resolving against root.
199
+ var urlPath = (req.url || "").split("?")[0];
200
+ if (mountPath && urlPath.indexOf(mountPath) === 0) {
201
+ urlPath = urlPath.slice(mountPath.length) || "/";
202
+ }
203
+ // Decode percent-encoded path. Reject decoding failures (malformed URI).
204
+ var decoded;
205
+ try { decoded = decodeURIComponent(urlPath); }
206
+ catch (_e) { return next(); }
207
+
208
+ var absPath = _resolveSafe(root, decoded);
209
+ if (!absPath) return next();
210
+
211
+ // Directory → index file (if configured)
212
+ var stat;
213
+ try { stat = await fsp.stat(absPath); }
214
+ catch (_e) { return next(); }
215
+ if (stat.isDirectory()) {
216
+ if (!indexFile) return next();
217
+ absPath = path.join(absPath, indexFile);
218
+ }
219
+
220
+ var meta = await _readMeta(absPath);
221
+ if (!meta) return next();
222
+
223
+ var cacheControl = _cacheControlFor(urlPath);
224
+
225
+ // 304 short-circuit
226
+ var ifNone = req.headers && req.headers["if-none-match"];
227
+ if (ifNone && ifNone === meta.etag) {
228
+ return _writeNotModified(res, meta.etag, cacheControl);
229
+ }
230
+
231
+ var headers = {
232
+ "Content-Type": _contentTypeFor(absPath, contentTypes),
233
+ "Content-Length": meta.size,
234
+ "ETag": meta.etag,
235
+ "Cache-Control": cacheControl,
236
+ // SRI hint for templates that want to <script integrity=…>; not
237
+ // required by clients but consumers can read it from response
238
+ // headers when they want to embed integrity in subsequent pages.
239
+ "X-Integrity": meta.integrity,
240
+ };
241
+
242
+ if (req.method === "HEAD") {
243
+ res.writeHead(200, headers);
244
+ return res.end();
245
+ }
246
+
247
+ res.writeHead(200, headers);
248
+ var stream = fs.createReadStream(absPath);
249
+ stream.on("error", function (e) {
250
+ // Mid-stream read error — best we can do is destroy the response;
251
+ // headers are already on the wire.
252
+ try { res.destroy(e); } catch (_) {}
253
+ });
254
+ stream.pipe(res);
255
+ };
256
+ }
257
+
258
+ // ---- Test helper ----
259
+ function _resetCacheForTest() { _cache.clear(); }
260
+
261
+ module.exports = {
262
+ create: create,
263
+ integrity: integrity,
264
+ DEFAULT_MAX_AGE_SEC: DEFAULT_MAX_AGE_SEC,
265
+ IMMUTABLE_MAX_AGE_SEC: IMMUTABLE_MAX_AGE_SEC,
266
+ DEFAULT_HASHED_PATTERN: DEFAULT_HASHED_PATTERN,
267
+ _resetCacheForTest: _resetCacheForTest,
268
+ };