@blamejs/core 0.7.18 → 0.7.20

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 (169) hide show
  1. package/CHANGELOG.md +427 -423
  2. package/README.md +150 -150
  3. package/bin/blamejs.js +0 -0
  4. package/index.js +310 -308
  5. package/lib/api-key.js +660 -660
  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-external.js +365 -0
  16. package/lib/auth/jwt.js +337 -311
  17. package/lib/auth/lockout.js +436 -436
  18. package/lib/auth/oauth.js +721 -721
  19. package/lib/auth/passkey.js +181 -181
  20. package/lib/auth/password.js +628 -594
  21. package/lib/backup/bundle.js +217 -217
  22. package/lib/backup/crypto.js +176 -176
  23. package/lib/backup/index.js +515 -515
  24. package/lib/backup/manifest.js +282 -282
  25. package/lib/break-glass.js +1338 -1338
  26. package/lib/bundler.js +441 -441
  27. package/lib/cache-redis.js +256 -256
  28. package/lib/cache.js +1206 -1206
  29. package/lib/canonical-json.js +115 -115
  30. package/lib/chain-writer.js +234 -234
  31. package/lib/cli-helpers.js +206 -206
  32. package/lib/cli.js +2334 -2334
  33. package/lib/cluster-provider-db.js +317 -317
  34. package/lib/cluster-storage.js +226 -226
  35. package/lib/cluster.js +703 -703
  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 +350 -315
  40. package/lib/credential-hash.js +322 -322
  41. package/lib/crypto.js +266 -266
  42. package/lib/csv.js +275 -275
  43. package/lib/db-declare-row-policy.js +267 -267
  44. package/lib/db-declare-view.js +420 -420
  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/forms.js +422 -422
  56. package/lib/framework-error.js +293 -293
  57. package/lib/framework-schema.js +717 -717
  58. package/lib/handlers.js +350 -350
  59. package/lib/http-client-cookie-jar.js +508 -508
  60. package/lib/http-client.js +1195 -1195
  61. package/lib/i18n.js +878 -878
  62. package/lib/jobs.js +185 -185
  63. package/lib/log-stream-cloudwatch.js +369 -369
  64. package/lib/log-stream-local.js +146 -146
  65. package/lib/log-stream-otlp-grpc.js +410 -410
  66. package/lib/log-stream-otlp.js +286 -286
  67. package/lib/log-stream-syslog.js +302 -302
  68. package/lib/log-stream-webhook.js +199 -199
  69. package/lib/log-stream.js +330 -330
  70. package/lib/log.js +500 -500
  71. package/lib/mail-bounce.js +528 -528
  72. package/lib/mail-dkim.js +369 -369
  73. package/lib/mail.js +981 -981
  74. package/lib/metrics.js +683 -683
  75. package/lib/middleware/api-encrypt.js +936 -936
  76. package/lib/middleware/attach-user.js +157 -157
  77. package/lib/middleware/bearer-auth.js +152 -0
  78. package/lib/middleware/body-parser.js +1170 -1170
  79. package/lib/middleware/bot-guard.js +178 -178
  80. package/lib/middleware/compression.js +452 -452
  81. package/lib/middleware/cors.js +314 -314
  82. package/lib/middleware/csp-nonce.js +348 -348
  83. package/lib/middleware/csrf-protect.js +399 -316
  84. package/lib/middleware/db-role-for.js +264 -264
  85. package/lib/middleware/fetch-metadata.js +129 -0
  86. package/lib/middleware/health.js +392 -392
  87. package/lib/middleware/index.js +85 -79
  88. package/lib/middleware/rate-limit.js +358 -358
  89. package/lib/middleware/request-id.js +61 -61
  90. package/lib/middleware/request-log.js +168 -168
  91. package/lib/middleware/require-auth.js +104 -104
  92. package/lib/middleware/security-headers.js +121 -116
  93. package/lib/middleware/sse.js +166 -166
  94. package/lib/migrations.js +383 -383
  95. package/lib/mtls-ca.js +518 -518
  96. package/lib/mtls-engine-default.js +481 -481
  97. package/lib/network-dns.js +632 -632
  98. package/lib/network-heartbeat.js +290 -290
  99. package/lib/network-nts.js +574 -574
  100. package/lib/network-proxy.js +265 -265
  101. package/lib/network-tls.js +328 -328
  102. package/lib/network.js +233 -233
  103. package/lib/notify.js +612 -612
  104. package/lib/ntp-check.js +229 -229
  105. package/lib/numeric-bounds.js +111 -111
  106. package/lib/object-store/azure-blob-bucket-ops.js +349 -349
  107. package/lib/object-store/azure-blob.js +488 -488
  108. package/lib/object-store/gcs-bucket-ops.js +351 -351
  109. package/lib/object-store/gcs.js +519 -519
  110. package/lib/object-store/http-put.js +153 -153
  111. package/lib/object-store/index.js +197 -197
  112. package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
  113. package/lib/object-store/sigv4.js +903 -903
  114. package/lib/observability.js +151 -151
  115. package/lib/otel-export.js +269 -269
  116. package/lib/pagination.js +464 -464
  117. package/lib/parsers/index.js +80 -80
  118. package/lib/parsers/safe-env.js +642 -642
  119. package/lib/parsers/safe-ini.js +292 -292
  120. package/lib/parsers/safe-toml.js +784 -784
  121. package/lib/parsers/safe-xml.js +390 -390
  122. package/lib/parsers/safe-yaml.js +1015 -1015
  123. package/lib/permissions.js +708 -708
  124. package/lib/pqc-agent.js +87 -87
  125. package/lib/pqc-gate.js +279 -279
  126. package/lib/protobuf-encoder.js +190 -190
  127. package/lib/protocol-dispatcher.js +161 -161
  128. package/lib/pubsub-redis.js +167 -167
  129. package/lib/pubsub.js +429 -429
  130. package/lib/queue-local.js +476 -476
  131. package/lib/queue-redis.js +745 -745
  132. package/lib/queue-sqs.js +319 -319
  133. package/lib/queue.js +695 -695
  134. package/lib/redis-client.js +519 -519
  135. package/lib/request-helpers.js +340 -340
  136. package/lib/restore-bundle.js +237 -237
  137. package/lib/restore-rollback.js +259 -259
  138. package/lib/restore.js +409 -409
  139. package/lib/retry.js +376 -376
  140. package/lib/router.js +748 -748
  141. package/lib/safe-async.js +735 -735
  142. package/lib/safe-buffer.js +237 -237
  143. package/lib/safe-json.js +541 -541
  144. package/lib/safe-schema.js +1266 -1266
  145. package/lib/safe-url.js +159 -159
  146. package/lib/scheduler.js +706 -706
  147. package/lib/security-assert.js +373 -373
  148. package/lib/seeders.js +618 -618
  149. package/lib/session.js +535 -478
  150. package/lib/slug.js +269 -269
  151. package/lib/ssrf-guard.js +401 -401
  152. package/lib/static.js +7 -5
  153. package/lib/storage.js +471 -471
  154. package/lib/subject.js +281 -281
  155. package/lib/template.js +791 -791
  156. package/lib/testing.js +798 -798
  157. package/lib/time.js +310 -310
  158. package/lib/totp.js +302 -302
  159. package/lib/tracing.js +494 -494
  160. package/lib/uuid.js +132 -132
  161. package/lib/validate-opts.js +340 -340
  162. package/lib/vault/index.js +308 -308
  163. package/lib/vault/rotate.js +784 -784
  164. package/lib/vault/wrap.js +296 -296
  165. package/lib/vendor/noble-ciphers.cjs +9 -9
  166. package/lib/webhook.js +595 -595
  167. package/lib/websocket.js +1048 -1048
  168. package/package.json +77 -77
  169. 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
+ };