@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.
- package/README.md +69 -2
- package/bin/pmx-io-node.js +143 -0
- package/index.d.ts +381 -0
- package/index.js +27 -0
- package/index.mjs +47 -0
- package/lib/actions.js +120 -0
- package/lib/activate.js +402 -0
- package/lib/application.js +106 -0
- package/lib/bun.js +155 -0
- package/lib/config.js +67 -0
- package/lib/constants.js +17 -0
- package/lib/debug.js +48 -0
- package/lib/esm-hooks.mjs +111 -0
- package/lib/exit.js +79 -0
- package/lib/features/metrics.js +409 -0
- package/lib/features/notify.js +358 -0
- package/lib/features/profiling.js +208 -0
- package/lib/hooks.js +151 -0
- package/lib/inbound.js +174 -0
- package/lib/integrations.js +372 -0
- package/lib/itg/amqplib.js +169 -0
- package/lib/itg/bullmq.js +141 -0
- package/lib/itg/common.js +657 -0
- package/lib/itg/dbcomment.js +114 -0
- package/lib/itg/index.js +103 -0
- package/lib/itg/ioredis.js +212 -0
- package/lib/itg/kafkajs.js +205 -0
- package/lib/itg/mongodb.js +322 -0
- package/lib/itg/mysql2.js +205 -0
- package/lib/itg/nats.js +531 -0
- package/lib/itg/noderedis.js +236 -0
- package/lib/itg/pg.js +253 -0
- package/lib/itg/pgqueue.js +305 -0
- package/lib/itg/postgres.js +252 -0
- package/lib/itg/redis-common.js +265 -0
- package/lib/itg/sql.js +302 -0
- package/lib/metrics/service.js +286 -0
- package/lib/metrics/types.js +342 -0
- package/lib/modules.js +260 -0
- package/lib/node-options.js +62 -0
- package/lib/otel/bridge.js +797 -0
- package/lib/otel/env.js +114 -0
- package/lib/outbound.js +362 -0
- package/lib/pmx.js +757 -0
- package/lib/relay/hub.js +279 -0
- package/lib/relay/transport.js +253 -0
- package/lib/standalone/activate.js +307 -0
- package/lib/standalone/capture.js +55 -0
- package/lib/standalone/container.js +909 -0
- package/lib/standalone/transport.js +1023 -0
- package/lib/tracing/context.js +90 -0
- package/lib/tracing/control.js +124 -0
- package/lib/tracing/exporter.js +115 -0
- package/lib/tracing/index.js +242 -0
- package/lib/tracing/logs.js +176 -0
- package/lib/tracing/options.js +105 -0
- package/lib/tracing/paths.js +34 -0
- package/lib/tracing/tracer.js +850 -0
- package/lib/tracing/w3c.js +415 -0
- package/lib/transport/ipc.js +97 -0
- package/lib/transport/ws.js +511 -0
- package/lib/util.js +109 -0
- package/package.json +48 -4
- package/preload.cjs +26 -0
- package/preload.mjs +25 -0
- package/standalone.cjs +16 -0
- 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 }
|