@aidra/pmx-io 0.0.0-stage → 9.14.0

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 (67) hide show
  1. package/README.md +69 -2
  2. package/bin/pmx-io-node.js +143 -0
  3. package/index.d.ts +381 -0
  4. package/index.js +27 -0
  5. package/index.mjs +47 -0
  6. package/lib/actions.js +120 -0
  7. package/lib/activate.js +402 -0
  8. package/lib/application.js +106 -0
  9. package/lib/bun.js +155 -0
  10. package/lib/config.js +67 -0
  11. package/lib/constants.js +17 -0
  12. package/lib/debug.js +48 -0
  13. package/lib/esm-hooks.mjs +111 -0
  14. package/lib/exit.js +79 -0
  15. package/lib/features/metrics.js +409 -0
  16. package/lib/features/notify.js +358 -0
  17. package/lib/features/profiling.js +208 -0
  18. package/lib/hooks.js +151 -0
  19. package/lib/inbound.js +174 -0
  20. package/lib/integrations.js +372 -0
  21. package/lib/itg/amqplib.js +169 -0
  22. package/lib/itg/bullmq.js +141 -0
  23. package/lib/itg/common.js +657 -0
  24. package/lib/itg/dbcomment.js +114 -0
  25. package/lib/itg/index.js +103 -0
  26. package/lib/itg/ioredis.js +212 -0
  27. package/lib/itg/kafkajs.js +205 -0
  28. package/lib/itg/mongodb.js +322 -0
  29. package/lib/itg/mysql2.js +205 -0
  30. package/lib/itg/nats.js +531 -0
  31. package/lib/itg/noderedis.js +236 -0
  32. package/lib/itg/pg.js +253 -0
  33. package/lib/itg/pgqueue.js +305 -0
  34. package/lib/itg/postgres.js +252 -0
  35. package/lib/itg/redis-common.js +265 -0
  36. package/lib/itg/sql.js +302 -0
  37. package/lib/metrics/service.js +286 -0
  38. package/lib/metrics/types.js +342 -0
  39. package/lib/modules.js +260 -0
  40. package/lib/node-options.js +62 -0
  41. package/lib/otel/bridge.js +797 -0
  42. package/lib/otel/env.js +114 -0
  43. package/lib/outbound.js +362 -0
  44. package/lib/pmx.js +757 -0
  45. package/lib/relay/hub.js +279 -0
  46. package/lib/relay/transport.js +253 -0
  47. package/lib/standalone/activate.js +307 -0
  48. package/lib/standalone/capture.js +55 -0
  49. package/lib/standalone/container.js +909 -0
  50. package/lib/standalone/transport.js +1023 -0
  51. package/lib/tracing/context.js +90 -0
  52. package/lib/tracing/control.js +124 -0
  53. package/lib/tracing/exporter.js +115 -0
  54. package/lib/tracing/index.js +242 -0
  55. package/lib/tracing/logs.js +176 -0
  56. package/lib/tracing/options.js +105 -0
  57. package/lib/tracing/paths.js +34 -0
  58. package/lib/tracing/tracer.js +850 -0
  59. package/lib/tracing/w3c.js +415 -0
  60. package/lib/transport/ipc.js +97 -0
  61. package/lib/transport/ws.js +511 -0
  62. package/lib/util.js +109 -0
  63. package/package.json +48 -4
  64. package/preload.cjs +26 -0
  65. package/preload.mjs +25 -0
  66. package/standalone.cjs +16 -0
  67. package/standalone.mjs +17 -0
