@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// PostgreSQL-backed job queues (82 §11): pg-boss (ITG-222) and graphile-worker (ITG-223), with the
|
|
5
|
+
// opt-in payload carrier of ITG-220 (`messaging.payloadCarrier`): the producer adds
|
|
6
|
+
// `__pmx_trace__` = { traceparent, tracestate?, baggage? } to a copy of a job payload that is a
|
|
7
|
+
// plain object (never to arrays or scalars) and lacks the key; the consumer wrapper removes the key
|
|
8
|
+
// before the handler/task sees the payload, whatever the setting. Without the option producer and
|
|
9
|
+
// consumer spans are still created and share `messaging.message.id` (the job id). The SQL these
|
|
10
|
+
// libraries run inside a PRODUCER span is its children (the span is current during the call).
|
|
11
|
+
|
|
12
|
+
const c = require('./common')
|
|
13
|
+
|
|
14
|
+
const KEY = c.TRACE_KEY
|
|
15
|
+
|
|
16
|
+
function isPlain (v) {
|
|
17
|
+
if (!v || typeof v !== 'object' || Array.isArray(v)) return false
|
|
18
|
+
const p = Object.getPrototypeOf(v)
|
|
19
|
+
return p === Object.prototype || p === null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A payload copy carrying the span's context (ITG-220), or the payload unchanged. */
|
|
23
|
+
function carry (payload, span) {
|
|
24
|
+
const t = c.msgTracer()
|
|
25
|
+
if (!span || !t || !t.messaging.payloadCarrier || !isPlain(payload) || Object.prototype.hasOwnProperty.call(payload, KEY)) return payload
|
|
26
|
+
return Object.assign({}, payload, { [KEY]: c.traceCarrier(span) })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The context in a payload's carrier, removing the key in place (the object is the library's). */
|
|
30
|
+
function take (payload) {
|
|
31
|
+
if (!isPlain(payload) || !Object.prototype.hasOwnProperty.call(payload, KEY)) return null
|
|
32
|
+
const carrier = payload[KEY]
|
|
33
|
+
try {
|
|
34
|
+
delete payload[KEY]
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
return carrier && typeof carrier === 'object' ? c.extract(carrier) : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Run fn with the PRODUCER span current; `args(span)` builds the arguments (carrier). */
|
|
40
|
+
function produce (system, destination, tags, fn, thisArg, args, idOf, origArgs) {
|
|
41
|
+
const span = c.producer(system, destination, tags)
|
|
42
|
+
if (!span) return { span: null, result: fn.apply(thisArg, origArgs) }
|
|
43
|
+
const a = args(span)
|
|
44
|
+
let r
|
|
45
|
+
try {
|
|
46
|
+
const store = require('../tracing/context').current()
|
|
47
|
+
r = require('../tracing/context').run({ span, bag: span.bag, server: store && store.server, itg: system }, fn, thisArg, a)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
c.end(span, err)
|
|
50
|
+
throw err
|
|
51
|
+
}
|
|
52
|
+
if (!c.isThenable(r)) {
|
|
53
|
+
c.end(span)
|
|
54
|
+
return { span, result: r }
|
|
55
|
+
}
|
|
56
|
+
return { span, result: c.settle(r, span, (v) => {
|
|
57
|
+
const id = idOf ? idOf(v) : undefined
|
|
58
|
+
if (id !== undefined && id !== null) span.setAttribute('messaging.message.id', String(id))
|
|
59
|
+
}) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function nestedIn (system) {
|
|
63
|
+
const s = require('../tracing/context').current()
|
|
64
|
+
return Boolean(s && s.itg === system)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// -- pg-boss ----------------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
const PGBOSS = 'pg-boss'
|
|
70
|
+
|
|
71
|
+
/** send(name, data, options) | send({ name, data, options }) → [name, data, rebuild(data')]. */
|
|
72
|
+
function sendArgs (args) {
|
|
73
|
+
if (typeof args[0] === 'string') {
|
|
74
|
+
return { name: args[0], data: args[1], rebuild: (d) => { const a = args.slice(); a[1] = d; return a } }
|
|
75
|
+
}
|
|
76
|
+
if (args[0] && typeof args[0] === 'object') {
|
|
77
|
+
const req = args[0]
|
|
78
|
+
return { name: req.name, data: req.data, rebuild: (d) => { const a = args.slice(); a[0] = Object.assign({}, req, { data: d }); return a } }
|
|
79
|
+
}
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function wrapBossSend (orig) {
|
|
84
|
+
return function send () {
|
|
85
|
+
const args = Array.prototype.slice.call(arguments)
|
|
86
|
+
if (!c.msgTracer() || nestedIn(PGBOSS)) return orig.apply(this, args)
|
|
87
|
+
const s = sendArgs(args)
|
|
88
|
+
if (!s || internalQueue(s.name)) return orig.apply(this, args)
|
|
89
|
+
return produce(PGBOSS, s.name, {}, orig, this, (span) => s.rebuild(carry(s.data, span)), (id) => (typeof id === 'string' ? id : undefined), args).result
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function wrapBossPublish (orig) {
|
|
94
|
+
return function publish (event, data) {
|
|
95
|
+
if (!c.msgTracer() || nestedIn(PGBOSS)) return orig.apply(this, arguments)
|
|
96
|
+
const args = Array.prototype.slice.call(arguments)
|
|
97
|
+
return produce(PGBOSS, event, {}, orig, this, (span) => { const a = args.slice(); a[1] = carry(data, span); return a }, undefined, args).result
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function wrapBossInsert (orig) {
|
|
102
|
+
return function insert () {
|
|
103
|
+
const args = Array.prototype.slice.call(arguments)
|
|
104
|
+
if (!c.msgTracer() || nestedIn(PGBOSS)) return orig.apply(this, args)
|
|
105
|
+
// 9.x/10.x: insert(jobs); 11+: insert(name, jobs, options)
|
|
106
|
+
const named = typeof args[0] === 'string'
|
|
107
|
+
const jobs = named ? args[1] : args[0]
|
|
108
|
+
if (!Array.isArray(jobs)) return orig.apply(this, args)
|
|
109
|
+
const names = new Set(named ? [args[0]] : jobs.map((j) => j && j.name))
|
|
110
|
+
const tags = {}
|
|
111
|
+
if (jobs.length > 1) tags['messaging.batch.message_count'] = String(jobs.length)
|
|
112
|
+
return produce(PGBOSS, names.size === 1 ? Array.from(names)[0] : undefined, tags, orig, this, (span) => {
|
|
113
|
+
const copy = jobs.map((j) => (j && typeof j === 'object' ? Object.assign({}, j, { data: carry(j.data, span) }) : j))
|
|
114
|
+
const a = args.slice()
|
|
115
|
+
a[named ? 1 : 0] = copy
|
|
116
|
+
return a
|
|
117
|
+
}, undefined, args).result
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function jobTags (job) {
|
|
122
|
+
const tags = {}
|
|
123
|
+
if (job && job.id !== undefined) tags['messaging.message.id'] = String(job.id)
|
|
124
|
+
const rc = job && (job.retryCount !== undefined ? job.retryCount : job.retrycount)
|
|
125
|
+
if (typeof rc === 'number') tags['messaging.pmx.attempt'] = String(rc + 1)
|
|
126
|
+
return tags
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function wrapBossHandler (queue, handler) {
|
|
130
|
+
const w = function (arg) {
|
|
131
|
+
if (!c.msgTracer()) {
|
|
132
|
+
if (Array.isArray(arg)) arg.forEach((j) => take(j && j.data))
|
|
133
|
+
else take(arg && arg.data)
|
|
134
|
+
return handler.apply(this, arguments)
|
|
135
|
+
}
|
|
136
|
+
let span
|
|
137
|
+
if (Array.isArray(arg) && arg.length !== 1) {
|
|
138
|
+
const ctxs = arg.map((j) => take(j && j.data))
|
|
139
|
+
const tags = {}
|
|
140
|
+
if (arg.length > 1) tags['messaging.batch.message_count'] = String(arg.length)
|
|
141
|
+
span = c.batchConsumer(PGBOSS, queue, ctxs, tags, 'process')
|
|
142
|
+
} else {
|
|
143
|
+
const job = Array.isArray(arg) ? arg[0] : arg
|
|
144
|
+
span = c.consumer(PGBOSS, queue, take(job && job.data), jobTags(job))
|
|
145
|
+
}
|
|
146
|
+
return c.runConsumer(span, handler, this, arguments)
|
|
147
|
+
}
|
|
148
|
+
return c.copyMeta(w, handler)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const internalQueue = (name) => typeof name === 'string' && name.startsWith('__pgboss__')
|
|
152
|
+
|
|
153
|
+
function wrapBossWork (orig) {
|
|
154
|
+
return function work (name) {
|
|
155
|
+
const args = Array.prototype.slice.call(arguments)
|
|
156
|
+
const i = args.length - 1
|
|
157
|
+
if (!c.tracer() || i < 1 || typeof args[i] !== 'function' || internalQueue(name)) return orig.apply(this, args)
|
|
158
|
+
args[i] = wrapBossHandler(typeof name === 'string' ? name : (name && name.name), args[i])
|
|
159
|
+
// the worker's own polling (fetch) runs in this context: marked, it makes no receive spans
|
|
160
|
+
const ctx = require('../tracing/context')
|
|
161
|
+
const store = ctx.current()
|
|
162
|
+
return ctx.run({ span: store && store.span, bag: store ? store.bag : [], server: store && store.server, itg: PGBOSS }, orig, this, args)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function wrapBossFetch (orig) {
|
|
167
|
+
return function fetch (name) {
|
|
168
|
+
if (!c.msgTracer() || nestedIn(PGBOSS) || internalQueue(name)) return orig.apply(this, arguments)
|
|
169
|
+
const span = c.batchConsumer(PGBOSS, typeof name === 'string' ? name : undefined, null, {}, 'receive')
|
|
170
|
+
let r
|
|
171
|
+
try {
|
|
172
|
+
r = orig.apply(this, arguments)
|
|
173
|
+
} catch (err) {
|
|
174
|
+
c.end(span, err)
|
|
175
|
+
throw err
|
|
176
|
+
}
|
|
177
|
+
if (!c.isThenable(r)) return r
|
|
178
|
+
return r.then((res) => {
|
|
179
|
+
try {
|
|
180
|
+
const jobs = Array.isArray(res) ? res : (res ? [res] : [])
|
|
181
|
+
for (const j of jobs) c.addLink(span, take(j && j.data))
|
|
182
|
+
if (jobs.length >= 2) span.setAttribute('messaging.batch.message_count', String(jobs.length))
|
|
183
|
+
if (jobs.length > 0) c.end(span)
|
|
184
|
+
} catch (_) {}
|
|
185
|
+
return res
|
|
186
|
+
}, (err) => {
|
|
187
|
+
c.end(span, err)
|
|
188
|
+
throw err
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** pg-boss manager (9.x/10.x `src/manager.js` CommonJS; 11+ `dist/manager.js` ES module). */
|
|
194
|
+
function patchBoss (exports, info) {
|
|
195
|
+
const Manager = info && info.esm ? exports.default : (exports && (exports.default || exports))
|
|
196
|
+
const proto = Manager && Manager.prototype
|
|
197
|
+
if (!proto || typeof proto.send !== 'function' || typeof proto.work !== 'function') {
|
|
198
|
+
c.disabled('pg-boss', 'Manager.send/work not found')
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
for (const m of ['send', 'sendAfter', 'sendThrottled', 'sendDebounced']) {
|
|
202
|
+
if (typeof proto[m] === 'function') c.wrap(proto, m, wrapBossSend)
|
|
203
|
+
}
|
|
204
|
+
if (typeof proto.publish === 'function') c.wrap(proto, 'publish', wrapBossPublish)
|
|
205
|
+
if (typeof proto.insert === 'function') c.wrap(proto, 'insert', wrapBossInsert)
|
|
206
|
+
c.wrap(proto, 'work', wrapBossWork)
|
|
207
|
+
if (typeof proto.fetch === 'function') c.wrap(proto, 'fetch', wrapBossFetch)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// -- graphile-worker --------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
const GRAPHILE = 'graphile-worker'
|
|
213
|
+
|
|
214
|
+
function wrapAddJob (orig) {
|
|
215
|
+
if (typeof orig !== 'function' || orig[c.WRAPPED]) return orig
|
|
216
|
+
const w = function addJob (identifier, payload, spec) {
|
|
217
|
+
if (!c.msgTracer()) return orig.apply(this, arguments)
|
|
218
|
+
const tags = {}
|
|
219
|
+
if (spec && spec.queueName) tags['messaging.graphile_worker.queue_name'] = String(spec.queueName)
|
|
220
|
+
const args = Array.prototype.slice.call(arguments)
|
|
221
|
+
return produce(GRAPHILE, identifier, tags, orig, this, (span) => { const a = args.slice(); a[1] = carry(payload, span); return a }, (job) => job && job.id, args).result
|
|
222
|
+
}
|
|
223
|
+
return c.copyMeta(w, orig)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function wrapTask (identifier, task) {
|
|
227
|
+
const w = function (payload, helpers) {
|
|
228
|
+
const job = helpers && helpers.job
|
|
229
|
+
if (!c.msgTracer()) {
|
|
230
|
+
take(payload)
|
|
231
|
+
return task.apply(this, arguments)
|
|
232
|
+
}
|
|
233
|
+
const ctx = take(payload)
|
|
234
|
+
const tags = {}
|
|
235
|
+
if (job && job.id !== undefined) tags['messaging.message.id'] = String(job.id)
|
|
236
|
+
if (job && typeof job.attempts === 'number') tags['messaging.pmx.attempt'] = String(job.attempts)
|
|
237
|
+
if (job && job.queue_name) tags['messaging.graphile_worker.queue_name'] = String(job.queue_name)
|
|
238
|
+
const span = c.consumer(GRAPHILE, identifier, ctx, tags)
|
|
239
|
+
return c.runConsumer(span, task, this, arguments)
|
|
240
|
+
}
|
|
241
|
+
return c.copyMeta(w, task)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const taskCache = new WeakMap()
|
|
245
|
+
|
|
246
|
+
function tasksProxy (tasks) {
|
|
247
|
+
if (!tasks || typeof tasks !== 'object') return tasks
|
|
248
|
+
return new Proxy(tasks, {
|
|
249
|
+
get (target, key, receiver) {
|
|
250
|
+
const v = Reflect.get(target, key, receiver)
|
|
251
|
+
if (typeof v !== 'function' || typeof key !== 'string') return v
|
|
252
|
+
let w = taskCache.get(v)
|
|
253
|
+
if (!w) {
|
|
254
|
+
w = wrapTask(key, v)
|
|
255
|
+
taskCache.set(v, w)
|
|
256
|
+
}
|
|
257
|
+
return w
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** helpers.js: makeAddJob(compiledSharedOptions, withPgClient) returns addJob. */
|
|
263
|
+
function patchHelpers (exports) {
|
|
264
|
+
const make = exports && exports.makeAddJob
|
|
265
|
+
if (typeof make !== 'function') {
|
|
266
|
+
c.disabled('graphile-worker', 'makeAddJob not found')
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
const w = c.copyMeta(function makeAddJob () {
|
|
270
|
+
return wrapAddJob(make.apply(this, arguments))
|
|
271
|
+
}, make)
|
|
272
|
+
return replace(exports, 'makeAddJob', w)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** worker.js: makeNewWorker(workerShared, params) with params.tasks. */
|
|
276
|
+
function patchWorker (exports) {
|
|
277
|
+
const make = exports && exports.makeNewWorker
|
|
278
|
+
if (typeof make !== 'function') {
|
|
279
|
+
c.disabled('graphile-worker', 'makeNewWorker not found')
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
const w = c.copyMeta(function makeNewWorker (shared, params) {
|
|
283
|
+
if (!params || typeof params !== 'object' || !params.tasks || !c.tracer()) return make.apply(this, arguments)
|
|
284
|
+
const args = Array.prototype.slice.call(arguments)
|
|
285
|
+
args[1] = Object.assign({}, params, { tasks: tasksProxy(params.tasks) })
|
|
286
|
+
return make.apply(this, args)
|
|
287
|
+
}, make)
|
|
288
|
+
return replace(exports, 'makeNewWorker', w)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function replace (exports, name, value) {
|
|
292
|
+
const d = Object.getOwnPropertyDescriptor(exports, name)
|
|
293
|
+
if (d && d.writable) {
|
|
294
|
+
exports[name] = value
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
if (d && d.configurable) {
|
|
298
|
+
Object.defineProperty(exports, name, { value, writable: true, enumerable: d.enumerable, configurable: true })
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
// a getter-only (non-configurable) binding: return a copy with the replaced export
|
|
302
|
+
return require('../integrations').replaceExport(exports, name, value)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = { patchBoss, patchHelpers, patchWorker, carry, take }
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// postgres.js (82 §5.4, ITG-115–118). Every way a postgres.js query runs — awaiting it (`then`,
|
|
5
|
+
// `catch`, `finally`), `execute()`, `forEach()`, `cursor()` iteration, `describe()`, `simple()`,
|
|
6
|
+
// `values()`, `raw()`, `sql.unsafe`, `sql.file`, transactions and reserved connections — goes
|
|
7
|
+
// through `Query.prototype.handle`, called once when execution starts. Wrapping that one method
|
|
8
|
+
// (instead of the `sql` function) keeps every query object lazy and every property of `sql`
|
|
9
|
+
// untouched: a query object that is never awaited never calls it and creates no span. The span
|
|
10
|
+
// ends when the query's `resolve`/`reject` runs (also when postgres.js replaces them for cursor
|
|
11
|
+
// iteration). The BEGIN/COMMIT/ROLLBACK/SAVEPOINT statements that `sql.begin` issues itself make no
|
|
12
|
+
// span (ITG-117: the transaction has no span of its own); queries inside it do.
|
|
13
|
+
|
|
14
|
+
const c = require('./common')
|
|
15
|
+
const sql = require('./sql')
|
|
16
|
+
const dbc = require('./dbcomment')
|
|
17
|
+
|
|
18
|
+
const SPAN = Symbol('pmx-pgjs-span')
|
|
19
|
+
const PREFIX = Symbol('pmx-pgjs-comment')
|
|
20
|
+
const TX_OPS = new Set(['BEGIN', 'COMMIT', 'ROLLBACK', 'SAVEPOINT'])
|
|
21
|
+
|
|
22
|
+
function optionTags (options) {
|
|
23
|
+
const tags = { 'db.system': 'postgresql' }
|
|
24
|
+
if (!options || typeof options !== 'object') return tags
|
|
25
|
+
if (options.database) tags['db.name'] = String(options.database)
|
|
26
|
+
if (options.user) tags['db.user'] = String(options.user)
|
|
27
|
+
const host = Array.isArray(options.host) ? options.host[0] : options.host
|
|
28
|
+
const port = Array.isArray(options.port) ? options.port[0] : options.port
|
|
29
|
+
if (options.path) {
|
|
30
|
+
tags['net.peer.name'] = String(options.path)
|
|
31
|
+
} else {
|
|
32
|
+
if (host) tags['net.peer.name'] = String(host)
|
|
33
|
+
if (port) tags['net.peer.port'] = String(port)
|
|
34
|
+
}
|
|
35
|
+
return tags
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function templateText (q) {
|
|
39
|
+
const strings = q.strings
|
|
40
|
+
if (!Array.isArray(strings)) return ''
|
|
41
|
+
let s = String(strings[0] === undefined ? '' : strings[0])
|
|
42
|
+
for (let i = 1; i < strings.length; i++) s += '$' + i + strings[i]
|
|
43
|
+
return s
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Keep `prop` of `q` wrapped by `make(current)` even when postgres.js assigns a new value. */
|
|
47
|
+
function intercept (q, prop, make) {
|
|
48
|
+
let current = q[prop]
|
|
49
|
+
let wrapped = typeof current === 'function' ? make(current) : current
|
|
50
|
+
Object.defineProperty(q, prop, {
|
|
51
|
+
configurable: true,
|
|
52
|
+
enumerable: Object.prototype.propertyIsEnumerable.call(q, prop),
|
|
53
|
+
get () {
|
|
54
|
+
return wrapped
|
|
55
|
+
},
|
|
56
|
+
set (v) {
|
|
57
|
+
current = v
|
|
58
|
+
wrapped = typeof v === 'function' ? make(v) : v
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function instrument (q, span, t) {
|
|
64
|
+
let delivered = 0
|
|
65
|
+
let streaming = false
|
|
66
|
+
const finish = (err, result) => {
|
|
67
|
+
if (span.ended) return
|
|
68
|
+
try {
|
|
69
|
+
let st = q.statement && typeof q.statement.string === 'string' ? q.statement.string : null
|
|
70
|
+
// the trace comment is never part of db.statement (96 SVM-504)
|
|
71
|
+
const prefix = q[PREFIX]
|
|
72
|
+
if (st !== null && prefix && st.startsWith(prefix)) st = st.slice(prefix.length)
|
|
73
|
+
if (st !== null) {
|
|
74
|
+
const tag = sql.statementTag(st, t.db.statement, false)
|
|
75
|
+
if (tag !== undefined) span.setAttribute('db.statement', tag)
|
|
76
|
+
if (!span.attributes['db.operation']) {
|
|
77
|
+
// sql.file(): the text was read after execution started
|
|
78
|
+
const op = sql.operation(st)
|
|
79
|
+
if (op) {
|
|
80
|
+
span.setAttribute('db.operation', op)
|
|
81
|
+
const db = span.attributes['db.name']
|
|
82
|
+
span.updateName(db ? op + ' ' + db : op)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!err) {
|
|
87
|
+
if (streaming) span.setAttribute('db.rows', String(delivered))
|
|
88
|
+
else if (result && typeof result.count === 'number') span.setAttribute('db.rows', String(result.count))
|
|
89
|
+
}
|
|
90
|
+
} catch (_) {}
|
|
91
|
+
c.end(span, err, err && typeof err.code === 'string' ? err.code : undefined)
|
|
92
|
+
}
|
|
93
|
+
const onResolve = (fn) => function (x) {
|
|
94
|
+
finish(undefined, x)
|
|
95
|
+
return fn.apply(this, arguments)
|
|
96
|
+
}
|
|
97
|
+
const onReject = (fn) => function (err) {
|
|
98
|
+
finish(err || new Error('query failed'))
|
|
99
|
+
return fn.apply(this, arguments)
|
|
100
|
+
}
|
|
101
|
+
const count = (fn) => function (v) {
|
|
102
|
+
delivered += Array.isArray(v) ? v.length : 1
|
|
103
|
+
return fn.apply(this, arguments)
|
|
104
|
+
}
|
|
105
|
+
if (q.cursorRows || typeof q.cursorFn === 'function') {
|
|
106
|
+
// cursor iteration assigns new resolve/reject/cursorFn at every next(): keep them wrapped
|
|
107
|
+
streaming = true
|
|
108
|
+
intercept(q, 'resolve', onResolve)
|
|
109
|
+
intercept(q, 'reject', onReject)
|
|
110
|
+
intercept(q, 'cursorFn', count)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
// every other path: the functions present now are the ones postgres.js calls (cheaper)
|
|
114
|
+
if (typeof q.resolve === 'function') q.resolve = onResolve(q.resolve)
|
|
115
|
+
if (typeof q.reject === 'function') q.reject = onReject(q.reject)
|
|
116
|
+
if (typeof q.forEachFn === 'function') {
|
|
117
|
+
streaming = true
|
|
118
|
+
q.forEachFn = count(q.forEachFn)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 96 SVM-501/502: prepend the trace comment to the query's first string. postgres.js prepares
|
|
124
|
+
* (and caches by text) every non-simple query unless `prepare: false` (instance or query option):
|
|
125
|
+
* those are skipped with `on`; with `force` the query is sent unprepared.
|
|
126
|
+
*/
|
|
127
|
+
function comment (q, t, span, text) {
|
|
128
|
+
try {
|
|
129
|
+
const strings = q.strings
|
|
130
|
+
if (!Array.isArray(strings) || typeof strings[0] !== 'string') return
|
|
131
|
+
const qo = q.options && typeof q.options === 'object' ? q.options : {}
|
|
132
|
+
const io = optionsOf(q) || {}
|
|
133
|
+
const prepared = !qo.simple && io.prepare !== false && !('prepare' in qo && !qo.prepare)
|
|
134
|
+
if (!dbc.wants(t, span, text, prepared)) return
|
|
135
|
+
const prefix = dbc.sqlPrefix(t, span)
|
|
136
|
+
const next = Array.prototype.slice.call(strings)
|
|
137
|
+
next[0] = prefix + next[0]
|
|
138
|
+
q.strings = next
|
|
139
|
+
q[PREFIX] = prefix
|
|
140
|
+
if (prepared) q.options = Object.assign({}, qo, { prepare: false })
|
|
141
|
+
} catch (_) {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function wrapHandle (orig) {
|
|
145
|
+
return function handle () {
|
|
146
|
+
if (this.executed || this[SPAN] !== undefined) return orig.apply(this, arguments)
|
|
147
|
+
const t = c.dbTracer()
|
|
148
|
+
if (t && !c.nested()) {
|
|
149
|
+
try {
|
|
150
|
+
const text = templateText(this)
|
|
151
|
+
const op = sql.operation(text)
|
|
152
|
+
const isTx = TX_OPS.has(op) || (op === 'PREPARE' && /^\s*prepare\s+transaction\b/i.test(text))
|
|
153
|
+
if (!isTx) {
|
|
154
|
+
const base = optionTags(optionsOf(this))
|
|
155
|
+
const tags = Object.assign({}, base)
|
|
156
|
+
if (op) tags['db.operation'] = op
|
|
157
|
+
const st = sql.statementTag(text, t.db.statement, false)
|
|
158
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
159
|
+
const name = op ? (base['db.name'] ? op + ' ' + base['db.name'] : op) : (base['db.name'] || 'postgresql')
|
|
160
|
+
const span = c.clientSpan(t, name, tags)
|
|
161
|
+
this[SPAN] = span
|
|
162
|
+
if (span) {
|
|
163
|
+
instrument(this, span, t)
|
|
164
|
+
comment(this, t, span, text)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (_) {}
|
|
168
|
+
}
|
|
169
|
+
return orig.apply(this, arguments)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The instance options (database, user, host, port) are not reachable from a query object, only
|
|
174
|
+
// its `handler` is. The wrapped factory learns the handler of each instance by creating (never
|
|
175
|
+
// executing) one query object, and maps it to the instance's options; queries of transactions and
|
|
176
|
+
// reserved connections have their own handlers and use the options of the instance created last.
|
|
177
|
+
const OPTIONS = new WeakMap()
|
|
178
|
+
let lastOptions = null
|
|
179
|
+
|
|
180
|
+
function remember (sqlFn) {
|
|
181
|
+
if (typeof sqlFn !== 'function' || !sqlFn.options) return
|
|
182
|
+
lastOptions = sqlFn.options
|
|
183
|
+
const probe = sqlFn`select 1`
|
|
184
|
+
if (probe && typeof probe.handler === 'function') OPTIONS.set(probe.handler, sqlFn.options)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function optionsOf (q) {
|
|
188
|
+
return (q.handler && OPTIONS.get(q.handler)) || lastOptions
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 96 SVM-505: `connection.application_name` = the pmx app name unless the app set one (options,
|
|
193
|
+
* URL query or PGAPPNAME); postgres.js's own default ('postgres.js') counts as not set.
|
|
194
|
+
*/
|
|
195
|
+
function withAppName (argsIn) {
|
|
196
|
+
const t = c.dbTracer()
|
|
197
|
+
const name = t && dbc.connectionAppName(t)
|
|
198
|
+
if (!name || process.env.PGAPPNAME) return argsIn
|
|
199
|
+
const args = Array.prototype.slice.call(argsIn)
|
|
200
|
+
const url = typeof args[0] === 'string' ? args[0] : ''
|
|
201
|
+
if (/[?&]application_name=/i.test(url)) return argsIn
|
|
202
|
+
const i = url || args[0] === undefined ? 1 : 0
|
|
203
|
+
const opts = args[i]
|
|
204
|
+
if (opts !== undefined && (opts === null || typeof opts !== 'object')) return argsIn
|
|
205
|
+
const conn = opts && opts.connection
|
|
206
|
+
if (conn && typeof conn === 'object' && conn.application_name) return argsIn
|
|
207
|
+
args[i] = Object.assign({}, opts || {}, { connection: Object.assign({}, conn || {}, { application_name: name }) })
|
|
208
|
+
return args
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function wrapFactory (Postgres) {
|
|
212
|
+
if (typeof Postgres !== 'function' || Postgres[c.WRAPPED]) return Postgres
|
|
213
|
+
const wrapped = function postgres () {
|
|
214
|
+
let args = arguments
|
|
215
|
+
try {
|
|
216
|
+
args = withAppName(arguments)
|
|
217
|
+
} catch (_) {}
|
|
218
|
+
const sqlFn = Postgres.apply(this, args)
|
|
219
|
+
try {
|
|
220
|
+
remember(sqlFn)
|
|
221
|
+
} catch (_) {}
|
|
222
|
+
return sqlFn
|
|
223
|
+
}
|
|
224
|
+
return c.copyMeta(wrapped, Postgres)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Patch query.js (CommonJS `cjs/src/query.js`, ESM `src/query.js`): Query.prototype.handle. */
|
|
228
|
+
function patchQuery (exports) {
|
|
229
|
+
const Query = exports && exports.Query
|
|
230
|
+
if (!Query || !Query.prototype || typeof Query.prototype.handle !== 'function') {
|
|
231
|
+
c.disabled('postgres', 'Query.prototype.handle not found')
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
c.wrap(Query.prototype, 'handle', wrapHandle)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Wrap the default export (the factory) so instance options can be found for tags. */
|
|
238
|
+
function patchIndex (exports, info) {
|
|
239
|
+
const fn = info && info.esm ? exports.default : exports
|
|
240
|
+
if (typeof fn !== 'function') {
|
|
241
|
+
c.disabled('postgres', 'default export is not a function')
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
const w = wrapFactory(fn)
|
|
245
|
+
if (info && info.esm) {
|
|
246
|
+
exports.default = w
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
return w
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = { patchQuery, patchIndex, optionTags, templateText }
|