dd-trace 5.103.0 → 5.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +90 -102
- package/index.d.ts +107 -6
- package/package.json +18 -17
- package/packages/datadog-core/src/storage.js +1 -1
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/ai.js +8 -7
- package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
- package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
- package/packages/datadog-instrumentations/src/cucumber.js +181 -35
- package/packages/datadog-instrumentations/src/dns.js +54 -18
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/fastify.js +142 -82
- package/packages/datadog-instrumentations/src/graphql.js +188 -67
- package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
- package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
- package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
- package/packages/datadog-instrumentations/src/hono.js +54 -3
- package/packages/datadog-instrumentations/src/http/client.js +2 -2
- package/packages/datadog-instrumentations/src/http/server.js +9 -4
- package/packages/datadog-instrumentations/src/ioredis.js +3 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
- package/packages/datadog-instrumentations/src/jest.js +390 -183
- package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/memcached.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
- package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
- package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
- package/packages/datadog-instrumentations/src/mongoose.js +10 -12
- package/packages/datadog-instrumentations/src/mysql.js +2 -2
- package/packages/datadog-instrumentations/src/mysql2.js +1 -1
- package/packages/datadog-instrumentations/src/nats.js +182 -0
- package/packages/datadog-instrumentations/src/nyc.js +38 -1
- package/packages/datadog-instrumentations/src/openai.js +33 -18
- package/packages/datadog-instrumentations/src/oracledb.js +6 -1
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/pino.js +17 -5
- package/packages/datadog-instrumentations/src/playwright.js +537 -297
- package/packages/datadog-instrumentations/src/router.js +80 -34
- package/packages/datadog-instrumentations/src/stripe.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +246 -149
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
- package/packages/datadog-plugin-bunyan/src/index.js +28 -0
- package/packages/datadog-plugin-cucumber/src/index.js +17 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
- package/packages/datadog-plugin-cypress/src/support.js +69 -1
- package/packages/datadog-plugin-dns/src/lookup.js +8 -6
- package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +2 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
- package/packages/datadog-plugin-graphql/src/utils.js +4 -1
- package/packages/datadog-plugin-http/src/server.js +40 -15
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-jest/src/util.js +15 -8
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
- package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +19 -4
- package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
- package/packages/datadog-plugin-nats/src/consumer.js +43 -0
- package/packages/datadog-plugin-nats/src/index.js +20 -0
- package/packages/datadog-plugin-nats/src/producer.js +62 -0
- package/packages/datadog-plugin-nats/src/util.js +33 -0
- package/packages/datadog-plugin-next/src/index.js +5 -3
- package/packages/datadog-plugin-openai/src/tracing.js +15 -2
- package/packages/datadog-plugin-oracledb/src/index.js +13 -2
- package/packages/datadog-plugin-pino/src/index.js +42 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -4
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-redis/src/index.js +37 -2
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +33 -44
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +19 -0
- package/packages/datadog-plugin-vitest/src/index.js +24 -20
- package/packages/datadog-plugin-winston/src/index.js +30 -0
- package/packages/datadog-shimmer/src/shimmer.js +49 -21
- package/packages/dd-trace/src/aiguard/index.js +1 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/blocking.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +11 -4
- package/packages/dd-trace/src/appsec/reporter.js +24 -11
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
- package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
- package/packages/dd-trace/src/baggage.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
- package/packages/dd-trace/src/config/supported-configurations.json +36 -8
- package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
- package/packages/dd-trace/src/datastreams/context.js +4 -2
- package/packages/dd-trace/src/datastreams/writer.js +2 -4
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +124 -108
- package/packages/dd-trace/src/encode/0.5.js +114 -26
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
- package/packages/dd-trace/src/encode/agentless-json.js +4 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
- package/packages/dd-trace/src/encode/span-stats.js +16 -16
- package/packages/dd-trace/src/encode/tags-processors.js +16 -0
- package/packages/dd-trace/src/exporters/common/agents.js +3 -1
- package/packages/dd-trace/src/exporters/common/request.js +3 -1
- package/packages/dd-trace/src/id.js +17 -4
- package/packages/dd-trace/src/lambda/handler.js +2 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +10 -16
- package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
- package/packages/dd-trace/src/llmobs/tagger.js +9 -1
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/util.js +66 -3
- package/packages/dd-trace/src/log/index.js +1 -1
- package/packages/dd-trace/src/log/writer.js +3 -1
- package/packages/dd-trace/src/msgpack/chunk.js +394 -10
- package/packages/dd-trace/src/msgpack/index.js +96 -2
- package/packages/dd-trace/src/noop/span.js +3 -1
- package/packages/dd-trace/src/openfeature/encoding.js +70 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
- package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
- package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
- package/packages/dd-trace/src/opentracing/span.js +59 -19
- package/packages/dd-trace/src/opentracing/span_context.js +49 -0
- package/packages/dd-trace/src/plugins/apollo.js +3 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
- package/packages/dd-trace/src/plugins/database.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +4 -0
- package/packages/dd-trace/src/plugins/log_injection.js +56 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
- package/packages/dd-trace/src/plugins/outbound.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +15 -17
- package/packages/dd-trace/src/plugins/tracing.js +48 -8
- package/packages/dd-trace/src/plugins/util/git.js +3 -1
- package/packages/dd-trace/src/plugins/util/test.js +318 -13
- package/packages/dd-trace/src/plugins/util/web.js +89 -64
- package/packages/dd-trace/src/priority_sampler.js +2 -2
- package/packages/dd-trace/src/profiling/profiler.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
- package/packages/dd-trace/src/sampling_rule.js +7 -7
- package/packages/dd-trace/src/scope.js +7 -5
- package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
- package/packages/dd-trace/src/span_format.js +190 -58
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/standalone/index.js +3 -3
- package/packages/dd-trace/src/tagger.js +0 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
- package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/msgpack/encoder.js +0 -308
- package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
- package/vendor/dist/opentracing/LICENSE +0 -201
- package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
- package/vendor/dist/opentracing/constants.d.ts +0 -61
- package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
- package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
- package/vendor/dist/opentracing/functions.d.ts +0 -20
- package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
- package/vendor/dist/opentracing/index.d.ts +0 -12
- package/vendor/dist/opentracing/index.js +0 -1
- package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
- package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
- package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
- package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
- package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
- package/vendor/dist/opentracing/noop.d.ts +0 -8
- package/vendor/dist/opentracing/reference.d.ts +0 -33
- package/vendor/dist/opentracing/span.d.ts +0 -147
- package/vendor/dist/opentracing/span_context.d.ts +0 -26
- package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
- package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
- package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
- package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
- package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
- package/vendor/dist/opentracing/tracer.d.ts +0 -127
|
@@ -54,65 +54,119 @@ function wrapAddHook (addHook) {
|
|
|
54
54
|
|
|
55
55
|
if (typeof fn !== 'function') return addHook.apply(this, arguments)
|
|
56
56
|
|
|
57
|
-
arguments[arguments.length - 1] = shimmer.wrapFunction(fn, fn => function (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (cookieParserReadCh.hasSubscribers && hasCookies && !cookiesPublished.has(req)) {
|
|
73
|
-
ctx.res = getRes(reply)
|
|
74
|
-
ctx.abortController = new AbortController()
|
|
75
|
-
ctx.cookies = request.cookies
|
|
76
|
-
|
|
77
|
-
cookieParserReadCh.publish(ctx)
|
|
78
|
-
cookiesPublished.add(req)
|
|
79
|
-
|
|
80
|
-
if (ctx.abortController.signal.aborted) return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (name === 'onRequest' || name === 'preParsing') {
|
|
84
|
-
parsingContexts.set(req, ctx)
|
|
85
|
-
|
|
86
|
-
return callbackFinishCh.runStores(ctx, () => {
|
|
87
|
-
return doneCallback.apply(this, arguments)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
return doneCallback.apply(this, arguments)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return fn.apply(this, arguments)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const promise = fn.apply(this, arguments)
|
|
97
|
-
|
|
98
|
-
if (promise && typeof promise.catch === 'function') {
|
|
99
|
-
return promise.catch(err => {
|
|
100
|
-
ctx.error = err
|
|
101
|
-
return publishError(ctx)
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return promise
|
|
106
|
-
} catch (e) {
|
|
107
|
-
ctx.error = e
|
|
108
|
-
throw publishError(ctx)
|
|
57
|
+
arguments[arguments.length - 1] = shimmer.wrapFunction(fn, fn => function wrappedHook () {
|
|
58
|
+
// Fast path: every fastify request invokes each addHook'd handler, so the wrap
|
|
59
|
+
// runs in the user's hot path. The only side effects this wrapper carries are
|
|
60
|
+
// the three channels below; when none of them have a subscriber (the default
|
|
61
|
+
// plugin config, and the steady state once appsec / cookie subscribers detach),
|
|
62
|
+
// the wrap has nothing to do, and a `fn.apply(this, arguments)` forward keeps
|
|
63
|
+
// V8's CallApplyArguments fast path intact.
|
|
64
|
+
//
|
|
65
|
+
// The previous shape mutated `arguments[arguments.length - 1]` to swap `done`.
|
|
66
|
+
// That mutation materialises the magical arguments object and disables V8
|
|
67
|
+
// inlining of the enclosing function. The slow path below builds a fresh args
|
|
68
|
+
// array instead so the hot fast path keeps a clean forward.
|
|
69
|
+
if (errorChannel.hasSubscribers || cookieParserReadCh.hasSubscribers || callbackFinishCh.hasSubscribers) {
|
|
70
|
+
return invokeHookWithContext(name, fn, this, arguments)
|
|
109
71
|
}
|
|
72
|
+
return fn.apply(this, arguments)
|
|
110
73
|
})
|
|
111
74
|
|
|
112
75
|
return addHook.apply(this, arguments)
|
|
113
76
|
})
|
|
114
77
|
}
|
|
115
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Slow path of {@link wrapAddHook}; entered only when at least one wrap-fed
|
|
81
|
+
* channel has a subscriber. Allocates the per-request context, rewraps `done`,
|
|
82
|
+
* and forwards to the user-supplied hook.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} name Lifecycle phase the hook was registered against.
|
|
85
|
+
* @param {Function} fn User-supplied hook.
|
|
86
|
+
* @param {unknown} thisArg `this` Fastify passes to the hook.
|
|
87
|
+
* @param {ArrayLike<unknown>} args Fastify's positional args; the dispatcher always
|
|
88
|
+
* places `done` as the trailing positional (see fastify/lib/hooks.js hookIterator,
|
|
89
|
+
* onSendHookRunner, preParsingHookRunner, onRequestAbortHookRunner).
|
|
90
|
+
*/
|
|
91
|
+
function invokeHookWithContext (name, fn, thisArg, args) {
|
|
92
|
+
const request = args[0]
|
|
93
|
+
const reply = args[1]
|
|
94
|
+
const req = getReq(request)
|
|
95
|
+
const ctx = { req }
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const lastArg = args[args.length - 1]
|
|
99
|
+
|
|
100
|
+
if (typeof lastArg === 'function') {
|
|
101
|
+
// Copy the args so we can swap the trailing `done` without touching the
|
|
102
|
+
// caller's magical arguments object. Fastify hook arities are 2 to 4
|
|
103
|
+
// across lifecycle phases, but `done` is always last.
|
|
104
|
+
const callArgs = [...args]
|
|
105
|
+
callArgs[callArgs.length - 1] = wrapHookDone(ctx, request, reply, req, name, lastArg)
|
|
106
|
+
return fn.apply(thisArg, callArgs)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const promise = fn.apply(thisArg, args)
|
|
110
|
+
|
|
111
|
+
if (promise && typeof promise.catch === 'function') {
|
|
112
|
+
return promise.catch(error => {
|
|
113
|
+
ctx.error = error
|
|
114
|
+
return publishError(ctx)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return promise
|
|
119
|
+
} catch (error) {
|
|
120
|
+
ctx.error = error
|
|
121
|
+
throw publishError(ctx)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Per-request closure invoked when fastify resolves the user hook's `done`.
|
|
127
|
+
* Captures `ctx` plus the dispatcher-level fields needed to publish on the
|
|
128
|
+
* cookie / callback channels. The closure cannot be hoisted: fastify invokes
|
|
129
|
+
* `done` with a single `(err)` arg, so request / reply / req / name / doneCallback
|
|
130
|
+
* must close over rather than ride the call signature.
|
|
131
|
+
*
|
|
132
|
+
* @param {{ req: unknown, [key: string]: unknown }} ctx
|
|
133
|
+
* @param {{ cookies?: Record<string, unknown>, [key: string]: unknown }} request
|
|
134
|
+
* @param {object} reply
|
|
135
|
+
* @param {unknown} req
|
|
136
|
+
* @param {string} name
|
|
137
|
+
* @param {Function} doneCallback
|
|
138
|
+
*/
|
|
139
|
+
function wrapHookDone (ctx, request, reply, req, name, doneCallback) {
|
|
140
|
+
return function wrappedDone (error) {
|
|
141
|
+
ctx.error = error
|
|
142
|
+
publishError(ctx)
|
|
143
|
+
|
|
144
|
+
const hasCookies = request.cookies && Object.keys(request.cookies).length > 0
|
|
145
|
+
|
|
146
|
+
if (cookieParserReadCh.hasSubscribers && hasCookies && !cookiesPublished.has(req)) {
|
|
147
|
+
ctx.res = getRes(reply)
|
|
148
|
+
ctx.abortController = new AbortController()
|
|
149
|
+
ctx.cookies = request.cookies
|
|
150
|
+
|
|
151
|
+
cookieParserReadCh.publish(ctx)
|
|
152
|
+
cookiesPublished.add(req)
|
|
153
|
+
|
|
154
|
+
if (ctx.abortController.signal.aborted) return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (name === 'onRequest' || name === 'preParsing') {
|
|
158
|
+
parsingContexts.set(req, ctx)
|
|
159
|
+
|
|
160
|
+
if (callbackFinishCh.hasSubscribers) {
|
|
161
|
+
const self = this
|
|
162
|
+
const allArgs = arguments
|
|
163
|
+
return callbackFinishCh.runStores(ctx, () => doneCallback.apply(self, allArgs))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return doneCallback.apply(this, arguments)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
116
170
|
function onRequest (request, reply, done) {
|
|
117
171
|
if (typeof done !== 'function') return
|
|
118
172
|
|
|
@@ -157,45 +211,51 @@ function preValidation (request, reply, done) {
|
|
|
157
211
|
const ctx = parsingContexts.get(req)
|
|
158
212
|
ctx.res = res
|
|
159
213
|
|
|
160
|
-
|
|
161
|
-
let abortController
|
|
214
|
+
if (!ctx) return processInContext(request, ctx, done, req)
|
|
162
215
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
ctx.abortController = abortController
|
|
166
|
-
ctx.query = request.query
|
|
167
|
-
queryParamsReadCh.publish(ctx)
|
|
168
|
-
|
|
169
|
-
if (abortController.signal.aborted) return
|
|
170
|
-
}
|
|
216
|
+
preValidationCh.runStores(ctx, processInContext, undefined, request, ctx, done, req)
|
|
217
|
+
}
|
|
171
218
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
219
|
+
/**
|
|
220
|
+
* @param {{ query?: object, body?: object, params?: object, [key: string]: unknown }} request
|
|
221
|
+
* @param {{ res?: object, abortController?: AbortController, [key: string]: unknown }} ctx
|
|
222
|
+
* @param {Function} done
|
|
223
|
+
* @param {unknown} req
|
|
224
|
+
*/
|
|
225
|
+
function processInContext (request, ctx, done, req) {
|
|
226
|
+
let abortController
|
|
227
|
+
|
|
228
|
+
if (queryParamsReadCh.hasSubscribers && request.query) {
|
|
229
|
+
abortController ??= new AbortController()
|
|
230
|
+
ctx.abortController = abortController
|
|
231
|
+
ctx.query = request.query
|
|
232
|
+
queryParamsReadCh.publish(ctx)
|
|
233
|
+
|
|
234
|
+
if (abortController.signal.aborted) return
|
|
235
|
+
}
|
|
178
236
|
|
|
179
|
-
|
|
237
|
+
// Analyze body before schema validation
|
|
238
|
+
if (bodyParserReadCh.hasSubscribers && request.body && !bodyPublished.has(req)) {
|
|
239
|
+
abortController ??= new AbortController()
|
|
240
|
+
ctx.abortController = abortController
|
|
241
|
+
ctx.body = request.body
|
|
242
|
+
bodyParserReadCh.publish(ctx)
|
|
180
243
|
|
|
181
|
-
|
|
182
|
-
}
|
|
244
|
+
bodyPublished.add(req)
|
|
183
245
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
ctx.abortController = abortController
|
|
187
|
-
ctx.params = request.params
|
|
188
|
-
pathParamsReadCh.publish(ctx)
|
|
246
|
+
if (abortController.signal.aborted) return
|
|
247
|
+
}
|
|
189
248
|
|
|
190
|
-
|
|
191
|
-
|
|
249
|
+
if (pathParamsReadCh.hasSubscribers && request.params) {
|
|
250
|
+
abortController ??= new AbortController()
|
|
251
|
+
ctx.abortController = abortController
|
|
252
|
+
ctx.params = request.params
|
|
253
|
+
pathParamsReadCh.publish(ctx)
|
|
192
254
|
|
|
193
|
-
|
|
255
|
+
if (abortController.signal.aborted) return
|
|
194
256
|
}
|
|
195
257
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
preValidationCh.runStores(ctx, processInContext)
|
|
258
|
+
done()
|
|
199
259
|
}
|
|
200
260
|
|
|
201
261
|
function preParsing (request, reply, payload, done) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AsyncLocalStorage } = require('node:async_hooks')
|
|
4
|
+
|
|
3
5
|
const shimmer = require('../../datadog-shimmer')
|
|
4
6
|
const {
|
|
5
7
|
addHook,
|
|
@@ -10,7 +12,13 @@ const ddGlobal = globalThis[Symbol.for('dd-trace')]
|
|
|
10
12
|
|
|
11
13
|
/** cached objects */
|
|
12
14
|
|
|
15
|
+
// `contexts` is the fast resolver-side lookup; `executeCtx` is the fallback
|
|
16
|
+
// when `contextValue` is a primitive and cannot key a WeakMap.
|
|
13
17
|
const contexts = new WeakMap()
|
|
18
|
+
const executeCtx = new AsyncLocalStorage()
|
|
19
|
+
// Tracks normalized args already instrumented in an outer wrap so graphql-yoga
|
|
20
|
+
// (which stacks `execute` + `normalizedExecutor`) only emits one span per call.
|
|
21
|
+
const instrumentedArgs = new WeakSet()
|
|
14
22
|
const documentSources = new WeakMap()
|
|
15
23
|
const patchedResolvers = new WeakSet()
|
|
16
24
|
const patchedTypes = new WeakSet()
|
|
@@ -62,14 +70,17 @@ function getOperation (document, operationName) {
|
|
|
62
70
|
function normalizeArgs (args, defaultFieldResolver) {
|
|
63
71
|
if (args.length !== 1) return normalizePositional(args, defaultFieldResolver)
|
|
64
72
|
|
|
65
|
-
args[0]
|
|
66
|
-
|
|
73
|
+
const original = args[0]
|
|
74
|
+
const normalized = {
|
|
75
|
+
...original,
|
|
76
|
+
fieldResolver: wrapResolve(original.fieldResolver || defaultFieldResolver),
|
|
77
|
+
}
|
|
67
78
|
|
|
68
|
-
|
|
79
|
+
args[0] = normalized
|
|
80
|
+
return normalized
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function normalizePositional (args, defaultFieldResolver) {
|
|
72
|
-
args[3] = args[3] || {} // contextValue
|
|
73
84
|
args[6] = wrapResolve(args[6] || defaultFieldResolver) // fieldResolver
|
|
74
85
|
args.length = Math.max(args.length, 7)
|
|
75
86
|
|
|
@@ -84,6 +95,12 @@ function normalizePositional (args, defaultFieldResolver) {
|
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
|
|
98
|
+
// `WeakMap.set` throws `TypeError` on a non-object key; `get`/`has`/`delete`
|
|
99
|
+
// silently miss. Skip the WeakMap entirely for non-keyable `contextValue`.
|
|
100
|
+
function isWeakMapKey (value) {
|
|
101
|
+
return value !== null && typeof value === 'object'
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
function wrapParse (parse) {
|
|
88
105
|
return function (source) {
|
|
89
106
|
if (!parseStartCh.hasSubscribers) {
|
|
@@ -155,14 +172,21 @@ function wrapExecute (execute) {
|
|
|
155
172
|
return exe.apply(this, arguments)
|
|
156
173
|
}
|
|
157
174
|
|
|
175
|
+
// The outer wrap leaves its normalized args object in `arguments[0]`; on
|
|
176
|
+
// graphql-yoga's inner wrap that reference is already known here.
|
|
177
|
+
if (instrumentedArgs.has(arguments[0])) {
|
|
178
|
+
return exe.apply(this, arguments)
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
const args = normalizeArgs(arguments, defaultFieldResolver)
|
|
159
182
|
const schema = args.schema
|
|
160
183
|
const document = args.document
|
|
161
184
|
const source = documentSources.get(document)
|
|
162
185
|
const contextValue = args.contextValue
|
|
186
|
+
const keyable = isWeakMapKey(contextValue)
|
|
163
187
|
const operation = getOperation(document, args.operationName)
|
|
164
188
|
|
|
165
|
-
if (contexts.has(contextValue)) {
|
|
189
|
+
if (keyable && contexts.has(contextValue)) {
|
|
166
190
|
return exe.apply(this, arguments)
|
|
167
191
|
}
|
|
168
192
|
|
|
@@ -171,19 +195,23 @@ function wrapExecute (execute) {
|
|
|
171
195
|
args,
|
|
172
196
|
docSource: source,
|
|
173
197
|
source,
|
|
174
|
-
fields:
|
|
198
|
+
fields: new Map(),
|
|
175
199
|
abortController: new AbortController(),
|
|
176
200
|
}
|
|
177
201
|
|
|
202
|
+
// Only the object form leaves a stable single-object handle in
|
|
203
|
+
// `arguments[0]` for the inner wrap to see.
|
|
204
|
+
if (args === arguments[0]) instrumentedArgs.add(args)
|
|
205
|
+
|
|
178
206
|
return startExecuteCh.runStores(ctx, () => {
|
|
179
207
|
if (schema) {
|
|
180
208
|
wrapFields(schema._queryType)
|
|
181
209
|
wrapFields(schema._mutationType)
|
|
182
210
|
}
|
|
183
211
|
|
|
184
|
-
contexts.set(contextValue, ctx)
|
|
212
|
+
if (keyable) contexts.set(contextValue, ctx)
|
|
185
213
|
|
|
186
|
-
|
|
214
|
+
const finish = (err, res) => {
|
|
187
215
|
if (finishResolveCh.hasSubscribers) finishResolvers(ctx)
|
|
188
216
|
|
|
189
217
|
const error = err || (res && res.errors && res.errors[0])
|
|
@@ -194,8 +222,16 @@ function wrapExecute (execute) {
|
|
|
194
222
|
}
|
|
195
223
|
|
|
196
224
|
ctx.res = res
|
|
225
|
+
if (keyable) contexts.delete(contextValue)
|
|
226
|
+
instrumentedArgs.delete(args)
|
|
197
227
|
finishExecuteCh.publish(ctx)
|
|
198
|
-
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Skip the ALS entry on the common object-`contextValue` path; the
|
|
231
|
+
// resolver reaches `ctx` via the WeakMap there.
|
|
232
|
+
return keyable
|
|
233
|
+
? callInAsyncScope(exe, this, arguments, ctx.abortController, finish)
|
|
234
|
+
: executeCtx.run(ctx, () => callInAsyncScope(exe, this, arguments, ctx.abortController, finish))
|
|
199
235
|
})
|
|
200
236
|
}
|
|
201
237
|
}
|
|
@@ -207,18 +243,40 @@ function wrapResolve (resolve) {
|
|
|
207
243
|
function resolveAsync (source, args, contextValue, info) {
|
|
208
244
|
if (!startResolveCh.hasSubscribers) return resolve.apply(this, arguments)
|
|
209
245
|
|
|
210
|
-
|
|
246
|
+
// `WeakMap.get(primitive)` returns `undefined`, so the fallback covers
|
|
247
|
+
// executes that ran with a primitive `contextValue`.
|
|
248
|
+
const ctx = contexts.get(contextValue) ?? executeCtx.getStore()
|
|
211
249
|
|
|
250
|
+
/* istanbul ignore if: resolver invoked outside execute(), so no per-execute ctx was registered */
|
|
212
251
|
if (!ctx) return resolve.apply(this, arguments)
|
|
213
252
|
|
|
214
253
|
const field = assertField(ctx, info, args)
|
|
215
254
|
|
|
216
|
-
|
|
217
|
-
field
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
255
|
+
if (ctx.abortController.signal.aborted) {
|
|
256
|
+
publishResolverFinish(field, null)
|
|
257
|
+
throw new AbortError('Aborted')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = resolve.call(this, source, args, contextValue, info)
|
|
262
|
+
if (result !== null && typeof result?.then === 'function') {
|
|
263
|
+
return result.then(
|
|
264
|
+
res => {
|
|
265
|
+
publishResolverFinish(field, null)
|
|
266
|
+
return res
|
|
267
|
+
},
|
|
268
|
+
error => {
|
|
269
|
+
publishResolverFinish(field, error)
|
|
270
|
+
throw error
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
publishResolverFinish(field, null)
|
|
275
|
+
return result
|
|
276
|
+
} catch (error) {
|
|
277
|
+
publishResolverFinish(field, error)
|
|
278
|
+
throw error
|
|
279
|
+
}
|
|
222
280
|
}
|
|
223
281
|
|
|
224
282
|
patchedResolvers.add(resolveAsync)
|
|
@@ -226,72 +284,130 @@ function wrapResolve (resolve) {
|
|
|
226
284
|
return resolveAsync
|
|
227
285
|
}
|
|
228
286
|
|
|
229
|
-
|
|
230
|
-
|
|
287
|
+
/**
|
|
288
|
+
* @param {{ ctx: object, error: unknown }} field
|
|
289
|
+
* @param {unknown} error
|
|
290
|
+
*/
|
|
291
|
+
function publishResolverFinish (field, error) {
|
|
292
|
+
const fieldCtx = field.ctx
|
|
293
|
+
fieldCtx.error = error
|
|
294
|
+
fieldCtx.field = field
|
|
295
|
+
updateFieldCh.publish(fieldCtx)
|
|
296
|
+
}
|
|
231
297
|
|
|
232
|
-
|
|
298
|
+
function callInAsyncScope (fn, thisArg, args, abortController, cb) {
|
|
299
|
+
if (abortController.signal.aborted) {
|
|
233
300
|
cb(null, null)
|
|
234
301
|
throw new AbortError('Aborted')
|
|
235
302
|
}
|
|
236
303
|
|
|
237
304
|
try {
|
|
238
305
|
const result = fn.apply(thisArg, args)
|
|
239
|
-
if (result && typeof result
|
|
306
|
+
if (result !== null && typeof result?.then === 'function') {
|
|
240
307
|
return result.then(
|
|
241
308
|
res => {
|
|
242
309
|
cb(null, res)
|
|
243
310
|
return res
|
|
244
311
|
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
312
|
+
/* istanbul ignore next: graphql.execute() rejects only via custom executors (graphql-yoga / graphql-tools) */
|
|
313
|
+
error => {
|
|
314
|
+
cb(error)
|
|
315
|
+
throw error
|
|
248
316
|
}
|
|
249
317
|
)
|
|
250
318
|
}
|
|
251
319
|
cb(null, result)
|
|
252
320
|
return result
|
|
253
|
-
} catch (
|
|
254
|
-
cb(
|
|
255
|
-
throw
|
|
321
|
+
} catch (error) {
|
|
322
|
+
cb(error)
|
|
323
|
+
throw error
|
|
256
324
|
}
|
|
257
325
|
}
|
|
258
326
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
327
|
+
/**
|
|
328
|
+
* @typedef {{ prev: PathNode | undefined, key: string | number }} PathNode
|
|
329
|
+
*
|
|
330
|
+
* @typedef {{ error: unknown, ctx: object }} TrackedField
|
|
331
|
+
*/
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @param {{
|
|
335
|
+
* fields: Map<object, TrackedField>,
|
|
336
|
+
* collapse: boolean,
|
|
337
|
+
* collapsedFields?: Map<string, TrackedField>,
|
|
338
|
+
* pathCache?: Map<PathNode, string>,
|
|
339
|
+
* }} rootCtx
|
|
340
|
+
* @param {import('graphql').GraphQLResolveInfo} info
|
|
341
|
+
* @param {Record<string, unknown>} args
|
|
342
|
+
*/
|
|
273
343
|
function assertField (rootCtx, info, args) {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
344
|
+
const path = info.path
|
|
345
|
+
const collapse = rootCtx.collapse
|
|
346
|
+
|
|
347
|
+
const cache = rootCtx.pathCache ??= new Map()
|
|
348
|
+
const prev = path.prev
|
|
349
|
+
const key = path.key
|
|
350
|
+
const segment = collapse && typeof key !== 'string' ? '*' : key
|
|
351
|
+
|
|
352
|
+
const pathString = prev === undefined
|
|
353
|
+
? String(segment)
|
|
354
|
+
: (cache.get(prev) ?? buildCachedPathString(prev, cache, collapse)) + '.' + segment
|
|
355
|
+
cache.set(path, pathString)
|
|
356
|
+
|
|
357
|
+
const fieldCtx = {
|
|
358
|
+
rootCtx,
|
|
359
|
+
args,
|
|
360
|
+
path,
|
|
361
|
+
pathString,
|
|
362
|
+
fieldName: info.fieldName,
|
|
363
|
+
returnType: info.returnType,
|
|
364
|
+
fieldNode: info.fieldNodes[0],
|
|
365
|
+
variableValues: info.variableValues,
|
|
366
|
+
}
|
|
367
|
+
// Publish per resolver call, before the collapse / depth dedupe below.
|
|
368
|
+
// IAST mutates each call's own args object; if siblings 2..N skip the
|
|
369
|
+
// publish, those args objects never get tainted.
|
|
370
|
+
startResolveCh.publish(fieldCtx)
|
|
371
|
+
|
|
372
|
+
let collapsedFields
|
|
373
|
+
if (collapse) {
|
|
374
|
+
collapsedFields = rootCtx.collapsedFields ??= new Map()
|
|
375
|
+
const existing = collapsedFields.get(pathString)
|
|
376
|
+
// Subsequent siblings of a collapsed list share the first sibling's field
|
|
377
|
+
// so updateFieldCh fires for every call and the span's finishTime tracks
|
|
378
|
+
// the last sibling's completion, not the first.
|
|
379
|
+
if (existing !== undefined) return existing
|
|
290
380
|
}
|
|
291
381
|
|
|
382
|
+
const field = { error: null, ctx: fieldCtx }
|
|
383
|
+
rootCtx.fields.set(path, field)
|
|
384
|
+
if (collapsedFields !== undefined) collapsedFields.set(pathString, field)
|
|
292
385
|
return field
|
|
293
386
|
}
|
|
294
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Cold path for assertField. graphql-js inserts a synthetic array-index
|
|
390
|
+
* node between a list field and its items, and that node never reaches a
|
|
391
|
+
* resolver — so assertField has no chance to cache it. The first child of
|
|
392
|
+
* the list item that hits the path cache lands here to walk and populate
|
|
393
|
+
* back to a cached ancestor.
|
|
394
|
+
*
|
|
395
|
+
* @param {PathNode} path
|
|
396
|
+
* @param {Map<PathNode, string>} cache
|
|
397
|
+
* @param {boolean} collapse
|
|
398
|
+
*/
|
|
399
|
+
function buildCachedPathString (path, cache, collapse) {
|
|
400
|
+
const key = path.key
|
|
401
|
+
const segment = collapse && typeof key !== 'string' ? '*' : key
|
|
402
|
+
const prev = path.prev
|
|
403
|
+
|
|
404
|
+
const pathString = prev === undefined
|
|
405
|
+
? String(segment)
|
|
406
|
+
: (cache.get(prev) ?? buildCachedPathString(prev, cache, collapse)) + '.' + segment
|
|
407
|
+
cache.set(path, pathString)
|
|
408
|
+
return pathString
|
|
409
|
+
}
|
|
410
|
+
|
|
295
411
|
function wrapFields (type) {
|
|
296
412
|
if (!type || !type._fields || patchedTypes.has(type)) {
|
|
297
413
|
return
|
|
@@ -323,14 +439,19 @@ function wrapFieldType (field) {
|
|
|
323
439
|
}
|
|
324
440
|
|
|
325
441
|
function finishResolvers ({ fields }) {
|
|
326
|
-
for (const field of
|
|
327
|
-
|
|
328
|
-
field
|
|
442
|
+
for (const field of fields.values()) {
|
|
443
|
+
const fieldCtx = field.ctx
|
|
444
|
+
// A depth-gated field publishes startResolveCh for IAST/AppSec but the
|
|
445
|
+
// resolve plugin's start short-circuits before creating a span, so there
|
|
446
|
+
// is no span here to finish.
|
|
447
|
+
if (fieldCtx.currentStore === undefined) continue
|
|
448
|
+
fieldCtx.finishTime = field.finishTime
|
|
449
|
+
fieldCtx.field = field
|
|
329
450
|
if (field.error) {
|
|
330
|
-
|
|
331
|
-
resolveErrorCh.publish(
|
|
451
|
+
fieldCtx.error = field.error
|
|
452
|
+
resolveErrorCh.publish(fieldCtx)
|
|
332
453
|
}
|
|
333
|
-
finishResolveCh.publish(
|
|
454
|
+
finishResolveCh.publish(fieldCtx)
|
|
334
455
|
}
|
|
335
456
|
}
|
|
336
457
|
|
|
@@ -343,11 +464,11 @@ addHook({ name: '@graphql-tools/executor', versions: ['>=0.0.14'] }, executor =>
|
|
|
343
464
|
return executor
|
|
344
465
|
})
|
|
345
466
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
467
|
+
// TODO(BridgeAR): graphql >=17.0.0-alpha.9 routes execute() through
|
|
468
|
+
// experimentalExecuteIncrementally(), bypassing this hook. The same
|
|
469
|
+
// function returns { initialResult, subsequentResults } for @defer /
|
|
470
|
+
// @stream which callInAsyncScope does not handle — execute finishes
|
|
471
|
+
// before the streamed payloads land.
|
|
351
472
|
addHook({ name: 'graphql', file: 'execution/execute.js', versions: ['>=0.10'] }, execute => {
|
|
352
473
|
shimmer.wrap(execute, 'execute', wrapExecute(execute))
|
|
353
474
|
return execute
|