@@ -0,0 +1,114 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // `OTEL_*` wiring for Mode S (85 OTX-052, the rules of OTX-201…205 without the receiver and the
5
+ // pm_id key): `pmx-io-node run` with PMX_IO_OTEL_EXPORT=1 points an app's own OpenTelemetry SDK at
6
+ // prodman's OTLP endpoint (`<PRODMAN_URL>/otlp`, protocol http/protobuf, header
7
+ // `Authorization=Basic%20<base64(public:secret)>`), names the service after PM2_APP_NAME and adds
8
+ // resource attributes — each only where the environment does not set it (OTX-208: nothing the
9
+ // app sets is replaced; merges append). Opt-in because it places the bucket secret in the
10
+ // environment of every child process (PYI-152 / B.15 otherwise avoid it). Loaded by the launcher
11
+ // only when the opt-in is set.
12
+
13
+ const ENDPOINTS = ['OTEL_EXPORTER_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT', 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT']
14
+ const PROTOCOLS = ['OTEL_EXPORTER_OTLP_PROTOCOL', 'OTEL_EXPORTER_OTLP_TRACES_PROTOCOL', 'OTEL_EXPORTER_OTLP_LOGS_PROTOCOL', 'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL']
15
+ const SIGNAL_HEADERS = ['OTEL_EXPORTER_OTLP_TRACES_HEADERS', 'OTEL_EXPORTER_OTLP_LOGS_HEADERS', 'OTEL_EXPORTER_OTLP_METRICS_HEADERS']
16
+
17
+ const set = (env, k) => typeof env[k] === 'string' && env[k] !== ''
18
+
19
+ /** Percent-encoding of the OTel environment specification (UTF-8, RFC 3986 unreserved kept). */
20
+ function pct (v) {
21
+ return Array.from(Buffer.from(String(v), 'utf8')).map((b) => {
22
+ const c = String.fromCharCode(b)
23
+ return /[A-Za-z0-9\-._~]/.test(c) ? c : '%' + b.toString(16).toUpperCase().padStart(2, '0')
24
+ }).join('')
25
+ }
26
+
27
+ function names (list) {
28
+ const out = new Set()
29
+ for (const part of String(list || '').split(',')) {
30
+ const i = part.indexOf('=')
31
+ if (i > 0) out.add(decodeURIComponent(part.slice(0, i).trim()).toLowerCase())
32
+ }
33
+ return out
34
+ }
35
+
36
+ function appendPairs (current, pairs) {
37
+ const have = names(current)
38
+ const add = pairs.filter(([k, v]) => v !== undefined && v !== null && v !== '' && !have.has(k.toLowerCase())).map(([k, v]) => k + '=' + pct(v))
39
+ if (add.length === 0) return current || ''
40
+ return current ? current + ',' + add.join(',') : add.join(',')
41
+ }
42
+
43
+ /**
44
+ * Mutate `env` (the app's environment) per OTX-052. `info`: { url (normalised prodman URL),
45
+ * machine (the standalone server name, CTR-015), app (default PM2_APP_NAME), container:
46
+ * [[key, value]] }. Standalone has no pm_id: `service.instance.id` is the server name (as pmx-io).
47
+ * Returns the list of variables set or changed.
48
+ */
49
+ function wire (env, info) {
50
+ const changed = []
51
+ const put = (k, v) => {
52
+ if (env[k] !== v) {
53
+ env[k] = v
54
+ changed.push(k)
55
+ }
56
+ }
57
+ if (!ENDPOINTS.some((k) => set(env, k))) {
58
+ put('OTEL_EXPORTER_OTLP_ENDPOINT', info.url.replace(/\/+$/, '') + '/otlp')
59
+ if (!PROTOCOLS.some((k) => set(env, k))) put('OTEL_EXPORTER_OTLP_PROTOCOL', 'http/protobuf')
60
+ const auth = 'Basic%20' + Buffer.from(`${env.PM2_PUBLIC_KEY || ''}:${env.PM2_SECRET_KEY || ''}`).toString('base64')
61
+ const withAuth = (v) => (names(v).has('authorization') ? v : (v ? v + ',' : '') + 'Authorization=' + auth)
62
+ put('OTEL_EXPORTER_OTLP_HEADERS', withAuth(env.OTEL_EXPORTER_OTLP_HEADERS || ''))
63
+ for (const k of SIGNAL_HEADERS) if (set(env, k)) put(k, withAuth(env[k]))
64
+ }
65
+ const app = info.app || env.PM2_APP_NAME
66
+ const machine = info.machine
67
+ // pmx 97 PAP-051/052: `pmx.application` always, `service.namespace` when not `default`
68
+ const application = info.application || 'default'
69
+ const ns = require('../application').serviceNamespace(application, null)
70
+ const resource = appendPairs(env.OTEL_RESOURCE_ATTRIBUTES || '', [
71
+ ['service.instance.id', machine],
72
+ ['pmx.app', app],
73
+ ['pmx.server', machine],
74
+ ['pmx.application', application]
75
+ ].concat(ns ? [['service.namespace', ns]] : []).concat(info.container || []))
76
+ if (resource !== (env.OTEL_RESOURCE_ATTRIBUTES || '')) put('OTEL_RESOURCE_ATTRIBUTES', resource)
77
+ if (!set(env, 'OTEL_SERVICE_NAME') && app && !names(env.OTEL_RESOURCE_ATTRIBUTES).has('service.name')) put('OTEL_SERVICE_NAME', app)
78
+ return changed
79
+ }
80
+
81
+ /**
82
+ * The container resource attributes of OTX-203 known without network calls (95 §2: the identity
83
+ * of CTR-014; the ECS task metadata endpoint is not queried at launch, so the aws.ecs.* keys and
84
+ * the zone are left out). `info` is a ContainerInfo (lib/standalone/container.js).
85
+ */
86
+ function containerPairs (info, app, host) {
87
+ const out = []
88
+ try {
89
+ if (!info || !info.inContainer()) return out
90
+ const id = info.identity(app || null, host)
91
+ out.push(['container.id', id.id], ['container.name', id.name], ['container.image.name', id.image],
92
+ ['k8s.pod.name', info.podName()], ['k8s.namespace.name', id.namespace],
93
+ ['pmx.container.service', id.service], ['pmx.container.instance', id.instance])
94
+ } catch (_) {}
95
+ return out.filter(([, v]) => v !== undefined && v !== null && v !== '')
96
+ }
97
+
98
+ /**
99
+ * The launcher's OTX-052 step: resolve the standalone configuration as the app will (NIO-262,
100
+ * CTR-015 server name) and wire `env`; nothing when a key, the secret or the app name is missing
101
+ * (the standalone root then prints its missing-values line). Returns the variables changed.
102
+ */
103
+ function wireLauncher (env) {
104
+ const container = require('../standalone/container')
105
+ const { resolveConfig } = require('../standalone/transport')
106
+ const info = container.ContainerInfo.detect(env, container.SysRoot.host(), process.platform === 'linux')
107
+ const cfg = resolveConfig(undefined, env, info)
108
+ if (cfg.missing.length || !cfg.serverName) return []
109
+ // PAP-052: the standalone resolution (no init value at launch: PMX_APPLICATION > default)
110
+ const application = require('../application').resolve(undefined, { managed: false, env })
111
+ return wire(env, { url: cfg.url, machine: cfg.serverName, app: cfg.appName, application, container: containerPairs(info, cfg.appName, container.hostname()) })
112
+ }
113
+
114
+ module.exports = { wire, wireLauncher, containerPairs, pct }
@@ -0,0 +1,362 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Outbound HTTP (80 NIO-055, NIO-222), shared by the `HTTP Outbound*` metrics and the CLIENT
5
+ // spans. Every request goes through exactly one layer:
6
+ // * http/https (ClientRequest): diagnostics channels. Header injection needs
7
+ // `http.client.request.created` (Node ≥ 22.12 / ≥ 23.2, Bun), published while the headers can
8
+ // still be set; on older Node, when CLIENT spans are wanted, http.request/get and
9
+ // https.request/get are wrapped instead, each when it is loaded (see below).
10
+ // * the global fetch: Node's undici diagnostics channels (`undici:request:*`, headers added with
11
+ // the request's addHeader); Bun: globalThis.fetch is wrapped.
12
+ // Requests made by @aidra/pmx-io itself never use these APIs (raw sockets, transport/ws.js).
13
+
14
+ const hooks = require('./hooks')
15
+ const debug = require('./debug')('axm:tracing')
16
+
17
+ const OUT = Symbol.for('@pmx/io:outbound-state')
18
+ const consumers = { metrics: null, tracing: null }
19
+ // resolved on first use: reading globalThis.performance loads the perf_hooks internals (NIO-024)
20
+ let perf = null
21
+ function pnow () {
22
+ if (perf === null) perf = globalThis.performance || require('perf_hooks').performance
23
+ return perf.now()
24
+ }
25
+ const BUN = typeof process.versions.bun === 'string'
26
+
27
+ function nodeVersion () {
28
+ const [a, b] = String(process.versions.node || '0.0').split('.').map(Number)
29
+ return { major: a || 0, minor: b || 0 }
30
+ }
31
+
32
+ /** `http.client.request.created` is published (Node ≥ 22.12 on 22, ≥ 23.2; Bun). */
33
+ function hasCreatedChannel () {
34
+ if (BUN) return true
35
+ const v = nodeVersion()
36
+ return (v.major === 22 && v.minor >= 12) || (v.major === 23 && v.minor >= 2) || v.major >= 24
37
+ }
38
+
39
+ function hasErrorChannel () {
40
+ return BUN || nodeVersion().major >= 20
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------------------------
44
+ // shared start/end
45
+
46
+ function startState (canInject, info, inject) {
47
+ const st = { t0: pnow(), span: null, ended: false }
48
+ const t = consumers.tracing
49
+ if (t && canInject) {
50
+ try {
51
+ const i = info()
52
+ if (i) {
53
+ st.span = t.clientSpan(i.method, i.url, i.host, i.port, i.request)
54
+ if (st.span) inject(t.headersFor(st.span))
55
+ }
56
+ } catch (err) {
57
+ debug('outbound span failed: %s', err && err.message)
58
+ }
59
+ }
60
+ return st
61
+ }
62
+
63
+ function endState (st, status, err) {
64
+ if (!st || st.ended) return
65
+ st.ended = true
66
+ const m = consumers.metrics
67
+ if (m) {
68
+ try {
69
+ m(pnow() - st.t0)
70
+ } catch (_) {}
71
+ }
72
+ if (st.span && consumers.tracing) {
73
+ try {
74
+ consumers.tracing.endClient(st.span, status, err)
75
+ } catch (_) {}
76
+ }
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------------------------
80
+ // http/https ClientRequest
81
+
82
+ function clientInfo (req) {
83
+ const protocol = req.protocol || 'http:'
84
+ let hostHeader
85
+ try {
86
+ hostHeader = req.getHeader('host')
87
+ } catch (_) {}
88
+ let host = req.host || 'localhost'
89
+ let port
90
+ if (typeof hostHeader === 'string' && hostHeader) {
91
+ const m = /^(\[[^\]]+\]|[^:]+)(?::(\d+))?$/.exec(hostHeader)
92
+ if (m) {
93
+ host = m[1].replace(/^\[|\]$/g, '')
94
+ if (m[2]) port = Number(m[2])
95
+ }
96
+ }
97
+ if (!port) port = protocol === 'https:' ? 443 : 80
98
+ const authority = typeof hostHeader === 'string' && hostHeader ? hostHeader : host
99
+ return { method: req.method || 'GET', url: `${protocol}//${authority}${req.path || '/'}`, host, port, request: req }
100
+ }
101
+
102
+ function setOn (req, st) {
103
+ try {
104
+ Object.defineProperty(req, OUT, { value: st, enumerable: false, configurable: true, writable: true })
105
+ } catch (_) {}
106
+ }
107
+
108
+ function onClientCreated (req, canInject) {
109
+ if (!req || req[OUT]) return
110
+ const st = startState(canInject, () => clientInfo(req), (headers) => {
111
+ for (const [name, value] of headers) {
112
+ try {
113
+ if (req.getHeader(name) === undefined) req.setHeader(name, value)
114
+ } catch (_) { /* headers already sent (array form) */ }
115
+ }
116
+ })
117
+ setOn(req, st)
118
+ if (!hasErrorChannel() && typeof req.once === 'function') {
119
+ // Node 18: no error channel; 'close' always comes (never add an 'error' listener)
120
+ req.once('close', () => {
121
+ if (!st.ended) setImmediate(() => endState(st, undefined, new Error('request closed')))
122
+ })
123
+ }
124
+ }
125
+
126
+ let dcInstalled = false
127
+ function installDc () {
128
+ if (dcInstalled) return
129
+ dcInstalled = true
130
+ let dc
131
+ try {
132
+ dc = require('diagnostics_channel')
133
+ } catch (_) {
134
+ return
135
+ }
136
+ const created = hasCreatedChannel()
137
+ if (created) dc.subscribe('http.client.request.created', ({ request }) => onClientCreated(request, true))
138
+ dc.subscribe('http.client.request.start', ({ request }) => onClientCreated(request, false))
139
+ dc.subscribe('http.client.response.finish', ({ request, response }) => {
140
+ endState(request && request[OUT], response && response.statusCode)
141
+ })
142
+ if (hasErrorChannel()) {
143
+ dc.subscribe('http.client.request.error', ({ request, error }) => endState(request && request[OUT], undefined, error))
144
+ }
145
+ if (!BUN) installUndici(dc)
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------------------------
149
+ // Node < 22.12: wrap http/https request/get for CLIENT spans (header injection)
150
+ //
151
+ // Loading http and https at activation costs start-up (https loads tls and crypto, NIO-024), so
152
+ // each module is wrapped when it is loaded: on its first CommonJS require (before the caller
153
+ // receives it, so `const { request } = require('http')` gets the wrapper), and otherwise —
154
+ // an ES-module `import` of node:http, which require does not see — when a span starts
155
+ // (`spanStarting`, called by the tracer); module.syncBuiltinESMExports() then updates the live
156
+ // named imports. CLIENT spans exist only inside a span, so no request that could get one is
157
+ // made before its module is wrapped, except through a named import bound before the first span
158
+ // (updated by syncBuiltinESMExports) — covered.
159
+
160
+ const clientWrap = { wanted: false, done: new Set(), seen: 0, requireHooked: false }
161
+
162
+ function wrapClientModule (name) {
163
+ if (clientWrap.done.has(name)) return
164
+ clientWrap.done.add(name)
165
+ const r = hooks.registry()
166
+ const key = 'httpClient:' + name
167
+ if (r.installed[key]) return
168
+ r.installed[key] = true
169
+ const mod = require(name)
170
+ const origRequest = mod.request
171
+ const request = function request () {
172
+ const req = origRequest.apply(this, arguments)
173
+ try {
174
+ onClientCreated(req, true)
175
+ } catch (_) {}
176
+ return req
177
+ }
178
+ const get = function get () {
179
+ const req = request.apply(this, arguments)
180
+ req.end()
181
+ return req
182
+ }
183
+ copyMeta(request, origRequest)
184
+ copyMeta(get, mod.get)
185
+ mod.request = request
186
+ mod.get = get
187
+ try {
188
+ require('module').syncBuiltinESMExports()
189
+ } catch (_) {}
190
+ }
191
+
192
+ function loadedBuiltin (name) {
193
+ const list = process.moduleLoadList
194
+ return !Array.isArray(list) || list.includes('NativeModule ' + name)
195
+ }
196
+
197
+ /** Wrap whichever of http/https are loaded by now (cheap when nothing changed). */
198
+ function wrapLoadedClients () {
199
+ if (!clientWrap.wanted || clientWrap.done.size === 2) return
200
+ const list = process.moduleLoadList
201
+ if (Array.isArray(list) && list.length === clientWrap.seen) return
202
+ clientWrap.seen = Array.isArray(list) ? list.length : 0
203
+ for (const name of ['http', 'https']) if (loadedBuiltin(name)) wrapClientModule(name)
204
+ }
205
+
206
+ function wantClientWrap () {
207
+ if (clientWrap.wanted || hasCreatedChannel()) return
208
+ clientWrap.wanted = true
209
+ wrapLoadedClients()
210
+ if (clientWrap.done.size === 2 || clientWrap.requireHooked) return
211
+ clientWrap.requireHooked = true
212
+ try {
213
+ const proto = require('./util').cjsModule().prototype
214
+ const orig = proto.require
215
+ const req = function require (id) {
216
+ const exports = orig.apply(this, arguments)
217
+ if (clientWrap.done.size < 2 && typeof id === 'string' && (id === 'http' || id === 'https' || id === 'node:http' || id === 'node:https')) {
218
+ try {
219
+ wrapClientModule(id.replace('node:', ''))
220
+ } catch (_) {}
221
+ }
222
+ return exports
223
+ }
224
+ copyMeta(req, orig)
225
+ proto.require = req
226
+ } catch (err) {
227
+ debug('require observer failed: %s', err && err.message)
228
+ }
229
+ }
230
+
231
+ /** Called by the tracer when a span starts (NIO-222 on Node < 22.12). */
232
+ function spanStarting () {
233
+ if (clientWrap.wanted && clientWrap.done.size < 2) wrapLoadedClients()
234
+ }
235
+
236
+ function copyMeta (fn, orig) {
237
+ try {
238
+ Object.defineProperty(fn, 'length', { value: orig.length })
239
+ Object.defineProperty(fn, 'name', { value: orig.name })
240
+ } catch (_) {}
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------------------------
244
+ // fetch: Node (undici diagnostics channels)
245
+
246
+ const undiciStates = new WeakMap()
247
+
248
+ function installUndici (dc) {
249
+ dc.subscribe('undici:request:create', ({ request }) => {
250
+ if (!request || undiciStates.has(request)) return
251
+ const tr = consumers.tracing
252
+ const covered = Boolean(tr && typeof tr.fetchCovered === 'function' && tr.fetchCovered())
253
+ const st = startState(!covered, () => {
254
+ const origin = String(request.origin || '')
255
+ let host = 'localhost'
256
+ let port
257
+ let protocol = 'http:'
258
+ try {
259
+ const u = new URL(origin)
260
+ host = u.hostname.replace(/^\[|\]$/g, '')
261
+ protocol = u.protocol
262
+ port = u.port ? Number(u.port) : (u.protocol === 'https:' ? 443 : 80)
263
+ } catch (_) {}
264
+ return { method: request.method || 'GET', url: origin + (request.path || '/'), host, port, request, protocol }
265
+ }, (headers) => {
266
+ for (const [name, value] of headers) {
267
+ if (!undiciHas(request, name)) request.addHeader(name, value)
268
+ }
269
+ })
270
+ undiciStates.set(request, st)
271
+ })
272
+ dc.subscribe('undici:request:headers', ({ request, response }) => {
273
+ const st = undiciStates.get(request)
274
+ if (st) st.status = response && response.statusCode
275
+ })
276
+ dc.subscribe('undici:request:trailers', ({ request }) => {
277
+ const st = undiciStates.get(request)
278
+ if (st) endState(st, st.status)
279
+ })
280
+ dc.subscribe('undici:request:error', ({ request, error }) => {
281
+ const st = undiciStates.get(request)
282
+ if (st) endState(st, st.status, error)
283
+ })
284
+ }
285
+
286
+ function undiciHas (request, name) {
287
+ const h = request.headers
288
+ if (Array.isArray(h)) {
289
+ for (let i = 0; i < h.length; i += 2) {
290
+ if (String(h[i]).toLowerCase() === name) return true
291
+ }
292
+ return false
293
+ }
294
+ if (typeof h === 'string') return new RegExp('(^|\\r\\n)' + name + ':', 'i').test(h)
295
+ return false
296
+ }
297
+
298
+ // ---------------------------------------------------------------------------------------------
299
+ // fetch: Bun (globalThis.fetch wrapper)
300
+
301
+ let bunFetchWrapped = false
302
+ function wrapBunFetch () {
303
+ if (bunFetchWrapped || !BUN || typeof globalThis.fetch !== 'function') return
304
+ bunFetchWrapped = true
305
+ const orig = globalThis.fetch
306
+ const fetch = function fetch (input, init) {
307
+ let st
308
+ let args = arguments
309
+ try {
310
+ st = startState(true, () => {
311
+ const isReq = typeof Request === 'function' && input instanceof Request
312
+ const url = new URL(isReq ? input.url : String(input))
313
+ const method = String((init && init.method) || (isReq ? input.method : 'GET')).toUpperCase()
314
+ return { method, url: url.href, host: url.hostname, port: url.port ? Number(url.port) : (url.protocol === 'https:' ? 443 : 80), request: input }
315
+ }, (headers) => {
316
+ const isReq = typeof Request === 'function' && input instanceof Request
317
+ const h = new Headers((init && init.headers) || (isReq ? input.headers : undefined))
318
+ let changed = false
319
+ for (const [name, value] of headers) {
320
+ if (!h.has(name)) {
321
+ h.set(name, value)
322
+ changed = true
323
+ }
324
+ }
325
+ if (changed) args = [input, Object.assign({}, init || {}, { headers: h })]
326
+ })
327
+ } catch (_) {}
328
+ const p = orig.apply(this, args)
329
+ if (!st || !p || typeof p.then !== 'function') return p
330
+ return p.then((res) => {
331
+ endState(st, res && res.status)
332
+ return res
333
+ }, (err) => {
334
+ endState(st, undefined, err)
335
+ throw err
336
+ })
337
+ }
338
+ for (const k of Object.getOwnPropertyNames(orig)) {
339
+ if (k === 'length' || k === 'name' || k === 'prototype') continue
340
+ try {
341
+ Object.defineProperty(fetch, k, Object.getOwnPropertyDescriptor(orig, k))
342
+ } catch (_) {}
343
+ }
344
+ copyMeta(fetch, orig)
345
+ globalThis.fetch = fetch
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------------------------
349
+
350
+ function refresh () {
351
+ if (!consumers.metrics && !consumers.tracing) return
352
+ installDc()
353
+ if (BUN) wrapBunFetch()
354
+ if (consumers.tracing && consumers.tracing.outbound && !hasCreatedChannel()) wantClientWrap()
355
+ }
356
+
357
+ function setConsumer (name, value) {
358
+ consumers[name] = value || null
359
+ refresh()
360
+ }
361
+
362
+ module.exports = { setConsumer, consumers, OUT, clientInfo, hasCreatedChannel, spanStarting, clientWrap }