dd-trace 6.12.0 → 6.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 (97) hide show
  1. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  2. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  3. package/index.d.ts +127 -26
  4. package/package.json +10 -10
  5. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  6. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  10. package/packages/datadog-instrumentations/src/jest.js +140 -18
  11. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  12. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  13. package/packages/datadog-instrumentations/src/pino.js +15 -4
  14. package/packages/datadog-instrumentations/src/playwright.js +34 -20
  15. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -4
  16. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  17. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  18. package/packages/datadog-instrumentations/src/winston.js +6 -3
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-http/src/client.js +2 -15
  21. package/packages/datadog-plugin-http2/src/client.js +2 -15
  22. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  23. package/packages/datadog-plugin-playwright/src/index.js +9 -1
  24. package/packages/datadog-plugin-undici/src/index.js +2 -15
  25. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  26. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  27. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  28. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  29. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  30. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  31. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  33. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  36. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  37. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  38. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  39. package/packages/dd-trace/src/config/index.js +2 -1
  40. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  42. package/packages/dd-trace/src/dogstatsd.js +45 -18
  43. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  44. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  45. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  46. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  47. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  48. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  49. package/packages/dd-trace/src/flush.js +102 -0
  50. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  51. package/packages/dd-trace/src/llmobs/experiments/dataset.js +51 -2
  52. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  53. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  54. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  55. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  56. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  57. package/packages/dd-trace/src/llmobs/index.js +80 -17
  58. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  59. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  60. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  61. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  62. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  63. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  64. package/packages/dd-trace/src/noop/span.js +5 -0
  65. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  66. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  67. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  68. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  69. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  70. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  71. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  72. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  73. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  74. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  75. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  76. package/packages/dd-trace/src/opentracing/propagation/text_map.js +410 -342
  77. package/packages/dd-trace/src/opentracing/span.js +28 -0
  78. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  79. package/packages/dd-trace/src/plugin_manager.js +2 -0
  80. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  81. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  82. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  83. package/packages/dd-trace/src/profiling/oom.js +4 -3
  84. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  85. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  86. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  87. package/packages/dd-trace/src/proxy.js +36 -8
  88. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  89. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  90. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  91. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  92. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  93. package/packages/dd-trace/src/serverless.js +37 -20
  94. package/packages/dd-trace/src/span_stats.js +40 -3
  95. package/packages/dd-trace/src/tracer.js +22 -0
  96. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  97. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
