@blamejs/core 0.7.4 → 0.7.18

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 (180) hide show
  1. package/CHANGELOG.md +423 -395
  2. package/README.md +150 -149
  3. package/bin/blamejs.js +0 -0
  4. package/index.js +308 -284
  5. package/lib/api-key.js +660 -663
  6. package/lib/api-snapshot.js +338 -338
  7. package/lib/app-shutdown.js +385 -385
  8. package/lib/app.js +365 -365
  9. package/lib/archive.js +250 -250
  10. package/lib/atomic-file.js +544 -544
  11. package/lib/audit-chain.js +177 -177
  12. package/lib/audit-sign.js +344 -344
  13. package/lib/audit-tools.js +677 -677
  14. package/lib/audit.js +766 -766
  15. package/lib/auth/jwt.js +311 -311
  16. package/lib/auth/lockout.js +436 -436
  17. package/lib/auth/oauth.js +721 -721
  18. package/lib/auth/passkey.js +181 -181
  19. package/lib/auth/password.js +594 -594
  20. package/lib/backup/bundle.js +217 -217
  21. package/lib/backup/crypto.js +176 -176
  22. package/lib/backup/index.js +515 -515
  23. package/lib/backup/manifest.js +282 -282
  24. package/lib/break-glass.js +1338 -1338
  25. package/lib/bundler.js +441 -441
  26. package/lib/cache-redis.js +256 -256
  27. package/lib/cache.js +1206 -1206
  28. package/lib/canonical-json.js +115 -115
  29. package/lib/chain-writer.js +234 -234
  30. package/lib/cli-helpers.js +206 -206
  31. package/lib/cli.js +2334 -2334
  32. package/lib/cluster-provider-db.js +317 -317
  33. package/lib/cluster-storage.js +226 -226
  34. package/lib/cluster.js +703 -703
  35. package/lib/codepoint-class.js +196 -0
  36. package/lib/config-drift.js +301 -301
  37. package/lib/consent.js +222 -222
  38. package/lib/constants.js +191 -191
  39. package/lib/cookies.js +315 -315
  40. package/lib/credential-hash.js +322 -322
  41. package/lib/crypto.js +266 -266
  42. package/lib/csv.js +275 -286
  43. package/lib/db-declare-row-policy.js +267 -267
  44. package/lib/db-declare-view.js +420 -421
  45. package/lib/db-query.js +406 -406
  46. package/lib/db-schema.js +319 -319
  47. package/lib/db.js +1288 -1288
  48. package/lib/deprecate.js +222 -222
  49. package/lib/dev.js +335 -335
  50. package/lib/dual-control.js +473 -473
  51. package/lib/error-page.js +420 -420
  52. package/lib/external-db-migrate.js +441 -441
  53. package/lib/external-db.js +1061 -1061
  54. package/lib/file-type.js +273 -273
  55. package/lib/file-upload.js +213 -10
  56. package/lib/forms.js +422 -422
  57. package/lib/framework-error.js +293 -215
  58. package/lib/framework-schema.js +717 -717
  59. package/lib/gate-contract.js +971 -0
  60. package/lib/guard-all.js +405 -0
  61. package/lib/guard-archive.js +739 -0
  62. package/lib/guard-csv.js +816 -0
  63. package/lib/guard-email.js +744 -0
  64. package/lib/guard-filename.js +724 -0
  65. package/lib/guard-html.js +976 -0
  66. package/lib/guard-json.js +729 -0
  67. package/lib/guard-markdown.js +586 -0
  68. package/lib/guard-svg.js +976 -0
  69. package/lib/guard-xml.js +405 -0
  70. package/lib/guard-yaml.js +529 -0
  71. package/lib/handlers.js +350 -350
  72. package/lib/http-client-cookie-jar.js +508 -508
  73. package/lib/http-client.js +1195 -1195
  74. package/lib/i18n.js +878 -878
  75. package/lib/jobs.js +185 -185
  76. package/lib/log-stream-cloudwatch.js +369 -369
  77. package/lib/log-stream-local.js +146 -146
  78. package/lib/log-stream-otlp-grpc.js +410 -410
  79. package/lib/log-stream-otlp.js +286 -286
  80. package/lib/log-stream-syslog.js +302 -302
  81. package/lib/log-stream-webhook.js +199 -199
  82. package/lib/log-stream.js +330 -330
  83. package/lib/log.js +500 -500
  84. package/lib/mail-bounce.js +528 -528
  85. package/lib/mail-dkim.js +369 -362
  86. package/lib/mail.js +981 -962
  87. package/lib/metrics.js +683 -683
  88. package/lib/middleware/api-encrypt.js +936 -936
  89. package/lib/middleware/attach-user.js +157 -157
  90. package/lib/middleware/body-parser.js +1170 -1091
  91. package/lib/middleware/bot-guard.js +178 -178
  92. package/lib/middleware/compression.js +452 -452
  93. package/lib/middleware/cors.js +314 -314
  94. package/lib/middleware/csp-nonce.js +348 -348
  95. package/lib/middleware/csrf-protect.js +316 -316
  96. package/lib/middleware/db-role-for.js +264 -264
  97. package/lib/middleware/health.js +392 -392
  98. package/lib/middleware/index.js +79 -79
  99. package/lib/middleware/rate-limit.js +358 -358
  100. package/lib/middleware/request-id.js +61 -61
  101. package/lib/middleware/request-log.js +168 -168
  102. package/lib/middleware/require-auth.js +104 -104
  103. package/lib/middleware/security-headers.js +116 -116
  104. package/lib/middleware/sse.js +166 -166
  105. package/lib/migrations.js +383 -383
  106. package/lib/mtls-ca.js +518 -518
  107. package/lib/mtls-engine-default.js +481 -481
  108. package/lib/network-dns.js +632 -632
  109. package/lib/network-heartbeat.js +290 -290
  110. package/lib/network-nts.js +574 -574
  111. package/lib/network-proxy.js +265 -265
  112. package/lib/network-tls.js +328 -328
  113. package/lib/network.js +233 -233
  114. package/lib/notify.js +612 -612
  115. package/lib/ntp-check.js +229 -229
  116. package/lib/numeric-bounds.js +111 -91
  117. package/lib/object-store/azure-blob-bucket-ops.js +349 -349
  118. package/lib/object-store/azure-blob.js +488 -488
  119. package/lib/object-store/gcs-bucket-ops.js +351 -351
  120. package/lib/object-store/gcs.js +519 -519
  121. package/lib/object-store/http-put.js +153 -153
  122. package/lib/object-store/index.js +197 -197
  123. package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
  124. package/lib/object-store/sigv4.js +903 -903
  125. package/lib/observability.js +151 -151
  126. package/lib/otel-export.js +269 -269
  127. package/lib/pagination.js +464 -464
  128. package/lib/parsers/index.js +80 -80
  129. package/lib/parsers/safe-env.js +642 -642
  130. package/lib/parsers/safe-ini.js +292 -292
  131. package/lib/parsers/safe-toml.js +784 -784
  132. package/lib/parsers/safe-xml.js +390 -390
  133. package/lib/parsers/safe-yaml.js +1015 -1015
  134. package/lib/permissions.js +708 -708
  135. package/lib/pqc-agent.js +87 -87
  136. package/lib/pqc-gate.js +279 -279
  137. package/lib/protobuf-encoder.js +190 -190
  138. package/lib/protocol-dispatcher.js +161 -161
  139. package/lib/pubsub-redis.js +167 -167
  140. package/lib/pubsub.js +429 -429
  141. package/lib/queue-local.js +476 -476
  142. package/lib/queue-redis.js +745 -745
  143. package/lib/queue-sqs.js +319 -319
  144. package/lib/queue.js +695 -695
  145. package/lib/redis-client.js +519 -519
  146. package/lib/request-helpers.js +340 -340
  147. package/lib/restore-bundle.js +237 -237
  148. package/lib/restore-rollback.js +259 -259
  149. package/lib/restore.js +409 -409
  150. package/lib/retry.js +376 -376
  151. package/lib/router.js +748 -748
  152. package/lib/safe-async.js +735 -735
  153. package/lib/safe-buffer.js +237 -237
  154. package/lib/safe-json.js +541 -541
  155. package/lib/safe-schema.js +1266 -1266
  156. package/lib/safe-url.js +159 -159
  157. package/lib/scheduler.js +706 -706
  158. package/lib/security-assert.js +373 -373
  159. package/lib/seeders.js +618 -618
  160. package/lib/session.js +478 -478
  161. package/lib/slug.js +269 -269
  162. package/lib/ssrf-guard.js +401 -401
  163. package/lib/static.js +184 -4
  164. package/lib/storage.js +471 -471
  165. package/lib/subject.js +281 -281
  166. package/lib/template.js +791 -791
  167. package/lib/testing.js +798 -798
  168. package/lib/time.js +310 -310
  169. package/lib/totp.js +302 -302
  170. package/lib/tracing.js +494 -494
  171. package/lib/uuid.js +132 -132
  172. package/lib/validate-opts.js +340 -319
  173. package/lib/vault/index.js +308 -308
  174. package/lib/vault/rotate.js +784 -784
  175. package/lib/vault/wrap.js +296 -296
  176. package/lib/vendor/noble-ciphers.cjs +9 -9
  177. package/lib/webhook.js +595 -595
  178. package/lib/websocket.js +1048 -1048
  179. package/package.json +77 -77
  180. package/sbom.cyclonedx.json +7 -7
