dd-trace 6.14.0 → 6.16.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 (98) hide show
  1. package/README.md +15 -0
  2. package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
  3. package/ci/test-optimization-validation/package-check.js +4 -1
  4. package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
  5. package/index.d.ts +12 -0
  6. package/next.d.ts +27 -0
  7. package/next.js +3 -0
  8. package/package.json +17 -13
  9. package/packages/datadog-esbuild/index.js +14 -1
  10. package/packages/datadog-esbuild/src/resolver.js +284 -0
  11. package/packages/datadog-esbuild/src/utils.js +163 -119
  12. package/packages/datadog-instrumentations/src/ai.js +39 -97
  13. package/packages/datadog-instrumentations/src/anthropic.js +76 -134
  14. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
  15. package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
  16. package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
  17. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  18. package/packages/datadog-instrumentations/src/bullmq.js +1 -1
  19. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  20. package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
  21. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  22. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
  23. package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
  24. package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
  25. package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
  33. package/packages/datadog-instrumentations/src/jest.js +141 -25
  34. package/packages/datadog-instrumentations/src/langchain.js +1 -1
  35. package/packages/datadog-instrumentations/src/langgraph.js +1 -1
  36. package/packages/datadog-instrumentations/src/mercurius.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  38. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
  39. package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
  40. package/packages/datadog-instrumentations/src/openai.js +54 -76
  41. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  42. package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
  45. package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
  48. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
  49. package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
  50. package/packages/datadog-turbopack/index.js +212 -0
  51. package/packages/datadog-turbopack/src/constants.js +5 -0
  52. package/packages/datadog-turbopack/src/loader.js +500 -0
  53. package/packages/dd-trace/src/aiguard/index.js +2 -1
  54. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
  55. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
  56. package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
  57. package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
  58. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
  59. package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
  60. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
  64. package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
  65. package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
  66. package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
  67. package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
  68. package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
  69. package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
  70. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  71. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  72. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  73. package/packages/dd-trace/src/config/helper.js +10 -2
  74. package/packages/dd-trace/src/config/index.js +20 -5
  75. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  76. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  77. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
  78. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  79. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  80. package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
  81. package/packages/dd-trace/src/evp_proxy/path.js +12 -3
  82. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  83. package/packages/dd-trace/src/exporters/common/docker.js +4 -1
  84. package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
  85. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
  88. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
  90. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  91. package/packages/dd-trace/src/plugin_manager.js +9 -3
  92. package/packages/dd-trace/src/plugins/index.js +1 -0
  93. package/packages/dd-trace/src/plugins/util/test.js +4 -1
  94. package/packages/dd-trace/src/priority_sampler.js +4 -0
  95. package/packages/dd-trace/src/sampling_rule.js +3 -1
  96. package/packages/dd-trace/src/span_processor.js +18 -3
  97. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
  98. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const { builtinModules } = require('node:module')
4
+
5
+ const satisfies = require('../../../../vendor/dist/semifies')
6
+ const { getValueFromEnvSources } = require('../../../dd-trace/src/config/helper')
7
+ const { isRelativeRequire } = require('./shared-utils')
8
+
9
+ /**
10
+ * @param {string|undefined} version
11
+ * @param {string[]|undefined} ranges
12
+ * @returns {boolean}
13
+ */
14
+ function matchVersion (version, ranges) {
15
+ return !version || !ranges || ranges.some(range => satisfies(version, range))
16
+ }
17
+
18
+ /**
19
+ * @param {string} name
20
+ * @param {string} [file]
21
+ * @returns {string}
22
+ */
23
+ function filename (name, file) {
24
+ return file ? `${name}/${file}` : name
25
+ }
26
+
27
+ /**
28
+ * @param {string} name
29
+ * @param {string|undefined} version
30
+ * @param {string} moduleName
31
+ * @param {{ file?: string, filePattern?: string, versions?: string[] }} instrumentation
32
+ * @returns {boolean}
33
+ */
34
+ function matchesInstrumentation (name, version, moduleName, instrumentation) {
35
+ const { file, filePattern, versions } = instrumentation
36
+ let matchesFile = moduleName === filename(name, file)
37
+ if (!matchesFile && isRelativeRequire(name)) matchesFile = true
38
+ if (!matchesFile && filePattern) {
39
+ matchesFile = new RegExp(filename(name, filePattern)).test(moduleName)
40
+ }
41
+ return matchesFile && matchVersion(version, versions)
42
+ }
43
+
44
+ /**
45
+ * @returns {Set<string>}
46
+ */
47
+ function getDisabledInstrumentations () {
48
+ const disabled = new Set(
49
+ getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',').filter(Boolean)
50
+ )
51
+ if (disabled.size === 0) return disabled
52
+
53
+ const expanded = new Set(disabled)
54
+ const builtins = new Set(builtinModules)
55
+
56
+ for (const name of disabled) {
57
+ const prefixed = name.startsWith('node:')
58
+ if (!prefixed && !builtins.has(name)) continue
59
+
60
+ const counterpart = prefixed ? name.slice(5) : `node:${name}`
61
+ expanded.add(counterpart)
62
+ }
63
+
64
+ return expanded
65
+ }
66
+
67
+ module.exports = {
68
+ filename,
69
+ getDisabledInstrumentations,
70
+ matchesInstrumentation,
71
+ matchVersion,
72
+ }
@@ -1,20 +1,20 @@
1
1
  'use strict'
