@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/modules.js ADDED
@@ -0,0 +1,260 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Library instrumentation mechanism (80 NIO-210): libraries are wrapped when the app loads them,
5
+ // never loaded by @aidra/pmx-io itself.
6
+ // * CommonJS (Node): Module.prototype._compile is observed; when a file of a target package has
7
+ // been compiled, the integration patches its exports (in place, or by replacing
8
+ // module.exports) before the caller receives them. This also covers CJS packages imported
9
+ // from ES modules (Node runs them through the same loader). Files already loaded when an
10
+ // integration is enabled are patched from require.cache.
11
+ // * CommonJS (Bun): Module.prototype.require is observed (Bun does not use _compile).
12
+ // * ES modules: hooks registered by preload.mjs when tracing is enabled at spawn (NIO-085,
13
+ // esm-hooks.mjs) replace a target module by a wrapper module that re-exports it with the
14
+ // patched exports (globalThis[Symbol.for('@pmx/io:esm')] below).
15
+ // Integrations are declared in integrations.js: { pkg, files: { '<path in package>': patch } }.
16
+ // A patch receives (exports, info) and returns undefined (patched in place) or a replacement.
17
+ // Wrapping is transparent (82 ITG-003); a module that fails to patch is left untouched.
18
+
19
+ const path = require('path')
20
+ const debug = require('./debug')('axm:tracing')
21
+
22
+ // The packages integrations.js patches (checked before loading the integrations themselves)
23
+ const TARGET_PKGS = new Set(['express', 'router', 'koa', '@nestjs/core', 'h3', 'hono', 'pino', 'winston', 'bunyan',
24
+ // database and broker integrations (82, lib/itg/index.js)
25
+ 'pg', 'pg-pool', 'postgres', 'ioredis', '@redis/client', 'mysql2', 'mongodb', 'nats', '@nats-io/nats-core',
26
+ '@nats-io/jetstream', 'kafkajs', 'amqplib', 'bullmq', 'pg-boss', 'graphile-worker'])
27
+
28
+ const REGISTRY = Symbol.for('@pmx/io:modules')
29
+ const ESM_KEY = Symbol.for('@pmx/io:esm')
30
+ const PATCHED = Symbol.for('@pmx/io:patched')
31
+
32
+ function registry () {
33
+ let r = globalThis[REGISTRY]
34
+ if (!r) {
35
+ r = { installed: false, integrations: null, wants: { errors: false, tracing: false }, tracing: null, done: new Set() }
36
+ Object.defineProperty(globalThis, REGISTRY, { value: r, enumerable: false, configurable: true })
37
+ }
38
+ return r
39
+ }
40
+
41
+ /** (pkg, file in package) of a path under node_modules, or null. */
42
+ function packageOf (filename) {
43
+ if (typeof filename !== 'string') return null
44
+ const norm = filename.split(path.sep).join('/')
45
+ const i = norm.lastIndexOf('/node_modules/')
46
+ if (i === -1) return null
47
+ const rest = norm.slice(i + 14)
48
+ const parts = rest.split('/')
49
+ if (parts[0].startsWith('@')) {
50
+ if (parts.length < 3) return null
51
+ return { pkg: parts[0] + '/' + parts[1], file: parts.slice(2).join('/'), dir: norm.slice(0, i + 14) + parts[0] + '/' + parts[1] }
52
+ }
53
+ if (parts.length < 2) return null
54
+ return { pkg: parts[0], file: parts.slice(1).join('/'), dir: norm.slice(0, i + 14) + parts[0] }
55
+ }
56
+
57
+ function integrations () {
58
+ const r = registry()
59
+ if (!r.integrations) r.integrations = require('./integrations').list()
60
+ return r.integrations
61
+ }
62
+
63
+ function findPatch (pkg, file) {
64
+ for (const it of integrations()) {
65
+ if (it.pkg !== pkg) continue
66
+ const fn = it.files[file]
67
+ if (fn) return { it, fn }
68
+ }
69
+ return null
70
+ }
71
+
72
+ function wanted (it) {
73
+ const r = registry()
74
+ const w = r.wants
75
+ if (it.for === 'db' || it.for === 'messaging' || it.for === 'db+messaging') {
76
+ // 82 ITG-001: database / broker integrations only with tracing and their switch on
77
+ const t = w.tracing && r.tracing && r.tracing._tracer
78
+ if (!t) return false
79
+ return (it.for !== 'messaging' && Boolean(t.db)) || (it.for !== 'db' && Boolean(t.messaging))
80
+ }
81
+ return (it.for === 'errors' && (w.errors || w.tracing)) || (it.for === 'tracing' && w.tracing) || (it.for === 'both' && (w.errors || w.tracing))
82
+ }
83
+
84
+ /** Apply the patch for (pkg, file) to `exports`; returns the (possibly replaced) exports. */
85
+ function applyPatch (pkg, file, exports, info) {
86
+ const found = findPatch(pkg, file)
87
+ if (!found || !wanted(found.it)) return exports
88
+ if (exports === null || exports === undefined) return exports
89
+ if ((typeof exports === 'object' || typeof exports === 'function') && exports[PATCHED]) return exports
90
+ try {
91
+ const out = found.fn(exports, Object.assign({ pkg, file }, info || {}))
92
+ const result = out === undefined ? exports : out
93
+ try {
94
+ Object.defineProperty(result, PATCHED, { value: true, enumerable: false, configurable: true })
95
+ if (result !== exports) Object.defineProperty(exports, PATCHED, { value: true, enumerable: false, configurable: true })
96
+ } catch (_) {}
97
+ debug('instrumented %s/%s', pkg, file)
98
+ return result
99
+ } catch (err) {
100
+ debug('could not instrument %s/%s: %s', pkg, file, err && err.message)
101
+ return exports
102
+ }
103
+ }
104
+
105
+ function onCompiled (mod, filename) {
106
+ const p = packageOf(filename)
107
+ if (!p || !TARGET_PKGS.has(p.pkg) || !findPatch(p.pkg, p.file)) return
108
+ const before = mod.exports
109
+ const after = applyPatch(p.pkg, p.file, before, { dir: p.dir, filename })
110
+ if (after !== before) mod.exports = after
111
+ }
112
+
113
+ function installNode (Module) {
114
+ const proto = Module.prototype
115
+ const orig = proto._compile
116
+ if (typeof orig !== 'function') return false
117
+ proto._compile = function _compile (content, filename) {
118
+ const ret = orig.apply(this, arguments)
119
+ if (typeof filename === 'string' && filename.indexOf('node_modules') !== -1) {
120
+ try {
121
+ onCompiled(this, filename)
122
+ } catch (_) {}
123
+ }
124
+ return ret
125
+ }
126
+ return true
127
+ }
128
+
129
+ function installBun (Module) {
130
+ const proto = Module.prototype
131
+ const orig = proto.require
132
+ proto.require = function require (id) {
133
+ const exports = orig.apply(this, arguments)
134
+ if (typeof id !== 'string' || id.startsWith('.') || id.startsWith('/') || id.startsWith('node:')) return exports
135
+ try {
136
+ return onBunRequire(id, exports, this)
137
+ } catch (_) {
138
+ return exports
139
+ }
140
+ }
141
+ }
142
+
143
+ /** Bun: a bare require of a target package patches its target files from the cache. */
144
+ function onBunRequire (id, exports, parent) {
145
+ const parts = id.split('/')
146
+ const pkg = id.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
147
+ if (!TARGET_PKGS.has(pkg)) return exports
148
+ const its = integrations().filter((it) => it.pkg === pkg && wanted(it))
149
+ if (its.length === 0) return exports
150
+ let mainFile
151
+ try {
152
+ mainFile = require('module').createRequire(parent && parent.filename ? parent.filename : path.join(process.cwd(), 'x.js')).resolve(id)
153
+ } catch (_) {
154
+ return exports
155
+ }
156
+ const p = packageOf(mainFile)
157
+ if (!p) return exports
158
+ let result = exports
159
+ for (const it of its) {
160
+ for (const file of Object.keys(it.files)) {
161
+ const full = path.join(p.dir, file)
162
+ if (full === mainFile) {
163
+ result = applyPatch(pkg, file, result, { dir: p.dir, filename: full })
164
+ continue
165
+ }
166
+ const cached = require.cache[full]
167
+ if (cached) {
168
+ const after = applyPatch(pkg, file, cached.exports, { dir: p.dir, filename: full })
169
+ if (after !== cached.exports) cached.exports = after
170
+ } else if (it.eager && it.eager.includes(file)) {
171
+ try {
172
+ const req = require('module').createRequire(full)
173
+ const ex = req(full)
174
+ applyPatch(pkg, file, ex, { dir: p.dir, filename: full })
175
+ } catch (_) {}
176
+ }
177
+ }
178
+ }
179
+ return result
180
+ }
181
+
182
+ /** Patch target files that were loaded before an integration became wanted. */
183
+ function patchLoaded () {
184
+ const cache = require.cache || {}
185
+ for (const filename of Object.keys(cache)) {
186
+ if (filename.indexOf('node_modules') === -1) continue
187
+ const p = packageOf(filename)
188
+ if (!p || !TARGET_PKGS.has(p.pkg) || !findPatch(p.pkg, p.file)) continue
189
+ const mod = cache[filename]
190
+ if (!mod || !mod.loaded) continue
191
+ const after = applyPatch(p.pkg, p.file, mod.exports, { dir: p.dir, filename })
192
+ if (after !== mod.exports) mod.exports = after
193
+ }
194
+ }
195
+
196
+ function install () {
197
+ const r = registry()
198
+ if (r.installed) return
199
+ r.installed = true
200
+ const Module = require('./util').cjsModule()
201
+ if (typeof process.versions.bun === 'string') installBun(Module)
202
+ else installNode(Module)
203
+ // ES-module wrappers (esm-hooks.mjs) call this with the original namespace
204
+ Object.defineProperty(globalThis, ESM_KEY, {
205
+ configurable: true,
206
+ enumerable: false,
207
+ value: (pkg, file, ns) => {
208
+ const copy = {}
209
+ for (const k of Object.keys(ns)) copy[k] = ns[k]
210
+ return applyPatch(pkg, file, copy, { esm: true })
211
+ }
212
+ })
213
+ // CommonJS files evaluated by in-thread module hooks bypass _compile (esm-hooks.mjs trailer)
214
+ const cjs = function (mod, filename) {
215
+ try {
216
+ onCompiled(mod, filename)
217
+ } catch (_) {}
218
+ }
219
+ cjs.wants = (url) => {
220
+ const p = packageOf(require('url').fileURLToPath(url))
221
+ return Boolean(p && TARGET_PKGS.has(p.pkg) && findPatch(p.pkg, p.file))
222
+ }
223
+ cjs.read = (url) => require('fs').readFileSync(require('url').fileURLToPath(url), 'utf8')
224
+ Object.defineProperty(globalThis, Symbol.for('@pmx/io:cjs'), { configurable: true, enumerable: false, value: cjs })
225
+ // Fastify publishes each new instance; the integrations load only when one is created
226
+ try {
227
+ require('diagnostics_channel').subscribe('fastify.initialization', (msg) => require('./integrations').onFastify(msg && msg.fastify))
228
+ } catch (_) {}
229
+ }
230
+
231
+ function enableErrors () {
232
+ const r = registry()
233
+ r.wants.errors = true
234
+ install()
235
+ patchLoaded()
236
+ }
237
+
238
+ function enableTracing (feature) {
239
+ const r = registry()
240
+ r.wants.tracing = true
241
+ r.tracing = feature
242
+ install()
243
+ patchLoaded()
244
+ }
245
+
246
+ function disableTracing () {
247
+ const r = registry()
248
+ r.wants.tracing = false
249
+ r.tracing = null
250
+ }
251
+
252
+ function disableErrors () {
253
+ registry().wants.errors = false
254
+ }
255
+
256
+ function wants () {
257
+ return registry().wants
258
+ }
259
+
260
+ module.exports = { TARGET_PKGS, install, enableErrors, enableTracing, disableTracing, disableErrors, packageOf, applyPatch, wants, registry, ESM_KEY }
@@ -0,0 +1,62 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // NODE_OPTIONS tokenising that keeps each token's original text (quoting included), so the
5
+ // preload can remove its own `--require`/`--import` tokens and leave the rest intact (NIO-021).
6
+
7
+ /**
8
+ * Split NODE_OPTIONS into tokens: [{ raw, value }]. Double quotes group, and backslash escapes
9
+ * the next character inside quotes (Node's NODE_OPTIONS rules).
10
+ */
11
+ function tokenize (str) {
12
+ const tokens = []
13
+ let i = 0
14
+ const n = str.length
15
+ while (i < n) {
16
+ while (i < n && /\s/.test(str[i])) i++
17
+ if (i >= n) break
18
+ const start = i
19
+ let value = ''
20
+ let inQuotes = false
21
+ while (i < n && (inQuotes || !/\s/.test(str[i]))) {
22
+ const c = str[i]
23
+ if (c === '"') {
24
+ inQuotes = !inQuotes
25
+ i++
26
+ continue
27
+ }
28
+ if (c === '\\' && inQuotes && i + 1 < n) {
29
+ value += str[i + 1]
30
+ i += 2
31
+ continue
32
+ }
33
+ value += c
34
+ i++
35
+ }
36
+ tokens.push({ raw: str.slice(start, i), value })
37
+ }
38
+ return tokens
39
+ }
40
+
41
+ /**
42
+ * Remove `--require <p>` / `-r <p>` / `--require=<p>` / `--import <p>` / `--import=<p>` where
43
+ * `isOurs(p)` is true. Returns the new NODE_OPTIONS string ('' when nothing remains).
44
+ */
45
+ function stripPreloads (str, isOurs) {
46
+ const tokens = tokenize(str || '')
47
+ const kept = []
48
+ for (let i = 0; i < tokens.length; i++) {
49
+ const t = tokens[i]
50
+ const v = t.value
51
+ if ((v === '--require' || v === '-r' || v === '--import') && i + 1 < tokens.length && isOurs(tokens[i + 1].value)) {
52
+ i++
53
+ continue
54
+ }
55
+ const eq = /^(--require|-r|--import)=(.*)$/s.exec(v)
56
+ if (eq && isOurs(eq[2])) continue
57
+ kept.push(t.raw)
58
+ }
59
+ return kept.join(' ')
60
+ }
61
+
62
+ module.exports = { tokenize, stripPreloads }