@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
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ /**
3
+ * api-snapshot — public API surface walker + breaking-change detector.
4
+ *
5
+ * The framework's LTS-contract enforcement at the type level. Walks
6
+ * the framework's module exports recursively, records every member's
7
+ * type, and compares two snapshots to find:
8
+ *
9
+ * - removed a member present in the old snapshot but not the new
10
+ * (BREAKING — fails CI)
11
+ * - typeChanged a member's category flipped (function → object, etc.)
12
+ * (BREAKING — fails CI)
13
+ * - added a new member that wasn't in the old snapshot
14
+ * (ADDITIVE — does not fail; signals the snapshot is
15
+ * out-of-date and the operator should rerun capture)
16
+ *
17
+ * var snap = b.apiSnapshot.capture(require("@blamejs/core"));
18
+ * // { version, frameworkVersion, createdAt,
19
+ * // exports: { ... nested tree ... } }
20
+ *
21
+ * b.apiSnapshot.write(snap, "./api-snapshot.json");
22
+ * var loaded = b.apiSnapshot.read("./api-snapshot.json");
23
+ *
24
+ * var diff = b.apiSnapshot.compare(loaded, snap);
25
+ * // { breaking: [{ path, kind, was?, is? }],
26
+ * // typeChanged: [{ path, was, is }],
27
+ * // additive: [{ path, type }] }
28
+ *
29
+ * if (diff.breaking.length > 0 || diff.typeChanged.length > 0) {
30
+ * console.error(b.apiSnapshot.formatDiff(diff));
31
+ * process.exit(1);
32
+ * }
33
+ *
34
+ * Walker rules:
35
+ * - Functions record as { type: 'function', arity: fn.length }.
36
+ * Class constructors are still 'function' — recursive scope walks
37
+ * prototype only when the operator explicitly opts in via
38
+ * opts.includeClassPrototypes.
39
+ * - Plain objects recurse into their own enumerable string keys.
40
+ * - Primitives (string, number, boolean, null, undefined) record as
41
+ * { type: 'primitive', valueType: typeof v }. Specific values are
42
+ * NOT captured — only the type — so a version-string change in
43
+ * constants doesn't fail CI.
44
+ * - Members whose key starts with '_' are skipped (test seams,
45
+ * internal helpers).
46
+ * - Cycles are detected and short-circuit as { type: 'cycle' }.
47
+ * - Non-plain objects (Map, Set, Buffer, Date, RegExp, Error, etc.)
48
+ * are recorded as { type: 'instance', constructor: name } without
49
+ * recursion — they're terminal nodes.
50
+ */
51
+
52
+ var fs = require("fs");
53
+ var safeJson = require("./safe-json");
54
+ var { FrameworkError } = require("./framework-error");
55
+
56
+ class ApiSnapshotError extends FrameworkError {
57
+ constructor(code, message) {
58
+ super(message, code);
59
+ this.name = "ApiSnapshotError";
60
+ this.permanent = true;
61
+ this.isApiSnapshotError = true;
62
+ }
63
+ }
64
+
65
+ var SNAPSHOT_FORMAT_VERSION = 1;
66
+
67
+ function _isPlainObject(v) {
68
+ if (v === null || typeof v !== "object") return false;
69
+ var proto = Object.getPrototypeOf(v);
70
+ return proto === Object.prototype || proto === null;
71
+ }
72
+
73
+ function _walkNode(value, depth, maxDepth, seen, skipUnderscore) {
74
+ if (value === null) return { type: "primitive", valueType: "null" };
75
+ var t = typeof value;
76
+ if (t === "undefined") return { type: "primitive", valueType: "undefined" };
77
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol") {
78
+ return { type: "primitive", valueType: t };
79
+ }
80
+ if (t === "function") {
81
+ return { type: "function", arity: value.length };
82
+ }
83
+ // Object — guard cycles + depth
84
+ if (seen.has(value)) return { type: "cycle" };
85
+ if (depth >= maxDepth) return { type: "deep", note: "max depth" };
86
+
87
+ if (!_isPlainObject(value)) {
88
+ var name = value && value.constructor && value.constructor.name
89
+ ? value.constructor.name
90
+ : "Object";
91
+ // Use 'ctorName' instead of 'constructor' — json-safe.parse strips
92
+ // 'constructor' as a prototype-pollution defense, which would
93
+ // round-trip-mangle every instance node otherwise.
94
+ return { type: "instance", ctorName: name };
95
+ }
96
+
97
+ // Plain object — recurse
98
+ seen.add(value);
99
+ var members = {};
100
+ var keys = Object.keys(value);
101
+ // Stable sort for canonical snapshot bytes
102
+ keys.sort();
103
+ for (var i = 0; i < keys.length; i++) {
104
+ var k = keys[i];
105
+ if (skipUnderscore && k.charAt(0) === "_") continue;
106
+ members[k] = _walkNode(value[k], depth + 1, maxDepth, seen, skipUnderscore);
107
+ }
108
+ seen.delete(value);
109
+ return { type: "object", members: members };
110
+ }
111
+
112
+ function capture(target, opts) {
113
+ opts = opts || {};
114
+ if (!target || typeof target !== "object") {
115
+ throw new ApiSnapshotError("api-snapshot/bad-target",
116
+ "capture: target must be a module's exports object");
117
+ }
118
+ var maxDepth = typeof opts.maxDepth === "number" && opts.maxDepth > 0 ? opts.maxDepth : 8;
119
+ var skipUnderscore = opts.skipUnderscore !== false;
120
+ var snapshot = _walkNode(target, 0, maxDepth, new Set(), skipUnderscore);
121
+ if (snapshot.type !== "object") {
122
+ throw new ApiSnapshotError("api-snapshot/bad-target",
123
+ "capture: top-level target must be a plain object (got '" + snapshot.type + "')");
124
+ }
125
+ return {
126
+ version: SNAPSHOT_FORMAT_VERSION,
127
+ frameworkVersion: typeof opts.frameworkVersion === "string" && opts.frameworkVersion.length > 0
128
+ ? opts.frameworkVersion
129
+ : (target.version || "0.0.0"),
130
+ createdAt: opts.createdAt || new Date().toISOString(),
131
+ exports: snapshot.members,
132
+ };
133
+ }
134
+
135
+ function write(snapshot, filePath) {
136
+ if (!snapshot || typeof snapshot !== "object") {
137
+ throw new ApiSnapshotError("api-snapshot/bad-snapshot",
138
+ "write: snapshot must be a snapshot object (returned by capture)");
139
+ }
140
+ if (typeof filePath !== "string" || filePath.length === 0) {
141
+ throw new ApiSnapshotError("api-snapshot/bad-path",
142
+ "write: filePath is required");
143
+ }
144
+ // Stringify with stable key order via the explicit canonical form
145
+ var canonical = {
146
+ version: snapshot.version,
147
+ frameworkVersion: snapshot.frameworkVersion,
148
+ createdAt: snapshot.createdAt,
149
+ exports: snapshot.exports,
150
+ };
151
+ fs.writeFileSync(filePath, JSON.stringify(canonical, null, 2) + "\n", { mode: 0o644 });
152
+ return filePath;
153
+ }
154
+
155
+ function read(filePath) {
156
+ if (typeof filePath !== "string" || filePath.length === 0) {
157
+ throw new ApiSnapshotError("api-snapshot/bad-path",
158
+ "read: filePath is required");
159
+ }
160
+ if (!fs.existsSync(filePath)) {
161
+ throw new ApiSnapshotError("api-snapshot/missing",
162
+ "read: snapshot file not found at " + filePath);
163
+ }
164
+ var raw;
165
+ try { raw = fs.readFileSync(filePath, "utf8"); }
166
+ catch (e) {
167
+ throw new ApiSnapshotError("api-snapshot/read-failed",
168
+ "read: cannot read " + filePath + ": " + ((e && e.message) || String(e)));
169
+ }
170
+ var parsed;
171
+ try { parsed = safeJson.parse(raw); }
172
+ catch (e) {
173
+ throw new ApiSnapshotError("api-snapshot/bad-json",
174
+ "read: not valid JSON: " + ((e && e.message) || String(e)));
175
+ }
176
+ if (!parsed || parsed.version !== SNAPSHOT_FORMAT_VERSION) {
177
+ throw new ApiSnapshotError("api-snapshot/bad-version",
178
+ "read: snapshot version is " + (parsed && parsed.version) +
179
+ ", expected " + SNAPSHOT_FORMAT_VERSION);
180
+ }
181
+ if (!parsed.exports || typeof parsed.exports !== "object") {
182
+ throw new ApiSnapshotError("api-snapshot/bad-shape",
183
+ "read: snapshot is missing 'exports' object");
184
+ }
185
+ return parsed;
186
+ }
187
+
188
+ // Walk both trees in parallel under a path. Append to breaking,
189
+ // additive, typeChanged.
190
+ function _walkCompare(oldNode, newNode, prefix, breaking, additive, typeChanged) {
191
+ // Both should describe the same node. If types differ at the node
192
+ // level, that's a breaking type change.
193
+ if (!oldNode || !newNode) return;
194
+
195
+ if (oldNode.type !== newNode.type) {
196
+ typeChanged.push({
197
+ path: prefix,
198
+ was: oldNode.type,
199
+ is: newNode.type,
200
+ });
201
+ breaking.push({ path: prefix, kind: "type-changed", was: oldNode.type, is: newNode.type });
202
+ return;
203
+ }
204
+
205
+ if (oldNode.type === "object") {
206
+ var oldMembers = oldNode.members || {};
207
+ var newMembers = newNode.members || {};
208
+ var oldKeys = Object.keys(oldMembers);
209
+ var newKeys = Object.keys(newMembers);
210
+
211
+ // Removed: in old, not in new
212
+ for (var i = 0; i < oldKeys.length; i++) {
213
+ var ok = oldKeys[i];
214
+ var childPath = prefix ? (prefix + "." + ok) : ok;
215
+ if (!Object.prototype.hasOwnProperty.call(newMembers, ok)) {
216
+ breaking.push({ path: childPath, kind: "removed", was: oldMembers[ok].type });
217
+ } else {
218
+ _walkCompare(oldMembers[ok], newMembers[ok], childPath, breaking, additive, typeChanged);
219
+ }
220
+ }
221
+ // Added: in new, not in old
222
+ for (var j = 0; j < newKeys.length; j++) {
223
+ var nk = newKeys[j];
224
+ if (!Object.prototype.hasOwnProperty.call(oldMembers, nk)) {
225
+ var addPath = prefix ? (prefix + "." + nk) : nk;
226
+ additive.push({ path: addPath, type: newMembers[nk].type });
227
+ }
228
+ }
229
+ return;
230
+ }
231
+
232
+ // For function nodes, arity DROPS are flagged (operator removed a
233
+ // required parameter). Arity INCREASES are not flagged (added
234
+ // optional param at the end is additive).
235
+ if (oldNode.type === "function") {
236
+ if (typeof oldNode.arity === "number" && typeof newNode.arity === "number" &&
237
+ newNode.arity < oldNode.arity) {
238
+ breaking.push({
239
+ path: prefix,
240
+ kind: "arity-decreased",
241
+ was: "function/" + oldNode.arity,
242
+ is: "function/" + newNode.arity,
243
+ });
244
+ }
245
+ return;
246
+ }
247
+
248
+ // For instance nodes, a constructor-name change is breaking
249
+ if (oldNode.type === "instance") {
250
+ if (oldNode.ctorName !== newNode.ctorName) {
251
+ breaking.push({
252
+ path: prefix,
253
+ kind: "constructor-changed",
254
+ was: oldNode.ctorName,
255
+ is: newNode.ctorName,
256
+ });
257
+ }
258
+ return;
259
+ }
260
+
261
+ // For primitive nodes, a valueType change is breaking
262
+ if (oldNode.type === "primitive") {
263
+ if (oldNode.valueType !== newNode.valueType) {
264
+ breaking.push({
265
+ path: prefix,
266
+ kind: "primitive-type-changed",
267
+ was: oldNode.valueType,
268
+ is: newNode.valueType,
269
+ });
270
+ }
271
+ return;
272
+ }
273
+
274
+ // cycle / deep — terminal, nothing more to compare
275
+ }
276
+
277
+ function compare(oldSnapshot, newSnapshot) {
278
+ if (!oldSnapshot || !oldSnapshot.exports) {
279
+ throw new ApiSnapshotError("api-snapshot/bad-snapshot",
280
+ "compare: oldSnapshot is required (a snapshot from read()/capture())");
281
+ }
282
+ if (!newSnapshot || !newSnapshot.exports) {
283
+ throw new ApiSnapshotError("api-snapshot/bad-snapshot",
284
+ "compare: newSnapshot is required (a snapshot from capture())");
285
+ }
286
+ var breaking = [];
287
+ var additive = [];
288
+ var typeChanged = [];
289
+ // Wrap exports in an object node so the recursion treats them uniformly
290
+ _walkCompare(
291
+ { type: "object", members: oldSnapshot.exports },
292
+ { type: "object", members: newSnapshot.exports },
293
+ "", breaking, additive, typeChanged
294
+ );
295
+ return { breaking: breaking, additive: additive, typeChanged: typeChanged };
296
+ }
297
+
298
+ function formatDiff(diff) {
299
+ if (!diff || typeof diff !== "object") {
300
+ throw new ApiSnapshotError("api-snapshot/bad-diff",
301
+ "formatDiff: argument must be a diff result from compare()");
302
+ }
303
+ var lines = [];
304
+ if (diff.breaking.length === 0 && diff.additive.length === 0) {
305
+ return "[api-snapshot] no changes";
306
+ }
307
+ if (diff.breaking.length > 0) {
308
+ lines.push("[api-snapshot] BREAKING (" + diff.breaking.length + "):");
309
+ for (var i = 0; i < diff.breaking.length; i++) {
310
+ var b = diff.breaking[i];
311
+ var line = " - " + b.path + " (" + b.kind + ")";
312
+ if (b.was !== undefined) line += " was=" + JSON.stringify(b.was);
313
+ if (b.is !== undefined) line += " is=" + JSON.stringify(b.is);
314
+ lines.push(line);
315
+ }
316
+ }
317
+ if (diff.additive.length > 0) {
318
+ lines.push("[api-snapshot] additive (" + diff.additive.length + ", informational):");
319
+ for (var j = 0; j < diff.additive.length; j++) {
320
+ var a = diff.additive[j];
321
+ lines.push(" + " + a.path + " (" + a.type + ")");
322
+ }
323
+ }
324
+ return lines.join("\n");
325
+ }
326
+
327
+ module.exports = {
328
+ capture: capture,
329
+ write: write,
330
+ read: read,
331
+ compare: compare,
332
+ formatDiff: formatDiff,
333
+ SNAPSHOT_FORMAT_VERSION: SNAPSHOT_FORMAT_VERSION,
334
+ ApiSnapshotError: ApiSnapshotError,
335
+ };