@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
package/lib/hooks.js ADDED
@@ -0,0 +1,151 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Process-wide prototype hooks for HTTP/HTTPS servers and TCP sockets.
5
+ // Installed once per process (shared through a global registry, so several copies of
6
+ // the library never wrap twice) and gated by handlers that the active instance sets.
7
+
8
+ const context = require('./tracing/context')
9
+
10
+ const REGISTRY = Symbol.for('@pmx/io:hooks')
11
+
12
+ function registry () {
13
+ let r = globalThis[REGISTRY]
14
+ if (!r) {
15
+ r = { installed: {}, handlers: { request: null, socketWrite: null, socketRead: null } }
16
+ Object.defineProperty(globalThis, REGISTRY, { value: r, enumerable: false, configurable: true })
17
+ }
18
+ return r
19
+ }
20
+
21
+ function wrapServerEmit (proto, protocol) {
22
+ const orig = proto.emit
23
+ proto.emit = function emit (event, req, res) {
24
+ if (event === 'request') {
25
+ const h = registry().handlers.request
26
+ if (h) {
27
+ let store
28
+ try {
29
+ store = h(protocol, req, res)
30
+ } catch (_) {}
31
+ // the request listeners run inside the SERVER span's context (80 NIO-220)
32
+ if (store !== undefined) return context.run(store, orig, this, arguments)
33
+ }
34
+ }
35
+ return orig.apply(this, arguments)
36
+ }
37
+ }
38
+
39
+ function nodeMajor () {
40
+ return Number(String(process.versions.node || '0').split('.')[0])
41
+ }
42
+
43
+ /**
44
+ * Measure every http and https server. On Node >= 20 the built-in diagnostics channel
45
+ * `http.server.request.start` is used (it covers https too, and needs neither module to be
46
+ * loaded eagerly — loading them costs ~18 ms, NIO-024). Elsewhere (Bun, older Node) the
47
+ * servers' `emit` is wrapped.
48
+ */
49
+ function installHttpServers () {
50
+ const r = registry()
51
+ if (r.installed.http) return
52
+ r.installed.http = true
53
+ if (!process.versions.bun && nodeMajor() >= 20) {
54
+ try {
55
+ const dc = require('diagnostics_channel')
56
+ dc.subscribe('http.server.request.start', (msg) => {
57
+ const h = registry().handlers.request
58
+ if (!h || !msg) return
59
+ const req = msg.request
60
+ const protocol = req && req.socket && req.socket.encrypted ? 'https' : 'http'
61
+ let store
62
+ try {
63
+ store = h(protocol, req, msg.response)
64
+ } catch (_) {}
65
+ // The channel is published in the same synchronous execution as the server's 'request'
66
+ // event: entering the store here makes the request listeners run inside the SERVER
67
+ // span's context (80 NIO-220). Ignored requests enter an empty store, so a keep-alive
68
+ // connection never carries the previous request's context.
69
+ if (store !== undefined) {
70
+ try {
71
+ context.enter(store)
72
+ } catch (_) {}
73
+ }
74
+ })
75
+ return
76
+ } catch (_) { /* fall back to wrapping */ }
77
+ }
78
+ if (process.versions.bun) {
79
+ wrapServerEmit(require('http').Server.prototype, 'http')
80
+ wrapServerEmit(require('https').Server.prototype, 'https')
81
+ return
82
+ }
83
+ wrapNetServerEmit()
84
+ }
85
+
86
+ /**
87
+ * Node 18: http.Server and https.Server (through tls.Server) inherit their emit from
88
+ * net.Server.prototype, which is already loaded (the IPC channel uses net): wrapping it there
89
+ * avoids loading http and https — https brings tls and crypto — at activation (NIO-024). Only
90
+ * 'request' events whose request is an http.IncomingMessage (HTTP/1 servers, not http2) count.
91
+ */
92
+ function wrapNetServerEmit () {
93
+ const proto = require('net').Server.prototype
94
+ const own = Object.prototype.hasOwnProperty.call(proto, 'emit')
95
+ const parent = Object.getPrototypeOf(proto)
96
+ const inherited = own ? proto.emit : null
97
+ let IncomingMessage = null
98
+ proto.emit = function emit (event, req, res) {
99
+ const orig = inherited || parent.emit
100
+ if (event === 'request') {
101
+ const h = registry().handlers.request
102
+ if (h) {
103
+ if (IncomingMessage === null) IncomingMessage = require('http').IncomingMessage
104
+ if (req instanceof IncomingMessage) {
105
+ let store
106
+ try {
107
+ store = h(req.socket && req.socket.encrypted ? 'https' : 'http', req, res)
108
+ } catch (_) {}
109
+ // the request listeners run inside the SERVER span's context (80 NIO-220)
110
+ if (store !== undefined) return context.run(store, orig, this, arguments)
111
+ }
112
+ }
113
+ }
114
+ return orig.apply(this, arguments)
115
+ }
116
+ }
117
+
118
+ function installNet () {
119
+ const r = registry()
120
+ if (r.installed.net) return
121
+ r.installed.net = true
122
+ const net = require('net')
123
+ const proto = net.Socket.prototype
124
+ const origWrite = proto.write
125
+ proto.write = function write (chunk, encoding) {
126
+ const h = registry().handlers.socketWrite
127
+ if (h && chunk) {
128
+ try {
129
+ h(typeof chunk === 'string' ? Buffer.byteLength(chunk, typeof encoding === 'string' ? encoding : 'utf8') : (chunk.length || 0))
130
+ } catch (_) {}
131
+ }
132
+ return origWrite.apply(this, arguments)
133
+ }
134
+ // Every received chunk passes through push() exactly once (80 A.4.5 quirk fixed).
135
+ const origPush = proto.push
136
+ proto.push = function push (chunk) {
137
+ const h = registry().handlers.socketRead
138
+ if (h && chunk) {
139
+ try {
140
+ h(chunk.length || 0)
141
+ } catch (_) {}
142
+ }
143
+ return origPush.apply(this, arguments)
144
+ }
145
+ }
146
+
147
+ function setHandler (name, fn) {
148
+ registry().handlers[name] = fn
149
+ }
150
+
151
+ module.exports = { installHttpServers, installNet, setHandler, registry }
package/lib/inbound.js ADDED
@@ -0,0 +1,174 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Inbound HTTP requests, shared by the HTTP metrics (A.4.4), the SERVER spans (NIO-220/221) and
5
+ // the framework exception reports (NIO-223). One process-wide interception (hooks.js) calls the
6
+ // active consumers; a per-request state object travels on the request and the response.
7
+ //
8
+ // The state: { protocol, req, res, span, store, route, routeFn, error, info, start, done }.
9
+ // Framework integrations find it with stateOf(req|res) and set `routeFn` (read when the
10
+ // response starts, NIO-221) and `error`/`info` (NIO-223).
11
+
12
+ const hooks = require('./hooks')
13
+ const context = require('./tracing/context')
14
+
15
+ const STATE = Symbol.for('@pmx/io:request-state')
16
+
17
+ const consumers = { metrics: null, tracing: null, errors: null }
18
+ let installedWriteHead = false
19
+
20
+ function stateOf (obj) {
21
+ return obj ? obj[STATE] : undefined
22
+ }
23
+
24
+ function attach (obj, st) {
25
+ // a symbol-keyed property: invisible to Object.keys/JSON, and much cheaper than defineProperty
26
+ try {
27
+ obj[STATE] = st
28
+ } catch (_) {}
29
+ }
30
+
31
+ function anyConsumer () {
32
+ return Boolean(consumers.metrics || consumers.tracing || consumers.errors)
33
+ }
34
+
35
+ /** Read the framework route when the response starts (NIO-221). */
36
+ function captureRoute (st) {
37
+ if (!st || st.route !== undefined) return
38
+ let route
39
+ try {
40
+ if (st.routeFn) route = st.routeFn()
41
+ if (route === undefined || route === null) {
42
+ // Express (and NestJS on Express): req.baseUrl + req.route.path
43
+ const req = st.req
44
+ if (req && req.route && typeof req.route.path === 'string') route = (req.baseUrl || '') + req.route.path
45
+ }
46
+ } catch (_) {
47
+ route = undefined
48
+ }
49
+ if (typeof route === 'string' && route !== '') st.route = route
50
+ }
51
+
52
+ function installWriteHead (res) {
53
+ if (installedWriteHead) return
54
+ let proto = Object.getPrototypeOf(res)
55
+ while (proto && !Object.prototype.hasOwnProperty.call(proto, 'writeHead')) proto = Object.getPrototypeOf(proto)
56
+ if (!proto) return
57
+ installedWriteHead = true
58
+ const r = hooks.registry()
59
+ if (r.installed.writeHead) return
60
+ r.installed.writeHead = true
61
+ const orig = proto.writeHead
62
+ const wrapped = function writeHead () {
63
+ const st = this[STATE]
64
+ if (st && !st.done) captureRoute(st)
65
+ return orig.apply(this, arguments)
66
+ }
67
+ Object.defineProperty(wrapped, 'length', { value: orig.length })
68
+ Object.defineProperty(wrapped, 'name', { value: orig.name })
69
+ proto.writeHead = wrapped
70
+ }
71
+
72
+ /**
73
+ * NIO-203: the request's and the response's events ('data', 'end', 'finish', 'close', …) run in
74
+ * the SERVER span's context. They are emitted from the connection's I/O callbacks, which carry
75
+ * the context the connection was created in: on Node with async context frames (24+) and on
76
+ * the `emit`-wrapping path (Node 18, Bun) a handler continued from a body parser's 'end'
77
+ * listener would otherwise run without the span. Installed once, on the first traced request:
78
+ * an own `emit` on IncomingMessage.prototype and ServerResponse.prototype that runs the
79
+ * inherited one inside the request's store (requests without a state are untouched).
80
+ */
81
+ function installEmitBinding () {
82
+ const r = hooks.registry()
83
+ if (r.installed.emitBinding) return
84
+ r.installed.emitBinding = true
85
+ let http
86
+ try {
87
+ http = require('http')
88
+ } catch (_) {
89
+ return
90
+ }
91
+ for (const Cls of [http.IncomingMessage, http.ServerResponse]) {
92
+ const proto = Cls && Cls.prototype
93
+ if (!proto || Object.prototype.hasOwnProperty.call(proto, 'emit')) continue
94
+ const parent = Object.getPrototypeOf(proto)
95
+ const emit = function emit () {
96
+ const st = this[STATE]
97
+ const store = st && st.store
98
+ // the inherited emit is looked up on every call, so later patches of it still apply
99
+ if (store === undefined || !context.active()) return parent.emit.apply(this, arguments)
100
+ return context.run(store, parent.emit, this, arguments)
101
+ }
102
+ try {
103
+ Object.defineProperty(emit, 'length', { value: parent.emit.length })
104
+ Object.defineProperty(proto, 'emit', { value: emit, writable: true, configurable: true, enumerable: false })
105
+ } catch (_) {}
106
+ }
107
+ }
108
+
109
+ function finish (st, aborted) {
110
+ if (st.done) return
111
+ st.done = true
112
+ captureRoute(st)
113
+ const status = st.res && typeof st.res.statusCode === 'number' ? st.res.statusCode : undefined
114
+ if (consumers.errors && status >= 500 && st.error) {
115
+ try {
116
+ consumers.errors.report(st, status)
117
+ } catch (_) {}
118
+ }
119
+ if (st.span && consumers.tracing) {
120
+ try {
121
+ consumers.tracing.endServer(st, status, aborted)
122
+ } catch (_) {}
123
+ }
124
+ }
125
+
126
+ /** The process-wide request handler (hooks.js). Returns the store to run listeners in. */
127
+ function onRequest (protocol, req, res) {
128
+ const m = consumers.metrics
129
+ if (m) {
130
+ try {
131
+ m.request(protocol, req, res)
132
+ } catch (_) {}
133
+ }
134
+ if (!consumers.tracing && !consumers.errors) return undefined
135
+ if (!res || typeof res.once !== 'function') return undefined
136
+ const prev = req[STATE]
137
+ if (prev && !prev.done) return prev.store // the same request seen twice (nested servers)
138
+ const st = { protocol, req, res, span: null, store: undefined, route: undefined, routeFn: null, error: null, info: null, done: false }
139
+ attach(req, st)
140
+ attach(res, st)
141
+ if (consumers.tracing) {
142
+ try {
143
+ consumers.tracing.startServer(st)
144
+ } catch (_) {}
145
+ }
146
+ installWriteHead(res)
147
+ if (st.store !== undefined) installEmitBinding()
148
+ // 'close' always follows 'finish'; writableFinished tells an aborted response apart
149
+ res.once('close', () => finish(st, !res.writableFinished))
150
+ return st.store
151
+ }
152
+
153
+ function refresh () {
154
+ if (anyConsumer()) {
155
+ hooks.installHttpServers()
156
+ hooks.setHandler('request', onRequest)
157
+ } else {
158
+ hooks.setHandler('request', null)
159
+ }
160
+ }
161
+
162
+ function setConsumer (name, value) {
163
+ consumers[name] = value || null
164
+ refresh()
165
+ }
166
+
167
+ /** The state of the request being served in the current context (Bun.serve, Hono on Bun). */
168
+ function currentState () {
169
+ if (!context.active()) return undefined
170
+ const store = context.current()
171
+ return store && store.server ? store.server[STATE] : undefined
172
+ }
173
+
174
+ module.exports = { STATE, stateOf, attach, setConsumer, consumers, captureRoute, finish, currentState }
@@ -0,0 +1,372 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Framework integrations (80 NIO-221 route names, NIO-223 framework exceptions): Express 4/5,
5
+ // Koa 2/3, Fastify 4/5, NestJS 10/11, h3 1.x (Nitro/Nuxt), Hono 4. Next.js needs none (its
6
+ // requests reach the Node http server; names follow Part G.2). Bun.serve is bun.js.
7
+ //
8
+ // Every integration only observes: it records on the request state of inbound.js the route
9
+ // (`routeFn`, read when the response starts) and the error the framework turned into a response
10
+ // (`error`, reported by the errors consumer when the status is ≥ 500). Nothing is added to a
11
+ // framework's public middleware lists (NIO-224).
12
+
13
+ const inbound = require('./inbound')
14
+ const debug = require('./debug')('axm:tracing')
15
+
16
+ const HONO_INSTANCE = Symbol.for('@pmx/io:hono')
17
+
18
+ function copyMeta (fn, orig) {
19
+ try {
20
+ Object.defineProperty(fn, 'length', { value: orig.length })
21
+ Object.defineProperty(fn, 'name', { value: orig.name })
22
+ } catch (_) {}
23
+ return fn
24
+ }
25
+
26
+ function wrapMethod (proto, name, make) {
27
+ if (!proto || typeof proto[name] !== 'function') return false
28
+ const orig = proto[name]
29
+ if (orig.__pmxWrapped) return false
30
+ const w = copyMeta(make(orig), orig)
31
+ Object.defineProperty(w, '__pmxWrapped', { value: true })
32
+ proto[name] = w
33
+ return true
34
+ }
35
+
36
+ function userId (u) {
37
+ return u && typeof u === 'object' && u.id !== undefined ? u.id : undefined
38
+ }
39
+
40
+ /** Record the error a framework turned into a response (reported at finish when ≥ 500). */
41
+ function record (st, err, info) {
42
+ if (!st || st.done || err === undefined || err === null) return
43
+ st.error = err
44
+ if (info) st.info = info
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------------------------
48
+ // Express 4 (express/lib/router/layer.js) and Express 5 (router/lib/layer.js)
49
+
50
+ function expressInfo (req) {
51
+ return { params: req.params, query: req.query, user: userId(req.user) }
52
+ }
53
+
54
+ function wrapNext (st, req, next) {
55
+ if (typeof next !== 'function') return next
56
+ return function next2 (err) {
57
+ if (err && err !== 'route' && err !== 'router') record(st, err, expressInfo(req))
58
+ return next.apply(this, arguments)
59
+ }
60
+ }
61
+
62
+ function patchLayer (Layer, names) {
63
+ const proto = Layer && Layer.prototype
64
+ if (!proto) return
65
+ wrapMethod(proto, names.request, (orig) => function (req, res, next) {
66
+ const st = inbound.stateOf(req)
67
+ if (!st) return orig.apply(this, arguments)
68
+ return orig.call(this, req, res, wrapNext(st, req, next))
69
+ })
70
+ wrapMethod(proto, names.error, (orig) => function (error, req, res, next) {
71
+ const st = inbound.stateOf(req)
72
+ if (!st) return orig.apply(this, arguments)
73
+ record(st, error, expressInfo(req))
74
+ return orig.call(this, error, req, res, wrapNext(st, req, next))
75
+ })
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------------------------
79
+ // Koa (Application.prototype.createContext)
80
+
81
+ function patchKoa (Application) {
82
+ const proto = Application && Application.prototype
83
+ wrapMethod(proto, 'createContext', (orig) => function (req, res) {
84
+ const ctx = orig.apply(this, arguments)
85
+ const st = inbound.stateOf(req)
86
+ if (st && ctx) {
87
+ st.routeFn = () => ctx._matchedRoute
88
+ const protoOnerror = ctx.onerror
89
+ if (typeof protoOnerror === 'function') {
90
+ ctx.onerror = function onerror (err) {
91
+ if (err) {
92
+ const status = err.status || err.statusCode
93
+ if (!(typeof status === 'number' && status < 500)) {
94
+ record(st, err, { params: ctx.params, query: ctx.query, user: userId(ctx.state && ctx.state.user) })
95
+ }
96
+ }
97
+ return protoOnerror.apply(this, arguments)
98
+ }
99
+ }
100
+ }
101
+ return ctx
102
+ })
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------------------------
106
+ // Fastify (diagnostics channel `fastify.initialization`: hooks added to each new instance)
107
+
108
+ function onFastify (instance) {
109
+ if (!instance || typeof instance.addHook !== 'function') return
110
+ try {
111
+ instance.addHook('onRequest', function pmxIoOnRequest (request, reply, done) {
112
+ const st = inbound.stateOf(request.raw)
113
+ if (st) {
114
+ st.routeFn = () => {
115
+ if (request.is404) return undefined
116
+ const ro = request.routeOptions
117
+ return (ro && ro.url) || request.routerPath || (request.routeConfig && request.routeConfig.url)
118
+ }
119
+ }
120
+ done()
121
+ })
122
+ instance.addHook('onError', function pmxIoOnError (request, reply, error, done) {
123
+ const st = inbound.stateOf(request.raw)
124
+ if (st) record(st, error, { params: request.params, query: request.query, user: userId(request.user) })
125
+ done()
126
+ })
127
+ } catch (err) {
128
+ debug('fastify hooks failed: %s', err && err.message)
129
+ }
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------------------------
133
+ // NestJS (BaseExceptionFilter.prototype.handleUnknownError)
134
+
135
+ function patchNestFilter (exports) {
136
+ const BaseExceptionFilter = exports && exports.BaseExceptionFilter
137
+ wrapMethod(BaseExceptionFilter && BaseExceptionFilter.prototype, 'handleUnknownError', (orig) => function (exception, host) {
138
+ try {
139
+ const req = host && typeof host.switchToHttp === 'function' ? host.switchToHttp().getRequest() : undefined
140
+ const raw = req && req.raw ? req.raw : req
141
+ const st = inbound.stateOf(raw)
142
+ if (st && req) record(st, exception, { params: req.params, query: req.query, user: userId(req.user) })
143
+ } catch (_) {}
144
+ return orig.apply(this, arguments)
145
+ })
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------------------------
149
+ // h3 1.x (createApp options: onRequest / onError observers)
150
+
151
+ function patchH3 (exports) {
152
+ const orig = exports && exports.createApp
153
+ if (typeof orig !== 'function') return undefined
154
+ const createApp = copyMeta(function createApp (options) {
155
+ const opts = Object.assign({}, options || {})
156
+ const userOnRequest = opts.onRequest
157
+ const userOnError = opts.onError
158
+ opts.onRequest = function onRequest (event) {
159
+ try {
160
+ const st = inbound.stateOf(event && event.node && event.node.req)
161
+ if (st) {
162
+ st.routeFn = () => {
163
+ const m = event.context && event.context.matchedRoute
164
+ return m && typeof m.path === 'string' ? m.path : undefined
165
+ }
166
+ }
167
+ } catch (_) {}
168
+ return userOnRequest ? userOnRequest.apply(this, arguments) : undefined
169
+ }
170
+ opts.onError = function onError (error, event) {
171
+ try {
172
+ const st = inbound.stateOf(event && event.node && event.node.req)
173
+ const status = error && (error.statusCode || error.status)
174
+ if (st && !(typeof status === 'number' && status < 500)) {
175
+ const original = error && error.cause instanceof Error ? error.cause : error
176
+ record(st, original, { params: event.context && event.context.params, query: undefined, user: undefined })
177
+ }
178
+ } catch (_) {}
179
+ return userOnError ? userOnError.apply(this, arguments) : undefined
180
+ }
181
+ return orig.call(this, opts)
182
+ }, orig)
183
+ const desc = Object.getOwnPropertyDescriptor(exports, 'createApp')
184
+ if (desc && desc.writable && !Object.isFrozen(exports)) {
185
+ exports.createApp = createApp
186
+ return undefined
187
+ }
188
+ return replaceExport(exports, 'createApp', createApp)
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------------------------
192
+ // Hono 4 (class Hono: per-instance fetch, router.match and errorHandler observers)
193
+
194
+ let pendingDispatch = null
195
+
196
+ function honoRoute (matchResult) {
197
+ try {
198
+ const list = Array.isArray(matchResult) ? matchResult[0] : null
199
+ if (!Array.isArray(list)) return undefined
200
+ const routes = []
201
+ for (const e of list) {
202
+ const hr = Array.isArray(e) ? e[0] : undefined
203
+ const rr = Array.isArray(hr) ? hr[1] : undefined
204
+ if (rr && typeof rr.path === 'string') routes.push(rr)
205
+ }
206
+ for (let i = routes.length - 1; i >= 0; i--) if (routes[i].method !== 'ALL') return routes[i].path
207
+ for (let i = routes.length - 1; i >= 0; i--) if (!routes[i].path.endsWith('*')) return routes[i].path
208
+ return undefined
209
+ } catch (_) {
210
+ return undefined
211
+ }
212
+ }
213
+
214
+ function honoState (env) {
215
+ const incoming = env && env.incoming
216
+ return (incoming && inbound.stateOf(incoming)) || inbound.currentState()
217
+ }
218
+
219
+ const routers = new WeakSet()
220
+ function wrapRouter (router) {
221
+ if (!router || typeof router.match !== 'function' || routers.has(router)) return
222
+ routers.add(router)
223
+ const orig = router.match
224
+ router.match = copyMeta(function match () {
225
+ const r = orig.apply(this, arguments)
226
+ if (pendingDispatch && !pendingDispatch.result) pendingDispatch.result = r
227
+ return r
228
+ }, orig)
229
+ }
230
+
231
+ const errorWrappers = new WeakMap()
232
+ const errorOriginals = new WeakMap() // wrapper → the app's handler
233
+ // Hono's own default error handler(s), as found on new instances: while Hono's route() or
234
+ // basePath() runs, an instance's errorHandler reads as the raw default, because route() decides
235
+ // by identity (`app.errorHandler === errorHandler`) whether a sub-app's routes keep their
236
+ // errors for the parent's onError (NIO-224 transparency).
237
+ const honoDefaults = new WeakSet()
238
+ let honoInternal = 0
239
+
240
+ function unwrapHandler (h) {
241
+ return (typeof h === 'function' && errorOriginals.get(h)) || h
242
+ }
243
+
244
+ function wrapErrorHandler (handler) {
245
+ if (typeof handler !== 'function') return handler
246
+ handler = unwrapHandler(handler)
247
+ let w = errorWrappers.get(handler)
248
+ if (!w) {
249
+ w = copyMeta(function errorHandler (err, c) {
250
+ try {
251
+ const st = honoState(c && c.env)
252
+ const status = err && typeof err.getResponse === 'function' && typeof err.status === 'number' ? err.status : undefined
253
+ if (st && !(status !== undefined && status < 500)) record(st, err, { params: undefined, query: undefined, user: undefined })
254
+ } catch (_) {}
255
+ return handler.apply(this, arguments)
256
+ }, handler)
257
+ errorWrappers.set(handler, w)
258
+ errorOriginals.set(w, handler)
259
+ }
260
+ return w
261
+ }
262
+
263
+ /** route() and basePath() of Hono's base prototype: see honoDefaults; clones are instrumented. */
264
+ function patchHonoProto (app) {
265
+ let proto = Object.getPrototypeOf(app)
266
+ while (proto && !Object.prototype.hasOwnProperty.call(proto, 'route')) proto = Object.getPrototypeOf(proto)
267
+ if (!proto) return
268
+ const internal = (orig, after) => function () {
269
+ honoInternal++
270
+ try {
271
+ const r = orig.apply(this, arguments)
272
+ return after ? after(r) : r
273
+ } finally {
274
+ honoInternal--
275
+ }
276
+ }
277
+ wrapMethod(proto, 'route', (orig) => internal(orig))
278
+ // basePath() returns a clone made inside Hono (not through the exported class): wrap it too
279
+ wrapMethod(proto, 'basePath', (orig) => internal(orig, (clone) => (clone && typeof clone === 'object' ? wrapHonoInstance(clone) : clone)))
280
+ }
281
+
282
+ function wrapHonoInstance (app) {
283
+ if (!app || app[HONO_INSTANCE]) return app
284
+ Object.defineProperty(app, HONO_INSTANCE, { value: true })
285
+ patchHonoProto(app)
286
+ wrapRouter(app.router)
287
+ const origFetch = app.fetch
288
+ if (typeof origFetch === 'function') {
289
+ app.fetch = copyMeta(function fetch (request, env) {
290
+ const holder = { result: null }
291
+ const prev = pendingDispatch
292
+ pendingDispatch = holder
293
+ let r
294
+ try {
295
+ r = origFetch.apply(this, arguments)
296
+ } finally {
297
+ pendingDispatch = prev
298
+ try {
299
+ const route = holder.result ? honoRoute(holder.result) : undefined
300
+ const st = honoState(env)
301
+ if (st && route) st.routeFn = () => route
302
+ } catch (_) {}
303
+ }
304
+ return r
305
+ }, origFetch)
306
+ }
307
+ let handler = unwrapHandler(app.errorHandler)
308
+ // a new instance (not a clone, whose handler was copied from its parent) holds Hono's default
309
+ if (honoInternal === 0 && typeof handler === 'function') honoDefaults.add(handler)
310
+ try {
311
+ Object.defineProperty(app, 'errorHandler', {
312
+ configurable: true,
313
+ enumerable: true,
314
+ get () { return honoInternal > 0 && honoDefaults.has(handler) ? handler : wrapErrorHandler(handler) },
315
+ set (v) { handler = unwrapHandler(v) }
316
+ })
317
+ } catch (_) {}
318
+ return app
319
+ }
320
+
321
+ function patchHono (exports) {
322
+ const Hono = exports && exports.Hono
323
+ if (typeof Hono !== 'function') return undefined
324
+ const P = new Proxy(Hono, {
325
+ construct (target, args, newTarget) {
326
+ return wrapHonoInstance(Reflect.construct(target, args, newTarget))
327
+ }
328
+ })
329
+ return replaceExport(exports, 'Hono', P)
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------------------------
333
+
334
+ /** A copy of `exports` whose `name` is `value` (other properties keep their descriptors). */
335
+ function replaceExport (exports, name, value) {
336
+ const desc = Object.getOwnPropertyDescriptor(exports, name)
337
+ if (desc && desc.writable && desc.configurable !== false && !Object.isFrozen(exports)) {
338
+ exports[name] = value
339
+ return undefined
340
+ }
341
+ if (desc && desc.configurable) {
342
+ Object.defineProperty(exports, name, { value, writable: true, enumerable: desc.enumerable, configurable: true })
343
+ return undefined
344
+ }
345
+ const out = {}
346
+ for (const k of Reflect.ownKeys(exports)) {
347
+ if (k === name) continue
348
+ const d = Object.getOwnPropertyDescriptor(exports, k)
349
+ try {
350
+ Object.defineProperty(out, k, d)
351
+ } catch (_) {}
352
+ }
353
+ Object.defineProperty(out, name, { value, writable: true, enumerable: true, configurable: true })
354
+ return out
355
+ }
356
+
357
+ function list () {
358
+ return [
359
+ { pkg: 'express', for: 'both', files: { 'lib/router/layer.js': (L) => patchLayer(L, { request: 'handle_request', error: 'handle_error' }) } },
360
+ { pkg: 'router', for: 'both', files: { 'lib/layer.js': (L) => patchLayer(L, { request: 'handleRequest', error: 'handleError' }) } },
361
+ { pkg: 'koa', for: 'both', files: { 'lib/application.js': patchKoa }, eager: ['lib/application.js'] },
362
+ { pkg: '@nestjs/core', for: 'both', files: { 'exceptions/base-exception-filter.js': patchNestFilter }, eager: ['exceptions/base-exception-filter.js'] },
363
+ { pkg: 'h3', for: 'both', files: { 'dist/index.cjs': patchH3, 'dist/index.mjs': patchH3 } },
364
+ {
365
+ pkg: 'hono',
366
+ for: 'both',
367
+ files: { 'dist/cjs/hono.js': patchHono, 'dist/hono.js': patchHono }
368
+ }
369
+ ].concat(require('./tracing/logs').integrations(), require('./itg').list())
370
+ }
371
+
372
+ module.exports = { list, record, honoRoute, replaceExport, wrapHonoInstance, onFastify }