@aidra/pmx-io 0.0.0-stage → 9.13.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/bun.js ADDED
@@ -0,0 +1,155 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Bun.serve (80 NIO-101, NIO-220, NIO-221, NIO-223): HTTP/HTTPS metrics, SERVER spans (route =
5
+ // the `routes` key that matched) and handler exceptions for every Bun.serve server. Bun.serve is
6
+ // a writable property, so the function is wrapped at activation; the app's options object is
7
+ // copied, never changed.
8
+
9
+ const inbound = require('./inbound')
10
+ const context = require('./tracing/context')
11
+
12
+ let wrapped = false
13
+
14
+ function serverState (protocol, req, route) {
15
+ let url
16
+ try {
17
+ url = new URL(req.url)
18
+ } catch (_) {
19
+ url = { pathname: '/', search: '' }
20
+ }
21
+ const target = url.pathname + (url.search || '')
22
+ return {
23
+ protocol,
24
+ req: { url: target, method: req.method, headers: req.headers, path: url.pathname },
25
+ res: null,
26
+ span: null,
27
+ store: undefined,
28
+ route,
29
+ routeFn: null,
30
+ error: null,
31
+ info: null,
32
+ done: false,
33
+ bun: true
34
+ }
35
+ }
36
+
37
+ function wrapHandler (fn, protocol, route) {
38
+ const w = function (req) {
39
+ const c = inbound.consumers
40
+ if (!c.metrics && !c.tracing && !c.errors) return fn.apply(this, arguments)
41
+ const doneMetric = c.metrics && c.metrics.start ? c.metrics.start(protocol) : null
42
+ const st = serverState(protocol, req, route)
43
+ if (c.tracing) {
44
+ try {
45
+ const host = (req.headers && req.headers.get && req.headers.get('host')) || 'localhost'
46
+ const span = c.tracing.tracer.serverSpan({
47
+ method: req.method,
48
+ target: st.req.url,
49
+ scheme: protocol,
50
+ host,
51
+ userAgent: req.headers && req.headers.get ? req.headers.get('user-agent') : undefined,
52
+ headers: req.headers,
53
+ request: req
54
+ })
55
+ if (span) {
56
+ st.span = span
57
+ inbound.attach(span, st)
58
+ st.store = { span, bag: span.bag, server: span }
59
+ } else {
60
+ st.store = context.EMPTY_STORE
61
+ }
62
+ } catch (_) {}
63
+ }
64
+ const finish = (status) => {
65
+ if (st.done) return
66
+ st.done = true
67
+ if (doneMetric) {
68
+ try {
69
+ doneMetric()
70
+ } catch (_) {}
71
+ }
72
+ if (st.route === undefined && st.routeFn) {
73
+ try {
74
+ st.route = st.routeFn()
75
+ } catch (_) {}
76
+ }
77
+ if (c.errors && status >= 500 && st.error) {
78
+ try {
79
+ c.errors.report(st, status)
80
+ } catch (_) {}
81
+ }
82
+ if (st.span && c.tracing) {
83
+ try {
84
+ c.tracing.finishServerSpan(st.span, status, st.route, st.error)
85
+ } catch (_) {}
86
+ }
87
+ }
88
+ let r
89
+ try {
90
+ r = st.store ? context.run(st.store, fn, this, arguments) : fn.apply(this, arguments)
91
+ } catch (err) {
92
+ st.error = err
93
+ finish(500)
94
+ throw err
95
+ }
96
+ if (r && typeof r.then === 'function') {
97
+ return r.then((res) => {
98
+ finish(res && res.status)
99
+ return res
100
+ }, (err) => {
101
+ st.error = err
102
+ finish(500)
103
+ throw err
104
+ })
105
+ }
106
+ finish(r && r.status)
107
+ return r
108
+ }
109
+ try {
110
+ Object.defineProperty(w, 'length', { value: fn.length })
111
+ Object.defineProperty(w, 'name', { value: fn.name })
112
+ } catch (_) {}
113
+ return w
114
+ }
115
+
116
+ function wrapRoutes (routes, protocol) {
117
+ const out = {}
118
+ for (const key of Object.keys(routes)) {
119
+ const v = routes[key]
120
+ if (typeof v === 'function') {
121
+ out[key] = wrapHandler(v, protocol, key)
122
+ } else if (v && typeof v === 'object' && !(typeof Response === 'function' && v instanceof Response)) {
123
+ const per = {}
124
+ for (const m of Object.keys(v)) per[m] = typeof v[m] === 'function' ? wrapHandler(v[m], protocol, key) : v[m]
125
+ out[key] = per
126
+ } else {
127
+ out[key] = v
128
+ }
129
+ }
130
+ return out
131
+ }
132
+
133
+ function install () {
134
+ if (wrapped || typeof globalThis.Bun !== 'object' || !globalThis.Bun || typeof globalThis.Bun.serve !== 'function') return
135
+ wrapped = true
136
+ const Bun = globalThis.Bun
137
+ const orig = Bun.serve
138
+ const serve = function serve (options) {
139
+ if (!options || typeof options !== 'object') return orig.apply(this, arguments)
140
+ const protocol = options.tls ? 'https' : 'http'
141
+ const o = Object.assign({}, options)
142
+ if (typeof o.fetch === 'function') o.fetch = wrapHandler(o.fetch, protocol, undefined)
143
+ if (o.routes && typeof o.routes === 'object') o.routes = wrapRoutes(o.routes, protocol)
144
+ const args = Array.prototype.slice.call(arguments)
145
+ args[0] = o
146
+ return orig.apply(this, args)
147
+ }
148
+ try {
149
+ Bun.serve = serve
150
+ } catch (_) {
151
+ wrapped = false
152
+ }
153
+ }
154
+
155
+ module.exports = { install, wrapHandler }
package/lib/config.js ADDED
@@ -0,0 +1,67 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Configuration defaulting and the configuration report (80 A.2, A.8.3).
5
+
6
+ const { DEFAULT_CONFIG, VERSION } = require('./constants')
7
+ const { autocast, isPlainObject } = require('./util')
8
+
9
+ /** Shallow defaulting: keys given by the caller replace the default value wholesale. */
10
+ function normalizeConfig (config) {
11
+ const base = JSON.parse(JSON.stringify(DEFAULT_CONFIG)) // apmOptions (undefined) is dropped
12
+ if (!isPlainObject(config)) return base
13
+ return Object.assign(base, config)
14
+ }
15
+
16
+ function apmInfo () {
17
+ return { type: 'node', version: VERSION, impl: 'pmx' }
18
+ }
19
+
20
+ function redact (moduleConf) {
21
+ for (const key of ['password', 'passwd']) {
22
+ if (moduleConf[key] !== undefined && moduleConf[key] !== null && moduleConf[key] !== '') {
23
+ moduleConf[key] = 'Password hidden'
24
+ }
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Add apm / module_* keys and merge package.json `config` and the env var named after the
30
+ * module (A.8.3). Mutates and returns `conf`.
31
+ */
32
+ function decorate (conf, pkg, moduleName) {
33
+ conf.module_conf = {}
34
+ conf.apm = apmInfo()
35
+ conf.module_name = moduleName
36
+ if (pkg && typeof pkg.version === 'string') conf.module_version = pkg.version
37
+ if (pkg && isPlainObject(pkg.config)) {
38
+ Object.assign(conf, pkg.config)
39
+ Object.assign(conf.module_conf, pkg.config)
40
+ }
41
+ const raw = moduleName !== undefined ? process.env[moduleName] : undefined
42
+ if (typeof raw === 'string') {
43
+ let parsed
44
+ try {
45
+ parsed = JSON.parse(raw)
46
+ } catch (_) {
47
+ parsed = undefined
48
+ }
49
+ if (isPlainObject(parsed)) {
50
+ const cast = autocast(parsed)
51
+ Object.assign(conf, cast)
52
+ const forModule = Object.assign({}, cast)
53
+ delete forModule.probes
54
+ Object.assign(conf.module_conf, forModule)
55
+ }
56
+ }
57
+ redact(conf.module_conf)
58
+ return conf
59
+ }
60
+
61
+ const DEFAULT_WIDGET = {
62
+ type: 'generic',
63
+ logo: 'https://app.keymetrics.io/img/logo/keymetrics-300.png',
64
+ theme: ['#111111', '#1B2228', '#807C7C', '#807C7C']
65
+ }
66
+
67
+ module.exports = { normalizeConfig, decorate, apmInfo, DEFAULT_WIDGET }
@@ -0,0 +1,17 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ const VERSION = require('../package.json').version
5
+
6
+ module.exports = {
7
+ VERSION,
8
+ GLOBAL_SYMBOL: Symbol.for('@pm2/io'),
9
+ METRIC_PUSH_INTERVAL: 990,
10
+ DEFAULT_CONFIG: Object.freeze({
11
+ catchExceptions: true,
12
+ profiling: true,
13
+ metrics: { v8: true, network: false, eventLoop: true, runtime: true, http: true, httpOutbound: true },
14
+ standalone: false,
15
+ apmOptions: undefined
16
+ })
17
+ }
package/lib/debug.js ADDED
@@ -0,0 +1,48 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Minimal DEBUG namespace support (80 NIO-012): `*` wildcards, comma/space separated
5
+ // patterns, `-` prefix to exclude. Lines are `<namespace> <message>` on stderr.
6
+
7
+
8
+ let cachedSource
9
+ let include = []
10
+ let exclude = []
11
+
12
+ function toRegExp (pattern) {
13
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replace(/\*/g, '.*?')
14
+ return new RegExp('^' + escaped + '$')
15
+ }
16
+
17
+ function parse () {
18
+ const src = process.env.DEBUG || ''
19
+ if (src === cachedSource) return
20
+ cachedSource = src
21
+ include = []
22
+ exclude = []
23
+ for (const part of src.split(/[\s,]+/)) {
24
+ if (!part) continue
25
+ if (part[0] === '-') exclude.push(toRegExp(part.slice(1)))
26
+ else include.push(toRegExp(part))
27
+ }
28
+ }
29
+
30
+ function enabled (ns) {
31
+ parse()
32
+ if (exclude.some((r) => r.test(ns))) return false
33
+ return include.some((r) => r.test(ns))
34
+ }
35
+
36
+ function createDebug (ns) {
37
+ const log = function (...args) {
38
+ if (!enabled(ns)) return
39
+ try {
40
+ process.stderr.write(ns + ' ' + require('util').format(...args) + '\n')
41
+ } catch (_) { /* never break the app */ }
42
+ }
43
+ log.namespace = ns
44
+ Object.defineProperty(log, 'enabled', { get: () => enabled(ns) })
45
+ return log
46
+ }
47
+
48
+ module.exports = createDebug
@@ -0,0 +1,111 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+
3
+ // Module customization hooks (80 NIO-085, NIO-210), registered by preload.mjs only when tracing is
4
+ // enabled at spawn: `module.registerHooks` (in-thread, Node ≥ 22.15 / ≥ 23.5) or `module.register`
5
+ // (off-thread, Node ≥ 18.19 / ≥ 20.6). For an ES-module file of a target library the `load` hook
6
+ // returns a wrapper module instead:
7
+ //
8
+ // import * as ns from '<url>?pmx-io-orig'
9
+ // export * from '<url>?pmx-io-orig'
10
+ // const r = globalThis[Symbol.for('@pmx/io:esm')]?.('<pkg>', '<file>', ns) ?? ns
11
+ // const x0 = r['Name']; export { x0 as Name }
12
+ //
13
+ // Explicit exports take precedence over `export *`, so only the patched names change; the
14
+ // original module is evaluated once (under the `?pmx-io-orig` URL). Patching itself happens on
15
+ // the main thread (modules.js / integrations.js).
16
+
17
+ // [package, file in package, exported names to patch]
18
+ export const TARGETS = [
19
+ ['hono', 'dist/hono.js', ['Hono']],
20
+ ['h3', 'dist/index.mjs', ['createApp']],
21
+ // database and broker integrations (82, lib/itg): classes are patched in place through the
22
+ // namespace; replaced exports are listed by name ('default' re-exports the default export)
23
+ ['postgres', 'src/index.js', ['default']],
24
+ ['postgres', 'src/query.js', []],
25
+ ['bullmq', 'dist/esm/classes/queue.js', []],
26
+ ['bullmq', 'dist/esm/classes/worker.js', ['Worker']],
27
+ ['pg-boss', 'dist/manager.js', ['default']],
28
+ ['graphile-worker', 'dist/helpers.js', ['makeAddJob']],
29
+ ['graphile-worker', 'dist/worker.js', ['makeNewWorker']]
30
+ ]
31
+
32
+ const MARK = 'pmx-io-orig'
33
+
34
+ function match (url) {
35
+ if (typeof url !== 'string' || !url.startsWith('file:') || url.includes(MARK)) return null
36
+ const i = url.lastIndexOf('/node_modules/')
37
+ if (i === -1) return null
38
+ const rest = decodeURI(url.slice(i + 14)).split('?')[0]
39
+ for (const t of TARGETS) {
40
+ if (rest === t[0] + '/' + t[1]) return t
41
+ }
42
+ return null
43
+ }
44
+
45
+ function wrapper (url, t, hasDefault) {
46
+ const orig = JSON.stringify(url + (url.includes('?') ? '&' : '?') + MARK)
47
+ const lines = [
48
+ `import * as ns from ${orig};`,
49
+ `export * from ${orig};`,
50
+ `const f = globalThis[Symbol.for('@pmx/io:esm')];`,
51
+ `const r = typeof f === 'function' ? f(${JSON.stringify(t[0])}, ${JSON.stringify(t[1])}, ns) : ns;`
52
+ ]
53
+ // `export *` never re-exports `default`: carry it explicitly when the original has one
54
+ if (hasDefault && !t[2].includes('default')) lines.push('export default ns.default;')
55
+ t[2].forEach((name, i) => {
56
+ lines.push(`const x${i} = r[${JSON.stringify(name)}]; export { x${i} as ${name} };`)
57
+ })
58
+ // keep the module evaluated (and patched) even when only re-exported names are used
59
+ if (t[2].length === 0) lines.push('void r;')
60
+ return lines.join('\n')
61
+ }
62
+
63
+ const DEFAULT_RE = /\bexport\s+default\b|\bas\s+default\b/
64
+
65
+ function sourceText (r) {
66
+ const src = r && r.source
67
+ if (typeof src === 'string') return src
68
+ if (src && typeof src === 'object' && typeof src.byteLength === 'number') return Buffer.from(src.buffer || src, src.byteOffset || 0, src.byteLength).toString('utf8')
69
+ return ''
70
+ }
71
+
72
+ // In-thread hooks (module.registerHooks) make Node evaluate CommonJS files reached from `import`
73
+ // without Module.prototype._compile, where modules.js patches them. For those files of instrumented
74
+ // packages (asked through a main-thread predicate; off-thread hooks have none and need nothing) the
75
+ // source gets a trailer that hands `module` to the same patch, at the end of its evaluation.
76
+ const CJS_KEY = Symbol.for('@pmx/io:cjs')
77
+ const TRAILER = "\n;(function(){try{var f=globalThis[Symbol.for('@pmx/io:cjs')];if(typeof f==='function')f(module,__filename)}catch(e){}})();\n"
78
+
79
+ function cjsTarget (url) {
80
+ const f = globalThis[CJS_KEY]
81
+ if (typeof f !== 'function' || typeof url !== 'string' || !url.startsWith('file:') || !url.includes('/node_modules/')) return false
82
+ try {
83
+ return f.wants(url)
84
+ } catch (_) {
85
+ return false
86
+ }
87
+ }
88
+
89
+ function decide (url, t, r) {
90
+ // a target is wrapped only when it is an ES module (a package may ship the same path as
91
+ // CommonJS in older versions, e.g. graphile-worker 0.16)
92
+ if (t && r && r.format === 'module') return { format: 'module', shortCircuit: true, source: wrapper(url, t, DEFAULT_RE.test(sourceText(r))) }
93
+ if (r && r.format === 'commonjs' && cjsTarget(url)) {
94
+ let source = r.source
95
+ if (source === null || source === undefined) source = globalThis[CJS_KEY].read(url)
96
+ if (source !== null && source !== undefined) return Object.assign({}, r, { source: String(source) + TRAILER })
97
+ }
98
+ return r
99
+ }
100
+
101
+ // Synchronous for module.registerHooks, a promise for module.register (nextLoad decides)
102
+ export function load (url, context, nextLoad) {
103
+ const t = match(url)
104
+ const r = nextLoad(url, context)
105
+ if (r && typeof r.then === 'function') return t ? r.then((res) => decide(url, t, res)) : r
106
+ return decide(url, t, r)
107
+ }
108
+
109
+ export function resolve (specifier, context, nextResolve) {
110
+ return nextResolve(specifier, context)
111
+ }
package/lib/exit.js ADDED
@@ -0,0 +1,79 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // onExit hooks (80 NIO-075): cb(code, signal) once, at normal exit or on SIGINT/SIGTERM/SIGHUP;
5
+ // after signal-triggered callbacks settle (max 5 s) the process exits with code 0.
6
+ // Hooks are process-wide and survive init()/destroy() (apps commonly register
7
+ // `onExit(() => io.destroy())`).
8
+
9
+ const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP']
10
+ const SIGNAL_GRACE_MS = 5000
11
+
12
+ const callbacks = [] // { fn, called }
13
+ let installed = false
14
+ let signalled = false
15
+
16
+ function runSync (code, signal) {
17
+ for (const entry of callbacks.slice()) {
18
+ if (entry.called) continue
19
+ entry.called = true
20
+ try {
21
+ entry.fn(code, signal)
22
+ } catch (err) {
23
+ try {
24
+ console.error(err)
25
+ } catch (_) {}
26
+ }
27
+ }
28
+ }
29
+
30
+ async function runSignal (signal) {
31
+ const deadline = Date.now() + SIGNAL_GRACE_MS
32
+ for (const entry of callbacks.slice()) {
33
+ if (entry.called) continue
34
+ entry.called = true
35
+ try {
36
+ const ret = entry.fn(null, signal)
37
+ if (ret && typeof ret.then === 'function') {
38
+ const left = Math.max(0, deadline - Date.now())
39
+ let timer
40
+ await Promise.race([
41
+ Promise.resolve(ret).catch(() => {}),
42
+ new Promise((resolve) => { timer = setTimeout(resolve, left) })
43
+ ])
44
+ clearTimeout(timer)
45
+ }
46
+ } catch (err) {
47
+ try {
48
+ console.error(err)
49
+ } catch (_) {}
50
+ }
51
+ }
52
+ }
53
+
54
+ function onSignal (signal) {
55
+ if (signalled) return
56
+ signalled = true
57
+ runSignal(signal).then(() => process.exit(0), () => process.exit(0))
58
+ }
59
+
60
+ function install () {
61
+ if (installed) return
62
+ installed = true
63
+ process.on('exit', (code) => runSync(code, null))
64
+ for (const sig of SIGNALS) {
65
+ try {
66
+ process.on(sig, onSignal)
67
+ } catch (_) { /* signal not supported on this platform */ }
68
+ }
69
+ }
70
+
71
+ /** Register an exit callback. Returns undefined (non-functions are ignored). */
72
+ function onExit (fn) {
73
+ if (typeof fn !== 'function') return undefined
74
+ callbacks.push({ fn, called: false })
75
+ install()
76
+ return undefined
77
+ }
78
+
79
+ module.exports = { onExit }