@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/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 }
|
package/lib/constants.js
ADDED
|
@@ -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 }
|