2
2
 
3
- const { builtinModules } = require('module')
4
3
  const path = require('path')
5
4
  const { channel } = require('dc-polyfill')
6
- const satisfies = require('../../../../vendor/dist/semifies')
7
5
  const log = require('../../../dd-trace/src/log')
8
6
  const telemetry = require('../../../dd-trace/src/guardrails/telemetry')
9
7
  const { IS_SERVERLESS } = require('../../../dd-trace/src/serverless')
10
8
  const { getValueFromEnvSources } = require('../../../dd-trace/src/config/helper')
11
9
  const checkRequireCache = require('./check-require-cache')
12
10
  const Hook = require('./hook')
13
- const { isRelativeRequire } = require('./shared-utils')
11
+ const {
12
+ filename,
13
+ getDisabledInstrumentations,
14
+ matchesInstrumentation,
15
+ } = require('./instrumentation-utils')
14
16
  const rewriter = require('./rewriter')
15
17
 
16
- const DD_TRACE_DISABLED_INSTRUMENTATIONS =
17
- getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')
18
18
  const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG')
19
19
 
20
20
  const hooks = require('./hooks')
@@ -22,9 +22,7 @@ const instrumentations = require('./instrumentations')
22
22
  const names = Object.keys(hooks)
23
23
  const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
24
24
 
25
- const disabledInstrumentations = new Set(
26
- DD_TRACE_DISABLED_INSTRUMENTATIONS?.split(',')
27
- )
25
+ const disabledInstrumentations = getDisabledInstrumentations()
28
26
 
29
27
  const loadChannel = channel('dd-trace:instrumentation:load')
30
28
 
@@ -54,29 +52,6 @@ const instrumentedIntegrationsSuccess = new Map()
54
52
  /** @type {Set<string>} */
55
53
  const alreadyLoggedIncompatibleIntegrations = new Set()
56
54
 