@@ -0,0 +1,1597 @@
1
+ 'use strict'
2
+
3
+ const { errorMonitor } = require('node:events')
4
+ const { performance } = require('node:perf_hooks')
5
+
6
+ const shimmer = require('../../datadog-shimmer')
7
+ const { channel } = require('./helpers/instrument')
8
+ const { acquireWait, reportPoolAcquireError } = require('./helpers/pool-acquire')
9
+
10
+ const connectionStartCh = channel('apm:mariadb:connection:start')
11
+ const connectionFinishCh = channel('apm:mariadb:connection:finish')
12
+ const startCh = channel('apm:mariadb:query:start')
13
+ const finishCh = channel('apm:mariadb:query:finish')
14
+ const errorCh = channel('apm:mariadb:query:error')
15
+ const skipCh = channel('apm:mariadb:pool:skip')
16
+ const acquireStartCh = channel('apm:mariadb:pool:acquire:start')
17
+ const acquireFinishCh = channel('apm:mariadb:pool:acquire:finish')
18
+ const poolAcquireChannels = {
19
+ connectionFinishCh,
20
+ acquireStartCh,
21
+ acquireFinishCh,
22
+ }
23
+
24
+ const activeCommands = new WeakMap()
25
+ const commandMethods = ['query', 'execute', 'batch']
26
+ const trackedCommandMethods = ['changeUser', 'ping', 'prepare', 'reset']
27
+ const emptyConnectionContext = { currentStore: {} }
28
+ const emptyOptions = {}
29
+ const wrappedClients = new WeakSet()
30
+ const wrappedConnections = new WeakSet()
31
+ const IMPORT_FILE_RESOURCE = 'IMPORT FILE'
32
+ const noop = () => {}
33
+ const POOL_ACQUISITION_COMPACTION_THRESHOLD = 1024
34
+ const STATUS_IN_TRANSACTION = 1
35
+ const transactionMethods = [
36
+ ['beginTransaction', 'START TRANSACTION'],
37
+ ['commit', 'COMMIT'],
38
+ ['rollback', 'ROLLBACK'],
39
+ ]
40
+
41
+ /** @typedef {{ length: number, [index: number]: unknown } & Iterable<unknown>} ArgumentsLike */
42
+ /** @typedef {{ options: object, pendingRemovals: number }} ClusterNodeOptions */
43
+ /** @typedef {{ options?: object }} ClusterSelection */
44
+ /** @typedef {import('node:async_hooks').AsyncLocalStorage<ClusterSelection>} ClusterSelectionStorage */
45
+ /**
46
+ * @typedef {object} PoolAcquisition
47
+ * @property {Record<string, unknown>} [acquireCtx]
48
+ * @property {boolean} [acquired]
49
+ * @property {object} connectionCtx
50
+ * @property {unknown} [error]
51
+ * @property {boolean} [errorReported]
52
+ * @property {boolean} [finished]
53
+ * @property {boolean} measure
54
+ * @property {object} options
55
+ * @property {object} pool
56
+ * @property {object} [queryCtx]
57
+ * @property {boolean} [queryStarted]
58
+ * @property {boolean} [ready]
59
+ * @property {number} [start]
60
+ */
61
+ /** @typedef {import('node:async_hooks').AsyncLocalStorage<PoolAcquisition>} PoolAcquisitionStorage */
62
+ /** @typedef {{ acquisitions: Array<PoolAcquisition | undefined>, index: number }} PendingPoolAcquisitions */
63
+
64
+ /** @type {ClusterSelectionStorage | undefined} */
65
+ let clusterSelectionStorage
66
+
67
+ /** @type {PoolAcquisitionStorage | undefined} */
68
+ let poolAcquisitionStorage
69
+
70
+ /** @type {WeakMap<object, PendingPoolAcquisitions>} */
71
+ const pendingPoolAcquisitions = new WeakMap()
72
+
73
+ /**
74
+ * Creates cluster selection storage only when an application uses pool clusters.
75
+ *
76
+ * @returns {ClusterSelectionStorage}
77
+ */
78
+ function getClusterSelectionStorage () {
79
+ if (clusterSelectionStorage === undefined) {
80
+ const { AsyncLocalStorage } = require('node:async_hooks')
81
+ clusterSelectionStorage = new AsyncLocalStorage()
82
+ }
83
+ return clusterSelectionStorage
84
+ }
85
+
86
+ /**
87
+ * Creates pool acquisition storage only when an application uses a bundled pool.
88
+ *
89
+ * @returns {PoolAcquisitionStorage}
90
+ */
91
+ function getPoolAcquisitionStorage () {
92
+ if (poolAcquisitionStorage === undefined) {
93
+ const { AsyncLocalStorage } = require('node:async_hooks')
94
+ poolAcquisitionStorage = new AsyncLocalStorage()
95
+ }
96
+ return poolAcquisitionStorage
97
+ }
98
+
99
+ /**
100
+ * Extracts SQL from MariaDB's string and object command forms.
101
+ *
102
+ * @param {unknown} command
103
+ * @returns {unknown}
104
+ */
105
+ function normalizeSql (command) {
106
+ return command?.sql ?? command
107
+ }
108
+
109
+ /**
110
+ * Parses connection options without allowing instrumentation to break the factory call.
111
+ *
112
+ * @param {Function} defaultOptions
113
+ * @param {unknown} options
114
+ * @returns {object}
115
+ */
116
+ function normalizeOptions (defaultOptions, options) {
117
+ try {
118
+ return defaultOptions(options)
119
+ } catch {
120
+ return options !== null && typeof options === 'object' ? options : {}
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Creates acquisition state while the caller context is still active.
126
+ *
127
+ * @param {object} pool
128
+ * @param {object} options
129
+ * @param {object} [queryCtx]
130
+ * @param {'explicit' | 'measure' | 'observe'} mode
131
+ * @returns {PoolAcquisition}
132
+ */
133
+ function createPoolAcquisition (pool, options, queryCtx, mode) {
134
+ const explicit = mode === 'explicit'
135
+ const measure = explicit || mode === 'measure'
136
+ const connectionCtx = measure || queryCtx !== undefined ? {} : emptyConnectionContext
137
+ const acquisition = { connectionCtx, measure, options, pool, queryCtx }
138
+
139
+ if (connectionCtx !== emptyConnectionContext) connectionStartCh.publish(connectionCtx)
140
+ if (explicit) {
141
+ acquisition.acquireCtx = { conf: options }
142
+ acquireStartCh.publish(acquisition.acquireCtx)
143
+ }
144
+
145
+ return acquisition
146
+ }
147
+
148
+ /**
149
+ * Queues a delayed acquisition for pool events that no longer carry its async-local context.
150
+ *
151
+ * @param {PoolAcquisition} acquisition
152
+ * @returns {void}
153
+ */
154
+ function queuePoolAcquisition (acquisition) {
155
+ let pending = pendingPoolAcquisitions.get(acquisition.pool)
156
+ if (pending === undefined) {
157
+ pending = { acquisitions: [], index: 0 }
158
+ pendingPoolAcquisitions.set(acquisition.pool, pending)
159
+ }
160
+ pending.acquisitions.push(acquisition)
161
+ }
162
+
163
+ /**
164
+ * Reports whether an acquisition still needs a matching pool event.
165
+ *
166
+ * @param {PoolAcquisition} acquisition
167
+ * @returns {boolean}
168
+ */
169
+ function isPoolAcquisitionPending (acquisition) {
170
+ return !acquisition.acquired && !acquisition.finished && !acquisition.errorReported
171
+ }
172
+
173
+ /**
174
+ * Releases a consumed acquisition and periodically compacts its queue.
175
+ *
176
+ * @param {PendingPoolAcquisitions} pending
177
+ * @returns {void}
178
+ */
179
+ function discardPoolAcquisition (pending) {
180
+ pending.acquisitions[pending.index++] = undefined
181
+
182
+ if (
183
+ pending.index < POOL_ACQUISITION_COMPACTION_THRESHOLD ||
184
+ pending.index * 2 < pending.acquisitions.length ||
185
+ pending.index === pending.acquisitions.length
186
+ ) {
187
+ return
188
+ }
189
+
190
+ pending.acquisitions = pending.acquisitions.slice(pending.index)
191
+ pending.index = 0
192
+ }
193
+
194
+ /**
195
+ * Removes completed entries from the front of a pool's acquisition queue.
196
+ *
197
+ * @param {object} pool
198
+ * @returns {void}
199
+ */
200
+ function prunePoolAcquisitions (pool) {
201
+ const pending = pendingPoolAcquisitions.get(pool)
202
+ if (pending === undefined) return
203
+
204
+ while (pending.index < pending.acquisitions.length) {
205
+ const acquisition = /** @type {PoolAcquisition} */ (pending.acquisitions[pending.index])
206
+ if (isPoolAcquisitionPending(acquisition)) return
207
+
208
+ discardPoolAcquisition(pending)
209
+ }
210
+
211
+ pendingPoolAcquisitions.delete(pool)
212
+ }
213
+
214
+ /**
215
+ * Takes the next unfinished acquisition from a pool's public-operation queue.
216
+ *
217
+ * @param {object} pool
218
+ * @returns {PoolAcquisition | undefined}
219
+ */
220
+ function takePoolAcquisition (pool) {
221
+ const pending = pendingPoolAcquisitions.get(pool)
222
+ if (pending === undefined) return
223
+
224
+ while (pending.index < pending.acquisitions.length) {
225
+ const acquisition = /** @type {PoolAcquisition} */ (pending.acquisitions[pending.index])
226
+ discardPoolAcquisition(pending)
227
+
228
+ if (isPoolAcquisitionPending(acquisition)) return acquisition
229
+ }
230
+
231
+ pendingPoolAcquisitions.delete(pool)
232
+ }
233
+
234
+ /**
235
+ * Records the pool wait when MariaDB announces that the calling operation acquired a connection.
236
+ *
237
+ * @param {object} pool
238
+ * @returns {void}
239
+ */
240
+ function recordPoolAcquisition (pool) {
241
+ let acquisition = poolAcquisitionStorage?.getStore()
242
+ if (acquisition?.pool !== pool || !isPoolAcquisitionPending(acquisition)) {
243
+ acquisition = takePoolAcquisition(pool)
244
+ }
245
+ if (acquisition === undefined) return
246
+
247
+ acquisition.acquired = true
248
+ prunePoolAcquisitions(pool)
249
+ if (!acquisition.measure) {
250
+ startPoolCommand(acquisition)
251
+ return
252
+ }
253
+
254
+ const poolWaitTime = acquireWait(acquisition.start)
255
+
256
+ if (acquisition.queryCtx !== undefined) acquisition.queryCtx.poolWaitTime = poolWaitTime
257
+ if (acquisition.acquireCtx !== undefined) acquisition.acquireCtx.poolWaitTime = poolWaitTime
258
+
259
+ startPoolCommand(acquisition)
260
+ }
261
+
262
+ /**
263
+ * Starts a pooled command after MariaDB has acquired the connection that will execute it.
264
+ *
265
+ * @param {PoolAcquisition} acquisition
266
+ * @returns {void}
267
+ */
268
+ function startPoolCommand (acquisition) {
269
+ const queryCtx = acquisition.queryCtx
270
+ if (queryCtx === undefined || acquisition.queryStarted) return
271
+
272
+ acquisition.queryStarted = true
273
+ connectionFinishCh.runStores(acquisition.connectionCtx, runCommandStart, undefined, queryCtx)
274
+ }
275
+
276
+ /**
277
+ * Starts a command lifecycle without running connector work inside its span store.
278
+ *
279
+ * @param {object} ctx
280
+ * @returns {void}
281
+ */
282
+ function runCommandStart (ctx) {
283
+ startCh.runStores(ctx, noop)
284
+ }
285
+
286
+ /**
287
+ * Creates an acquire error span when a pooled query fails before receiving a connection.
288
+ *
289
+ * @param {PoolAcquisition | undefined} acquisition
290
+ * @param {unknown} error
291
+ * @returns {void}
292
+ */
293
+ function reportPoolQueryAcquireError (acquisition, error) {
294
+ if (acquisition === undefined || acquisition.acquired || acquisition.errorReported) return
295
+
296
+ acquisition.error = error
297
+ if (!acquisition.ready) return
298
+
299
+ acquisition.errorReported = true
300
+ prunePoolAcquisitions(acquisition.pool)
301
+ connectionFinishCh.runStores(acquisition.connectionCtx, reportPoolAcquireError, undefined,
302
+ acquisition.start, error, { conf: acquisition.options }, poolAcquireChannels)
303
+ }
304
+
305
+ /**
306
+ * Calls a method with its original receiver and arguments.
307
+ *
308
+ * @param {Function} method
309
+ * @param {unknown} receiver
310
+ * @param {ArgumentsLike} args
311
+ * @returns {unknown}
312
+ */
313
+ function callMethod (method, receiver, args) {
314
+ return method.apply(receiver, args)
315
+ }
316
+
317
+ /**
318
+ * Runs a public pool operation while associating its acquire event with that operation.
319
+ *
320
+ * @param {PoolAcquisition} acquisition
321
+ * @param {Function} method
322
+ * @param {object} receiver
323
+ * @param {ArgumentsLike} args
324
+ * @returns {unknown}
325
+ */
326
+ function runPoolAcquisition (acquisition, method, receiver, args) {
327
+ const result = getPoolAcquisitionStorage().run(acquisition, callMethod, method, receiver, args)
328
+
329
+ acquisition.ready = true
330
+ if (!acquisition.acquired && !acquisition.finished) {
331
+ queuePoolAcquisition(acquisition)
332
+ if (!acquisition.measure) return result
333
+
334
+ if (acquisition.error === undefined) {
335
+ acquisition.start = performance.now()
336
+ } else {
337
+ reportPoolQueryAcquireError(acquisition, acquisition.error)
338
+ }
339
+ }
340
+
341
+ return result
342
+ }
343
+
344
+ /**
345
+ * Completes tracking for a pool command that has returned to the caller.
346
+ *
347
+ * @param {PoolAcquisition | undefined} acquisition
348
+ * @param {unknown} [error]
349
+ * @returns {void}
350
+ */
351
+ function finishPoolCommandAcquisition (acquisition, error) {
352
+ if (acquisition === undefined) return
353
+ if (error && acquisition.measure) {
354
+ reportPoolQueryAcquireError(acquisition, error)
355
+ if (!acquisition.ready) return
356
+ }
357
+ if (!acquisition.acquired) acquisition.finished = true
358
+ prunePoolAcquisitions(acquisition.pool)
359
+ }
360
+
361
+ /**
362
+ * Runs a bundled pool method in the instrumentation skip context.
363
+ *
364
+ * @param {Function} method
365
+ * @param {unknown} receiver
366
+ * @param {ArgumentsLike} args
367
+ * @returns {unknown}
368
+ */
369
+ function runSkippedPoolMethod (method, receiver, args) {
370
+ return skipCh.runStores({}, method, receiver, ...args)
371
+ }
372
+
373
+ /**
374
+ * Finishes the acquire span created for a public getConnection call.
375
+ *
376
+ * @param {PoolAcquisition} acquisition
377
+ * @param {unknown} [error]
378
+ * @returns {void}
379
+ */
380
+ function finishExplicitPoolAcquisition (acquisition, error) {
381
+ if (acquisition.finished) return
382
+ acquisition.finished = true
383
+ prunePoolAcquisitions(acquisition.pool)
384
+
385
+ const acquireCtx = acquisition.acquireCtx
386
+ if (acquireCtx === undefined) return
387
+
388
+ acquireCtx.poolWaitTime ??= acquireWait(acquisition.start)
389
+ if (error) acquireCtx.error = error
390
+ acquireFinishCh.publish(acquireCtx)
391
+ }
392
+
393
+ /**
394
+ * Restores the acquisition observer if application listener cleanup removed it.
395
+ *
396
+ * @param {object} pool
397
+ * @param {Function} observer
398
+ * @returns {void}
399
+ */
400
+ function restorePoolAcquisitionObserver (pool, observer) {
401
+ const listeners = pool.listeners('acquire')
402
+ for (const listener of listeners) {
403
+ if (listener === observer) return
404
+ }
405
+ pool.prependListener('acquire', observer)
406
+ }
407
+
408
+ /**
409
+ * Observes public acquire events forwarded by a bundled pool.
410
+ *
411
+ * @param {object} pool
412
+ * @returns {void}
413
+ */
414
+ function observePoolAcquisitions (pool) {
415
+ const observer = () => {
416
+ if (connectionStartCh.hasSubscribers) recordPoolAcquisition(pool)
417
+ }
418
+ pool.prependListener('acquire', observer)
419
+
420
+ shimmer.wrap(pool, 'removeAllListeners', removeAllListeners => function (event) {
421
+ const result = removeAllListeners.apply(this, arguments)
422
+ if (event === undefined || event === 'acquire') restorePoolAcquisitionObserver(pool, observer)
423
+ return result
424
+ })
425
+ }
426
+
427
+ /**
428
+ * Marks a command as active on its owning public connection.
429
+ *
430
+ * @param {object | undefined} owner
431
+ * @returns {void}
432
+ */
433
+ function startCommand (owner) {
434
+ if (owner === undefined) return
435
+ activeCommands.set(owner, (activeCommands.get(owner) ?? 0) + 1)
436
+ }
437
+
438
+ /**
439
+ * Releases one active command without clearing concurrent commands.
440
+ *
441
+ * @param {object | undefined} owner
442
+ * @returns {void}
443
+ */
444
+ function endCommand (owner) {
445
+ if (owner === undefined) return
446
+ const active = activeCommands.get(owner)
447
+ if (active === 1) {
448
+ activeCommands.delete(owner)
449
+ } else if (active !== undefined) {
450
+ activeCommands.set(owner, active - 1)
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Publishes command completion state and releases its owner.
456
+ *
457
+ * @param {object} ctx
458
+ * @param {object | undefined} owner
459
+ * @param {Error} [error]
460
+ * @param {unknown} [result]
461
+ * @returns {void}
462
+ */
463
+ function finishCommandState (ctx, owner, error, result) {
464
+ endCommand(owner)
465
+ if (error) {
466
+ ctx.error = error
467
+ errorCh.publish(ctx)
468
+ }
469
+ ctx.result = result
470
+ }
471
+
472
+ /**
473
+ * Publishes a complete command lifecycle outside a callback continuation.
474
+ *
475
+ * @param {object} ctx
476
+ * @param {object | undefined} owner
477
+ * @param {Error} [error]
478
+ * @param {unknown} [result]
479
+ * @returns {void}
480
+ */
481
+ function finishCommand (ctx, owner, error, result) {
482
+ finishCommandState(ctx, owner, error, result)
483
+ finishCh.publish(ctx)
484
+ }
485
+
486
+ /**
487
+ * Replaces an existing callback or inserts one in the method's declared callback slot.
488
+ *
489
+ * @param {ArgumentsLike} args
490
+ * @param {number} callbackIndex
491
+ * @param {unknown} callback
492
+ * @param {(callback?: Function) => Function} createCallback
493
+ * @returns {void}
494
+ */
495
+ function setCommandCallback (args, callbackIndex, callback, createCallback) {
496
+ if (typeof callback === 'function') {
497
+ args[args.length - 1] = shimmer.wrapCallback(callback, createCallback)
498
+ return
499
+ }
500
+
501
+ const wrappedCallback = createCallback()
502
+ // MariaDB reads the declared callback slot, so appending after an explicit null leaves the wrapper unused.
503
+ if (callbackIndex >= 0 && args.length > callbackIndex && args[callbackIndex] == null) {
504
+ args[callbackIndex] = wrappedCallback
505
+ return
506
+ }
507
+
508
+ args.length = Math.max(args.length + 1, callbackIndex + 1)
509
+ args[args.length - 1] = wrappedCallback
510
+ }
511
+
512
+ /**
513
+ * Tracks an untraced promise command while MariaDB may have work queued for it.
514
+ *
515
+ * @param {Function} command
516
+ * @returns {Function}
517
+ */
518
+ function createTrackPromiseCommand (command) {
519
+ return function () {
520
+ if (!startCh.hasSubscribers) return command.apply(this, arguments)
521
+
522
+ const owner = this
523
+ startCommand(owner)
524
+
525
+ let result
526
+ try {
527
+ result = command.apply(this, arguments)
528
+ } catch (error) {
529
+ endCommand(owner)
530
+ throw error
531
+ }
532
+
533
+ return result.then(result => {
534
+ endCommand(owner)
535
+ return result
536
+ }, error => {
537
+ endCommand(owner)
538
+ throw error
539
+ })
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Tracks an untraced callback command while MariaDB may have work queued for it.
545
+ *
546
+ * @param {Function} command
547
+ * @returns {Function}
548
+ */
549
+ function createTrackCallbackCommand (command) {
550
+ const callbackIndex = command.length - 1
551
+
552
+ return function () {
553
+ if (!startCh.hasSubscribers) return command.apply(this, arguments)
554
+
555
+ const owner = this
556
+ let finished = false
557
+ const finish = () => {
558
+ if (finished) return
559
+ finished = true
560
+ endCommand(owner)
561
+ }
562
+ const createCallback = callback => function () {
563
+ finish()
564
+ if (typeof callback === 'function') return callback.apply(this, arguments)
565
+ }
566
+
567
+ const callback = arguments[arguments.length - 1]
568
+ setCommandCallback(arguments, callbackIndex, callback, createCallback)
569
+ startCommand(owner)
570
+
571
+ try {
572
+ return command.apply(this, arguments)
573
+ } catch (error) {
574
+ finish()
575
+ throw error
576
+ }
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Creates a promise-returning command wrapper.
582
+ *
583
+ * @param {object} options
584
+ * @param {unknown} [preparedSql]
585
+ * @param {object} [commandOwner]
586
+ * @param {number} [_commandArity] Reserved for callback command wrappers.
587
+ * @param {boolean} [trackActiveCommands]
588
+ * @param {'measure' | 'observe'} [poolAcquisition]
589
+ * @returns {(command: Function) => Function}
590
+ */
591
+ function createWrapPromiseCommand (
592
+ options,
593
+ preparedSql,
594
+ commandOwner,
595
+ _commandArity,
596
+ trackActiveCommands = true,
597
+ poolAcquisition
598
+ ) {
599
+ return function wrapCommand (command) {
600
+ return function (sql) {
601
+ if (!startCh.hasSubscribers) return command.apply(this, arguments)
602
+
603
+ const owner = trackActiveCommands ? (commandOwner ?? this) : undefined
604
+ const ctx = { sql: preparedSql ?? normalizeSql(sql), conf: options }
605
+ const acquisition = poolAcquisition === undefined
606
+ ? undefined
607
+ : createPoolAcquisition(this, options, ctx, poolAcquisition)
608
+
609
+ startCommand(owner)
610
+
611
+ let result
612
+ try {
613
+ result = acquisition === undefined
614
+ ? startCh.runStores(ctx, command, this, ...arguments)
615
+ : runPoolAcquisition(acquisition, command, this, arguments)
616
+ } catch (error) {
617
+ finishPoolCommandAcquisition(acquisition, error)
618
+ if (acquisition === undefined || acquisition.queryStarted) finishCommand(ctx, owner, error)
619
+ throw error
620
+ }
621
+
622
+ return result.then(result => {
623
+ finishPoolCommandAcquisition(acquisition)
624
+ if (acquisition === undefined || acquisition.queryStarted) finishCommand(ctx, owner, undefined, result)
625
+ return result
626
+ }, error => {
627
+ finishPoolCommandAcquisition(acquisition, error)
628
+ if (acquisition === undefined || acquisition.queryStarted) finishCommand(ctx, owner, error)
629
+ throw error
630
+ })
631
+ }
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Creates a callback command wrapper and supplies a completion callback when the caller omits one.
637
+ *
638
+ * @param {object} options
639
+ * @param {unknown} [preparedSql]
640
+ * @param {object} [commandOwner]
641
+ * @param {number} [commandArity] Original arity when command is wrapped by a variadic forwarding function.
642
+ * @param {boolean} [trackActiveCommands]
643
+ * @param {'measure' | 'observe'} [poolAcquisition]
644
+ * @returns {(command: Function) => Function}
645
+ */
646
+ function createWrapCallbackCommand (
647
+ options,
648
+ preparedSql,
649
+ commandOwner,
650
+ commandArity,
651
+ trackActiveCommands = true,
652
+ poolAcquisition
653
+ ) {
654
+ return function wrapCommand (command) {
655
+ const callbackIndex = (commandArity ?? command.length) - 1
656
+
657
+ return function (sql) {
658
+ if (!startCh.hasSubscribers) return command.apply(this, arguments)
659
+
660
+ const owner = trackActiveCommands ? (commandOwner ?? this) : undefined
661
+ const callback = arguments[arguments.length - 1]
662
+ const ctx = { sql: preparedSql ?? normalizeSql(sql), conf: options }
663
+ const acquisition = poolAcquisition === undefined
664
+ ? undefined
665
+ : createPoolAcquisition(this, options, ctx, poolAcquisition)
666
+ const wrapper = callback => function (error) {
667
+ finishPoolCommandAcquisition(acquisition, error)
668
+ if (acquisition !== undefined && !acquisition.queryStarted) {
669
+ return typeof callback === 'function'
670
+ ? connectionFinishCh.runStores(acquisition.connectionCtx, callback, this, ...arguments)
671
+ : undefined
672
+ }
673
+
674
+ finishCommandState(ctx, owner, error)
675
+
676
+ return typeof callback === 'function'
677
+ ? finishCh.runStores(ctx, callback, this, ...arguments)
678
+ : finishCh.runStores(ctx, noop, this)
679
+ }
680
+
681
+ setCommandCallback(arguments, callbackIndex, callback, wrapper)
682
+
683
+ startCommand(owner)
684
+
685
+ try {
686
+ return acquisition === undefined
687
+ ? startCh.runStores(ctx, command, this, ...arguments)
688
+ : runPoolAcquisition(acquisition, command, this, arguments)
689
+ } catch (error) {
690
+ finishPoolCommandAcquisition(acquisition, error)
691
+ if (acquisition === undefined || acquisition.queryStarted) finishCommand(ctx, owner, error)
692
+ throw error
693
+ }
694
+ }
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Traces a Readable-returning command without replacing the stream.
700
+ *
701
+ * @param {object} options
702
+ * @param {unknown} [preparedSql]
703
+ * @param {object} [commandOwner]
704
+ * @returns {(streamMethod: Function) => Function}
705
+ */
706
+ function createWrapStream (options, preparedSql, commandOwner) {
707
+ return function wrapStream (streamMethod) {
708
+ return function (sql) {
709
+ if (!startCh.hasSubscribers) return streamMethod.apply(this, arguments)
710
+
711
+ const owner = commandOwner ?? this
712
+ const ctx = { sql: preparedSql ?? normalizeSql(sql), conf: options }
713
+ let stream
714
+
715
+ startCommand(owner)
716
+ try {
717
+ stream = startCh.runStores(ctx, streamMethod, this, ...arguments)
718
+ } catch (error) {
719
+ finishCommand(ctx, owner, error)
720
+ throw error
721
+ }
722
+
723
+ let finished = false
724
+ const cleanup = () => {
725
+ stream.removeListener('end', onEnd)
726
+ stream.removeListener('close', onClose)
727
+ stream.removeListener(errorMonitor, onError)
728
+ }
729
+ const complete = error => {
730
+ if (finished) return
731
+ finished = true
732
+ cleanup()
733
+ finishCommand(ctx, owner, error)
734
+ }
735
+ const onEnd = () => complete()
736
+ const onClose = () => complete()
737
+ const onError = error => complete(error)
738
+
739
+ stream.once('end', onEnd)
740
+ stream.once('close', onClose)
741
+ stream.once(errorMonitor, onError)
742
+
743
+ return stream
744
+ }
745
+ }
746
+ }
747
+
748
+ /**
749
+ * Wraps command methods exposed by a bundled client.
750
+ *
751
+ * @param {object} client
752
+ * @param {(command: Function) => Function} wrapper
753
+ * @returns {void}
754
+ */
755
+ function wrapClientCommands (client, wrapper) {
756
+ if (wrappedClients.has(client)) return
757
+
758
+ wrappedClients.add(client)
759
+ for (const method of commandMethods) {
760
+ if (typeof client[method] === 'function') shimmer.wrap(client, method, wrapper)
761
+ }
762
+ }
763
+
764
+ /**
765
+ * Wraps commands exposed by a bundled pool and tracks acquisition for query and execute.
766
+ *
767
+ * @param {object} pool
768
+ * @param {object} options
769
+ * @param {(options: object, sql?: unknown, owner?: object, commandArity?: number,
770
+ * trackActiveCommands?: boolean, poolAcquisition?: 'measure' | 'observe') =>
771
+ * (command: Function) => Function} createWrapper
772
+ * @returns {void}
773
+ */
774
+ function wrapPoolCommands (pool, options, createWrapper) {
775
+ if (wrappedClients.has(pool)) return
776
+
777
+ wrappedClients.add(pool)
778
+ for (const method of commandMethods) {
779
+ if (typeof pool[method] !== 'function') continue
780
+ const poolAcquisition = method === 'query' || method === 'execute' ? 'measure' : 'observe'
781
+ shimmer.wrap(pool, method, createWrapPoolCommand(options, createWrapper, undefined, poolAcquisition))
782
+ }
783
+ }
784
+
785
+ /**
786
+ * Wraps transaction helpers whose bundled implementations bypass the public query methods.
787
+ *
788
+ * @param {object} client
789
+ * @param {object} options
790
+ * @param {(options: object, sql: string, owner?: object, commandArity?: number) =>
791
+ * (command: Function) => Function} createWrapper
792
+ * @returns {void}
793
+ */
794
+ function wrapTransactionMethods (client, options, createWrapper) {
795
+ for (const [method, sql] of transactionMethods) {
796
+ shimmer.wrap(client, method, createWrapTransaction(options, sql, createWrapper))
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Traces a transaction helper only when MariaDB sends its command.
802
+ *
803
+ * @param {object} options
804
+ * @param {string} sql
805
+ * @param {(options: object, sql: string, owner?: object, commandArity?: number) =>
806
+ * (command: Function) => Function} createWrapper
807
+ * @returns {(transaction: Function) => Function}
808
+ */
809
+ function createWrapTransaction (options, sql, createWrapper) {
810
+ return function wrapTransaction (transaction) {
811
+ const wrapCommand = createWrapper(options, sql, undefined, transaction.length)
812
+ const tracedTransaction = wrapCommand(function () {
813
+ return skipCh.runStores({}, transaction, this, ...arguments)
814
+ })
815
+
816
+ return function () {
817
+ if (!startCh.hasSubscribers) return transaction.apply(this, arguments)
818
+
819
+ if (sql !== 'START TRANSACTION' &&
820
+ !activeCommands.has(this) &&
821
+ !(this.info?.status & STATUS_IN_TRANSACTION)) {
822
+ return transaction.apply(this, arguments)
823
+ }
824
+
825
+ return tracedTransaction.apply(this, arguments)
826
+ }
827
+ }
828
+ }
829
+
830
+ /**
831
+ * Wraps a promise connection and the prepared statements it creates.
832
+ *
833
+ * @param {object} connection
834
+ * @param {object} options
835
+ * @returns {object}
836
+ */
837
+ function wrapPromiseConnection (connection, options) {
838
+ wrapClientCommands(connection, createWrapPromiseCommand(options))
839
+ if (wrappedConnections.has(connection)) return connection
840
+
841
+ wrappedConnections.add(connection)
842
+ for (const method of trackedCommandMethods) {
843
+ if (typeof connection[method] === 'function') shimmer.wrap(connection, method, createTrackPromiseCommand)
844
+ }
845
+ shimmer.wrap(connection, 'importFile', createWrapPromiseCommand(options, IMPORT_FILE_RESOURCE))
846
+ if (typeof connection.queryStream === 'function') {
847
+ shimmer.wrap(connection, 'queryStream', createWrapStream(options))
848
+ }
849
+ shimmer.wrap(connection, 'prepare', createWrapPromisePrepare(options))
850
+ wrapTransactionMethods(connection, options, createWrapPromiseCommand)
851
+
852
+ return connection
853
+ }
854
+
855
+ /**
856
+ * Wraps a callback connection and the prepared statements it creates.
857
+ *
858
+ * @param {object} connection
859
+ * @param {object} options
860
+ * @returns {object}
861
+ */
862
+ function wrapCallbackConnection (connection, options) {
863
+ wrapClientCommands(connection, createWrapCallbackCommand(options))
864
+ if (wrappedConnections.has(connection)) return connection
865
+
866
+ wrappedConnections.add(connection)
867
+ for (const method of trackedCommandMethods) {
868
+ if (typeof connection[method] === 'function') shimmer.wrap(connection, method, createTrackCallbackCommand)
869
+ }
870
+ shimmer.wrap(connection, 'importFile', createWrapCallbackCommand(options, IMPORT_FILE_RESOURCE))
871
+ if (typeof connection.queryStream === 'function') {
872
+ shimmer.wrap(connection, 'queryStream', createWrapStream(options))
873
+ }
874
+ shimmer.wrap(connection, 'prepare', createWrapCallbackPrepare(options))
875
+ wrapTransactionMethods(connection, options, createWrapCallbackCommand)
876
+
877
+ return connection
878
+ }
879
+
880
+ /**
881
+ * Wraps prepared statements created by a promise connection.
882
+ *
883
+ * @param {object} options
884
+ * @returns {(prepare: Function) => Function}
885
+ */
886
+ function createWrapPromisePrepare (options) {
887
+ return function wrapPrepare (prepare) {
888
+ return function (sql) {
889
+ const connection = this
890
+ const preparedSql = normalizeSql(sql)
891
+ return prepare.apply(this, arguments).then(statement => {
892
+ shimmer.wrap(statement, 'execute', createWrapPromiseCommand(options, preparedSql, connection))
893
+ if (typeof statement.executeStream === 'function') {
894
+ shimmer.wrap(statement, 'executeStream', createWrapStream(options, preparedSql, connection))
895
+ }
896
+ return statement
897
+ })
898
+ }
899
+ }
900
+ }
901
+
902
+ /**
903
+ * Wraps prepared statements created by a callback connection.
904
+ *
905
+ * @param {object} options
906
+ * @returns {(prepare: Function) => Function}
907
+ */
908
+ function createWrapCallbackPrepare (options) {
909
+ return function wrapPrepare (prepare) {
910
+ return function (sql) {
911
+ const connection = this
912
+ const preparedSql = normalizeSql(sql)
913
+ const callback = arguments[arguments.length - 1]
914
+ if (typeof callback !== 'function') return prepare.apply(this, arguments)
915
+
916
+ arguments[arguments.length - 1] = function () {
917
+ const statement = arguments[1]
918
+ if (statement) {
919
+ shimmer.wrap(
920
+ statement,
921
+ 'execute',
922
+ createWrapCallbackPreparedExecute(options, preparedSql, connection)
923
+ )
924
+ if (typeof statement.executeStream === 'function') {
925
+ shimmer.wrap(statement, 'executeStream', createWrapStream(options, preparedSql, connection))
926
+ }
927
+ }
928
+ return callback.apply(this, arguments)
929
+ }
930
+
931
+ return prepare.apply(this, arguments)
932
+ }
933
+ }
934
+ }
935
+
936
+ /**
937
+ * Wraps callback prepared statements, which return a promise when no callback is provided.
938
+ *
939
+ * @param {object} options
940
+ * @param {unknown} sql
941
+ * @param {object} connection
942
+ * @returns {(execute: Function) => Function}
943
+ */
944
+ function createWrapCallbackPreparedExecute (options, sql, connection) {
945
+ return function wrapExecute (execute) {
946
+ const wrapCallbackCommand = createWrapCallbackCommand(options, sql, connection)
947
+ const wrapPromiseCommand = createWrapPromiseCommand(options, sql, connection)
948
+ const executeWithCallback = wrapCallbackCommand(execute)
949
+ const executeWithPromise = wrapPromiseCommand(execute)
950
+
951
+ return function () {
952
+ const hasCallback = typeof arguments[1] === 'function' || typeof arguments[2] === 'function'
953
+ const wrappedExecute = hasCallback ? executeWithCallback : executeWithPromise
954
+ return wrappedExecute.apply(this, arguments)
955
+ }
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Runs bundled pool internals in the skip store while tracing the public command.
961
+ *
962
+ * @param {object} options
963
+ * @param {(options: object, sql?: unknown, owner?: object, commandArity?: number,
964
+ * trackActiveCommands?: boolean, poolAcquisition?: 'measure' | 'observe') =>
965
+ * (command: Function) => Function} createWrapper
966
+ * @param {unknown} [preparedSql]
967
+ * @param {'measure' | 'observe'} [poolAcquisition]
968
+ * @returns {(command: Function) => Function}
969
+ */
970
+ function createWrapPoolCommand (options, createWrapper, preparedSql, poolAcquisition) {
971
+ return function wrapPoolCommand (command) {
972
+ const wrapCommand = createWrapper(options, preparedSql, undefined, command.length, false, poolAcquisition)
973
+ return wrapCommand(function () {
974
+ return skipCh.runStores({}, command, this, ...arguments)
975
+ })
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Restores the caller context and instruments a pooled promise connection.
981
+ *
982
+ * @param {object} ctx
983
+ * @param {object} connection
984
+ * @param {object} options
985
+ * @returns {object}
986
+ */
987
+ function finishPromiseGetConnection (ctx, connection, options) {
988
+ return connectionFinishCh.runStores(ctx, wrapPromiseConnection, undefined, connection, options)
989
+ }
990
+
991
+ /**
992
+ * Restores the caller context when a promise acquisition fails.
993
+ *
994
+ * @param {object} ctx
995
+ * @param {Error} error
996
+ * @throws {Error} The connection acquisition error.
997
+ */
998
+ function finishPromiseGetConnectionError (ctx, error) {
999
+ return connectionFinishCh.runStores(ctx, () => { throw error })
1000
+ }
1001
+
1002
+ /**
1003
+ * Finishes an explicit promise acquisition and instruments its connection in the caller context.
1004
+ *
1005
+ * @param {PoolAcquisition} acquisition
1006
+ * @param {object} connection
1007
+ * @param {object} options
1008
+ * @returns {object}
1009
+ */
1010
+ function finishExplicitPromiseGetConnection (acquisition, connection, options) {
1011
+ return connectionFinishCh.runStores(acquisition.connectionCtx, () => {
1012
+ finishExplicitPoolAcquisition(acquisition)
1013
+ return wrapPromiseConnection(connection, options)
1014
+ })
1015
+ }
1016
+
1017
+ /**
1018
+ * Finishes a failed explicit promise acquisition in the caller context.
1019
+ *
1020
+ * @param {PoolAcquisition} acquisition
1021
+ * @param {Error} error
1022
+ * @throws {Error} The connection acquisition error.
1023
+ */
1024
+ function finishExplicitPromiseGetConnectionError (acquisition, error) {
1025
+ return connectionFinishCh.runStores(acquisition.connectionCtx, () => {
1026
+ finishExplicitPoolAcquisition(acquisition, error)
1027
+ throw error
1028
+ })
1029
+ }
1030
+
1031
+ /**
1032
+ * Wraps getConnection on a bundled promise pool.
1033
+ *
1034
+ * @param {object} options
1035
+ * @returns {(getConnection: Function) => Function}
1036
+ */
1037
+ function createWrapPromiseGetConnection (options) {
1038
+ return function wrapGetConnection (getConnection) {
1039
+ return function () {
1040
+ if (!connectionStartCh.hasSubscribers) return getConnection.apply(this, arguments)
1041
+
1042
+ if (!acquireStartCh.hasSubscribers) {
1043
+ const ctx = {}
1044
+ connectionStartCh.publish(ctx)
1045
+ return skipCh.runStores({}, getConnection, this, ...arguments).then(
1046
+ connection => finishPromiseGetConnection(ctx, connection, options),
1047
+ error => finishPromiseGetConnectionError(ctx, error)
1048
+ )
1049
+ }
1050
+
1051
+ const acquisition = createPoolAcquisition(this, options, undefined, 'explicit')
1052
+ let result
1053
+
1054
+ try {
1055
+ result = runPoolAcquisition(acquisition, runSkippedPoolMethod, undefined, [getConnection, this, arguments])
1056
+ } catch (error) {
1057
+ return finishExplicitPromiseGetConnectionError(acquisition, error)
1058
+ }
1059
+
1060
+ return result.then(
1061
+ connection => finishExplicitPromiseGetConnection(acquisition, connection, options),
1062
+ error => finishExplicitPromiseGetConnectionError(acquisition, error)
1063
+ )
1064
+ }
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * Wraps getConnection on a bundled callback pool.
1070
+ *
1071
+ * @param {object} options
1072
+ * @returns {(getConnection: Function) => Function}
1073
+ */
1074
+ function createWrapCallbackGetConnection (options) {
1075
+ return function wrapGetConnection (getConnection) {
1076
+ return function () {
1077
+ const callback = arguments[arguments.length - 1]
1078
+ if (typeof callback !== 'function') return getConnection.apply(this, arguments)
1079
+
1080
+ if (!connectionStartCh.hasSubscribers) return getConnection.apply(this, arguments)
1081
+
1082
+ if (!acquireStartCh.hasSubscribers) {
1083
+ const ctx = {}
1084
+ arguments[arguments.length - 1] = function () {
1085
+ const connection = arguments[1]
1086
+ if (connection) wrapCallbackConnection(connection, options)
1087
+ return connectionFinishCh.runStores(ctx, callback, this, ...arguments)
1088
+ }
1089
+
1090
+ connectionStartCh.publish(ctx)
1091
+ return skipCh.runStores({}, getConnection, this, ...arguments)
1092
+ }
1093
+
1094
+ const acquisition = createPoolAcquisition(this, options, undefined, 'explicit')
1095
+ arguments[arguments.length - 1] = function (error, connection) {
1096
+ if (connection) wrapCallbackConnection(connection, options)
1097
+ return connectionFinishCh.runStores(acquisition.connectionCtx, () => {
1098
+ finishExplicitPoolAcquisition(acquisition, error)
1099
+ return callback.apply(this, arguments)
1100
+ })
1101
+ }
1102
+
1103
+ try {
1104
+ return runPoolAcquisition(acquisition, runSkippedPoolMethod, undefined, [getConnection, this, arguments])
1105
+ } catch (error) {
1106
+ return finishExplicitPromiseGetConnectionError(acquisition, error)
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ /**
1113
+ * Instruments connection wrappers emitted when a bundled pool creates a connection.
1114
+ *
1115
+ * @param {object} pool
1116
+ * @param {object} options
1117
+ * @param {(connection: object, options: object) => object} wrapConnection
1118
+ * @returns {void}
1119
+ */
1120
+ function wrapPoolConnectionEvent (pool, options, wrapConnection) {
1121
+ shimmer.wrap(pool, 'emit', emit => function (event, connection) {
1122
+ if (event !== 'connection') return emit.apply(this, arguments)
1123
+ wrapConnection(connection, options)
1124
+ return connectionFinishCh.runStores(emptyConnectionContext, emit, this, ...arguments)
1125
+ })
1126
+ }
1127
+
1128
+ /**
1129
+ * Captures the connection options registered with a pool cluster.
1130
+ *
1131
+ * @param {object} cluster
1132
+ * @param {Function} defaultOptions
1133
+ * @param {ClusterSelectionStorage} selectionStorage
1134
+ * @returns {void}
1135
+ */
1136
+ function captureClusterOptions (cluster, defaultOptions, selectionStorage) {
1137
+ /** @type {Map<string, ClusterNodeOptions>} */
1138
+ const optionsByIdentifier = new Map()
1139
+ let nodeCounter = 0
1140
+
1141
+ const removeOptions = identifier => {
1142
+ const nodeOptions = optionsByIdentifier.get(identifier)
1143
+ if (nodeOptions?.pendingRemovals) {
1144
+ nodeOptions.pendingRemovals--
1145
+ } else {
1146
+ optionsByIdentifier.delete(identifier)
1147
+ }
1148
+ }
1149
+
1150
+ // ClusterCallback.on is bound to its private Cluster, so EventEmitter returns the internal
1151
+ // runtime object for both APIs. MariaDB does not expose the selected node on the returned
1152
+ // connection; capture _selectPool while the acquisition's async-local selection is active.
1153
+ const internalCluster = cluster.on('remove', removeOptions)
1154
+ if (typeof internalCluster._selectPool === 'function') {
1155
+ shimmer.wrap(internalCluster, '_selectPool', selectPool => function () {
1156
+ const identifier = selectPool.apply(this, arguments)
1157
+ const selection = selectionStorage.getStore()
1158
+ if (selection !== undefined) selection.options = optionsByIdentifier.get(identifier)?.options
1159
+ return identifier
1160
+ })
1161
+ }
1162
+
1163
+ shimmer.wrap(cluster, 'add', add => function (identifier, options) {
1164
+ const hasIdentifier = typeof identifier === 'string' ||
1165
+ Object.prototype.toString.call(identifier) === '[object String]'
1166
+ const generatedIdentifier = hasIdentifier ? String(identifier) : `PoolNode-${nodeCounter++}`
1167
+ const connectionOptions = hasIdentifier ? options : identifier
1168
+ const result = skipCh.runStores({}, add, this, ...arguments)
1169
+ const previousNodeOptions = optionsByIdentifier.get(generatedIdentifier)
1170
+
1171
+ // MariaDB deletes a failed node before emitting its delayed remove event. A successful
1172
+ // same-identifier add while options remain therefore adds one stale event to ignore.
1173
+ const nodeOptions = {
1174
+ options: normalizeOptions(defaultOptions, connectionOptions),
1175
+ pendingRemovals: previousNodeOptions === undefined ? 0 : previousNodeOptions.pendingRemovals + 1,
1176
+ }
1177
+ optionsByIdentifier.set(generatedIdentifier, nodeOptions)
1178
+
1179
+ return result
1180
+ })
1181
+
1182
+ shimmer.wrap(cluster, 'remove', remove => function (pattern) {
1183
+ const result = remove.apply(this, arguments)
1184
+ removeClusterOptions(optionsByIdentifier, pattern)
1185
+ return result
1186
+ })
1187
+
1188
+ shimmer.wrap(cluster, 'end', end => function () {
1189
+ const result = end.apply(this, arguments)
1190
+ optionsByIdentifier.clear()
1191
+ internalCluster.removeListener('remove', removeOptions)
1192
+ return result
1193
+ })
1194
+ }
1195
+
1196
+ /**
1197
+ * Removes options for every cluster node matching a selector.
1198
+ *
1199
+ * @param {Map<string, ClusterNodeOptions>} optionsByIdentifier
1200
+ * @param {string} pattern
1201
+ * @returns {void}
1202
+ */
1203
+ function removeClusterOptions (optionsByIdentifier, pattern) {
1204
+ const regularExpression = new RegExp(pattern)
1205
+
1206
+ for (const identifier of optionsByIdentifier.keys()) {
1207
+ regularExpression.lastIndex = 0
1208
+ if (regularExpression.test(identifier)) optionsByIdentifier.delete(identifier)
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Calls a cluster acquisition inside the pool-skip context while selection storage is active.
1214
+ *
1215
+ * @param {Function} getConnection
1216
+ * @param {object} receiver
1217
+ * @param {ArgumentsLike} args
1218
+ * @returns {unknown}
1219
+ */
1220
+ function runClusterGetConnection (getConnection, receiver, args) {
1221
+ return skipCh.runStores({}, getConnection, receiver, ...args)
1222
+ }
1223
+
1224
+ /**
1225
+ * Reports a failed bundled cluster node acquisition.
1226
+ *
1227
+ * @param {ClusterSelection} selection
1228
+ * @param {number | undefined} start
1229
+ * @param {unknown} error
1230
+ * @returns {void}
1231
+ */
1232
+ function reportBundledClusterAcquireError (selection, start, error) {
1233
+ if (!acquireStartCh.hasSubscribers) return
1234
+ reportPoolAcquireError(start, error, { conf: selection.options ?? emptyOptions }, poolAcquireChannels)
1235
+ }
1236
+
1237
+ /**
1238
+ * Restores promise caller context and reports a failed bundled cluster node acquisition.
1239
+ *
1240
+ * @param {object} ctx
1241
+ * @param {ClusterSelection} selection
1242
+ * @param {number | undefined} start
1243
+ * @param {unknown} error
1244
+ * @throws {unknown} The connection acquisition error.
1245
+ */
1246
+ function finishPromiseClusterGetConnectionError (ctx, selection, start, error) {
1247
+ return connectionFinishCh.runStores(ctx, () => {
1248
+ reportBundledClusterAcquireError(selection, start, error)
1249
+ throw error
1250
+ })
1251
+ }
1252
+
1253
+ /**
1254
+ * Restores callback caller context and finishes a bundled cluster acquisition.
1255
+ *
1256
+ * @param {ClusterSelection} selection
1257
+ * @param {number | undefined} start
1258
+ * @param {Function} callback
1259
+ * @param {unknown} receiver
1260
+ * @param {ArgumentsLike} args
1261
+ * @returns {unknown}
1262
+ */
1263
+ function finishCallbackClusterGetConnection (selection, start, callback, receiver, args) {
1264
+ const error = args[0]
1265
+ const connection = args[1]
1266
+ if (error) reportBundledClusterAcquireError(selection, start, error)
1267
+ if (connection) wrapCallbackConnection(connection, selection.options ?? emptyOptions)
1268
+ return callback.apply(receiver, args)
1269
+ }
1270
+
1271
+ /**
1272
+ * Wraps promise connections acquired from a bundled pool cluster.
1273
+ *
1274
+ * @param {ClusterSelectionStorage} selectionStorage
1275
+ * @returns {(getConnection: Function) => Function}
1276
+ */
1277
+ function createWrapPromiseClusterGetConnection (selectionStorage) {
1278
+ return function wrapGetConnection (getConnection) {
1279
+ return function () {
1280
+ const ctx = {}
1281
+ /** @type {ClusterSelection} */
1282
+ const selection = {}
1283
+ const start = acquireStartCh.hasSubscribers ? performance.now() : undefined
1284
+
1285
+ connectionStartCh.publish(ctx)
1286
+
1287
+ const result = selectionStorage.run(
1288
+ selection,
1289
+ runClusterGetConnection,
1290
+ getConnection,
1291
+ this,
1292
+ arguments
1293
+ )
1294
+
1295
+ return result.then(
1296
+ connection => finishPromiseGetConnection(ctx, connection, selection.options ?? emptyOptions),
1297
+ error => finishPromiseClusterGetConnectionError(ctx, selection, start, error)
1298
+ )
1299
+ }
1300
+ }
1301
+ }
1302
+
1303
+ /**
1304
+ * Wraps callback connections acquired from a bundled pool cluster.
1305
+ *
1306
+ * @param {ClusterSelectionStorage} selectionStorage
1307
+ * @returns {(getConnection: Function) => Function}
1308
+ */
1309
+ function createWrapCallbackClusterGetConnection (selectionStorage) {
1310
+ return function wrapGetConnection (getConnection) {
1311
+ return function () {
1312
+ const callback = arguments[arguments.length - 1]
1313
+ if (typeof callback !== 'function') return getConnection.apply(this, arguments)
1314
+
1315
+ const ctx = {}
1316
+ /** @type {ClusterSelection} */
1317
+ const selection = {}
1318
+ const start = acquireStartCh.hasSubscribers ? performance.now() : undefined
1319
+ arguments[arguments.length - 1] = function () {
1320
+ return connectionFinishCh.runStores(
1321
+ ctx,
1322
+ finishCallbackClusterGetConnection,
1323
+ undefined,
1324
+ selection,
1325
+ start,
1326
+ callback,
1327
+ this,
1328
+ arguments
1329
+ )
1330
+ }
1331
+
1332
+ connectionStartCh.publish(ctx)
1333
+
1334
+ return selectionStorage.run(
1335
+ selection,
1336
+ runClusterGetConnection,
1337
+ getConnection,
1338
+ this,
1339
+ arguments
1340
+ )
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ /**
1346
+ * Wraps promise connections acquired from a bundled pool cluster.
1347
+ *
1348
+ * @param {object} cluster
1349
+ * @param {Function} defaultOptions
1350
+ * @returns {object}
1351
+ */
1352
+ function wrapPromiseCluster (cluster, defaultOptions) {
1353
+ const selectionStorage = getClusterSelectionStorage()
1354
+ captureClusterOptions(cluster, defaultOptions, selectionStorage)
1355
+
1356
+ shimmer.wrap(cluster, 'getConnection', createWrapPromiseClusterGetConnection(selectionStorage))
1357
+
1358
+ return cluster
1359
+ }
1360
+
1361
+ /**
1362
+ * Wraps callback connections acquired from a bundled pool cluster.
1363
+ *
1364
+ * @param {object} cluster
1365
+ * @param {Function} defaultOptions
1366
+ * @returns {object}
1367
+ */
1368
+ function wrapCallbackCluster (cluster, defaultOptions) {
1369
+ const selectionStorage = getClusterSelectionStorage()
1370
+ captureClusterOptions(cluster, defaultOptions, selectionStorage)
1371
+
1372
+ shimmer.wrap(cluster, 'getConnection', createWrapCallbackClusterGetConnection(selectionStorage))
1373
+ // The filtered callback facade delegates to a private Cluster instance, bypassing the public method above.
1374
+ shimmer.wrap(cluster, 'of', of => function () {
1375
+ const filteredCluster = of.apply(this, arguments)
1376
+ shimmer.wrap(filteredCluster, 'getConnection', createWrapCallbackClusterGetConnection(selectionStorage))
1377
+ return filteredCluster
1378
+ })
1379
+
1380
+ return cluster
1381
+ }
1382
+
1383
+ /**
1384
+ * Wraps the createConnection factory from a bundled promise entry.
1385
+ *
1386
+ * @param {Function} defaultOptions
1387
+ * @returns {(createConnection: Function) => Function}
1388
+ */
1389
+ function createWrapPromiseConnectionFactory (defaultOptions) {
1390
+ return function wrapCreateConnection (createConnection) {
1391
+ return function (options) {
1392
+ return createConnection.apply(this, arguments).then(connection => {
1393
+ return wrapPromiseConnection(connection, normalizeOptions(defaultOptions, options))
1394
+ })
1395
+ }
1396
+ }
1397
+ }
1398
+
1399
+ /**
1400
+ * Wraps the createConnection factory from a bundled callback entry.
1401
+ *
1402
+ * @param {Function} defaultOptions
1403
+ * @returns {(createConnection: Function) => Function}
1404
+ */
1405
+ function createWrapCallbackConnectionFactory (defaultOptions) {
1406
+ return function wrapCreateConnection (createConnection) {
1407
+ return function (options) {
1408
+ const connection = createConnection.apply(this, arguments)
1409
+ return wrapCallbackConnection(connection, normalizeOptions(defaultOptions, options))
1410
+ }
1411
+ }
1412
+ }
1413
+
1414
+ /**
1415
+ * Wraps the createPool factory from a bundled promise entry.
1416
+ *
1417
+ * @param {Function} defaultOptions
1418
+ * @returns {(createPool: Function) => Function}
1419
+ */
1420
+ function createWrapPromisePoolFactory (defaultOptions) {
1421
+ return function wrapCreatePool (createPool) {
1422
+ return function (options) {
1423
+ const pool = skipCh.runStores({}, createPool, this, ...arguments)
1424
+ const normalizedOptions = normalizeOptions(defaultOptions, options)
1425
+
1426
+ observePoolAcquisitions(pool)
1427
+ wrapPoolConnectionEvent(pool, normalizedOptions, wrapPromiseConnection)
1428
+ wrapPoolCommands(pool, normalizedOptions, createWrapPromiseCommand)
1429
+ shimmer.wrap(
1430
+ pool,
1431
+ 'importFile',
1432
+ createWrapPoolCommand(normalizedOptions, createWrapPromiseCommand, IMPORT_FILE_RESOURCE, 'observe')
1433
+ )
1434
+ shimmer.wrap(pool, 'getConnection', createWrapPromiseGetConnection(normalizedOptions))
1435
+
1436
+ return pool
1437
+ }
1438
+ }
1439
+ }
1440
+
1441
+ /**
1442
+ * Wraps the createPool factory from a bundled callback entry.
1443
+ *
1444
+ * @param {Function} defaultOptions
1445
+ * @returns {(createPool: Function) => Function}
1446
+ */
1447
+ function createWrapCallbackPoolFactory (defaultOptions) {
1448
+ return function wrapCreatePool (createPool) {
1449
+ return function (options) {
1450
+ const pool = skipCh.runStores({}, createPool, this, ...arguments)
1451
+ const normalizedOptions = normalizeOptions(defaultOptions, options)
1452
+
1453
+ observePoolAcquisitions(pool)
1454
+ wrapPoolConnectionEvent(pool, normalizedOptions, wrapCallbackConnection)
1455
+ wrapPoolCommands(pool, normalizedOptions, createWrapCallbackCommand)
1456
+ shimmer.wrap(
1457
+ pool,
1458
+ 'importFile',
1459
+ createWrapPoolCommand(normalizedOptions, createWrapCallbackCommand, IMPORT_FILE_RESOURCE, 'observe')
1460
+ )
1461
+ shimmer.wrap(pool, 'getConnection', createWrapCallbackGetConnection(normalizedOptions))
1462
+
1463
+ return pool
1464
+ }
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * Wraps the createPoolCluster factory from a bundled promise entry.
1470
+ *
1471
+ * @param {Function} defaultOptions
1472
+ * @returns {(createPoolCluster: Function) => Function}
1473
+ */
1474
+ function createWrapPromiseClusterFactory (defaultOptions) {
1475
+ return function wrapCreatePoolCluster (createPoolCluster) {
1476
+ return function () {
1477
+ return wrapPromiseCluster(createPoolCluster.apply(this, arguments), defaultOptions)
1478
+ }
1479
+ }
1480
+ }
1481
+
1482
+ /**
1483
+ * Wraps the createPoolCluster factory from a bundled callback entry.
1484
+ *
1485
+ * @param {Function} defaultOptions
1486
+ * @returns {(createPoolCluster: Function) => Function}
1487
+ */
1488
+ function createWrapCallbackClusterFactory (defaultOptions) {
1489
+ return function wrapCreatePoolCluster (createPoolCluster) {
1490
+ return function () {
1491
+ return wrapCallbackCluster(createPoolCluster.apply(this, arguments), defaultOptions)
1492
+ }
1493
+ }
1494
+ }
1495
+
1496
+ /**
1497
+ * Wraps the top-level importFile helper from a bundled promise entry.
1498
+ *
1499
+ * @param {Function} defaultOptions
1500
+ * @returns {(importFile: Function) => Function}
1501
+ */
1502
+ function createWrapPromiseImportFile (defaultOptions) {
1503
+ return function wrapImportFile (importFile) {
1504
+ return function (options) {
1505
+ const wrapCommand = createWrapPromiseCommand(
1506
+ normalizeOptions(defaultOptions, options),
1507
+ IMPORT_FILE_RESOURCE,
1508
+ undefined,
1509
+ undefined,
1510
+ false
1511
+ )
1512
+ const wrapper = wrapCommand(importFile)
1513
+ return wrapper.apply(this, arguments)
1514
+ }
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * Wraps the top-level importFile helper from a bundled callback entry.
1520
+ *
1521
+ * @param {Function} defaultOptions
1522
+ * @returns {(importFile: Function) => Function}
1523
+ */
1524
+ function createWrapCallbackImportFile (defaultOptions) {
1525
+ return function wrapImportFile (importFile) {
1526
+ return function (options) {
1527
+ const wrapCommand = createWrapCallbackCommand(
1528
+ normalizeOptions(defaultOptions, options),
1529
+ IMPORT_FILE_RESOURCE,
1530
+ undefined,
1531
+ undefined,
1532
+ false
1533
+ )
1534
+ const wrapper = wrapCommand(importFile)
1535
+ return wrapper.apply(this, arguments)
1536
+ }
1537
+ }
1538
+ }
1539
+
1540
+ /**
1541
+ * Wraps selected CommonJS factories in the mutable default export and its non-configurable namespace getters.
1542
+ *
1543
+ * @param {object} mariadb
1544
+ * @param {Array<[string, (factory: Function) => Function]>} factories
1545
+ * @returns {object}
1546
+ */
1547
+ function wrapBundle (mariadb, factories) {
1548
+ const defaultExport = mariadb.default
1549
+ let wrappedBundle = mariadb
1550
+
1551
+ for (const [name, wrapper] of factories) {
1552
+ wrappedBundle = shimmer.wrap(wrappedBundle, name, wrapper, { replaceGetter: true })
1553
+ }
1554
+ for (const [name] of factories) {
1555
+ shimmer.wrap(defaultExport, name, () => wrappedBundle[name])
1556
+ }
1557
+
1558
+ return wrappedBundle
1559
+ }
1560
+
1561
+ /**
1562
+ * Instruments the promise API exported by MariaDB's 3.5.3+ CommonJS bundle.
1563
+ *
1564
+ * @param {object} mariadb
1565
+ * @param {string} _version
1566
+ * @param {boolean} isIitm
1567
+ * @returns {object}
1568
+ */
1569
+ function wrapPromiseBundle (mariadb, _version, isIitm) {
1570
+ if (isIitm) return mariadb
1571
+
1572
+ const defaultOptions = mariadb.defaultOptions
1573
+ return wrapBundle(mariadb, [
1574
+ ['createConnection', createWrapPromiseConnectionFactory(defaultOptions)],
1575
+ ['createPool', createWrapPromisePoolFactory(defaultOptions)],
1576
+ ['createPoolCluster', createWrapPromiseClusterFactory(defaultOptions)],
1577
+ ['importFile', createWrapPromiseImportFile(defaultOptions)],
1578
+ ])
1579
+ }
1580
+
1581
+ /**
1582
+ * Instruments the callback API exported by MariaDB's 3.5.3+ CommonJS bundle.
1583
+ *
1584
+ * @param {object} mariadb
1585
+ * @returns {object}
1586
+ */
1587
+ function wrapCallbackBundle (mariadb) {
1588
+ const defaultOptions = mariadb.defaultOptions
1589
+ return wrapBundle(mariadb, [
1590
+ ['createConnection', createWrapCallbackConnectionFactory(defaultOptions)],
1591
+ ['createPool', createWrapCallbackPoolFactory(defaultOptions)],
1592
+ ['createPoolCluster', createWrapCallbackClusterFactory(defaultOptions)],
1593
+ ['importFile', createWrapCallbackImportFile(defaultOptions)],
1594
+ ])
1595
+ }
1596
+
1597
+ module.exports = { wrapCallbackBundle, wrapPromiseBundle }