@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/tracing.js ADDED
@@ -0,0 +1,484 @@
1
+ "use strict";
2
+ /**
3
+ * tracing — OpenTelemetry seam without an OTel runtime dependency.
4
+ *
5
+ * The framework doesn't bundle the OTel SDK — operators install
6
+ * `@opentelemetry/api` (and an exporter) themselves when they want
7
+ * tracing. This module:
8
+ *
9
+ * - Detects if @opentelemetry/api is installed (try/catch require,
10
+ * cached). When it's there, every framework span call routes into
11
+ * OTel's real tracer and shows up in the operator's exporter
12
+ * (Jaeger, Zipkin, OTLP, console, whatever they wired).
13
+ * - When OTel ISN'T installed, every call is a pass-through. The
14
+ * wrapped function still executes, return values still propagate,
15
+ * thrown errors still escape — but no span is created and no
16
+ * overhead is paid beyond one cached lookup.
17
+ *
18
+ * Public API:
19
+ *
20
+ * var t = b.tracing.create({
21
+ * instrumentationName: "blamejs",
22
+ * instrumentationVersion: "1.0.0",
23
+ * });
24
+ *
25
+ * // Wrap async work in a span. Returns whatever fn returns.
26
+ * var result = await t.span("my-op", async function (span) {
27
+ * span.setAttribute("user_id", "abc");
28
+ * span.addEvent("cache-miss");
29
+ * return await doWork();
30
+ * }, { kind: "internal", attributes: { route: "/users" } });
31
+ *
32
+ * // Sync variant.
33
+ * var x = t.spanSync("compute", function (span) { ... return v; });
34
+ *
35
+ * // Read / write the current active span.
36
+ * t.currentSpan(); // null when no active span
37
+ * t.setAttributes({ user_id: "abc" }); // sets on current
38
+ * t.recordException(err); // records on current
39
+ *
40
+ * // HTTP propagation. contextHeaders() returns headers to add to
41
+ * // outbound requests (W3C `traceparent`); extractContext(headers)
42
+ * // pulls a parent context from inbound headers.
43
+ * var headers = t.contextHeaders();
44
+ * var parentCtx = t.extractContext(req.headers);
45
+ *
46
+ * // Auto-span request middleware — wraps each handler in a span
47
+ * // named after method + route pattern.
48
+ * router.use(t.requestMiddleware());
49
+ *
50
+ * // Framework-internal hot-path tap — wraps fn in a span named
51
+ * // `name` if a registry is active; pass-through otherwise. Like
52
+ * // metrics.tap() but for tracing instead of counting.
53
+ * b.tracing.tap("audit.record", attributes, fn);
54
+ *
55
+ * Even WITHOUT @opentelemetry/api installed:
56
+ * - contextHeaders() / extractContext() still parse and emit the
57
+ * W3C traceparent format. So a framework process without OTel
58
+ * can still propagate trace IDs through logs and HTTP for
59
+ * correlation, even without span telemetry. Operators get the
60
+ * "trace ID per request" plumbing as a free baseline.
61
+ * - currentSpan() returns a minimal pass-through "span" object so
62
+ * operator code can call setAttribute / addEvent / recordException
63
+ * unconditionally — they're no-ops without OTel.
64
+ *
65
+ * Why no @otel runtime dep:
66
+ * - The framework keeps zero npm runtime deps. Apps that want tracing
67
+ * install OTel themselves; apps that don't pay nothing.
68
+ * - The OTel API is unstable enough that pinning a vendored version
69
+ * would create more churn than it saves.
70
+ *
71
+ * Out of scope (with structural reasons):
72
+ * - Vendoring the SDK: see above.
73
+ * - Sampling decisions: OTel handles this when wired; without OTel
74
+ * there's nothing to sample.
75
+ * - Exporter integration: belongs to the OTel SDK, not the framework.
76
+ * - Custom propagators: framework ships W3C traceparent only. OTel
77
+ * adds others (b3, jaeger) when wired by the operator.
78
+ * - Async-context propagation across setTimeout / setImmediate:
79
+ * OTel's NodeSDK auto-instrumentation handles this when installed;
80
+ * without OTel, framework code uses fn-passing rather than
81
+ * async-context, which is fine for the surfaces we instrument.
82
+ */
83
+
84
+ var nodeCrypto = require("node:crypto");
85
+ var validateOpts = require("./validate-opts");
86
+ var { defineClass } = require("./framework-error");
87
+ var { resolveRoute, captureResponseStatus } = require("./request-helpers");
88
+
89
+ var TracingError = defineClass("TracingError", { alwaysPermanent: true });
90
+
91
+ // ---- OTel API detection (cached) ----
92
+ //
93
+ // Tri-state: undefined=not yet checked, null=not available, object=available.
94
+ // We cache the lookup so the per-call cost is one === comparison after
95
+ // the first call.
96
+
97
+ var _otel = undefined;
98
+
99
+ function _getOtel() {
100
+ if (_otel !== undefined) return _otel;
101
+ try { _otel = require("@opentelemetry/api"); }
102
+ catch (_e) { _otel = null; }
103
+ return _otel;
104
+ }
105
+
106
+ // Test seam — letting tests force the OTel resolution. Pass `null` to
107
+ // simulate "not installed" or an object to simulate a vendored OTel API.
108
+ function _setOtelForTest(value) { _otel = value === undefined ? undefined : value; }
109
+
110
+ // ---- W3C traceparent parsing ----
111
+ //
112
+ // Format: 00-<32 hex trace-id>-<16 hex span-id>-<2 hex flags>
113
+ // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
114
+ //
115
+ // We accept only version 00 (the only released W3C version). Future
116
+ // versions are technically forward-compatible but would extend the
117
+ // suffix; we ignore unrecognized versions to be safe.
118
+
119
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
120
+ // Special-case all-zeros: invalid per spec but malicious clients send them.
121
+ var ZERO_TRACE_ID = "0".repeat(32);
122
+ var ZERO_SPAN_ID = "0".repeat(16);
123
+
124
+ function _parseTraceparent(value) {
125
+ if (typeof value !== "string") return null;
126
+ var m = value.match(TRACEPARENT_RE);
127
+ if (!m) return null;
128
+ if (m[1] === ZERO_TRACE_ID || m[2] === ZERO_SPAN_ID) return null;
129
+ return { traceId: m[1], spanId: m[2], flags: m[3] };
130
+ }
131
+
132
+ function _formatTraceparent(traceId, spanId, flags) {
133
+ return "00-" + traceId + "-" + spanId + "-" + (flags || "01");
134
+ }
135
+
136
+ function _newTraceId() {
137
+ return nodeCrypto.randomBytes(16).toString("hex");
138
+ }
139
+ function _newSpanId() {
140
+ return nodeCrypto.randomBytes(8).toString("hex");
141
+ }
142
+
143
+ // ---- Pass-through span (used when OTel isn't installed) ----
144
+ //
145
+ // Operator code does span.setAttribute() / span.addEvent() /
146
+ // span.recordException() unconditionally. When OTel is absent these
147
+ // calls go to a minimal stub that no-ops but keeps the API shape
148
+ // compatible. Trace ID is still tracked so log correlation works.
149
+
150
+ function _passthroughSpan(traceId, spanId, parentSpanId) {
151
+ return {
152
+ spanContext: function () {
153
+ return { traceId: traceId, spanId: spanId, traceFlags: 1, isRemote: false };
154
+ },
155
+ setAttribute: function () { return this; },
156
+ setAttributes: function () { return this; },
157
+ addEvent: function () { return this; },
158
+ recordException: function () { return this; },
159
+ setStatus: function () { return this; },
160
+ updateName: function () { return this; },
161
+ end: function () { },
162
+ // Internal — used by the registry to thread parent context.
163
+ _isPassthrough: true,
164
+ _parentSpanId: parentSpanId,
165
+ };
166
+ }
167
+
168
+ // ---- Pass-through tracer (used when OTel isn't installed) ----
169
+ //
170
+ // Implements just enough of the OTel Tracer interface to drive the
171
+ // framework's span() / spanSync() helpers. Real OTel takes over when
172
+ // installed; the wrapper code below doesn't change.
173
+
174
+ function _passthroughTracer() {
175
+ // Active-span stack. Single-threaded JS lets us use a process-level
176
+ // stack rather than async-context. Pure synchronous span lifetimes
177
+ // are correct; async work that spans suspension points won't have
178
+ // currentSpan() right inside continuations — but the operator's fn
179
+ // still receives the span as its first arg, so attribute/event
180
+ // setting via that reference always works.
181
+ var stack = [];
182
+ return {
183
+ startSpan: function (_name, _opts) {
184
+ var traceId = stack.length > 0 ? stack[stack.length - 1].spanContext().traceId : _newTraceId();
185
+ var span = _passthroughSpan(traceId, _newSpanId(),
186
+ stack.length > 0 ? stack[stack.length - 1].spanContext().spanId : null);
187
+ return span;
188
+ },
189
+ _push: function (span) { stack.push(span); },
190
+ _pop: function () { return stack.pop(); },
191
+ _peek: function () { return stack.length > 0 ? stack[stack.length - 1] : null; },
192
+ _isPassthrough: true,
193
+ };
194
+ }
195
+
196
+ // ---- Registry factory ----
197
+
198
+ function create(opts) {
199
+ opts = opts || {};
200
+ validateOpts(opts, [
201
+ "instrumentationName", "instrumentationVersion",
202
+ ], "b.tracing");
203
+ var instrumentationName = opts.instrumentationName || "blamejs";
204
+ var instrumentationVersion = opts.instrumentationVersion || "0.0.0";
205
+
206
+ // Resolve the tracer once. If @opentelemetry/api is installed, this
207
+ // is a real Tracer; otherwise a passthrough. Cached per-registry so
208
+ // the passthrough stack persists across span() and currentSpan() calls
209
+ // (otherwise each call would see a fresh empty stack).
210
+ var _cachedPassthrough = null;
211
+ function _tracer() {
212
+ var otel = _getOtel();
213
+ if (otel) return otel.trace.getTracer(instrumentationName, instrumentationVersion);
214
+ if (!_cachedPassthrough) _cachedPassthrough = _passthroughTracer();
215
+ return _cachedPassthrough;
216
+ }
217
+
218
+ function _isReal() { return _getOtel() !== null; }
219
+
220
+ // Wrap fn in a span. The span is automatically end()'d on return /
221
+ // throw / promise rejection. exception is recorded on throw and
222
+ // span status set to ERROR.
223
+ function span(name, fn, sopts) {
224
+ if (typeof name !== "string" || name.length === 0) {
225
+ throw new TracingError("tracing/bad-name",
226
+ "span name must be a non-empty string");
227
+ }
228
+ if (typeof fn !== "function") {
229
+ throw new TracingError("tracing/bad-fn",
230
+ "span body must be a function");
231
+ }
232
+ sopts = sopts || {};
233
+ var tracer = _tracer();
234
+ var otel = _getOtel();
235
+
236
+ // Real OTel path — use context.with so async-context propagation
237
+ // works automatically once the SDK is wired.
238
+ if (otel) {
239
+ var spanInst = tracer.startSpan(name, {
240
+ kind: _kindFromString(otel, sopts.kind),
241
+ attributes: sopts.attributes,
242
+ });
243
+ // Run fn inside the span's context so child spans nest.
244
+ var ctx = otel.trace.setSpan(otel.context.active(), spanInst);
245
+ return otel.context.with(ctx, function () {
246
+ try {
247
+ var ret = fn(spanInst);
248
+ if (ret && typeof ret.then === "function") {
249
+ return ret.then(
250
+ function (v) { spanInst.end(); return v; },
251
+ function (e) {
252
+ spanInst.recordException(e);
253
+ spanInst.setStatus({ code: 2, message: (e && e.message) || String(e) });
254
+ spanInst.end();
255
+ throw e;
256
+ }
257
+ );
258
+ }
259
+ spanInst.end();
260
+ return ret;
261
+ } catch (e) {
262
+ spanInst.recordException(e);
263
+ spanInst.setStatus({ code: 2, message: (e && e.message) || String(e) });
264
+ spanInst.end();
265
+ throw e;
266
+ }
267
+ });
268
+ }
269
+
270
+ // Pass-through path — still wrap so operator code that calls
271
+ // span.setAttribute / addEvent / recordException works, just
272
+ // without telemetry output.
273
+ var pSpan = tracer.startSpan(name, sopts);
274
+ if (sopts.attributes) pSpan.setAttributes(sopts.attributes);
275
+ tracer._push(pSpan);
276
+ var done = false;
277
+ function _finish() { if (!done) { done = true; tracer._pop(); pSpan.end(); } }
278
+ try {
279
+ var res = fn(pSpan);
280
+ if (res && typeof res.then === "function") {
281
+ return res.then(
282
+ function (v) { _finish(); return v; },
283
+ function (e) { _finish(); throw e; }
284
+ );
285
+ }
286
+ _finish();
287
+ return res;
288
+ } catch (e) {
289
+ _finish();
290
+ throw e;
291
+ }
292
+ }
293
+
294
+ function spanSync(name, fn, sopts) {
295
+ if (typeof fn !== "function") {
296
+ throw new TracingError("tracing/bad-fn", "spanSync body must be a function");
297
+ }
298
+ // Same shape as span(); fn must return a non-Promise. We don't
299
+ // enforce that, but we don't await either — async fn used here
300
+ // ends the span before resolution.
301
+ return span(name, fn, sopts);
302
+ }
303
+
304
+ function currentSpan() {
305
+ var otel = _getOtel();
306
+ if (otel) {
307
+ var s = otel.trace.getActiveSpan();
308
+ return s || null;
309
+ }
310
+ var t = _tracer();
311
+ return t._peek();
312
+ }
313
+
314
+ function setAttributes(attrs) {
315
+ var s = currentSpan();
316
+ if (s) s.setAttributes(attrs);
317
+ }
318
+
319
+ function recordException(err) {
320
+ var s = currentSpan();
321
+ if (s) {
322
+ s.recordException(err);
323
+ s.setStatus({ code: 2, message: (err && err.message) || String(err) });
324
+ }
325
+ }
326
+
327
+ // ---- W3C propagation (works with or without OTel) ----
328
+
329
+ function contextHeaders() {
330
+ var s = currentSpan();
331
+ if (!s) return {};
332
+ var sc = s.spanContext ? s.spanContext() : null;
333
+ if (!sc || !sc.traceId || sc.traceId === ZERO_TRACE_ID) return {};
334
+ return {
335
+ traceparent: _formatTraceparent(sc.traceId, sc.spanId,
336
+ (sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(16).padStart(2, "0")),
337
+ };
338
+ }
339
+
340
+ // Parse traceparent from incoming headers. With OTel installed we
341
+ // use its propagation API for correctness across propagator types
342
+ // an operator might have configured; without OTel we fall back to
343
+ // our own parser which always understands W3C.
344
+ function extractContext(headers) {
345
+ if (!headers || typeof headers !== "object") return null;
346
+ var raw = headers.traceparent || headers["Traceparent"] || headers["TRACEPARENT"];
347
+ var parsed = _parseTraceparent(raw);
348
+ if (!parsed) return null;
349
+ return parsed;
350
+ }
351
+
352
+ // ---- request middleware ----
353
+
354
+ function requestMiddleware() {
355
+ return function tracingMiddleware(req, res, next) {
356
+ // Span starts BEFORE the router populates req.routePattern, so
357
+ // initial name+http.route come from the URL fallback. We promote
358
+ // both to the template form at res.end if the matcher set one.
359
+ var initialRoute = resolveRoute(req);
360
+ var spanName = "HTTP " + (req.method || "GET") + " " + initialRoute;
361
+ var attrs = {
362
+ "http.method": req.method || "GET",
363
+ "http.route": initialRoute,
364
+ "http.url": req.url || "",
365
+ };
366
+ var parent = extractContext(req.headers);
367
+ if (parent) {
368
+ attrs["traceparent.parent"] = parent.traceId + "-" + parent.spanId;
369
+ }
370
+ span(spanName, function (s) {
371
+ req.span = s;
372
+ captureResponseStatus(res, function (status) {
373
+ try {
374
+ s.setAttribute("http.status_code", status);
375
+ // Promote to route template if the router resolved one.
376
+ var finalRoute = resolveRoute(req);
377
+ if (finalRoute !== initialRoute) {
378
+ s.setAttribute("http.route", finalRoute);
379
+ if (typeof s.updateName === "function") {
380
+ s.updateName("HTTP " + (req.method || "GET") + " " + finalRoute);
381
+ }
382
+ }
383
+ } catch (_e) { /* span attr write must not break the response */ }
384
+ });
385
+ return next();
386
+ }, { attributes: attrs }).catch(function () {
387
+ // Span error already recorded by span() wrapper; the next()
388
+ // chain handles request-level error handling separately.
389
+ });
390
+ };
391
+ }
392
+
393
+ // ---- framework auto-tap ----
394
+ //
395
+ // Like metrics.tap, the tracing tap routes framework hot-path calls
396
+ // into spans when this registry is the active one.
397
+
398
+ var _activeOnTap = null;
399
+
400
+ function tap(name, attributes, fn) {
401
+ if (typeof attributes === "function") {
402
+ fn = attributes; attributes = null;
403
+ }
404
+ if (typeof fn !== "function") {
405
+ throw new TracingError("tracing/bad-fn", "tap fn must be a function");
406
+ }
407
+ if (_activeOnTap !== this && registry._isActive !== true) {
408
+ // No active registry — execute fn directly. Caller never knows.
409
+ return fn(currentSpan());
410
+ }
411
+ return span(name, fn, { attributes: attributes });
412
+ }
413
+
414
+ // OTel SpanKind enum values (from @opentelemetry/api). When OTel
415
+ // isn't installed, kind is just an attribute string.
416
+ function _kindFromString(otel, kindStr) {
417
+ if (!otel || !otel.SpanKind) return undefined;
418
+ if (kindStr === "server") return otel.SpanKind.SERVER;
419
+ if (kindStr === "client") return otel.SpanKind.CLIENT;
420
+ if (kindStr === "producer") return otel.SpanKind.PRODUCER;
421
+ if (kindStr === "consumer") return otel.SpanKind.CONSUMER;
422
+ return otel.SpanKind.INTERNAL;
423
+ }
424
+
425
+ var registry = {
426
+ span: span,
427
+ spanSync: spanSync,
428
+ currentSpan: currentSpan,
429
+ setAttributes: setAttributes,
430
+ recordException: recordException,
431
+ contextHeaders: contextHeaders,
432
+ extractContext: extractContext,
433
+ requestMiddleware: requestMiddleware,
434
+ tap: tap,
435
+ isReal: _isReal,
436
+ _isActive: true,
437
+ deactivate: function () {
438
+ registry._isActive = false;
439
+ if (_globalRegistry === registry) _globalRegistry = null;
440
+ },
441
+ };
442
+ _globalRegistry = registry;
443
+ return registry;
444
+ }
445
+
446
+ // ---- Global tap stub for framework modules ----
447
+ //
448
+ // Same pattern as metrics.tap: framework modules call b.tracing.tap()
449
+ // at hot paths. Without an active registry the call is a pass-through
450
+ // that just executes fn. With one, it routes into span().
451
+
452
+ var _globalRegistry = null;
453
+
454
+ function tap(name, attributes, fn) {
455
+ if (typeof attributes === "function") {
456
+ fn = attributes; attributes = null;
457
+ }
458
+ if (typeof fn !== "function") {
459
+ throw new TracingError("tracing/bad-fn", "tap fn must be a function");
460
+ }
461
+ if (_globalRegistry === null || _globalRegistry._isActive !== true) {
462
+ return fn(null);
463
+ }
464
+ return _globalRegistry.span(name, fn, { attributes: attributes });
465
+ }
466
+
467
+ function _resetForTest() {
468
+ _globalRegistry = null;
469
+ _setOtelForTest(undefined);
470
+ }
471
+
472
+ module.exports = {
473
+ create: create,
474
+ tap: tap,
475
+ TracingError: TracingError,
476
+ _setOtelForTest: _setOtelForTest,
477
+ _resetForTest: _resetForTest,
478
+ // Internal helpers exposed for tests
479
+ _parseTraceparent: _parseTraceparent,
480
+ _formatTraceparent: _formatTraceparent,
481
+ _newTraceId: _newTraceId,
482
+ _newSpanId: _newSpanId,
483
+ TRACEPARENT_RE: TRACEPARENT_RE,
484
+ };
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * validate-opts — shared Tier-A helper for primitive create() functions.
4
+ *
5
+ * Catches silent operator typos: `cors({ allowedOrigins: [] })` (wrong
6
+ * key name) currently looks like config but does nothing — the primitive
7
+ * sees opts.origins as undefined and falls back to defaults. With this
8
+ * helper the create() throws at boot with a clear message instead of
9
+ * the typo silently weakening the operator's setup.
10
+ *
11
+ * var validateOpts = require("./validate-opts");
12
+ *
13
+ * function create(opts) {
14
+ * opts = opts || {};
15
+ * validateOpts(opts, [
16
+ * "origins", "siteOrigin", "methods", "headers",
17
+ * "exposeHeaders", "credentials", "maxAgeSeconds", "refuseUnknown",
18
+ * ], "middleware.cors");
19
+ * ...
20
+ * }
21
+ *
22
+ * The exported `optional(...)` form lets a primitive accept a
23
+ * sparsely-populated opts object and only validate keys that ARE present.
24
+ *
25
+ * Throws a plain Error with a code-shaped message — primitives that want
26
+ * a typed error wrap the call.
27
+ */
28
+
29
+ function _format(primitive, unknownKey, allowedKeys) {
30
+ return primitive + ": unknown option '" + unknownKey + "'. " +
31
+ "Allowed keys: " + allowedKeys.slice().sort().join(", ") + ".";
32
+ }
33
+
34
+ function check(opts, allowedKeys, primitive) {
35
+ if (opts == null) return;
36
+ if (typeof opts !== "object") {
37
+ throw new Error(primitive + ": opts must be an object (got " + typeof opts + ")");
38
+ }
39
+ if (!Array.isArray(allowedKeys) || allowedKeys.length === 0) {
40
+ throw new Error("validate-opts: allowedKeys must be a non-empty array");
41
+ }
42
+ if (typeof primitive !== "string" || primitive.length === 0) {
43
+ throw new Error("validate-opts: primitive name must be a non-empty string");
44
+ }
45
+ var allowSet = Object.create(null);
46
+ for (var i = 0; i < allowedKeys.length; i++) allowSet[allowedKeys[i]] = true;
47
+ var keys = Object.keys(opts);
48
+ for (var j = 0; j < keys.length; j++) {
49
+ if (!allowSet[keys[j]]) {
50
+ throw new Error(_format(primitive, keys[j], allowedKeys));
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = check;
56
+ module.exports.check = check;