57
- // Always disable prefixed and unprefixed node modules if one is disabled.
58
- if (disabledInstrumentations.size) {
59
- const builtinsSet = new Set(builtinModules)
60
- const disabledBuiltinCounterparts = []
61
- for (const name of disabledInstrumentations) {
62
- const hasPrefix = name.startsWith('node:')
63
- if (hasPrefix || builtinsSet.has(name)) {
64
- if (hasPrefix) {
65
- const unprefixedName = name.slice(5)
66
- if (!disabledInstrumentations.has(unprefixedName)) {
67
- disabledBuiltinCounterparts.push(unprefixedName)
68
- }
69
- } else if (!disabledInstrumentations.has(`node:${name}`)) {
70
- disabledBuiltinCounterparts.push(`node:${name}`)
71
- }
72
- }
73
- }
74
- for (const name of disabledBuiltinCounterparts) {
75
- disabledInstrumentations.add(name)
76
- }
77
- builtinsSet.clear()
78
- }
79
-
80
55
  for (const name of names) {
81
56
  if (disabledInstrumentations.has(name)) continue
82
57
 
@@ -111,21 +86,9 @@ for (const name of names) {
111
86
  instrumentedNodeModules.set(name, moduleExports)
112
87
  }
113
88
 
114
- for (const { file, versions, hook, filePattern, patchDefault } of instrumentations[name]) {
115
- const fullFilename = filename(name, file)
116
-
117
- let matchesFile = moduleName === fullFilename
118
-
119
- if (!matchesFile && isRelativeRequire(name)) matchesFile = true
120
-
121
- const fullFilePattern = filePattern && filename(name, filePattern)
122
- if (fullFilePattern) {
123
- // Some libraries include a hash in their filenames when installed,
124
- // so our instrumentation has to include a '.*' to match them for more than a single version.
125
- matchesFile ||= new RegExp(fullFilePattern).test(moduleName)
126
- }
127
-
128
- if (matchesFile && matchVersion(moduleVersion, versions)) {
89
+ for (const instrumentation of instrumentations[name]) {
90
+ if (matchesInstrumentation(name, moduleVersion, moduleName, instrumentation)) {
91
+ const { hook, patchDefault } = instrumentation
129
92
  // IITM invokes this callback for every module in the package. Only unwrap the namespace after its file and
130
93
  // version match, otherwise a default export from an unrelated internal module can replace that module.
131
94
  if (isIitm && patchDefault === !!moduleExports.default) {
@@ -188,26 +151,8 @@ function logAbortedIntegrations () {
188
151
  instrumentedIntegrationsSuccess.clear()
189
152
  }
190
153
 
191
- /**
192
- * @param {string|undefined} version
193
- * @param {string[]|undefined} ranges
194
- */
195
- function matchVersion (version, ranges) {
196
- return !version || !ranges || ranges.some(range => satisfies(version, range))
197
- }
198
-
199
- /**
200
- * @param {string} name
201
- * @param {string} [file]
202
- * @returns {string}
203
- */
204
- function filename (name, file) {
205
- return file ? `${name}/${file}` : name
206
- }
207
-
208
154
  module.exports = {
209
155
  filename,
210
156
  pathSepExpr,
211
157
  loadChannel,
212
- matchVersion,
213
158
  }
@@ -1,8 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { readFileSync } = require('fs')
4
- const { join } = require('path')
5
- const { pathToFileURL } = require('url')
3
+ const { readFileSync } = require('node:fs')
4
+ const { join } = require('node:path')
5
+ const { pathToFileURL } = require('node:url')
6
+
6
7
  const log = require('../../../../dd-trace/src/log')
7
8
  const instrumentations = require('./instrumentations')
8
9
  const { getRewriteTarget } = require('./targets')
@@ -13,7 +14,12 @@ const { getRewriteTarget } = require('./targets')
13
14
  * @property {(moduleName: string, version: string|undefined, filePath: string) => Transformer|undefined} getTransformer
14
15
  *
15
16
  * @typedef {object} Transformer
16
- * @property {(source: string, moduleType: 'cjs'|'esm') => { code: string, map?: string }} transform
17
+ * @property {(source: string, moduleType: 'cjs'|'esm', sourceMap?: string|object) =>
18
+ * { code: string, map?: string|object }} transform
19
+ *
20
+ * @typedef {(content: string|Buffer|ArrayBuffer|Uint8Array, filename: string, format?: string,
21
+ * target?: { moduleName: string, filePath: string }, sourceMap?: string|object) =>
22
+ * { code: string|Buffer|ArrayBuffer|Uint8Array, map?: string|object }} BundlerRewriter
17
23
  */
18
24
 
19
25
  /**
@@ -79,46 +85,71 @@ function rewrite (content, filename, format, target) {
79
85
  return content
80
86
  }
81
87
 
88
+ /**
89
+ * @param {string} dcModule
90
+ * @returns {BundlerRewriter}
91
+ */
92
+ function createBundlerRewriter (dcModule) {
93
+ const matcher = createMatcher(dcModule)
94
+
95
+ return function rewriteBundled (content, filename, format, target, sourceMap) {
96
+ if (!content) return { code: content, map: sourceMap }
97
+
98
+ target ||= getRewriteTarget(filename)
99
+ if (!target) return { code: content, map: sourceMap }
100
+
101
+ filename = filename.replace('file://', '')
102
+ const moduleType = format === 'module' ? 'esm' : 'cjs'
103
+ const { moduleName, filePath } = target
104
+ const transformer = matcher.getTransformer(moduleName, getVersion(filename, filePath), filePath)
105
+ if (!transformer) return { code: content, map: sourceMap }
106
+
107
+ try {
108
+ return transformer.transform(getSourceText(content), moduleType, sourceMap)
109
+ } catch (error) {
110
+ log.error(error)
111
+ return { code: content, map: sourceMap }
112
+ }
113
+ }
114
+ }
115
+
82
116
  /**
83
117
  * @param {'cjs'|'esm'} moduleType
84
118
  * @returns {InstrumentationMatcher}
85
119
  */
86
120
  function getMatcher (moduleType) {
87
121
  if (moduleType === 'esm') {
88
- matcherEsm ??= createMatcher(moduleType)
122
+ matcherEsm ??= createMatcher(getDcPolyfillSpecifier(moduleType))
89
123
 
90
124
  return matcherEsm
91
125
  }
92
126
 
93
- matcherCjs ??= createMatcher(moduleType)
127
+ matcherCjs ??= createMatcher(getDcPolyfillSpecifier(moduleType))
94
128
 
95
129
  return matcherCjs
96
130
  }
97
131
 
98
132
  /**
99
- * @param {'cjs'|'esm'} moduleType
133
+ * @param {string|undefined} dcModule
100
134
  * @returns {InstrumentationMatcher}
101
135
  */
102
- function createMatcher (moduleType) {
136
+ function createMatcher (dcModule) {
103
137
  const { create } = require('../../../../../vendor/dist/@apm-js-collab/code-transformer')
104
138
  const {
105
139
  awaitContextCallback,
106
- awaitContextCallbackAtFunctionStart,
107
- awaitContextCallbackAtTryStart,
108
140
  configureGraphqlFastPath,
109
141
  configureGraphqlJitCompileObject,
110
142
  configureGraphqlJitDeferredField,
111
143
  configureGraphqlJitExecute,
112
144
  configureGraphqlJitRuntime,
113
145
  configureMercuriusRequest,
146
+ publishDurableOrchestrationFailure,
114
147
  waitForAsyncEnd,
115
148
  } = require('./transforms')
116
149
 
117
- const matcher = create(instrumentations, getDcPolyfillSpecifier(moduleType))
150
+ const matcher = create(instrumentations, dcModule)
118
151
 
119
152
  matcher.addTransform('awaitContextCallback', awaitContextCallback)
120
- matcher.addTransform('awaitContextCallbackAtFunctionStart', awaitContextCallbackAtFunctionStart)
121
- matcher.addTransform('awaitContextCallbackAtTryStart', awaitContextCallbackAtTryStart)
122
153
  matcher.addTransform('waitForAsyncEnd', waitForAsyncEnd)
123
154
  matcher.addTransform('configureGraphqlFastPath', configureGraphqlFastPath)
124
155
  matcher.addTransform('configureGraphqlJitCompileObject', configureGraphqlJitCompileObject)
@@ -126,6 +157,7 @@ function createMatcher (moduleType) {
126
157
  matcher.addTransform('configureGraphqlJitExecute', configureGraphqlJitExecute)
127
158
  matcher.addTransform('configureGraphqlJitRuntime', configureGraphqlJitRuntime)
128
159
  matcher.addTransform('configureMercuriusRequest', configureMercuriusRequest)
160
+ matcher.addTransform('publishDurableOrchestrationFailure', publishDurableOrchestrationFailure)
129
161
 
130
162
  return matcher
131
163
  }
@@ -189,4 +221,4 @@ function getVersion (filename, filePath) {
189
221
  return moduleVersions[basename]
190
222
  }
191
223
 
192
- module.exports = { rewrite, disable }
224
+ module.exports = { createBundlerRewriter, disable, rewrite }
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ module.exports = [
4
+ {
5
+ module: {
6
+ name: 'durable-functions',
7
+ versionRange: '>=3 <4',
8
+ filePath: 'lib/src/orchestrations/TaskOrchestrationExecutor.js',
9
+ },
10
+ functionQuery: {
11
+ className: 'TaskOrchestrationExecutor',
12
+ methodName: 'execute',
13
+ },
14
+ transform: 'publishDurableOrchestrationFailure',
15
+ channelName: 'TaskOrchestrationExecutor_failure',
16
+ },
17
+ ]
@@ -3,6 +3,7 @@
3
3
  module.exports = [
4
4
  ...require('./ai'),
5
5
  ...require('./azure-cosmos'),
6
+ ...require('./azure-durable-functions'),
6
7
  ...require('./bullmq'),
7
8
  ...require('./claude-agent-sdk'),
8
9
  ...require('./graphql'),
@@ -99,11 +99,7 @@ module.exports = [
99
99
  versionRange: '>=1.60.0',
100
100
  filePath: 'lib/coreBundle.js',
101
101
  },
102
- astQuery: 'AssignmentExpression[left.name="Page2"] > ClassExpression > ClassBody > ' +
103
- 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
104
- 'VariableDeclarator[id.name="Page2"] > ClassExpression > ClassBody > ' +
105
- 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
106
- 'ClassDeclaration[id.name="Page2"] > ClassBody > ' +
102
+ astQuery: 'ClassExpression[id.name="_Page"] > ClassBody > ' +
107
103
  'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async]',
108
104
  functionQuery: {
109
105
  methodName: 'goto',
@@ -428,9 +428,9 @@ module.exports = [
428
428
  versionRange: '>=9.0.0',
429
429
  filePath: 'build/index.js',
430
430
  },
431
- astQuery: 'VariableDeclarator[id.name="testFrameworkFnWrapper"] > FunctionExpression TryStatement',
431
+ astQuery: 'VariableDeclarator[id.name="testFrameworkFnWrapper"] > FunctionExpression',
432
432
  channelName: 'testFrameworkFnWrapper',
433
- transform: 'awaitContextCallbackAtFunctionStart',
433
+ transform: 'awaitContextCallback',
434
434
  transformOptions: {
435
435
  callbackName: 'rumCleanupCallback',
436
436
  },
@@ -453,12 +453,11 @@ module.exports = [
453
453
  versionRange: '>=9.0.0',
454
454
  filePath: 'build/index.js',
455
455
  },
456
- astQuery: 'FunctionDeclaration[id.name="executeAsync"] CatchClause ' +
457
- 'IfStatement[test.operator=">"][test.left.object.name="retries"]' +
458
- '[test.left.property.name="limit"][test.right.object.name="retries"]' +
459
- '[test.right.property.name="attempts"]',
456
+ astQuery: 'TryStatement:has(CatchClause IfStatement[test.operator=">"]' +
457
+ '[test.left.object.name="retries"][test.left.property.name="limit"]' +
458
+ '[test.right.object.name="retries"][test.right.property.name="attempts"]) > BlockStatement',
460
459
  channelName: 'executeAsync',
461
- transform: 'awaitContextCallbackAtTryStart',
460
+ transform: 'awaitContextCallback',
462
461
  transformOptions: {
463
462
  callbackName: 'rumStartCallback',
464
463
  },
@@ -43,6 +43,7 @@
43
43
  "bullmq/dist/esm/classes/flow-producer.js": "bullmq",
44
44
  "bullmq/dist/esm/classes/queue.js": "bullmq",
45
45
  "bullmq/dist/esm/classes/worker.js": "bullmq",
46
+ "durable-functions/lib/src/orchestrations/TaskOrchestrationExecutor.js": "durable-functions",
46
47
  "graphql-jit/dist/cjs/execution.js": "graphql-jit",
47
48
  "graphql-jit/dist/esm/execution.js": "graphql-jit",
48
49
  "graphql-jit/dist/execution.js": "graphql-jit",
@@ -20,67 +20,77 @@ const identifierPattern = /^[$A-Z_a-z][$\w]*$/
20
20
 
21
21
  module.exports = {
22
22
  awaitContextCallback,
23
- awaitContextCallbackAtFunctionStart,
24
- awaitContextCallbackAtTryStart,
25
23
  configureGraphqlJitCompileObject,
26
24
  configureGraphqlJitDeferredField,
27
25
  configureGraphqlJitExecute,
28
26
  configureGraphqlJitRuntime,
29
27
  configureGraphqlFastPath,
30
28
  configureMercuriusRequest,
29
+ publishDurableOrchestrationFailure,
31
30
  waitForAsyncEnd,
32
31
  }
33
32
 
34
33
  /**
35
- * Awaits an optional context callback at the start of the matched node's enclosing async function.
34
+ * Publishes the error captured by Durable Functions immediately before its
35
+ * executor converts that error into a serialized failed orchestration state.
36
36
  *
37
- * @param {Parameters<typeof awaitContextCallback>[0]} state
38
- * @param {import('estree').Node} node
37
+ * @param {{
38
+ * channelName: string,
39
+ * transforms: { tracingChannelDeclaration: Function }
40
+ * }} state
41
+ * @param {import('estree').FunctionExpression} node
39
42
  * @param {import('estree').Node} _parent
40
43
  * @param {import('estree').Node[]} ancestry
41
44
  * @returns {void}
42
45
  */
43
- function awaitContextCallbackAtFunctionStart (state, node, _parent, ancestry) {
44
- let enclosingFunction = functionTypes.has(node.type)
45
- ? node
46
- : ancestry.find(ancestor => functionTypes.has(ancestor.type))
47
- let callbackAncestry = ancestry
48
-
49
- if (enclosingFunction === node) {
50
- callbackAncestry = [node, ...ancestry]
51
- if (!node.async) {
52
- // Function queries create a synchronous trace wrapper before custom transforms run.
53
- const [wrappedFunction] = query(node,
54
- 'VariableDeclarator[id.name="__apm$traced"] > ArrowFunctionExpression > BlockStatement > ' +
55
- 'VariableDeclaration > VariableDeclarator[id.name="__apm$wrapped"] > ' +
56
- ':matches(FunctionDeclaration, FunctionExpression)[async=true]')
57
- if (wrappedFunction) {
58
- enclosingFunction = wrappedFunction
59
- callbackAncestry.unshift(wrappedFunction)
60
- }
61
- }
62
- }
63
- assert(enclosingFunction?.async && enclosingFunction.body?.type === 'BlockStatement',
64
- 'awaitContextCallbackAtFunctionStart: expected an enclosing async function with a block body')
46
+ function publishDurableOrchestrationFailure (state, node, _parent, ancestry) {
47
+ // Class queries also visit the owning ClassDeclaration so Orchestrion can
48
+ // synthesize missing methods. This transform targets the concrete method only.
49
+ if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') return
50
+
51
+ const failureBranches = query(node, 'IfStatement').filter(({ test }) =>
52
+ test.type === 'BinaryExpression' &&
53
+ test.operator === '!==' &&
54
+ test.left?.type === 'MemberExpression' &&
55
+ test.left.object?.type === 'ThisExpression' &&
56
+ test.left.property?.name === 'exception' &&
57
+ test.right?.type === 'Identifier' &&
58
+ test.right.name === 'undefined'
59
+ )
65
60
 
66
- const generatedCallback = createAwaitedContextCallback(
67
- state,
68
- enclosingFunction.body,
69
- callbackAncestry,
70
- 'awaitContextCallbackAtFunctionStart'
61
+ assert.strictEqual(
62
+ failureBranches.length,
63
+ 1,
64
+ 'publishDurableOrchestrationFailure: executor failure branch not found'
71
65
  )
72
- if (!generatedCallback) return
66
+ assert.strictEqual(
67
+ failureBranches[0].consequent.type,
68
+ 'BlockStatement',
69
+ 'publishDurableOrchestrationFailure: expected a block failure branch'
70
+ )
71
+
72
+ const program = ancestry.at(-1)
73
+ state.transforms.tracingChannelDeclaration(state, program)
73
74
 
74
- let insertionIndex = 0
75
- while (typeof enclosingFunction.body.body[insertionIndex]?.directive === 'string') insertionIndex++
76
- enclosingFunction.body.body.splice(insertionIndex, 0, ...generatedCallback.callbackStatements)
75
+ const channelVariable = `tr_ch_apm$${state.channelName.replaceAll(/[^\w]/g, '_')}`
76
+ const publishStatements = parse(`
77
+ if (${channelVariable}.end.hasSubscribers) {
78
+ ${channelVariable}.end.publish({
79
+ arguments: [context, history],
80
+ error: this.exception
81
+ })
82
+ }
83
+ `).body
84
+
85
+ failureBranches[0].consequent.body.unshift(...publishStatements)
77
86
  }
78
87
 
79
88
  /**
80
- * Awaits an optional context callback before continuing through a matched conditional branch.
89
+ * Awaits an optional context callback at the start of a matched function or block, or before continuing through a
90
+ * matched conditional branch.
81
91
  *
82
- * The branch condition is checked again after the callback settles so the
83
- * original body does not run against state that changed while awaiting.
92
+ * A matched conditional branch is checked again after the callback settles so its original body does not run
93
+ * against state that changed while awaiting.
84
94
  *
85
95
  * @param {{
86
96
  * transformOptions?: {
@@ -89,14 +99,62 @@ function awaitContextCallbackAtFunctionStart (state, node, _parent, ancestry) {
89
99
  * callbackThis?: boolean
90
100
  * }
91
101
  * }} state
92
- * @param {import('estree').IfStatement} node
102
+ * @param {
103
+ * import('estree').ArrowFunctionExpression|
104
+ * import('estree').BlockStatement|
105
+ * import('estree').FunctionDeclaration|
106
+ * import('estree').FunctionExpression|
107
+ * import('estree').IfStatement
108
+ * } node
93
109
  * @param {import('estree').Node} _parent
94
110
  * @param {import('estree').Node[]} ancestry
95
111
  * @returns {void}
96
112
  */
97
113
  function awaitContextCallback (state, node, _parent, ancestry) {
114
+ let insertionTarget
115
+ let callbackAncestry = ancestry
116
+
117
+ if (node.type === 'BlockStatement') {
118
+ insertionTarget = node
119
+ } else if (functionTypes.has(node.type)) {
120
+ let callbackFunction = node
121
+ callbackAncestry = [node, ...ancestry]
122
+
123
+ if (!node.async) {
124
+ // Built-in function transforms run before later custom transforms. Keep knowledge of their generated wrapper
125
+ // here so instrumentation queries can continue to target the original function.
126
+ const [wrappedFunction] = query(node,
127
+ 'VariableDeclarator[id.name="__apm$traced"] > ArrowFunctionExpression > BlockStatement > ' +
128
+ 'VariableDeclaration > VariableDeclarator[id.name="__apm$wrapped"] > ' +
129
+ ':matches(ArrowFunctionExpression, FunctionDeclaration, FunctionExpression)[async=true]')
130
+ if (wrappedFunction) {
131
+ callbackFunction = wrappedFunction
132
+ callbackAncestry.unshift(wrappedFunction)
133
+ }
134
+ }
135
+
136
+ assert(callbackFunction.async && callbackFunction.body?.type === 'BlockStatement',
137
+ 'awaitContextCallback: expected an async function with a block body')
138
+ insertionTarget = callbackFunction.body
139
+ }
140
+
141
+ if (insertionTarget) {
142
+ const generatedCallback = createAwaitedContextCallback(
143
+ state,
144
+ insertionTarget,
145
+ callbackAncestry,
146
+ 'awaitContextCallback'
147
+ )
148
+ if (!generatedCallback) return
149
+
150
+ let insertionIndex = 0
151
+ while (typeof insertionTarget.body[insertionIndex]?.directive === 'string') insertionIndex++
152
+ insertionTarget.body.splice(insertionIndex, 0, ...generatedCallback.callbackStatements)
153
+ return
154
+ }
155
+
98
156
  assert(node.type === 'IfStatement' && node.consequent?.type === 'BlockStatement',
99
- 'awaitContextCallback: expected an if statement with a block body')
157
+ 'awaitContextCallback: expected a function, a block, or an if statement with a block body')
100
158
 
101
159
  const originalStatements = node.consequent.body
102
160
  const generatedCallback = createAwaitedContextCallback(
@@ -121,35 +179,6 @@ function awaitContextCallback (state, node, _parent, ancestry) {
121
179
  node.consequent.body = [...callbackStatements, callbackBranch]
122
180
  }
123
181
 
124
- /**
125
- * Awaits an optional context callback before entering the matched node's enclosing try block.
126
- *
127
- * @param {Parameters<typeof awaitContextCallback>[0]} state
128
- * @param {import('estree').Node} node
129
- * @param {import('estree').Node} _parent
130
- * @param {import('estree').Node[]} ancestry
131
- * @returns {void}
132
- */
133
- function awaitContextCallbackAtTryStart (state, node, _parent, ancestry) {
134
- let tryStatement = node.type === 'TryStatement' ? node : undefined
135
- for (const ancestor of ancestry) {
136
- if (tryStatement || functionTypes.has(ancestor.type)) break
137
- if (ancestor.type === 'TryStatement') tryStatement = ancestor
138
- }
139
- assert(tryStatement?.block?.type === 'BlockStatement',
140
- 'awaitContextCallbackAtTryStart: expected an enclosing try statement with a block body')
141
-
142
- const generatedCallback = createAwaitedContextCallback(
143
- state,
144
- tryStatement.block,
145
- ancestry,
146
- 'awaitContextCallbackAtTryStart'
147
- )
148
- if (!generatedCallback) return
149
-
150
- tryStatement.block.body.unshift(...generatedCallback.callbackStatements)
151
- }
152
-
153
182
  /**
154
183
  * @param {Parameters<typeof awaitContextCallback>[0]} state
155
184
  * @param {import('estree').BlockStatement} insertionTarget