@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
@@ -0,0 +1,358 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Errors: notifyError, framework handlers, uncaught capture (80 A.5.1–A.5.4, NIO-070..072).
5
+
6
+ const fs = require('fs')
7
+ const path = require('path')
8
+ const { fileURLToPath } = require('url')
9
+ const { PKG_DIR, isPlainObject } = require('../util')
10
+ const debug = require('../debug')('axm:features:notify')
11
+
12
+ const MAX_MESSAGE = 1000
13
+ const CACHE_TTL_MS = 30 * 60 * 1000
14
+ const REPORTED = Symbol.for('@pmx/io:reported')
15
+ const REJECTION_PREFIX = 'You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection:'
16
+
17
+ // ---- non-Error conversion (A.5.1) ----
18
+
19
+ function toError (value) {
20
+ if (value instanceof Error) return value
21
+ if (value && typeof value === 'object' && typeof value.message === 'string' && typeof value.stack === 'string') return value
22
+ let message
23
+ try {
24
+ message = 'Non-error value: ' + JSON.stringify(value)
25
+ } catch (err) {
26
+ try {
27
+ message = 'Unserializable non-error value: ' + String(err)
28
+ } catch (_) {
29
+ message = 'Unserializable non-error value that cannot be converted to a string'
30
+ }
31
+ }
32
+ if (message.length > MAX_MESSAGE) message = message.slice(0, MAX_MESSAGE) + '...'
33
+ const e = new Error(message)
34
+ return e
35
+ }
36
+
37
+ // ---- source excerpt (A.5.4) ----
38
+
39
+ const fileCache = new Map() // path -> { at, lines }
40
+
41
+ function readLines (file) {
42
+ const now = Date.now()
43
+ const hit = fileCache.get(file)
44
+ if (hit && now - hit.at < CACHE_TTL_MS) return hit.lines
45
+ const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/)
46
+ fileCache.set(file, { at: now, lines })
47
+ if (fileCache.size > 100) fileCache.delete(fileCache.keys().next().value)
48
+ return lines
49
+ }
50
+
51
+ const FRAME_RES = [
52
+ /\(([^()]*):(\d+):\d+\)$/, // at fn (/path/file.js:1:2)
53
+ /^at (.*):(\d+):\d+$/, // at /path/file.js:1:2
54
+ /^(\/.*|file:\/\/.*):(\d+)$/ // header line of a fatal exception: /path/file.js:3
55
+ ]
56
+
57
+ function frames (stack) {
58
+ const out = []
59
+ for (const raw of String(stack).split('\n')) {
60
+ const line = raw.trim()
61
+ let m = null
62
+ for (const re of FRAME_RES) {
63
+ m = re.exec(line)
64
+ if (m) break
65
+ }
66
+ if (!m) continue
67
+ let file = m[1]
68
+ if (file.startsWith('async ')) file = file.slice(6)
69
+ if (file.startsWith('file://')) {
70
+ try {
71
+ file = fileURLToPath(file)
72
+ } catch (_) {
73
+ continue
74
+ }
75
+ }
76
+ out.push({ file, line: Number(m[2]) })
77
+ }
78
+ return out
79
+ }
80
+
81
+ const LIB_DIR = path.join(PKG_DIR, 'lib') + path.sep
82
+ const LIB_ENTRIES = ['index.js', 'index.mjs', 'preload.cjs', 'preload.mjs'].map((f) => path.join(PKG_DIR, f))
83
+
84
+ function isLibraryFile (abs) {
85
+ return abs.startsWith(LIB_DIR) || LIB_ENTRIES.includes(abs)
86
+ }
87
+
88
+ function sourceExcerpt (err) {
89
+ if (!err || typeof err.stack !== 'string') return null
90
+ for (const f of frames(err.stack)) {
91
+ if (!(path.isAbsolute(f.file) || f.file.startsWith('.'))) continue
92
+ if (f.file.includes('node_modules')) continue
93
+ const abs = path.resolve(f.file)
94
+ if (isLibraryFile(abs)) continue
95
+ let lines
96
+ try {
97
+ lines = readLines(abs)
98
+ } catch (_) {
99
+ return null
100
+ }
101
+ const idx = f.line - 1
102
+ if (idx < 0 || idx >= lines.length) return null
103
+ const before = lines.slice(Math.max(0, idx - 5), idx)
104
+ const current = lines[idx].replace(/\t/g, ' ').replace(' ', '>>')
105
+ const afterL = lines.slice(idx + 1, idx + 6)
106
+ return { callsite: `${f.file}:${f.line}`, context: before.concat([current], afterL).join('\n') }
107
+ }
108
+ return null
109
+ }
110
+
111
+ function addExcerpt (payload, err) {
112
+ try {
113
+ const ex = sourceExcerpt(err)
114
+ if (ex) {
115
+ payload.callsite = ex.callsite
116
+ payload.context = ex.context
117
+ }
118
+ } catch (e) {
119
+ debug('excerpt failed: %s', e && e.message)
120
+ }
121
+ return payload
122
+ }
123
+
124
+ function markReported (err) {
125
+ if (err && (typeof err === 'object' || typeof err === 'function')) {
126
+ try {
127
+ Object.defineProperty(err, REPORTED, { value: true, enumerable: false, configurable: true })
128
+ } catch (_) {}
129
+ }
130
+ }
131
+
132
+ /** NIO-259: the current span's ids, or null. */
133
+ function currentTraceIds () {
134
+ const r = globalThis[Symbol.for('@pmx/io:context')]
135
+ if (!r || !r.als) return null
136
+ const store = r.als.getStore()
137
+ const span = store && store.span
138
+ if (!span || typeof span.traceId !== 'string' || typeof span.spanId !== 'string') return null
139
+ return { trace_id: span.traceId, span_id: span.spanId }
140
+ }
141
+
142
+ function addTraceIds (payload) {
143
+ const ids = currentTraceIds()
144
+ if (!ids) return payload
145
+ const meta = payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : {}
146
+ if (meta.trace_id === undefined) meta.trace_id = ids.trace_id
147
+ if (meta.span_id === undefined) meta.span_id = ids.span_id
148
+ payload.metadata = meta
149
+ return payload
150
+ }
151
+
152
+ /** notifyError payload (A.5.1). */
153
+ function notifyPayload (value, context) {
154
+ const err = toError(value)
155
+ const payload = {
156
+ message: err.message,
157
+ stack: err.stack,
158
+ name: err.name,
159
+ metadata: isPlainObject(context) ? context : {}
160
+ }
161
+ return addExcerpt(payload, err)
162
+ }
163
+
164
+ /** Uncaught payload (NIO-070): all JSON-serialisable own properties plus name/message/stack. */
165
+ function uncaughtPayload (value) {
166
+ const err = toError(value)
167
+ const payload = {}
168
+ for (const key of Object.getOwnPropertyNames(err)) {
169
+ let v
170
+ try {
171
+ v = err[key]
172
+ if (typeof v === 'function' || typeof v === 'symbol' || v === undefined) continue
173
+ if (typeof v === 'bigint') continue
174
+ JSON.stringify(v)
175
+ } catch (_) {
176
+ continue
177
+ }
178
+ payload[key] = v
179
+ }
180
+ payload.name = err.name
181
+ payload.message = err.message
182
+ payload.stack = err.stack
183
+ return addExcerpt(payload, err)
184
+ }
185
+
186
+ class NotifyFeature {
187
+ /**
188
+ * @param {object} transport
189
+ * @param {boolean} capture install uncaught/unhandled handlers
190
+ */
191
+ constructor (transport) {
192
+ this._transport = transport
193
+ this._onUncaught = null
194
+ this._onRejection = null
195
+ }
196
+
197
+ setTracing (on) {
198
+ this._tracing = on === true
199
+ }
200
+
201
+ notifyError (err, context) {
202
+ const payload = notifyPayload(err, context)
203
+ if (this._tracing) addTraceIds(payload)
204
+ markReported(err)
205
+ this._transport.send('process:exception', payload)
206
+ }
207
+
208
+ /**
209
+ * Worker threads (NIO-293): reported through `uncaughtExceptionMonitor`, which does not change
210
+ * Node's default termination of the worker (no 'uncaughtException' listener is added).
211
+ */
212
+ enableThreadCapture () {
213
+ this._onMonitor = (err) => {
214
+ let payload
215
+ try {
216
+ payload = uncaughtPayload(err)
217
+ } catch (_) {
218
+ payload = { message: String(err && err.message) }
219
+ }
220
+ if (this._tracing) addTraceIds(payload)
221
+ try {
222
+ this._transport.send('process:exception', payload)
223
+ } catch (_) {}
224
+ }
225
+ process.on('uncaughtExceptionMonitor', this._onMonitor)
226
+ }
227
+
228
+ enableCapture () {
229
+ this._onUncaught = (err) => {
230
+ try {
231
+ console.error(err)
232
+ } catch (_) {}
233
+ const onlyListener = process.listeners('uncaughtException').length <= 1
234
+ let exited = false
235
+ const exit = () => {
236
+ if (exited) return
237
+ exited = true
238
+ if (onlyListener) process.exit(1)
239
+ }
240
+ let payload
241
+ try {
242
+ payload = uncaughtPayload(err)
243
+ } catch (_) {
244
+ payload = { message: String(err && err.message) }
245
+ }
246
+ if (this._tracing) addTraceIds(payload)
247
+ try {
248
+ const g = globalThis[Symbol.for('@pmx/io:standalone')]
249
+ if (g && typeof g.onUncaught === 'function') g.onUncaught(err)
250
+ } catch (_) {}
251
+ try {
252
+ this._transport.send('process:exception', payload, exit)
253
+ } catch (_) {}
254
+ if (onlyListener) {
255
+ // Exit after the send callback or 100 ms, whichever comes first (NIO-070).
256
+ setTimeout(exit, 100)
257
+ }
258
+ }
259
+ this._onRejection = (reason) => {
260
+ // NIO-070 uses the cluster wrapper's behaviour (40 §4.1) in both modes: a falsy reason
261
+ // (e.g. `Promise.reject()`) still prints the warning, followed by the value itself, and is
262
+ // reported as "No error but unhandledRejection was caught!" (70 T-INT-022/024, Q-NIO-030).
263
+ try {
264
+ console.error(REJECTION_PREFIX)
265
+ console.error(reason)
266
+ } catch (_) {}
267
+ let payload
268
+ if (!reason) {
269
+ payload = { message: 'No error but unhandledRejection was caught!' }
270
+ } else {
271
+ try {
272
+ payload = uncaughtPayload(reason)
273
+ } catch (_) {
274
+ payload = { message: String(reason && reason.message) }
275
+ }
276
+ }
277
+ if (this._tracing && payload) addTraceIds(payload)
278
+ try {
279
+ this._transport.send('process:exception', payload)
280
+ } catch (_) {}
281
+ }
282
+ process.on('uncaughtException', this._onUncaught)
283
+ process.on('unhandledRejection', this._onRejection)
284
+ }
285
+
286
+ expressErrorHandler () {
287
+ this._transport.send('axm:option:configuration', { error: true })
288
+ return (err, req, res, next) => {
289
+ try {
290
+ const e = toError(err)
291
+ const payload = {
292
+ message: e.message,
293
+ stack: e.stack,
294
+ name: e.name,
295
+ metadata: {
296
+ http: {
297
+ url: req.url,
298
+ params: req.params,
299
+ method: req.method,
300
+ query: req.query,
301
+ body: req.body,
302
+ path: req.path,
303
+ route: req.route && req.route.path !== undefined ? req.route.path : undefined
304
+ },
305
+ custom: { user: req.user && typeof req.user === 'object' ? req.user.id : undefined }
306
+ }
307
+ }
308
+ markReported(err)
309
+ this._transport.send('process:exception', payload)
310
+ } catch (_) {}
311
+ return next(err)
312
+ }
313
+ }
314
+
315
+ koaErrorHandler () {
316
+ this._transport.send('axm:option:configuration', { error: true })
317
+ return async (ctx, next) => {
318
+ try {
319
+ await next()
320
+ } catch (err) {
321
+ try {
322
+ const e = toError(err)
323
+ const request = ctx.request || {}
324
+ markReported(err)
325
+ this._transport.send('process:exception', {
326
+ message: e.message,
327
+ stack: e.stack,
328
+ name: e.name,
329
+ metadata: {
330
+ http: {
331
+ url: request.url,
332
+ params: ctx.params,
333
+ method: request.method,
334
+ query: request.query,
335
+ body: request.body,
336
+ path: request.path,
337
+ route: ctx._matchedRoute
338
+ },
339
+ custom: { user: ctx.user && typeof ctx.user === 'object' ? ctx.user.id : undefined }
340
+ }
341
+ })
342
+ } catch (_) {}
343
+ throw err
344
+ }
345
+ }
346
+ }
347
+
348
+ destroy () {
349
+ if (this._onUncaught) process.removeListener('uncaughtException', this._onUncaught)
350
+ if (this._onRejection) process.removeListener('unhandledRejection', this._onRejection)
351
+ if (this._onMonitor) process.removeListener('uncaughtExceptionMonitor', this._onMonitor)
352
+ this._onMonitor = null
353
+ this._onUncaught = null
354
+ this._onRejection = null
355
+ }
356
+ }
357
+
358
+ module.exports = { NotifyFeature, toError, sourceExcerpt, notifyPayload, uncaughtPayload, REJECTION_PREFIX, REPORTED, markReported }
@@ -0,0 +1,208 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // Remote-triggered profiling through the built-in inspector (80 A.7, NIO-077, 02 D-06).
5
+
6
+ const { shortUuid } = require('../util')
7
+ const debug = require('../debug')('axm:features:profiling')
8
+
9
+ const DISABLED_FLAGS = {
10
+ heapdump: false,
11
+ 'feature.profiler.heap_snapshot': false,
12
+ 'feature.profiler.heap_sampling': false,
13
+ 'feature.profiler.cpu_js': false
14
+ }
15
+ const ENABLED_FLAGS = {
16
+ heapdump: true,
17
+ 'feature.profiler.heapsnapshot': false,
18
+ 'feature.profiler.heapsampling': true,
19
+ 'feature.profiler.cpu_js': true
20
+ }
21
+
22
+ function canUseInspector () {
23
+ if (process.versions.bun) return false
24
+ // process.features.inspector tells without loading the module (keeps startup cheap).
25
+ if (process.features && typeof process.features.inspector === 'boolean') return process.features.inspector
26
+ try {
27
+ const inspector = require('inspector')
28
+ return typeof inspector.Session === 'function'
29
+ } catch (_) {
30
+ return false
31
+ }
32
+ }
33
+
34
+ function initiatedOf (opts) {
35
+ return opts && typeof opts.initiated === 'string' ? opts.initiated : 'manual'
36
+ }
37
+
38
+ function timeoutOf (opts) {
39
+ if (!opts) return null
40
+ const t = typeof opts.timeout === 'string' ? Number(opts.timeout) : opts.timeout
41
+ return typeof t === 'number' && Number.isFinite(t) && t > 0 ? t : null
42
+ }
43
+
44
+ class ProfilingFeature {
45
+ constructor (transport, actions) {
46
+ this._transport = transport
47
+ this._actions = actions
48
+ this._session = null
49
+ this._available = false
50
+ this._running = null // { kind: 'cpu'|'heap', uuid, at, initiated, timer, started }
51
+ this._enabling = null
52
+ }
53
+
54
+ /**
55
+ * IO-005 step 2: decide whether the inspector is usable. The in-process session itself is
56
+ * connected on first use, so that an instrumented app that is never profiled pays nothing.
57
+ */
58
+ connect () {
59
+ this._available = canUseInspector()
60
+ return this._available
61
+ }
62
+
63
+ _ensureSession () {
64
+ if (this._session) return true
65
+ if (!this._available) return false
66
+ try {
67
+ const inspector = require('inspector')
68
+ const session = new inspector.Session()
69
+ session.connect()
70
+ this._session = session
71
+ return true
72
+ } catch (err) {
73
+ debug('inspector unusable: %s', err && err.message)
74
+ this._available = false
75
+ return false
76
+ }
77
+ }
78
+
79
+ /** Enable the Profiler and HeapProfiler domains on first use (keeps init cheap). */
80
+ _enable () {
81
+ if (!this._enabling) {
82
+ this._enabling = Promise.all([this._post('Profiler.enable'), this._post('HeapProfiler.enable')])
83
+ }
84
+ return this._enabling
85
+ }
86
+
87
+ _post (method, params) {
88
+ return new Promise((resolve, reject) => {
89
+ if (!this._session) return reject(new Error('Profiler not available'))
90
+ this._session.post(method, params || {}, (err, res) => (err ? reject(err) : resolve(res)))
91
+ })
92
+ }
93
+
94
+ /** Register the five actions (after sending the profiler flags). */
95
+ init () {
96
+ if (!this._available) {
97
+ if (!process.versions.bun) console.error('Failed to require the profiler via inspector, disabling profiling ...')
98
+ this._transport.send('axm:option:configuration', DISABLED_FLAGS)
99
+ return false
100
+ }
101
+ this._transport.send('axm:option:configuration', ENABLED_FLAGS)
102
+ const a = this._actions
103
+ a.register('km:heapdump', undefined, (opts, reply) => this._heapdump(opts, reply))
104
+ a.register('km:cpu:profiling:start', undefined, (opts, reply) => this._start('cpu', opts, reply))
105
+ a.register('km:cpu:profiling:stop', undefined, (reply) => this._stop('cpu', reply))
106
+ a.register('km:heap:sampling:start', undefined, (opts, reply) => this._start('heap', opts, reply))
107
+ a.register('km:heap:sampling:stop', undefined, (reply) => this._stop('heap', reply))
108
+ return true
109
+ }
110
+
111
+ _start (kind, opts, reply) {
112
+ if (!this._ensureSession()) return reply({ err: 'Profiler not available', success: false })
113
+ if (this._running) return reply({ err: 'A profiling is already running', success: false })
114
+ const uuid = shortUuid()
115
+ const run = { kind, uuid, at: Date.now(), initiated: initiatedOf(opts), timer: null }
116
+ this._running = run
117
+ reply({ success: true, uuid })
118
+ const samplingInterval = Number(opts && opts.samplingInterval) > 0 ? Number(opts.samplingInterval) : 16384
119
+ const started = this._enable().then(() => kind === 'cpu'
120
+ ? this._post('Profiler.start')
121
+ : this._post('HeapProfiler.startSampling', { samplingInterval }))
122
+ started.catch((err) => {
123
+ debug('profiler start failed: %s', err && err.message)
124
+ if (this._running === run) this._running = null
125
+ })
126
+ const timeout = timeoutOf(opts)
127
+ if (timeout) {
128
+ run.timer = setTimeout(() => {
129
+ if (this._running === run) this._finish(run).catch(() => {})
130
+ }, timeout)
131
+ if (run.timer.unref) run.timer.unref()
132
+ }
133
+ }
134
+
135
+ _stop (kind, reply) {
136
+ const run = this._running
137
+ if (!run || run.kind !== kind) return reply({ err: 'No profiling are already running', success: false })
138
+ reply({ success: true, uuid: run.uuid })
139
+ this._finish(run).catch((err) => debug('profiler stop failed: %s', err && err.message))
140
+ }
141
+
142
+ async _finish (run) {
143
+ if (run.timer) clearTimeout(run.timer)
144
+ this._running = null
145
+ let profile
146
+ if (run.kind === 'cpu') {
147
+ profile = (await this._post('Profiler.stop')).profile
148
+ } else {
149
+ profile = (await this._post('HeapProfiler.stopSampling')).profile
150
+ }
151
+ const type = run.kind === 'cpu' ? 'cpuprofile' : 'heapprofile'
152
+ const payload = {
153
+ uuid: run.uuid,
154
+ duration: Date.now() - run.at,
155
+ at: run.at,
156
+ data: JSON.stringify(profile),
157
+ success: true,
158
+ initiated: run.initiated,
159
+ type
160
+ }
161
+ payload[type] = true
162
+ this._transport.send('profilings', payload)
163
+ }
164
+
165
+ _heapdump (opts, reply) {
166
+ if (!this._ensureSession()) return reply({ err: 'Profiler not available', success: false })
167
+ reply({ success: true })
168
+ const at = Date.now()
169
+ const initiated = initiatedOf(opts)
170
+ const t = setTimeout(() => {
171
+ const chunks = []
172
+ const onChunk = (m) => chunks.push(m.params.chunk)
173
+ this._session.on('HeapProfiler.addHeapSnapshotChunk', onChunk)
174
+ this._enable().then(() => this._post('HeapProfiler.takeHeapSnapshot', { reportProgress: false }))
175
+ .then(() => {
176
+ this._session.removeListener('HeapProfiler.addHeapSnapshotChunk', onChunk)
177
+ this._transport.send('profilings', {
178
+ data: chunks.join(''),
179
+ at,
180
+ initiated,
181
+ duration: Date.now() - at,
182
+ type: 'heapdump'
183
+ })
184
+ }, (err) => {
185
+ this._session.removeListener('HeapProfiler.addHeapSnapshotChunk', onChunk)
186
+ reply({ success: err && err.message, err })
187
+ })
188
+ }, 200)
189
+ if (t.unref) t.unref()
190
+ }
191
+
192
+ destroy () {
193
+ if (this._running) {
194
+ if (this._running.timer) clearTimeout(this._running.timer)
195
+ const kind = this._running.kind
196
+ this._running = null
197
+ this._post(kind === 'cpu' ? 'Profiler.stop' : 'HeapProfiler.stopSampling').catch(() => {})
198
+ }
199
+ if (this._session) {
200
+ try {
201
+ this._session.disconnect()
202
+ } catch (_) {}
203
+ }
204
+ this._session = null
205
+ }
206
+ }
207
+
208
+ module.exports = { ProfilingFeature, canUseInspector, DISABLED_FLAGS, ENABLED_FLAGS }