package/lib/tracing.js CHANGED
@@ -1,494 +1,494 @@
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 C = require("./constants");
85
- var crypto = require("./crypto");
86
- var validateOpts = require("./validate-opts");
87
- var { defineClass } = require("./framework-error");
88
- var { resolveRoute, captureResponseStatus } = require("./request-helpers");
89
-
90
- // W3C trace-context fixed widths (RFC TraceContext §3.2.2.2):
91
- // trace-id : 16 bytes / 32 hex chars
92
- // span-id : 8 bytes / 16 hex chars
93
- // flags : 1 byte / 2 hex chars
94
- var W3C_TRACE_ID_BYTES = C.BYTES.bytes(16);
95
- var W3C_SPAN_ID_BYTES = C.BYTES.bytes(8);
96
- var HEX_RADIX = 0x10;
97
-
98
- var TracingError = defineClass("TracingError", { alwaysPermanent: true });
99
-
100
- // ---- OTel API detection (cached) ----
101
- //
102
- // Tri-state: undefined=not yet checked, null=not available, object=available.
103
- // We cache the lookup so the per-call cost is one === comparison after
104
- // the first call.
105
-
106
- var _otel = undefined;
107
-
108
- function _getOtel() {
109
- if (_otel !== undefined) return _otel;
110
- try { _otel = require("@opentelemetry/api"); }
111
- catch (_e) { _otel = null; }
112
- return _otel;
113
- }
114
-
115
- // Test seam — letting tests force the OTel resolution. Pass `null` to
116
- // simulate "not installed" or an object to simulate a vendored OTel API.
117
- function _setOtelForTest(value) { _otel = value === undefined ? undefined : value; }
118
-
119
- // ---- W3C traceparent parsing ----
120
- //
121
- // Format: 00-<32 hex trace-id>-<16 hex span-id>-<2 hex flags>
122
- // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
123
- //
124
- // We accept only version 00 (the only released W3C version). Future
125
- // versions are technically forward-compatible but would extend the
126
- // suffix; we ignore unrecognized versions to be safe.
127
-
128
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
129
- // Special-case all-zeros: invalid per spec but malicious clients send them.
130
- // (32 = W3C_TRACE_ID_BYTES * 2 hex chars; 16 = W3C_SPAN_ID_BYTES * 2.)
131
- var ZERO_TRACE_ID = "0".repeat(W3C_TRACE_ID_BYTES * 2);
132
- var ZERO_SPAN_ID = "0".repeat(W3C_SPAN_ID_BYTES * 2);
133
-
134
- function _parseTraceparent(value) {
135
- if (typeof value !== "string") return null;
136
- var m = value.match(TRACEPARENT_RE);
137
- if (!m) return null;
138
- if (m[1] === ZERO_TRACE_ID || m[2] === ZERO_SPAN_ID) return null;
139
- return { traceId: m[1], spanId: m[2], flags: m[3] };
140
- }
141
-
142
- function _formatTraceparent(traceId, spanId, flags) {
143
- return "00-" + traceId + "-" + spanId + "-" + (flags || "01");
144
- }
145
-
146
- function _newTraceId() {
147
- return crypto.generateToken(W3C_TRACE_ID_BYTES);
148
- }
149
- function _newSpanId() {
150
- return crypto.generateToken(W3C_SPAN_ID_BYTES);
151
- }
152
-
153
- // ---- Pass-through span (used when OTel isn't installed) ----
154
- //
155
- // Operator code does span.setAttribute() / span.addEvent() /
156
- // span.recordException() unconditionally. When OTel is absent these
157
- // calls go to a minimal stub that no-ops but keeps the API shape
158
- // compatible. Trace ID is still tracked so log correlation works.
159
-
160
- function _passthroughSpan(traceId, spanId, parentSpanId) {
161
- return {
162
- spanContext: function () {
163
- return { traceId: traceId, spanId: spanId, traceFlags: 1, isRemote: false };
164
- },
165
- setAttribute: function () { return this; },
166
- setAttributes: function () { return this; },
167
- addEvent: function () { return this; },
168
- recordException: function () { return this; },
169
- setStatus: function () { return this; },
170
- updateName: function () { return this; },
171
- end: function () { },
172
- // Internal — used by the registry to thread parent context.
173
- _isPassthrough: true,
174
- _parentSpanId: parentSpanId,
175
- };
176
- }
177
-
178
- // ---- Pass-through tracer (used when OTel isn't installed) ----
179
- //
180
- // Implements just enough of the OTel Tracer interface to drive the
181
- // framework's span() / spanSync() helpers. Real OTel takes over when
182
- // installed; the wrapper code below doesn't change.
183
-
184
- function _passthroughTracer() {
185
- // Active-span stack. Single-threaded JS lets us use a process-level
186
- // stack rather than async-context. Pure synchronous span lifetimes
187
- // are correct; async work that spans suspension points won't have
188
- // currentSpan() right inside continuations — but the operator's fn
189
- // still receives the span as its first arg, so attribute/event
190
- // setting via that reference always works.
191
- var stack = [];
192
- return {
193
- startSpan: function (_name, _opts) {
194
- var traceId = stack.length > 0 ? stack[stack.length - 1].spanContext().traceId : _newTraceId();
195
- var span = _passthroughSpan(traceId, _newSpanId(),
196
- stack.length > 0 ? stack[stack.length - 1].spanContext().spanId : null);
197
- return span;
198
- },
199
- _push: function (span) { stack.push(span); },
200
- _pop: function () { return stack.pop(); },
201
- _peek: function () { return stack.length > 0 ? stack[stack.length - 1] : null; },
202
- _isPassthrough: true,
203
- };
204
- }
205
-
206
- // ---- Registry factory ----
207
-
208
- function create(opts) {
209
- opts = opts || {};
210
- validateOpts(opts, [
211
- "instrumentationName", "instrumentationVersion",
212
- ], "b.tracing");
213
- var instrumentationName = opts.instrumentationName || "blamejs";
214
- var instrumentationVersion = opts.instrumentationVersion || "0.0.0";
215
-
216
- // Resolve the tracer once. If @opentelemetry/api is installed, this
217
- // is a real Tracer; otherwise a passthrough. Cached per-registry so
218
- // the passthrough stack persists across span() and currentSpan() calls
219
- // (otherwise each call would see a fresh empty stack).
220
- var _cachedPassthrough = null;
221
- function _tracer() {
222
- var otel = _getOtel();
223
- if (otel) return otel.trace.getTracer(instrumentationName, instrumentationVersion);
224
- if (!_cachedPassthrough) _cachedPassthrough = _passthroughTracer();
225
- return _cachedPassthrough;
226
- }
227
-
228
- function _isReal() { return _getOtel() !== null; }
229
-
230
- // Wrap fn in a span. The span is automatically end()'d on return /
231
- // throw / promise rejection. exception is recorded on throw and
232
- // span status set to ERROR.
233
- function span(name, fn, sopts) {
234
- if (typeof name !== "string" || name.length === 0) {
235
- throw new TracingError("tracing/bad-name",
236
- "span name must be a non-empty string");
237
- }
238
- if (typeof fn !== "function") {
239
- throw new TracingError("tracing/bad-fn",
240
- "span body must be a function");
241
- }
242
- sopts = sopts || {};
243
- var tracer = _tracer();
244
- var otel = _getOtel();
245
-
246
- // Real OTel path — use context.with so async-context propagation
247
- // works automatically once the SDK is wired.
248
- if (otel) {
249
- var spanInst = tracer.startSpan(name, {
250
- kind: _kindFromString(otel, sopts.kind),
251
- attributes: sopts.attributes,
252
- });
253
- // Run fn inside the span's context so child spans nest.
254
- var ctx = otel.trace.setSpan(otel.context.active(), spanInst);
255
- return otel.context.with(ctx, function () {
256
- try {
257
- var ret = fn(spanInst);
258
- if (ret && typeof ret.then === "function") {
259
- return ret.then(
260
- function (v) { spanInst.end(); return v; },
261
- function (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
- spanInst.end();
270
- return ret;
271
- } catch (e) {
272
- spanInst.recordException(e);
273
- spanInst.setStatus({ code: 2, message: (e && e.message) || String(e) });
274
- spanInst.end();
275
- throw e;
276
- }
277
- });
278
- }
279
-
280
- // Pass-through path — still wrap so operator code that calls
281
- // span.setAttribute / addEvent / recordException works, just
282
- // without telemetry output.
283
- var pSpan = tracer.startSpan(name, sopts);
284
- if (sopts.attributes) pSpan.setAttributes(sopts.attributes);
285
- tracer._push(pSpan);
286
- var done = false;
287
- function _finish() { if (!done) { done = true; tracer._pop(); pSpan.end(); } }
288
- try {
289
- var res = fn(pSpan);
290
- if (res && typeof res.then === "function") {
291
- return res.then(
292
- function (v) { _finish(); return v; },
293
- function (e) { _finish(); throw e; }
294
- );
295
- }
296
- _finish();
297
- return res;
298
- } catch (e) {
299
- _finish();
300
- throw e;
301
- }
302
- }
303
-
304
- function spanSync(name, fn, sopts) {
305
- if (typeof fn !== "function") {
306
- throw new TracingError("tracing/bad-fn", "spanSync body must be a function");
307
- }
308
- // Same shape as span(); fn must return a non-Promise. We don't
309
- // enforce that, but we don't await either — async fn used here
310
- // ends the span before resolution.
311
- return span(name, fn, sopts);
312
- }
313
-
314
- function currentSpan() {
315
- var otel = _getOtel();
316
- if (otel) {
317
- var s = otel.trace.getActiveSpan();
318
- return s || null;
319
- }
320
- var t = _tracer();
321
- return t._peek();
322
- }
323
-
324
- function setAttributes(attrs) {
325
- var s = currentSpan();
326
- if (s) s.setAttributes(attrs);
327
- }
328
-
329
- function recordException(err) {
330
- var s = currentSpan();
331
- if (s) {
332
- s.recordException(err);
333
- s.setStatus({ code: 2, message: (err && err.message) || String(err) });
334
- }
335
- }
336
-
337
- // ---- W3C propagation (works with or without OTel) ----
338
-
339
- function contextHeaders() {
340
- var s = currentSpan();
341
- if (!s) return {};
342
- var sc = s.spanContext ? s.spanContext() : null;
343
- if (!sc || !sc.traceId || sc.traceId === ZERO_TRACE_ID) return {};
344
- return {
345
- traceparent: _formatTraceparent(sc.traceId, sc.spanId,
346
- (sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(HEX_RADIX).padStart(2, "0")),
347
- };
348
- }
349
-
350
- // Parse traceparent from incoming headers. With OTel installed we
351
- // use its propagation API for correctness across propagator types
352
- // an operator might have configured; without OTel we fall back to
353
- // our own parser which always understands W3C.
354
- function extractContext(headers) {
355
- if (!headers || typeof headers !== "object") return null;
356
- var raw = headers.traceparent || headers["Traceparent"] || headers["TRACEPARENT"];
357
- var parsed = _parseTraceparent(raw);
358
- if (!parsed) return null;
359
- return parsed;
360
- }
361
-
362
- // ---- request middleware ----
363
-
364
- function requestMiddleware() {
365
- return function tracingMiddleware(req, res, next) {
366
- // Span starts BEFORE the router populates req.routePattern, so
367
- // initial name+http.route come from the URL fallback. We promote
368
- // both to the template form at res.end if the matcher set one.
369
- var initialRoute = resolveRoute(req);
370
- var spanName = "HTTP " + (req.method || "GET") + " " + initialRoute;
371
- var attrs = {
372
- "http.method": req.method || "GET",
373
- "http.route": initialRoute,
374
- "http.url": req.url || "",
375
- };
376
- var parent = extractContext(req.headers);
377
- if (parent) {
378
- attrs["traceparent.parent"] = parent.traceId + "-" + parent.spanId;
379
- }
380
- span(spanName, function (s) {
381
- req.span = s;
382
- captureResponseStatus(res, function (status) {
383
- try {
384
- s.setAttribute("http.status_code", status);
385
- // Promote to route template if the router resolved one.
386
- var finalRoute = resolveRoute(req);
387
- if (finalRoute !== initialRoute) {
388
- s.setAttribute("http.route", finalRoute);
389
- if (typeof s.updateName === "function") {
390
- s.updateName("HTTP " + (req.method || "GET") + " " + finalRoute);
391
- }
392
- }
393
- } catch (_e) { /* span attr write must not break the response */ }
394
- });
395
- return next();
396
- }, { attributes: attrs }).catch(function () {
397
- // Span error already recorded by span() wrapper; the next()
398
- // chain handles request-level error handling separately.
399
- });
400
- };
401
- }
402
-
403
- // ---- framework auto-tap ----
404
- //
405
- // Like metrics.tap, the tracing tap routes framework hot-path calls
406
- // into spans when this registry is the active one.
407
-
408
- var _activeOnTap = null;
409
-
410
- function tap(name, attributes, fn) {
411
- if (typeof attributes === "function") {
412
- fn = attributes; attributes = null;
413
- }
414
- if (typeof fn !== "function") {
415
- throw new TracingError("tracing/bad-fn", "tap fn must be a function");
416
- }
417
- if (_activeOnTap !== this && registry._isActive !== true) {
418
- // No active registry — execute fn directly. Caller never knows.
419
- return fn(currentSpan());
420
- }
421
- return span(name, fn, { attributes: attributes });
422
- }
423
-
424
- // OTel SpanKind enum values (from @opentelemetry/api). When OTel
425
- // isn't installed, kind is just an attribute string.
426
- function _kindFromString(otel, kindStr) {
427
- if (!otel || !otel.SpanKind) return undefined;
428
- if (kindStr === "server") return otel.SpanKind.SERVER;
429
- if (kindStr === "client") return otel.SpanKind.CLIENT;
430
- if (kindStr === "producer") return otel.SpanKind.PRODUCER;
431
- if (kindStr === "consumer") return otel.SpanKind.CONSUMER;
432
- return otel.SpanKind.INTERNAL;
433
- }
434
-
435
- var registry = {
436
- span: span,
437
- spanSync: spanSync,
438
- currentSpan: currentSpan,
439
- setAttributes: setAttributes,
440
- recordException: recordException,
441
- contextHeaders: contextHeaders,
442
- extractContext: extractContext,
443
- requestMiddleware: requestMiddleware,
444
- tap: tap,
445
- isReal: _isReal,
446
- _isActive: true,
447
- deactivate: function () {
448
- registry._isActive = false;
449
- if (_globalRegistry === registry) _globalRegistry = null;
450
- },
451
- };
452
- _globalRegistry = registry;
453
- return registry;
454
- }
455
-
456
- // ---- Global tap stub for framework modules ----
457
- //
458
- // Same pattern as metrics.tap: framework modules call b.tracing.tap()
459
- // at hot paths. Without an active registry the call is a pass-through
460
- // that just executes fn. With one, it routes into span().
461
-
462
- var _globalRegistry = null;
463
-
464
- function tap(name, attributes, fn) {
465
- if (typeof attributes === "function") {
466
- fn = attributes; attributes = null;
467
- }
468
- if (typeof fn !== "function") {
469
- throw new TracingError("tracing/bad-fn", "tap fn must be a function");
470
- }
471
- if (_globalRegistry === null || _globalRegistry._isActive !== true) {
472
- return fn(null);
473
- }
474
- return _globalRegistry.span(name, fn, { attributes: attributes });
475
- }
476
-
477
- function _resetForTest() {
478
- _globalRegistry = null;
479
- _setOtelForTest(undefined);
480
- }
481
-
482
- module.exports = {
483
- create: create,
484
- tap: tap,
485
- TracingError: TracingError,
486
- _setOtelForTest: _setOtelForTest,
487
- _resetForTest: _resetForTest,
488
- // Internal helpers exposed for tests
489
- _parseTraceparent: _parseTraceparent,
490
- _formatTraceparent: _formatTraceparent,
491
- _newTraceId: _newTraceId,
492
- _newSpanId: _newSpanId,
493
- TRACEPARENT_RE: TRACEPARENT_RE,
494
- };
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 C = require("./constants");
85
+ var crypto = require("./crypto");
86
+ var validateOpts = require("./validate-opts");
87
+ var { defineClass } = require("./framework-error");
88
+ var { resolveRoute, captureResponseStatus } = require("./request-helpers");
89
+
90
+ // W3C trace-context fixed widths (RFC TraceContext §3.2.2.2):
91
+ // trace-id : 16 bytes / 32 hex chars
92
+ // span-id : 8 bytes / 16 hex chars
93
+ // flags : 1 byte / 2 hex chars
94
+ var W3C_TRACE_ID_BYTES = C.BYTES.bytes(16);
95
+ var W3C_SPAN_ID_BYTES = C.BYTES.bytes(8);
96
+ var HEX_RADIX = 0x10;
97
+
98
+ var TracingError = defineClass("TracingError", { alwaysPermanent: true });
99
+
100
+ // ---- OTel API detection (cached) ----
101
+ //
102
+ // Tri-state: undefined=not yet checked, null=not available, object=available.
103
+ // We cache the lookup so the per-call cost is one === comparison after
104
+ // the first call.
105
+
106
+ var _otel = undefined;
107
+
108
+ function _getOtel() {
109
+ if (_otel !== undefined) return _otel;
110
+ try { _otel = require("@opentelemetry/api"); }
111
+ catch (_e) { _otel = null; }
112
+ return _otel;
113
+ }
114
+
115
+ // Test seam — letting tests force the OTel resolution. Pass `null` to
116
+ // simulate "not installed" or an object to simulate a vendored OTel API.
117
+ function _setOtelForTest(value) { _otel = value === undefined ? undefined : value; }
118
+
119
+ // ---- W3C traceparent parsing ----
120
+ //
121
+ // Format: 00-<32 hex trace-id>-<16 hex span-id>-<2 hex flags>
122
+ // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
123
+ //
124
+ // We accept only version 00 (the only released W3C version). Future
125
+ // versions are technically forward-compatible but would extend the
126
+ // suffix; we ignore unrecognized versions to be safe.
127
+
128
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
129
+ // Special-case all-zeros: invalid per spec but malicious clients send them.
130
+ // (32 = W3C_TRACE_ID_BYTES * 2 hex chars; 16 = W3C_SPAN_ID_BYTES * 2.)
131
+ var ZERO_TRACE_ID = "0".repeat(W3C_TRACE_ID_BYTES * 2);
132
+ var ZERO_SPAN_ID = "0".repeat(W3C_SPAN_ID_BYTES * 2);
133
+
134
+ function _parseTraceparent(value) {
135
+ if (typeof value !== "string") return null;
136
+ var m = value.match(TRACEPARENT_RE);
137
+ if (!m) return null;
138
+ if (m[1] === ZERO_TRACE_ID || m[2] === ZERO_SPAN_ID) return null;
139
+ return { traceId: m[1], spanId: m[2], flags: m[3] };
140
+ }
141
+
142
+ function _formatTraceparent(traceId, spanId, flags) {
143
+ return "00-" + traceId + "-" + spanId + "-" + (flags || "01");
144
+ }
145
+
146
+ function _newTraceId() {
147
+ return crypto.generateToken(W3C_TRACE_ID_BYTES);
148
+ }
149
+ function _newSpanId() {
150
+ return crypto.generateToken(W3C_SPAN_ID_BYTES);
151
+ }
152
+
153
+ // ---- Pass-through span (used when OTel isn't installed) ----
154
+ //
155
+ // Operator code does span.setAttribute() / span.addEvent() /
156
+ // span.recordException() unconditionally. When OTel is absent these
157
+ // calls go to a minimal stub that no-ops but keeps the API shape
158
+ // compatible. Trace ID is still tracked so log correlation works.
159
+
160
+ function _passthroughSpan(traceId, spanId, parentSpanId) {
161
+ return {
162
+ spanContext: function () {
163
+ return { traceId: traceId, spanId: spanId, traceFlags: 1, isRemote: false };
164
+ },
165
+ setAttribute: function () { return this; },
166
+ setAttributes: function () { return this; },
167
+ addEvent: function () { return this; },
168
+ recordException: function () { return this; },
169
+ setStatus: function () { return this; },
170
+ updateName: function () { return this; },
171
+ end: function () { },
172
+ // Internal — used by the registry to thread parent context.
173
+ _isPassthrough: true,
174
+ _parentSpanId: parentSpanId,
175
+ };
176
+ }
177
+
178
+ // ---- Pass-through tracer (used when OTel isn't installed) ----
179
+ //
180
+ // Implements just enough of the OTel Tracer interface to drive the
181
+ // framework's span() / spanSync() helpers. Real OTel takes over when
182
+ // installed; the wrapper code below doesn't change.
183
+
184
+ function _passthroughTracer() {
185
+ // Active-span stack. Single-threaded JS lets us use a process-level
186
+ // stack rather than async-context. Pure synchronous span lifetimes
187
+ // are correct; async work that spans suspension points won't have
188
+ // currentSpan() right inside continuations — but the operator's fn
189
+ // still receives the span as its first arg, so attribute/event
190
+ // setting via that reference always works.
191
+ var stack = [];
192
+ return {
193
+ startSpan: function (_name, _opts) {
194
+ var traceId = stack.length > 0 ? stack[stack.length - 1].spanContext().traceId : _newTraceId();
195
+ var span = _passthroughSpan(traceId, _newSpanId(),
196
+ stack.length > 0 ? stack[stack.length - 1].spanContext().spanId : null);
197
+ return span;
198
+ },
199
+ _push: function (span) { stack.push(span); },
200
+ _pop: function () { return stack.pop(); },
201
+ _peek: function () { return stack.length > 0 ? stack[stack.length - 1] : null; },
202
+ _isPassthrough: true,
203
+ };
204
+ }
205
+
206
+ // ---- Registry factory ----
207
+
208
+ function create(opts) {
209
+ opts = opts || {};
210
+ validateOpts(opts, [
211
+ "instrumentationName", "instrumentationVersion",
212
+ ], "b.tracing");
213
+ var instrumentationName = opts.instrumentationName || "blamejs";
214
+ var instrumentationVersion = opts.instrumentationVersion || "0.0.0";
215
+
216
+ // Resolve the tracer once. If @opentelemetry/api is installed, this
217
+ // is a real Tracer; otherwise a passthrough. Cached per-registry so
218
+ // the passthrough stack persists across span() and currentSpan() calls
219
+ // (otherwise each call would see a fresh empty stack).
220
+ var _cachedPassthrough = null;
221
+ function _tracer() {
222
+ var otel = _getOtel();
223
+ if (otel) return otel.trace.getTracer(instrumentationName, instrumentationVersion);
224
+ if (!_cachedPassthrough) _cachedPassthrough = _passthroughTracer();
225
+ return _cachedPassthrough;
226
+ }
227
+
228
+ function _isReal() { return _getOtel() !== null; }
229
+
230
+ // Wrap fn in a span. The span is automatically end()'d on return /
231
+ // throw / promise rejection. exception is recorded on throw and
232
+ // span status set to ERROR.
233
+ function span(name, fn, sopts) {
234
+ if (typeof name !== "string" || name.length === 0) {
235
+ throw new TracingError("tracing/bad-name",
236
+ "span name must be a non-empty string");
237
+ }
238
+ if (typeof fn !== "function") {
239
+ throw new TracingError("tracing/bad-fn",
240
+ "span body must be a function");
241
+ }
242
+ sopts = sopts || {};
243
+ var tracer = _tracer();
244
+ var otel = _getOtel();
245
+
246
+ // Real OTel path — use context.with so async-context propagation
247
+ // works automatically once the SDK is wired.
248
+ if (otel) {
249
+ var spanInst = tracer.startSpan(name, {
250
+ kind: _kindFromString(otel, sopts.kind),
251
+ attributes: sopts.attributes,
252
+ });
253
+ // Run fn inside the span's context so child spans nest.
254
+ var ctx = otel.trace.setSpan(otel.context.active(), spanInst);
255
+ return otel.context.with(ctx, function () {
256
+ try {
257
+ var ret = fn(spanInst);
258
+ if (ret && typeof ret.then === "function") {
259
+ return ret.then(
260
+ function (v) { spanInst.end(); return v; },
261
+ function (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
+ spanInst.end();
270
+ return ret;
271
+ } catch (e) {
272
+ spanInst.recordException(e);
273
+ spanInst.setStatus({ code: 2, message: (e && e.message) || String(e) });
274
+ spanInst.end();
275
+ throw e;
276
+ }
277
+ });
278
+ }
279
+
280
+ // Pass-through path — still wrap so operator code that calls
281
+ // span.setAttribute / addEvent / recordException works, just
282
+ // without telemetry output.
283
+ var pSpan = tracer.startSpan(name, sopts);
284
+ if (sopts.attributes) pSpan.setAttributes(sopts.attributes);
285
+ tracer._push(pSpan);
286
+ var done = false;
287
+ function _finish() { if (!done) { done = true; tracer._pop(); pSpan.end(); } }
288
+ try {
289
+ var res = fn(pSpan);
290
+ if (res && typeof res.then === "function") {
291
+ return res.then(
292
+ function (v) { _finish(); return v; },
293
+ function (e) { _finish(); throw e; }
294
+ );
295
+ }
296
+ _finish();
297
+ return res;
298
+ } catch (e) {
299
+ _finish();
300
+ throw e;
301
+ }
302
+ }
303
+
304
+ function spanSync(name, fn, sopts) {
305
+ if (typeof fn !== "function") {
306
+ throw new TracingError("tracing/bad-fn", "spanSync body must be a function");
307
+ }
308
+ // Same shape as span(); fn must return a non-Promise. We don't
309
+ // enforce that, but we don't await either — async fn used here
310
+ // ends the span before resolution.
311
+ return span(name, fn, sopts);
312
+ }
313
+
314
+ function currentSpan() {
315
+ var otel = _getOtel();
316
+ if (otel) {
317
+ var s = otel.trace.getActiveSpan();
318
+ return s || null;
319
+ }
320
+ var t = _tracer();
321
+ return t._peek();
322
+ }
323
+
324
+ function setAttributes(attrs) {
325
+ var s = currentSpan();
326
+ if (s) s.setAttributes(attrs);
327
+ }
328
+
329
+ function recordException(err) {
330
+ var s = currentSpan();
331
+ if (s) {
332
+ s.recordException(err);
333
+ s.setStatus({ code: 2, message: (err && err.message) || String(err) });
334
+ }
335
+ }
336
+
337
+ // ---- W3C propagation (works with or without OTel) ----
338
+
339
+ function contextHeaders() {
340
+ var s = currentSpan();
341
+ if (!s) return {};
342
+ var sc = s.spanContext ? s.spanContext() : null;
343
+ if (!sc || !sc.traceId || sc.traceId === ZERO_TRACE_ID) return {};
344
+ return {
345
+ traceparent: _formatTraceparent(sc.traceId, sc.spanId,
346
+ (sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(HEX_RADIX).padStart(2, "0")),
347
+ };
348
+ }
349
+
350
+ // Parse traceparent from incoming headers. With OTel installed we
351
+ // use its propagation API for correctness across propagator types
352
+ // an operator might have configured; without OTel we fall back to
353
+ // our own parser which always understands W3C.
354
+ function extractContext(headers) {
355
+ if (!headers || typeof headers !== "object") return null;
356
+ var raw = headers.traceparent || headers["Traceparent"] || headers["TRACEPARENT"];
357
+ var parsed = _parseTraceparent(raw);
358
+ if (!parsed) return null;
359
+ return parsed;
360
+ }
361
+
362
+ // ---- request middleware ----
363
+
364
+ function requestMiddleware() {
365
+ return function tracingMiddleware(req, res, next) {
366
+ // Span starts BEFORE the router populates req.routePattern, so
367
+ // initial name+http.route come from the URL fallback. We promote
368
+ // both to the template form at res.end if the matcher set one.
369
+ var initialRoute = resolveRoute(req);
370
+ var spanName = "HTTP " + (req.method || "GET") + " " + initialRoute;
371
+ var attrs = {
372
+ "http.method": req.method || "GET",
373
+ "http.route": initialRoute,
374
+ "http.url": req.url || "",
375
+ };
376
+ var parent = extractContext(req.headers);
377
+ if (parent) {
378
+ attrs["traceparent.parent"] = parent.traceId + "-" + parent.spanId;
379
+ }
380
+ span(spanName, function (s) {
381
+ req.span = s;
382
+ captureResponseStatus(res, function (status) {
383
+ try {
384
+ s.setAttribute("http.status_code", status);
385
+ // Promote to route template if the router resolved one.
386
+ var finalRoute = resolveRoute(req);
387
+ if (finalRoute !== initialRoute) {
388
+ s.setAttribute("http.route", finalRoute);
389
+ if (typeof s.updateName === "function") {
390
+ s.updateName("HTTP " + (req.method || "GET") + " " + finalRoute);
391
+ }
392
+ }
393
+ } catch (_e) { /* span attr write must not break the response */ }
394
+ });
395
+ return next();
396
+ }, { attributes: attrs }).catch(function () {
397
+ // Span error already recorded by span() wrapper; the next()
398
+ // chain handles request-level error handling separately.
399
+ });
400
+ };
401
+ }
402
+
403
+ // ---- framework auto-tap ----
404
+ //
405
+ // Like metrics.tap, the tracing tap routes framework hot-path calls
406
+ // into spans when this registry is the active one.
407
+
408
+ var _activeOnTap = null;
409
+
410
+ function tap(name, attributes, fn) {
411
+ if (typeof attributes === "function") {
412
+ fn = attributes; attributes = null;
413
+ }
414
+ if (typeof fn !== "function") {
415
+ throw new TracingError("tracing/bad-fn", "tap fn must be a function");
416
+ }
417
+ if (_activeOnTap !== this && registry._isActive !== true) {
418
+ // No active registry — execute fn directly. Caller never knows.
419
+ return fn(currentSpan());
420
+ }
421
+ return span(name, fn, { attributes: attributes });
422
+ }
423
+
424
+ // OTel SpanKind enum values (from @opentelemetry/api). When OTel
425
+ // isn't installed, kind is just an attribute string.
426
+ function _kindFromString(otel, kindStr) {
427
+ if (!otel || !otel.SpanKind) return undefined;
428
+ if (kindStr === "server") return otel.SpanKind.SERVER;
429
+ if (kindStr === "client") return otel.SpanKind.CLIENT;
430
+ if (kindStr === "producer") return otel.SpanKind.PRODUCER;
431
+ if (kindStr === "consumer") return otel.SpanKind.CONSUMER;
432
+ return otel.SpanKind.INTERNAL;
433
+ }
434
+
435
+ var registry = {
436
+ span: span,
437
+ spanSync: spanSync,
438
+ currentSpan: currentSpan,
439
+ setAttributes: setAttributes,
440
+ recordException: recordException,
441
+ contextHeaders: contextHeaders,
442
+ extractContext: extractContext,
443
+ requestMiddleware: requestMiddleware,
444
+ tap: tap,
445
+ isReal: _isReal,
446
+ _isActive: true,
447
+ deactivate: function () {
448
+ registry._isActive = false;
449
+ if (_globalRegistry === registry) _globalRegistry = null;
450
+ },
451
+ };
452
+ _globalRegistry = registry;
453
+ return registry;
454
+ }
455
+
456
+ // ---- Global tap stub for framework modules ----
457
+ //
458
+ // Same pattern as metrics.tap: framework modules call b.tracing.tap()
459
+ // at hot paths. Without an active registry the call is a pass-through
460
+ // that just executes fn. With one, it routes into span().
461
+
462
+ var _globalRegistry = null;
463
+
464
+ function tap(name, attributes, fn) {
465
+ if (typeof attributes === "function") {
466
+ fn = attributes; attributes = null;
467
+ }
468
+ if (typeof fn !== "function") {
469
+ throw new TracingError("tracing/bad-fn", "tap fn must be a function");
470
+ }
471
+ if (_globalRegistry === null || _globalRegistry._isActive !== true) {
472
+ return fn(null);
473
+ }
474
+ return _globalRegistry.span(name, fn, { attributes: attributes });
475
+ }
476
+
477
+ function _resetForTest() {
478
+ _globalRegistry = null;
479
+ _setOtelForTest(undefined);
480
+ }
481
+
482
+ module.exports = {
483
+ create: create,
484
+ tap: tap,
485
+ TracingError: TracingError,
486
+ _setOtelForTest: _setOtelForTest,
487
+ _resetForTest: _resetForTest,
488
+ // Internal helpers exposed for tests
489
+ _parseTraceparent: _parseTraceparent,
490
+ _formatTraceparent: _formatTraceparent,
491
+ _newTraceId: _newTraceId,
492
+ _newSpanId: _newSpanId,
493
+ TRACEPARENT_RE: TRACEPARENT_RE,
494
+ };