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
@@ -718,7 +718,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
718
718
  const isAtf = attemptToFixTasks.has(task)
719
719
  const isEfd = efdRetryTasks.has(task)
720
720
  const shouldTrackStatuses = isEfd || isAtf
721
- const shouldFlipStatus = isEfd || isAtf
721
+ const shouldResetStatus = isEfd || isAtf
722
722
  const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
723
723
 
724
724
  // These clauses handle task.repeats, whether EFD is enabled or not
@@ -738,13 +738,10 @@ function wrapVitestTestRunner (VitestTestRunner) {
738
738
  if (shouldTrackStatuses && statuses) {
739
739
  statuses.push(lastExecutionStatus)
740
740
  }
741
- if (shouldFlipStatus) {
742
- // If we don't "reset" the result.state to "pass", once a repetition fails,
743
- // vitest will always consider the test as failed, so we can't read the actual status
744
- // This means that we change vitest's behavior:
745
- // if the last attempt passes, vitest would consider the test as failed
746
- // but after this change, it will consider the test as passed
747
- task.result.state = 'pass'
741
+ if (shouldResetStatus) {
742
+ // Reset to Vitest's neutral state so a previous failure does not affect the next repetition.
743
+ // A terminal pass state can leak into reporters before the next repetition determines its status.
744
+ task.result.state = 'run'
748
745
  }
749
746
  }
750
747
  } else if (numRepetition === task.repeats) {
@@ -759,8 +756,8 @@ function wrapVitestTestRunner (VitestTestRunner) {
759
756
  } else {
760
757
  testPassCh.publish({ task, ...ctx.currentStore })
761
758
  }
762
- if (shouldFlipStatus) {
763
- task.result.state = 'pass'
759
+ if (shouldResetStatus) {
760
+ task.result.state = 'run'
764
761
  }
765
762
  }
766
763
 
@@ -500,6 +500,8 @@ function extractMessagesFromConverseContent (role, contentBlocks) {
500
500
  if (block == null || typeof block !== 'object') continue
501
501
  if (typeof block.text === 'string') {
502
502
  content += block.text
503
+ } else if (typeof block.guardContent?.text?.text === 'string') {
504
+ content += block.guardContent.text.text
503
505
  } else if (block.toolUse) {
504
506
  toolCalls.push(buildToolCall(block.toolUse))
505
507
  } else if (block.toolResult) {
@@ -611,7 +613,11 @@ function extractRequestParamsConverse (params) {
611
613
  const prompt = []
612
614
  if (params.system) {
613
615
  for (const block of params.system) {
614
- if (typeof block?.text === 'string') prompt.push({ content: block.text, role: 'system' })
616
+ if (typeof block?.text === 'string') {
617
+ prompt.push({ content: block.text, role: 'system' })
618
+ } else if (typeof block?.guardContent?.text?.text === 'string') {
619
+ prompt.push({ content: block.guardContent.text.text, role: 'system' })
620
+ }
615
621
  }
616
622
  }
617
623
  if (params.messages) {
@@ -26,7 +26,7 @@ class AzureCosmosPlugin extends DatabasePlugin {
26
26
  const result = ctx.result
27
27
  if (result?.code) span.setTag('db.response.status_code', (result.code).toString())
28
28
  if (result?.substatus) span.setTag('cosmosdb.response.sub_status_code', result.substatus)
29
- span.finish()
29
+ this.finish(ctx)
30
30
  }
31
31
  }
32
32
 
@@ -1,6 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const { AUTO_KEEP } = require('../../../ext/priority')
5
+ const { writeTraceparent, writeTracestate } = require('../../dd-trace/src/carrier')
6
+
7
+ const ORCHESTRATION_FAILURE_END_CHANNEL =
8
+ 'tracing:orchestrion:durable-functions:TaskOrchestrationExecutor_failure:end'
9
+ const ORCHESTRATOR_COMPLETED_EVENT_TYPE = 13
4
10
 
5
11
  class AzureDurableFunctionsPlugin extends TracingPlugin {
6
12
  static get id () { return 'azure-durable-functions' }
@@ -9,9 +15,35 @@ class AzureDurableFunctionsPlugin extends TracingPlugin {
9
15
  static get type () { return 'serverless' }
10
16
  static get kind () { return 'server' }
11
17
 
18
+ addTraceSubs () {
19
+ super.addTraceSubs()
20
+ this.addSub(ORCHESTRATION_FAILURE_END_CHANNEL, this.orchestrationFailure)
21
+ }
22
+
12
23
  bindStart (ctx) {
24
+ // Continue the trace propagated by the Durable Functions host (W3C traceparent
25
+ // supplied on the invocation's traceContext) so activity/entity invocations join
26
+ // the same trace as the HTTP root instead of each starting a new root.
27
+ let childOf
28
+ if (ctx.traceparent) {
29
+ // extract() returns null when the carrier can't be parsed. Normalize to
30
+ // undefined so startSpan still falls back to any active in-process parent
31
+ // rather than being forced to start a brand new root span.
32
+ const carrier = {}
33
+
34
+ writeTraceparent(carrier, ctx.traceparent)
35
+ if (ctx.tracestate) {
36
+ writeTracestate(carrier, ctx.tracestate)
37
+ }
38
+
39
+ childOf = this.tracer.extract('text_map', carrier) ?? undefined
40
+ }
41
+ const extractedAsDrop = childOf?._sampling.priority < AUTO_KEEP
42
+
13
43
  const span = this.startSpan(this.operationName(), {
14
- kind: 'internal',
44
+ startTime: ctx.startTime,
45
+ childOf,
46
+ kind: ctx.trigger === 'Orchestration' ? 'server' : 'internal',
15
47
  type: 'serverless',
16
48
 
17
49
  meta: {
@@ -29,6 +61,19 @@ class AzureDurableFunctionsPlugin extends TracingPlugin {
29
61
  )
30
62
  }
31
63
 
64
+ // The host clears the W3C sampled flag in traceparent while datadog tracestate
65
+ // still says keep, so extraction would drop this chunk. Re-apply only the propagated
66
+ // `s` priority when it indicates keep, preserving the extracted sampling mechanism.
67
+ if (span._prioritySampler && childOf && extractedAsDrop) {
68
+ const propagatedPriority = propagatedSamplingPriority(childOf._tracestate)
69
+ if (propagatedPriority >= AUTO_KEEP) {
70
+ const spanContext = span.context()
71
+ if (spanContext._parentId === childOf._spanId) {
72
+ spanContext._sampling.priority = propagatedPriority
73
+ }
74
+ }
75
+ }
76
+
32
77
  ctx.span = span
33
78
  return ctx.currentStore
34
79
  }
@@ -44,6 +89,56 @@ class AzureDurableFunctionsPlugin extends TracingPlugin {
44
89
  asyncStart (ctx) {
45
90
  super.finish(ctx)
46
91
  }
92
+
93
+ /**
94
+ * Tags the active initial orchestration span or creates a failure-only span after
95
+ * a resumed orchestration throws. The executor publishes synchronously before
96
+ * Durable Functions serializes the error, so this does not add a promise to
97
+ * orchestration activations.
98
+ *
99
+ * @param {{ arguments?: unknown[], error?: unknown }} executorCtx
100
+ * @returns {void}
101
+ */
102
+ orchestrationFailure (executorCtx) {
103
+ const args = executorCtx?.arguments
104
+ if (!Array.isArray(args)) return
105
+
106
+ const invocationContext = args[0]
107
+ const history = args[1]
108
+ if (!Array.isArray(history)) return
109
+
110
+ const hasPreviousActivation = history.some(
111
+ event => event?.EventType === ORCHESTRATOR_COMPLETED_EVENT_TYPE
112
+ )
113
+
114
+ if (!hasPreviousActivation) {
115
+ this.addError(executorCtx.error)
116
+ return
117
+ }
118
+
119
+ const traceContext = invocationContext?.traceContext
120
+ const ctx = {
121
+ trigger: 'Orchestration',
122
+ functionName: invocationContext?.functionName,
123
+ traceparent: traceContext?.traceParent,
124
+ tracestate: traceContext?.traceState,
125
+ error: executorCtx.error,
126
+ }
127
+
128
+ this.bindStart(ctx)
129
+ this.error(ctx)
130
+ super.finish(ctx)
131
+ }
132
+ }
133
+
134
+ // Read the datadog-propagated sampling priority (`dd=...;s:<n>`) from a W3C
135
+ // tracestate. Returns undefined when there is no datadog tracestate or no valid
136
+ // `s` value, so callers can distinguish "no propagated decision" from a drop.
137
+ function propagatedSamplingPriority (tracestate) {
138
+ return tracestate?.forVendor('dd', state => {
139
+ const priority = Number(state.get('s'))
140
+ return Number.isInteger(priority) ? priority : undefined
141
+ })
47
142
  }
48
143
 
49
144
  module.exports = AzureDurableFunctionsPlugin
@@ -2,9 +2,8 @@
2
2
 
3
3
  const { getMessageSize } = require('../../dd-trace/src/datastreams')
4
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
- const SpanContext = require('../../dd-trace/src/opentracing/span_context')
6
- const id = require('../../dd-trace/src/id')
7
5
  const { storage } = require('../../datadog-core')
6
+ const reconstructPubSubRequestContext = require('./pubsub-request-context')
8
7
 
9
8
  /**
10
9
  * PULL SUBSCRIPTION: Service explicitly pulls messages from Pub/Sub and processes them.
@@ -75,27 +74,6 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
75
74
  })
76
75
  }
77
76
 
78
- #reconstructPubSubRequestContext (attrs) {
79
- const traceIdLower = attrs['_dd.pubsub_request.trace_id']
80
- const spanId = attrs['_dd.pubsub_request.span_id']
81
- const traceIdUpper = attrs['_dd.pubsub_request.p.tid']
82
-
83
- if (!traceIdLower || !spanId) return null
84
-
85
- const traceId128 = traceIdUpper ? traceIdUpper + traceIdLower : traceIdLower.padStart(32, '0')
86
- const traceId = id(traceId128, 16)
87
- const parentId = id(spanId, 16)
88
-
89
- const tags = {}
90
- if (traceIdUpper) tags['_dd.p.tid'] = traceIdUpper
91
-
92
- return new SpanContext({
93
- traceId,
94
- spanId: parentId,
95
- tags,
96
- })
97
- }
98
-
99
77
  start (ctx) {
100
78
  if (!this.config.dsmEnabled) return
101
79
  const { message } = ctx
@@ -128,7 +106,7 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
128
106
 
129
107
  const isFirstMessage = batchIndex === 0
130
108
  if (isFirstMessage && batchRequestSpanId) {
131
- const pubsubRequestContext = this.#reconstructPubSubRequestContext(message.attributes)
109
+ const pubsubRequestContext = reconstructPubSubRequestContext(message.attributes)
132
110
  if (pubsubRequestContext) {
133
111
  childOf = pubsubRequestContext
134
112
  }
@@ -1,9 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
- const SpanContext = require('../../dd-trace/src/opentracing/span_context')
5
- const id = require('../../dd-trace/src/id')
6
4
  const log = require('../../dd-trace/src/log')
5
+ const reconstructPubSubRequestContext = require('./pubsub-request-context')
7
6
 
8
7
  // WeakMap to track push receive spans by request
9
8
  const pushReceiveSpans = new WeakMap()
@@ -69,7 +68,7 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
69
68
  }
70
69
 
71
70
  const originalContext = this.#extractContext(messageData)
72
- const pubsubRequestContext = this.#reconstructPubSubContext(messageData.attrs) || originalContext
71
+ const pubsubRequestContext = reconstructPubSubRequestContext(messageData.attrs) || originalContext
73
72
  const isSameTrace = pubsubRequestContext &&
74
73
  originalContext?.toTraceId() === pubsubRequestContext.toTraceId()
75
74
 
@@ -109,39 +108,6 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
109
108
  return this.tracer.extract('text_map', messageData.attrs)
110
109
  }
111
110
 
112
- #reconstructPubSubContext (attrs) {
113
- /**
114
- * Reconstruct the batch publish span context from message attributes.
115
- *
116
- * When a batch is published, the producer injects:
117
- * - _dd.pubsub_request.trace_id: lower 64 bits of the batch span's trace ID (hex)
118
- * - _dd.pubsub_request.span_id: the batch span's span ID (hex)
119
- * - _dd.pubsub_request.p.tid: upper 64 bits of trace ID (hex, optional for 128-bit traces)
120
- *
121
- * This context represents the "pubsub.request" span on the producer side.
122
- * We use it to create span links, connecting each pubsub.push.receive span back to the original batch.
123
- */
124
- const traceIdLower = attrs['_dd.pubsub_request.trace_id']
125
- const spanId = attrs['_dd.pubsub_request.span_id']
126
- const traceIdUpper = attrs['_dd.pubsub_request.p.tid']
127
-
128
- if (!traceIdLower || !spanId) return null
129
-
130
- // Reconstruct full 128-bit trace ID (or pad 64-bit to 128-bit)
131
- const traceId128 = traceIdUpper ? traceIdUpper + traceIdLower : traceIdLower.padStart(32, '0')
132
- const traceId = id(traceId128, 16)
133
- const parentId = id(spanId, 16)
134
-
135
- const tags = {}
136
- if (traceIdUpper) tags['_dd.p.tid'] = traceIdUpper
137
-
138
- return new SpanContext({
139
- traceId,
140
- spanId: parentId,
141
- tags,
142
- })
143
- }
144
-
145
111
  #createPushReceiveSpan (messageData, parentContext, linkContext) {
146
112
  const { message, subscription, topicName, attrs } = messageData
147
113
  const subscriptionName = subscription?.slice(subscription.lastIndexOf('/') + 1) ?? subscription
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const SpanContext = require('../../dd-trace/src/opentracing/span_context')
4
+ const id = require('../../dd-trace/src/id')
5
+
6
+ /**
7
+ * @param {Record<string, string>} attributes
8
+ * @returns {import('../../dd-trace/src/opentracing/span_context') | null}
9
+ */
10
+ function reconstructPubSubRequestContext (attributes) {
11
+ const traceIdLower = attributes['_dd.pubsub_request.trace_id']
12
+ const spanId = attributes['_dd.pubsub_request.span_id']
13
+ const traceIdUpper = attributes['_dd.pubsub_request.p.tid']
14
+
15
+ if (!traceIdLower || !spanId) return null
16
+
17
+ const traceId128 = traceIdUpper ? traceIdUpper + traceIdLower : traceIdLower.padStart(32, '0')
18
+ const traceId = id(traceId128, 16)
19
+ const parentId = id(spanId, 16)
20
+
21
+ const tags = {}
22
+ if (traceIdUpper) tags['_dd.p.tid'] = traceIdUpper
23
+
24
+ return new SpanContext({
25
+ traceId,
26
+ spanId: parentId,
27
+ tags,
28
+ })
29
+ }
30
+
31
+ module.exports = reconstructPubSubRequestContext
@@ -61,28 +61,50 @@ class GraphQLToolsResolveErrorPlugin extends TracingPlugin {
61
61
  * @param {readonly object[]} [fieldNodes]
62
62
  */
63
63
  function recordResolveErrorForPath (error, path, fieldNodes) {
64
- const fieldNode = fieldNodes?.[0]
65
64
  const rootCtx = legacyStorage.getStore()?.graphqlRootCtx
66
- for (let field = rootCtx?.resolveFields; field; field = field.nextResolveField) {
67
- if ((!fieldNode || field.fieldNode === fieldNode) && matchesPath(field.pathString, path)) {
68
- recordResolveError(field, error)
69
- return
70
- }
71
- }
65
+ if (!rootCtx?.config.collapse || !rootCtx.fields) return
66
+
67
+ const field = findResolveField(rootCtx.fields, getPathString(path), fieldNodes?.[0])
68
+ if (field) recordResolveError(field, error)
72
69
  }
73
70
 
74
71
  /**
75
- * @param {string} fieldPath
76
72
  * @param {(string | number)[]} path
77
- * @returns {boolean}
73
+ * @returns {string}
78
74
  */
79
- function matchesPath (fieldPath, path) {
75
+ function getPathString (path) {
80
76
  let pathString = ''
81
77
  for (let index = 0; index < path.length; index++) {
82
78
  if (index !== 0) pathString += '.'
83
79
  pathString += typeof path[index] === 'number' ? '*' : path[index]
84
80
  }
85
- return pathString === fieldPath
81
+ return pathString
82
+ }
83
+
84
+ /**
85
+ * @param {Map<unknown, object>} fields
86
+ * @param {string} pathString
87
+ * @param {object} [fieldNode]
88
+ * @returns {object | undefined}
89
+ */
90
+ function findResolveField (fields, pathString, fieldNode) {
91
+ const field = fields.get(pathString)
92
+ if (field === undefined) return
93
+
94
+ const parentTypeFields = field.parentTypeFields
95
+ if (parentTypeFields === undefined) {
96
+ return fieldNode === undefined || field.fieldNode === fieldNode ? field : undefined
97
+ }
98
+ if (parentTypeFields.parentTypeName !== undefined) {
99
+ if (fieldNode === undefined || parentTypeFields.fieldNode === fieldNode) return parentTypeFields
100
+ return field.fieldNode === fieldNode ? field : undefined
101
+ }
102
+
103
+ let matchingField
104
+ for (const candidate of parentTypeFields.values()) {
105
+ if (fieldNode === undefined || candidate.fieldNode === fieldNode) matchingField = candidate
106
+ }
107
+ return matchingField
86
108
  }
87
109
 
88
110
  /**
@@ -0,0 +1,212 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const Module = require('node:module')
5
+ const path = require('node:path')
6
+
7
+ const satisfies = require('../../vendor/dist/semifies')
8
+ const hooks = require('../datadog-instrumentations/src/helpers/hooks')
9
+ const { SYNTHETIC_EXTENSION } = require('./src/constants')
10
+
11
+ const BUILTIN_MODULES = new Set(Module.builtinModules)
12
+ const loader = path.join(__dirname, 'src/loader.js')
13
+ const EXTENSIONLESS_PATH_PATTERN = /(?:^|\/)[^/.]+$/
14
+ const PHASE_PRODUCTION_SERVER = 'phase-production-server'
15
+ const SOURCE_EXTENSIONS = ['*.js', '*.cjs', '*.mjs', '*.jsx', '*.ts', '*.cts', '*.mts', '*.tsx']
16
+ const SOURCE_PATH_PATTERN = /\.(?:cjs|cts|js|jsx|mjs|mts|ts|tsx)$/
17
+ const PACKAGE_PATH_PATTERN = createPackagePathPattern()
18
+
19
+ /**
20
+ * Adds Datadog instrumentation to a Next.js Turbopack configuration.
21
+ *
22
+ * @param {object|Promise<object>|Function} [nextConfig]
23
+ * @returns {Function}
24
+ */
25
+ function withDatadogTurbopack (nextConfig = {}) {
26
+ const nextMajor = getNextMajor()
27
+
28
+ return function datadogNextConfig (...args) {
29
+ const config = typeof nextConfig === 'function'
30
+ ? nextConfig.apply(this, args)
31
+ : nextConfig
32
+ if (config && typeof config.then === 'function') {
33
+ return config.then(config => configureTurbopack(config, args[0], nextMajor))
34
+ }
35
+ return configureTurbopack(config, args[0], nextMajor)
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @param {object|undefined} config
41
+ * @param {unknown} phase
42
+ * @param {number} nextMajor
43
+ * @returns {object}
44
+ */
45
+ function configureTurbopack (config, phase, nextMajor) {
46
+ const normalized = normalizeConfig(config)
47
+ if (phase === PHASE_PRODUCTION_SERVER || hasDatadogLoader(normalized.turbopack?.rules)) return normalized
48
+
49
+ const turbopack = normalized.turbopack ?? {}
50
+ const configured = nextMajor === 15 ? addLegacyRule(turbopack) : addModernRules(turbopack)
51
+ return { ...normalized, turbopack: configured }
52
+ }
53
+
54
+ /**
55
+ * @param {object|undefined} config
56
+ * @returns {object}
57
+ */
58
+ function normalizeConfig (config) {
59
+ if (config === undefined) return {}
60
+ if (!isObject(config)) {
61
+ throw new TypeError('withDatadogTurbopack expects a Next.js configuration object, promise, or function')
62
+ }
63
+ if (config.turbopack !== undefined && !isObject(config.turbopack)) {
64
+ throw new TypeError('nextConfig.turbopack must be an object')
65
+ }
66
+ if (config.turbopack?.rules !== undefined && !isObject(config.turbopack.rules)) {
67
+ throw new TypeError('nextConfig.turbopack.rules must be an object')
68
+ }
69
+ if (config.turbopack?.conditions !== undefined && !isObject(config.turbopack.conditions)) {
70
+ throw new TypeError('nextConfig.turbopack.conditions must be an object')
71
+ }
72
+ return config
73
+ }
74
+
75
+ /**
76
+ * @param {unknown} value
77
+ * @returns {boolean}
78
+ */
79
+ function isObject (value) {
80
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
81
+ }
82
+
83
+ /**
84
+ * @param {object} turbopack
85
+ * @returns {object}
86
+ */
87
+ function addModernRules (turbopack) {
88
+ const rules = { ...turbopack.rules }
89
+ for (const extension of SOURCE_EXTENSIONS) {
90
+ appendRule(rules, extension, {
91
+ condition: { all: ['node', 'foreign', { path: PACKAGE_PATH_PATTERN }] },
92
+ loaders: [createLoader()],
93
+ })
94
+ }
95
+ appendRule(rules, '*', {
96
+ as: `*${SYNTHETIC_EXTENSION}`,
97
+ condition: {
98
+ all: ['node', 'foreign', { path: PACKAGE_PATH_PATTERN }, { path: EXTENSIONLESS_PATH_PATTERN }],
99
+ },
100
+ loaders: [createLoader()],
101
+ })
102
+ return { ...turbopack, rules }
103
+ }
104
+
105
+ /**
106
+ * @param {Record<string, object|object[]>} rules
107
+ * @param {string} extension
108
+ * @param {object} rule
109
+ */
110
+ function appendRule (rules, extension, rule) {
111
+ const existing = rules[extension]
112
+ if (existing === undefined) {
113
+ rules[extension] = rule
114
+ } else {
115
+ rules[extension] = Array.isArray(existing) ? [...existing, rule] : [existing, rule]
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param {object} turbopack
121
+ * @returns {object}
122
+ */
123
+ function addLegacyRule (turbopack) {
124
+ const name = '#dd-trace/modules'
125
+ const conditions = { ...turbopack.conditions }
126
+ const rules = { ...turbopack.rules }
127
+ if (Object.hasOwn(conditions, name) || Object.hasOwn(rules, name)) {
128
+ throw new Error(`Next.js Turbopack configuration already uses the reserved condition ${name}`)
129
+ }
130
+ conditions[name] = {
131
+ all: [
132
+ { path: PACKAGE_PATH_PATTERN },
133
+ { any: [{ path: SOURCE_PATH_PATTERN }, { path: EXTENSIONLESS_PATH_PATTERN }] },
134
+ ],
135
+ }
136
+ rules[name] = { node: { foreign: { loaders: [createLoader()] } } }
137
+ return { ...turbopack, conditions, rules }
138
+ }
139
+
140
+ /**
141
+ * @returns {{ loader: string, options: object }}
142
+ */
143
+ function createLoader () {
144
+ return { loader, options: {} }
145
+ }
146
+
147
+ /**
148
+ * @param {unknown} value
149
+ * @returns {boolean}
150
+ */
151
+ function hasDatadogLoader (value) {
152
+ if (Array.isArray(value)) return value.some(hasDatadogLoader)
153
+ if (!value || typeof value !== 'object') return false
154
+ if (/** @type {{ loader?: unknown }} */ (value).loader === loader) return true
155
+ return Object.values(value).some(hasDatadogLoader)
156
+ }
157
+
158
+ /**
159
+ * @returns {RegExp}
160
+ */
161
+ function createPackagePathPattern () {
162
+ const names = []
163
+ for (const name of Object.keys(hooks)) {
164
+ if (!name.startsWith('.') && !BUILTIN_MODULES.has(name)) names.push(escapeRegExp(name))
165
+ }
166
+ names.sort()
167
+ return new RegExp(`(?:^|/)(?:${names.join('|')})(?:/|$)`)
168
+ }
169
+
170
+ /**
171
+ * @param {string} value
172
+ * @returns {string}
173
+ */
174
+ function escapeRegExp (value) {
175
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)
176
+ }
177
+
178
+ /**
179
+ * @returns {number}
180
+ */
181
+ function getNextMajor () {
182
+ const entrypoints = []
183
+ if (require.main?.filename) entrypoints.push(require.main.filename)
184
+ entrypoints.push(path.join(process.cwd(), 'package.json'))
185
+
186
+ let version
187
+ let resolutionError
188
+ for (const entrypoint of entrypoints) {
189
+ try {
190
+ const appRequire = Module.createRequire(entrypoint)
191
+ version = JSON.parse(fs.readFileSync(appRequire.resolve('next/package.json'), 'utf8')).version
192
+ break
193
+ } catch (error) {
194
+ resolutionError = error
195
+ }
196
+ }
197
+ if (!version) {
198
+ throw new Error('withDatadogTurbopack could not resolve the active Next.js installation', {
199
+ cause: resolutionError,
200
+ })
201
+ }
202
+
203
+ if (!satisfies(version, '>=0')) {
204
+ throw new Error(`withDatadogTurbopack could not parse Next.js version ${version}`)
205
+ }
206
+ if (!satisfies(version, '>=15.5.0')) {
207
+ throw new RangeError(`withDatadogTurbopack requires Next.js 15.5 or newer; found ${version}`)
208
+ }
209
+ return Number.parseInt(version, 10)
210
+ }
211
+
212
+ module.exports = { withDatadogTurbopack }
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const SYNTHETIC_EXTENSION = '.__dd_trace_turbopack.js'
4
+
5
+ module.exports = { SYNTHETIC_EXTENSION }