@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,176 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Log correlation (80 B.14, NIO-255..NIO-258): pino, winston and bunyan records written while a
5
+ // span is current carry `trace_id` and `span_id` (values the app supplied are never
6
+ // overwritten); console lines are prefixed only on request (`logCorrelation: { console: true }`).
7
+ // The libraries are patched when the app loads them (modules.js); the patches look up the
8
+ // current span at log time and do nothing while correlation is off.
9
+
10
+ const ctx = require('./context')
11
+
12
+ const state = { tracer: null, console: false, consoleRestore: null }
13
+
14
+ function current () {
15
+ if (!state.tracer) return null
16
+ const span = ctx.currentSpan()
17
+ if (!span || typeof span.traceId !== 'string' || typeof span.spanId !== 'string') return null
18
+ return span
19
+ }
20
+
21
+ function copyStatics (to, from) {
22
+ for (const k of Object.getOwnPropertyNames(from)) {
23
+ if (k === 'length' || k === 'name' || k === 'prototype' || k === 'arguments' || k === 'caller') continue
24
+ try {
25
+ Object.defineProperty(to, k, Object.getOwnPropertyDescriptor(from, k))
26
+ } catch (_) {}
27
+ }
28
+ for (const s of Object.getOwnPropertySymbols(from)) {
29
+ try {
30
+ Object.defineProperty(to, s, Object.getOwnPropertyDescriptor(from, s))
31
+ } catch (_) {}
32
+ }
33
+ try {
34
+ Object.defineProperty(to, 'length', { value: from.length })
35
+ Object.defineProperty(to, 'name', { value: from.name })
36
+ } catch (_) {}
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------------------------
40
+ // pino (NIO-255): a mixin merged with the app's own
41
+
42
+ function pinoOptions (opts) {
43
+ const o = Object.assign({}, opts)
44
+ const appMixin = typeof o.mixin === 'function' ? o.mixin : null
45
+ o.mixin = function pmxIoMixin (mergeObject, level, logger) {
46
+ const app = appMixin ? appMixin.apply(this, arguments) : undefined
47
+ const span = current()
48
+ if (!span) return app || {}
49
+ const out = { trace_id: span.traceId, span_id: span.spanId }
50
+ if (mergeObject && typeof mergeObject === 'object') {
51
+ if (mergeObject.trace_id !== undefined) delete out.trace_id
52
+ if (mergeObject.span_id !== undefined) delete out.span_id
53
+ }
54
+ return app && typeof app === 'object' ? Object.assign(out, app) : out
55
+ }
56
+ return o
57
+ }
58
+
59
+ function patchPino (pino) {
60
+ if (typeof pino !== 'function') return undefined
61
+ const orig = pino
62
+ const wrapped = function pino () {
63
+ const args = Array.prototype.slice.call(arguments)
64
+ const first = args[0]
65
+ if (first === undefined || first === null) args[0] = pinoOptions({})
66
+ else if (typeof first === 'string') args[0] = pinoOptions({ name: first })
67
+ else if (typeof first === 'object' && typeof first.write === 'function') args.unshift(pinoOptions({}))
68
+ else if (typeof first === 'object') args[0] = pinoOptions(first)
69
+ return orig.apply(this, args)
70
+ }
71
+ copyStatics(wrapped, orig)
72
+ wrapped.pino = wrapped
73
+ wrapped.default = wrapped
74
+ return wrapped
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------------------------
78
+ // winston (NIO-256): a format placed before the app's formats
79
+
80
+ function patchWinston (winston) {
81
+ if (!winston || typeof winston.createLogger !== 'function' || !winston.format) return undefined
82
+ const orig = winston.createLogger
83
+ const correlate = winston.format((info) => {
84
+ const span = current()
85
+ if (span) {
86
+ if (info.trace_id === undefined) info.trace_id = span.traceId
87
+ if (info.span_id === undefined) info.span_id = span.spanId
88
+ }
89
+ return info
90
+ })
91
+ const createLogger = function createLogger (opts) {
92
+ const o = Object.assign({}, opts)
93
+ const appFormat = o.format || winston.format.json()
94
+ o.format = winston.format.combine(correlate(), appFormat)
95
+ return orig.call(this, o)
96
+ }
97
+ copyStatics(createLogger, orig)
98
+ const d = Object.getOwnPropertyDescriptor(winston, 'createLogger')
99
+ if (d && (d.writable || d.configurable)) {
100
+ Object.defineProperty(winston, 'createLogger', { value: createLogger, writable: true, enumerable: d.enumerable, configurable: true })
101
+ }
102
+ return undefined
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------------------------
106
+ // bunyan (NIO-257): records gain the fields before they are emitted
107
+
108
+ function patchBunyan (bunyan) {
109
+ const Logger = bunyan && bunyan.prototype && typeof bunyan.prototype._emit === 'function' ? bunyan : bunyan && bunyan.Logger
110
+ const proto = Logger && Logger.prototype
111
+ if (!proto || typeof proto._emit !== 'function' || proto._emit.__pmxWrapped) return undefined
112
+ const orig = proto._emit
113
+ const _emit = function _emit (rec) {
114
+ const span = current()
115
+ if (span && rec && typeof rec === 'object') {
116
+ if (rec.trace_id === undefined) rec.trace_id = span.traceId
117
+ if (rec.span_id === undefined) rec.span_id = span.spanId
118
+ }
119
+ return orig.apply(this, arguments)
120
+ }
121
+ Object.defineProperty(_emit, '__pmxWrapped', { value: true })
122
+ proto._emit = _emit
123
+ return undefined
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------------------------
127
+ // console (NIO-258, opt-in)
128
+
129
+ function enableConsole () {
130
+ if (state.consoleRestore) return
131
+ const saved = {}
132
+ for (const m of ['log', 'info', 'warn', 'error', 'debug']) {
133
+ const orig = console[m]
134
+ if (typeof orig !== 'function') continue
135
+ saved[m] = orig
136
+ console[m] = function () {
137
+ const span = state.console ? current() : null
138
+ if (!span) return orig.apply(this, arguments)
139
+ const prefix = `trace_id=${span.traceId} span_id=${span.spanId}`
140
+ const args = Array.prototype.slice.call(arguments)
141
+ if (typeof args[0] === 'string') args[0] = prefix + ' ' + args[0]
142
+ else args.unshift(prefix)
143
+ return orig.apply(this, args)
144
+ }
145
+ }
146
+ state.consoleRestore = () => {
147
+ for (const m of Object.keys(saved)) console[m] = saved[m]
148
+ }
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------------------------
152
+
153
+ function integrations () {
154
+ return [
155
+ { pkg: 'pino', for: 'tracing', files: { 'pino.js': patchPino } },
156
+ { pkg: 'winston', for: 'tracing', files: { 'lib/winston.js': patchWinston } },
157
+ { pkg: 'bunyan', for: 'tracing', files: { 'lib/bunyan.js': patchBunyan } }
158
+ ]
159
+ }
160
+
161
+ function enable (tracer) {
162
+ state.tracer = tracer
163
+ state.console = tracer.consoleCorrelation === true
164
+ if (state.console) enableConsole()
165
+ }
166
+
167
+ function disable () {
168
+ state.tracer = null
169
+ state.console = false
170
+ if (state.consoleRestore) {
171
+ state.consoleRestore()
172
+ state.consoleRestore = null
173
+ }
174
+ }
175
+
176
+ module.exports = { integrations, enable, disable, patchPino, patchWinston, patchBunyan }
@@ -0,0 +1,105 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Tracing options, their sources and their order (80 NIO-080, NIO-200, NIO-201; PYI-090a):
5
+ // defaults → env `io` (conf.tracing) → env `trace` (only `enabled`) → env PMX_TRACE_SAMPLE_RATIO,
6
+ // else OTEL_TRACES_SAMPLER_ARG for the trace-id-ratio samplers (only `sampleRatio`) → the app's
7
+ // init(config); env PMX_DB_COMMENT (env stage) sets `db.comment` (96 SVM-500). Objects combine
8
+ // key by key (`db`, `messaging`, `logCorrelation` too); an object that does not name `enabled`
9
+ // counts as `enabled: true`.
10
+
11
+ const debug = require('../debug')('axm:tracing')
12
+
13
+ const NESTED = ['db', 'messaging', 'logCorrelation']
14
+
15
+ function isObj (v) {
16
+ return v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof RegExp)
17
+ }
18
+
19
+ /** Apply one source to `out` (mutated). */
20
+ function applySource (out, src) {
21
+ if (src === true || src === false) {
22
+ out.enabled = src
23
+ return out
24
+ }
25
+ if (!isObj(src)) return out
26
+ if (!Object.prototype.hasOwnProperty.call(src, 'enabled')) out.enabled = true
27
+ for (const k of Object.keys(src)) {
28
+ const v = src[k]
29
+ if (v === undefined) continue
30
+ if (NESTED.includes(k) && isObj(v) && isObj(out[k])) {
31
+ out[k] = Object.assign({}, out[k], v)
32
+ } else if (NESTED.includes(k) && isObj(v)) {
33
+ out[k] = Object.assign({}, v)
34
+ } else {
35
+ out[k] = v
36
+ }
37
+ }
38
+ if (out.enabled !== true) out.enabled = out.enabled === true
39
+ return out
40
+ }
41
+
42
+ function envIoConf (env) {
43
+ if (typeof env.io !== 'string') return undefined
44
+ try {
45
+ const parsed = JSON.parse(env.io)
46
+ return parsed && isObj(parsed.conf) ? parsed.conf : undefined
47
+ } catch (err) {
48
+ debug('env io is not valid JSON: %s', err && err.message)
49
+ return undefined
50
+ }
51
+ }
52
+
53
+ function envRatio (env) {
54
+ let raw = env.PMX_TRACE_SAMPLE_RATIO
55
+ if (raw === undefined || raw === '') {
56
+ const sampler = String(env.OTEL_TRACES_SAMPLER || '').trim().toLowerCase()
57
+ if (sampler === 'traceidratio' || sampler === 'parentbased_traceidratio') raw = env.OTEL_TRACES_SAMPLER_ARG
58
+ }
59
+ if (raw === undefined || raw === '') return undefined
60
+ const n = Number(String(raw).trim())
61
+ if (String(raw).trim() === '' || !Number.isFinite(n)) {
62
+ debug('ignoring non-numeric sample ratio %s', raw)
63
+ return undefined
64
+ }
65
+ return n
66
+ }
67
+
68
+ /** The environment's contribution (everything before the app's init). */
69
+ function fromEnv (env) {
70
+ env = env || process.env
71
+ const out = { enabled: false }
72
+ const conf = envIoConf(env)
73
+ if (conf && conf.tracing !== undefined) applySource(out, conf.tracing)
74
+ if (env.trace === 'true') out.enabled = true
75
+ else if (env.trace === 'false') out.enabled = false
76
+ const ratio = envRatio(env)
77
+ if (ratio !== undefined) out.sampleRatio = ratio
78
+ // 96 SVM-500: PMX_DB_COMMENT=on|off|force (key by key into `db`; `db: false` stays off)
79
+ const dbc = typeof env.PMX_DB_COMMENT === 'string' ? env.PMX_DB_COMMENT.trim() : ''
80
+ if (dbc && out.db !== false) out.db = Object.assign({}, isObj(out.db) ? out.db : {}, { comment: dbc })
81
+ return out
82
+ }
83
+
84
+ /**
85
+ * The effective `tracing` object for an init(config): `hasKey` = the app's config names
86
+ * `tracing` (NIO-080: without the key, the environment's settings are kept).
87
+ */
88
+ function resolveTracing (appValue, hasKey, env) {
89
+ const out = fromEnv(env)
90
+ if (hasKey) applySource(out, appValue)
91
+ return out
92
+ }
93
+
94
+ /** `children` (NIO-290): config key, else env io conf, default true; PMX_IO_CHILDREN=0 → false. */
95
+ function childrenEnabled (config, env) {
96
+ env = env || process.env
97
+ if (env.PMX_IO_CHILDREN === '0' || env.PMX_IO_CHILDREN === 'false') return false
98
+ if (config && config.children === false) return false
99
+ if (config && config.children === true) return true
100
+ const conf = envIoConf(env)
101
+ if (conf && conf.children === false) return false
102
+ return true
103
+ }
104
+
105
+ module.exports = { resolveTracing, fromEnv, applySource, childrenEnabled, envIoConf }
@@ -0,0 +1,34 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Route-less SERVER span names (80 Part G.2): each path segment that looks like an identifier
5
+ // becomes `:id`; the result is cut to 200 characters.
6
+
7
+ const DIGITS = /^[0-9]+$/
8
+ const UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/
9
+ const HEX16PLUS = /^[0-9A-Fa-f]{16,}$/
10
+ const LONG_TOKEN = /^[A-Za-z0-9_-]{20,}$/
11
+ const HAS_DIGIT = /[0-9]/
12
+
13
+ function isId (seg) {
14
+ if (DIGITS.test(seg) || UUID.test(seg) || HEX16PLUS.test(seg)) return true
15
+ if (LONG_TOKEN.test(seg) && HAS_DIGIT.test(seg)) return true
16
+ return seg.indexOf('@') !== -1
17
+ }
18
+
19
+ /** The request path without query string, normalised (G.2). */
20
+ function normalizePath (target) {
21
+ let path = String(target || '/')
22
+ const q = path.indexOf('?')
23
+ if (q !== -1) path = path.slice(0, q)
24
+ const out = path.split('/').map((seg) => (seg !== '' && isId(seg) ? ':id' : seg)).join('/')
25
+ return out.length > 200 ? out.slice(0, 200) : out
26
+ }
27
+
28
+ function rawPath (target) {
29
+ const t = String(target || '/')
30
+ const q = t.indexOf('?')
31
+ return q === -1 ? t : t.slice(0, q)
32
+ }
33
+
34
+ module.exports = { normalizePath, rawPath, isId }