dd-trace 6.13.0 → 6.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +3 -1
- package/ci/init.js +8 -3
- package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
- package/ci/test-optimization-validation/ci-remediation.js +6 -6
- package/ci/test-optimization-validation/command-blocker.js +7 -5
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
- package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
- package/ci/test-optimization-validation/package-check.js +1 -1
- package/ci/test-optimization-validation/report-writer.js +7 -5
- package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
- package/ci/vitest-no-worker-init-setup.mjs +13 -13
- package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
- package/package.json +13 -10
- package/packages/datadog-esbuild/index.js +3 -1
- package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
- package/packages/datadog-instrumentations/src/fs.js +24 -20
- package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +16 -16
- package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
- package/packages/datadog-instrumentations/src/jest.js +25 -20
- package/packages/datadog-instrumentations/src/knex.js +76 -0
- package/packages/datadog-instrumentations/src/mariadb.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
- package/packages/datadog-instrumentations/src/net.js +14 -17
- package/packages/datadog-instrumentations/src/next.js +212 -62
- package/packages/datadog-instrumentations/src/openai.js +42 -40
- package/packages/datadog-instrumentations/src/pg.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +109 -13
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
- package/packages/datadog-instrumentations/src/sequelize.js +94 -2
- package/packages/datadog-instrumentations/src/undici.js +53 -4
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
- package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
- package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
- package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/util.js +2 -1
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
- package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
- package/packages/datadog-plugin-cucumber/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
- package/packages/datadog-plugin-graphql/src/execute.js +998 -196
- package/packages/datadog-plugin-graphql/src/index.js +27 -7
- package/packages/datadog-plugin-graphql/src/jit.js +136 -0
- package/packages/datadog-plugin-graphql/src/request.js +18 -27
- package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
- package/packages/datadog-plugin-graphql/src/utils.js +208 -120
- package/packages/datadog-plugin-graphql/src/validate.js +10 -23
- package/packages/datadog-plugin-jest/src/index.js +0 -6
- package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
- package/packages/datadog-plugin-mocha/src/index.js +403 -75
- package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
- package/packages/datadog-plugin-mysql/src/index.js +41 -15
- package/packages/datadog-plugin-net/src/tcp.js +3 -1
- package/packages/datadog-plugin-next/src/index.js +63 -29
- package/packages/datadog-plugin-playwright/src/index.js +167 -27
- package/packages/datadog-plugin-undici/src/index.js +27 -41
- package/packages/datadog-plugin-vitest/src/index.js +6 -12
- package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
- package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
- package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
- package/packages/dd-trace/src/appsec/graphql.js +6 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/lambda.js +44 -26
- package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
- package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
- package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
- package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
- package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +73 -13
- package/packages/dd-trace/src/config/parsers.js +11 -1
- package/packages/dd-trace/src/config/stable.js +3 -3
- package/packages/dd-trace/src/config/supported-configurations.json +20 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
- package/packages/dd-trace/src/debugger/config.js +12 -1
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
- package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
- package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
- package/packages/dd-trace/src/debugger/index.js +23 -3
- package/packages/dd-trace/src/dogstatsd.js +510 -76
- package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
- package/packages/dd-trace/src/encode/0.4.js +5 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
- package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
- package/packages/dd-trace/src/exporters/agent/index.js +2 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
- package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
- package/packages/dd-trace/src/exporters/common/agents.js +28 -6
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
- package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
- package/packages/dd-trace/src/exporters/common/request.js +31 -11
- package/packages/dd-trace/src/exporters/common/url.js +40 -1
- package/packages/dd-trace/src/exporters/common/writer.js +9 -7
- package/packages/dd-trace/src/flush.js +6 -3
- package/packages/dd-trace/src/id.js +14 -0
- package/packages/dd-trace/src/index.js +5 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
- package/packages/dd-trace/src/llmobs/index.js +32 -32
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
- package/packages/dd-trace/src/llmobs/util.js +0 -33
- package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
- package/packages/dd-trace/src/log/index.js +7 -1
- package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
- package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
- package/packages/dd-trace/src/payload-tagging/index.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
- package/packages/dd-trace/src/plugins/tracing.js +12 -10
- package/packages/dd-trace/src/plugins/util/ci.js +3 -1
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
- package/packages/dd-trace/src/plugins/util/test.js +83 -20
- package/packages/dd-trace/src/plugins/util/url.js +2 -2
- package/packages/dd-trace/src/plugins/util/web.js +4 -9
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/oom.js +7 -2
- package/packages/dd-trace/src/profiling/profiler.js +23 -13
- package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
- package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
- package/packages/dd-trace/src/proxy.js +75 -5
- package/packages/dd-trace/src/remote_config/index.js +319 -101
- package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
- package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
- package/packages/dd-trace/src/sampling_rule.js +36 -9
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
- package/packages/dd-trace/src/serverless/vercel.js +53 -20
- package/packages/dd-trace/src/serverless.js +10 -3
- package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
- package/packages/dd-trace/src/span_stats.js +24 -28
- package/packages/dd-trace/src/startup-log.js +3 -2
- package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
- package/packages/dd-trace/src/telemetry/send-data.js +18 -16
- package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
- package/packages/dd-trace/src/tracer.js +4 -5
- package/packages/dd-trace/src/tracer_metadata.js +11 -3
- 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/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
- package/packages/dd-trace/src/encode/agentless-json.js +0 -210
|
@@ -1,31 +1,52 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
+
|
|
3
5
|
const dc = require('dc-polyfill')
|
|
4
6
|
|
|
5
7
|
const { storage } = require('../../datadog-core')
|
|
6
8
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
7
9
|
const GraphQLParsePlugin = require('./parse')
|
|
8
|
-
const {
|
|
10
|
+
const { recordResolveError } = require('./resolve-error')
|
|
11
|
+
const {
|
|
12
|
+
extractErrorIntoSpanEvent,
|
|
13
|
+
getBaseTypeName,
|
|
14
|
+
getOperation,
|
|
15
|
+
getSignature,
|
|
16
|
+
isApolloHealthCheck,
|
|
17
|
+
refineRequestSpanMetadata,
|
|
18
|
+
} = require('./utils')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {import('../../dd-trace/src/opentracing/span')} DatadogSpan
|
|
22
|
+
* @typedef {import('../../datadog-instrumentations/src/helpers/graphql-jit-runtime').JitFieldDescriptor} JitDescriptor
|
|
23
|
+
* @typedef {import('graphql').GraphQLFieldResolver<unknown, unknown>} GraphQLFieldResolver
|
|
24
|
+
* @typedef {Parameters<GraphQLFieldResolver> & { [index: number]: unknown, length: number }} ArgumentsList
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* schema?: import('graphql').GraphQLSchema,
|
|
27
|
+
* document?: import('graphql').DocumentNode,
|
|
28
|
+
* rootValue?: unknown,
|
|
29
|
+
* contextValue?: unknown,
|
|
30
|
+
* variableValues?: Record<string, unknown>,
|
|
31
|
+
* operationName?: string | null,
|
|
32
|
+
* fieldResolver?: GraphQLFieldResolver
|
|
33
|
+
* }} ExecutionArguments
|
|
34
|
+
* @typedef {(value: unknown) => unknown} ThenableCallback
|
|
35
|
+
* @typedef {{ then: (onFulfilled?: ThenableCallback, onRejected?: ThenableCallback) => unknown }} Thenable
|
|
36
|
+
* @typedef {import('../../datadog-instrumentations/src/helpers/graphql-jit-runtime').ArgumentFactory} ArgumentFactory
|
|
37
|
+
*/
|
|
9
38
|
|
|
10
39
|
const legacyStorage = storage('legacy')
|
|
11
40
|
|
|
12
41
|
const iastResolveCh = dc.channel('apm:graphql:resolve:start')
|
|
13
42
|
const resolverStartCh = dc.channel('datadog:graphql:resolver:start')
|
|
14
|
-
const updateFieldCh = dc.channel('apm:graphql:resolve:updateField')
|
|
15
|
-
|
|
16
|
-
// AppSec/WAF abort gate. Published synchronously from bindStart with a
|
|
17
|
-
// payload carrying the abortController; subscribers that call
|
|
18
|
-
// `payload.abortController.abort()` signal a pre-execute abort. bindStart
|
|
19
|
-
// observes the aborted signal by replacing `ctx.arguments[0]` with a Proxy
|
|
20
|
-
// whose getters throw AbortError — the orchestrion-emitted wrapper's
|
|
21
|
-
// `try { __apm$traced() } catch { ...; throw err }` block then propagates
|
|
22
|
-
// the AbortError to the caller of graphql.execute.
|
|
23
|
-
const startExecuteCh = dc.channel('apm:graphql:execute:start')
|
|
24
43
|
|
|
25
44
|
const contexts = new WeakMap()
|
|
26
45
|
const instrumentedArgs = new WeakSet()
|
|
27
46
|
|
|
28
47
|
const patchedResolvers = new WeakSet()
|
|
48
|
+
const jitResolvers = new WeakMap()
|
|
49
|
+
const originalResolvers = new WeakMap()
|
|
29
50
|
|
|
30
51
|
// Visited types per caller-owned schema. The walk reaches union members and
|
|
31
52
|
// interface implementations through the schema (`getTypes`/`getPossibleTypes`),
|
|
@@ -44,6 +65,18 @@ let depthDisabled = false
|
|
|
44
65
|
// operation's variableValues is undefined.
|
|
45
66
|
const NO_VARIABLES_CACHED = Symbol('noVariablesCached')
|
|
46
67
|
|
|
68
|
+
// `field.endTime` stays numeric: 0 is pending, -1 means a collapsed span was reused,
|
|
69
|
+
// and positive values are exact completion times. Pending and reused spans finish at the execution boundary.
|
|
70
|
+
const PENDING_FIELD_END_TIME = 0
|
|
71
|
+
const REUSED_FIELD_END_TIME = -1
|
|
72
|
+
|
|
73
|
+
let asyncDisposeSymbol = Symbol.asyncDispose
|
|
74
|
+
// @graphql-tools/executor uses this fallback before Node.js exposes Symbol.asyncDispose.
|
|
75
|
+
/* istanbul ignore if */
|
|
76
|
+
if (asyncDisposeSymbol === undefined) {
|
|
77
|
+
asyncDisposeSymbol = Symbol.for('asyncDispose')
|
|
78
|
+
}
|
|
79
|
+
|
|
47
80
|
class AbortError extends Error {
|
|
48
81
|
constructor (message) {
|
|
49
82
|
super(message)
|
|
@@ -51,6 +84,58 @@ class AbortError extends Error {
|
|
|
51
84
|
}
|
|
52
85
|
}
|
|
53
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {GraphQLExecutePlugin} plugin
|
|
89
|
+
* @param {DatadogSpan} executeSpan
|
|
90
|
+
* @param {string | undefined} source
|
|
91
|
+
* @param {AbortController | undefined} abortController
|
|
92
|
+
* @param {{ fields: object[] } | undefined} jitPlan
|
|
93
|
+
* @param {Record<string, unknown> | undefined} variableValues
|
|
94
|
+
* @returns {object}
|
|
95
|
+
*/
|
|
96
|
+
function createRootContext (plugin, executeSpan, source, abortController, jitPlan, variableValues) {
|
|
97
|
+
const hasIastSub = iastResolveCh.hasSubscribers
|
|
98
|
+
const hasResolverSub = abortController !== undefined
|
|
99
|
+
const traceResolvers = plugin.config.depth !== 0
|
|
100
|
+
|
|
101
|
+
// Read by the generated gate in every inlined default field.
|
|
102
|
+
const traceAll = hasIastSub || hasResolverSub ||
|
|
103
|
+
(traceResolvers && !plugin.config.collapse)
|
|
104
|
+
|
|
105
|
+
const rootCtx = {
|
|
106
|
+
source,
|
|
107
|
+
config: plugin.config,
|
|
108
|
+
abortController,
|
|
109
|
+
executeSpan,
|
|
110
|
+
plugin,
|
|
111
|
+
filteredVariablesKey: NO_VARIABLES_CACHED,
|
|
112
|
+
filteredVariables: undefined,
|
|
113
|
+
depthDisabled: !traceResolvers,
|
|
114
|
+
hasIastSub,
|
|
115
|
+
hasResolverSub,
|
|
116
|
+
jitTraceAll: traceAll,
|
|
117
|
+
jitTraceFirst: !traceAll && traceResolvers && plugin.config.collapse,
|
|
118
|
+
variableValues,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (jitPlan) {
|
|
122
|
+
rootCtx.jitPlan = jitPlan
|
|
123
|
+
if (traceResolvers) {
|
|
124
|
+
if (plugin.config.collapse) {
|
|
125
|
+
rootCtx.jitFields = new Array(jitPlan.fields.length)
|
|
126
|
+
} else {
|
|
127
|
+
rootCtx.jitFieldsByPath = new Map()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (traceResolvers && (!jitPlan || !plugin.config.collapse)) {
|
|
132
|
+
rootCtx.fields = new Map()
|
|
133
|
+
rootCtx.pathCache = new Map()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return rootCtx
|
|
137
|
+
}
|
|
138
|
+
|
|
54
139
|
class GraphQLExecutePlugin extends TracingPlugin {
|
|
55
140
|
static id = 'graphql'
|
|
56
141
|
static operation = 'execute'
|
|
@@ -95,10 +180,13 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
95
180
|
}
|
|
96
181
|
}
|
|
97
182
|
|
|
98
|
-
|
|
183
|
+
/**
|
|
184
|
+
* @param {object} ctx
|
|
185
|
+
* @returns {ExecutionArguments | undefined}
|
|
186
|
+
*/
|
|
187
|
+
readExecutionArgs (ctx) {
|
|
99
188
|
const rawArgs = ctx.arguments
|
|
100
|
-
const
|
|
101
|
-
const args = readArgs(rawArgs, objectForm)
|
|
189
|
+
const args = readArgs(rawArgs, isObjectForm(rawArgs))
|
|
102
190
|
|
|
103
191
|
// Re-entrant execute() short-circuit (yoga's normalizedExecutor calls
|
|
104
192
|
// execute internally with the same arguments object — without this we'd
|
|
@@ -106,36 +194,93 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
106
194
|
// check also catches primitive contexts.
|
|
107
195
|
if (instrumentedArgs.has(rawArgs?.[0])) {
|
|
108
196
|
ctx.ddSkipped = true
|
|
109
|
-
|
|
197
|
+
ctx.currentStore = legacyStorage.getStore()
|
|
198
|
+
return
|
|
110
199
|
}
|
|
111
200
|
|
|
112
201
|
const { contextValue } = args
|
|
113
202
|
if (contextValue && typeof contextValue === 'object' && contexts.has(contextValue)) {
|
|
114
203
|
ctx.ddSkipped = true
|
|
115
|
-
return
|
|
204
|
+
return
|
|
116
205
|
}
|
|
117
206
|
|
|
207
|
+
return args
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {object} ctx
|
|
212
|
+
* @param {ExecutionArguments} args
|
|
213
|
+
*/
|
|
214
|
+
wrapExecutionResolvers (ctx, args) {
|
|
215
|
+
const rawArgs = ctx.arguments
|
|
216
|
+
ctx.ddArgs = setWrappedFieldResolver(rawArgs, args, isObjectForm(rawArgs), defaultFieldResolver)
|
|
217
|
+
if (ctx.ddArgs && typeof ctx.ddArgs === 'object') {
|
|
218
|
+
instrumentedArgs.add(ctx.ddArgs)
|
|
219
|
+
ctx.ddInstrumentedArgs = ctx.ddArgs
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const { schema } = args
|
|
223
|
+
if (schema) {
|
|
224
|
+
wrapFields(schema._queryType, schema)
|
|
225
|
+
wrapFields(schema._mutationType, schema)
|
|
226
|
+
wrapFields(schema._subscriptionType, schema)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @param {object} ctx
|
|
232
|
+
* @param {unknown} contextValue
|
|
233
|
+
* @param {object} rootCtx
|
|
234
|
+
*/
|
|
235
|
+
storeRootContext (ctx, contextValue, rootCtx) {
|
|
236
|
+
ctx.currentStore.graphqlRootCtx = rootCtx
|
|
237
|
+
if (isWeakMapKey(contextValue)) {
|
|
238
|
+
contexts.set(contextValue, rootCtx)
|
|
239
|
+
ctx.ddContextValue = contextValue
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {object} ctx
|
|
245
|
+
*/
|
|
246
|
+
bindStart (ctx) {
|
|
247
|
+
const args = this.readExecutionArgs(ctx)
|
|
248
|
+
if (!args) return ctx.currentStore
|
|
249
|
+
|
|
250
|
+
const { contextValue } = args
|
|
118
251
|
const document = args.document
|
|
119
252
|
const docSource = document ? GraphQLParsePlugin.documentSources.get(document) : undefined
|
|
120
253
|
const operation = getOperation(document, args.operationName)
|
|
121
254
|
|
|
122
255
|
const type = operation?.operation
|
|
123
|
-
const name = operation?.name?.value
|
|
256
|
+
const name = operation?.name?.value ?? args.operationName ?? undefined
|
|
257
|
+
let signature = name ?? ''
|
|
124
258
|
const source = this.config.source && docSource
|
|
125
259
|
|
|
126
260
|
// Apollo Server may execute a cached document without parsing it first.
|
|
127
261
|
// Match the full gateway operation here so caller-owned AST transformations
|
|
128
262
|
// cannot suppress execute/resolver AppSec and IAST channels.
|
|
129
263
|
if (name === '__ApolloServiceHealthCheck__' &&
|
|
130
|
-
document
|
|
264
|
+
document?.definitions?.length === 1 &&
|
|
131
265
|
isApolloHealthCheck(operation)) {
|
|
132
266
|
ctx.ddSkipped = true
|
|
133
267
|
return ctx.currentStore
|
|
134
268
|
}
|
|
135
269
|
|
|
136
|
-
|
|
270
|
+
const requestStore =
|
|
271
|
+
/** @type {{ graphqlRequestSpan?: DatadogSpan } | undefined} */ (legacyStorage.getStore())
|
|
272
|
+
if (type !== undefined) {
|
|
273
|
+
signature = getSignature(
|
|
274
|
+
/** @type {import('graphql').DocumentNode} */ (document),
|
|
275
|
+
name,
|
|
276
|
+
type,
|
|
277
|
+
this.config.signature
|
|
278
|
+
)
|
|
279
|
+
refineRequestSpanMetadata(requestStore?.graphqlRequestSpan, signature, type, name)
|
|
280
|
+
}
|
|
137
281
|
|
|
138
|
-
|
|
282
|
+
ctx.collapse = this.config.collapse
|
|
283
|
+
ctx.ddOperationType = type
|
|
139
284
|
|
|
140
285
|
const span = this.startSpan(this.operationName(), {
|
|
141
286
|
service: this.config.service || this.serviceName(),
|
|
@@ -151,71 +296,13 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
151
296
|
|
|
152
297
|
addVariableTags(this.config, span, args.variableValues)
|
|
153
298
|
|
|
154
|
-
const abortController = new AbortController()
|
|
155
|
-
|
|
156
|
-
// AppSec/WAF synchronous-abort gate. Publish before any resolver-wrapping
|
|
157
|
-
// work — if a subscriber aborts, none of that work matters because we'll
|
|
158
|
-
// make execute's body throw AbortError before it reaches any resolvers.
|
|
159
|
-
// bindStart runs as a bindStore transform on
|
|
160
|
-
// tracing:orchestrion:graphql:apm:graphql:execute:start, which fires
|
|
161
|
-
// BEFORE the orchestrion-emitted wrapper publishes its own :start and
|
|
162
|
-
// BEFORE the wrapped fn runs. The subscriber gets the abortController
|
|
163
|
-
// synchronously; on return we observe `signal.aborted` and act.
|
|
164
|
-
if (startExecuteCh.hasSubscribers) {
|
|
165
|
-
startExecuteCh.publish({ abortController, args })
|
|
166
|
-
if (abortController.signal.aborted) {
|
|
167
|
-
// Replace ctx.arguments[0] with a Proxy that throws AbortError on any
|
|
168
|
-
// property access. The orchestrion wrapper calls
|
|
169
|
-
// `__apm$wrapped.apply(this, ctx.arguments)`; graphql.execute's body
|
|
170
|
-
// begins with `const { schema, document, ... } = args` so the
|
|
171
|
-
// destructure triggers the trap immediately. The wrapper catches and
|
|
172
|
-
// rethrows, propagating AbortError to graphql.execute's caller.
|
|
173
|
-
ctx.arguments[0] = new Proxy({}, {
|
|
174
|
-
get () { throw new AbortError('Aborted') },
|
|
175
|
-
has () { throw new AbortError('Aborted') },
|
|
176
|
-
})
|
|
177
|
-
ctx.ddAborted = true
|
|
178
|
-
return ctx.currentStore
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
ctx.ddArgs = setWrappedFieldResolver(rawArgs, args, objectForm, defaultFieldResolver)
|
|
183
|
-
if (ctx.ddArgs && typeof ctx.ddArgs === 'object') {
|
|
184
|
-
instrumentedArgs.add(ctx.ddArgs)
|
|
185
|
-
ctx.ddInstrumentedArgs = ctx.ddArgs
|
|
186
|
-
}
|
|
299
|
+
const abortController = resolverStartCh.hasSubscribers ? new AbortController() : undefined
|
|
187
300
|
|
|
188
|
-
|
|
189
|
-
if (schema) {
|
|
190
|
-
wrapFields(schema._queryType, schema)
|
|
191
|
-
wrapFields(schema._mutationType, schema)
|
|
192
|
-
wrapFields(schema._subscriptionType, schema)
|
|
193
|
-
}
|
|
301
|
+
this.wrapExecutionResolvers(ctx, args)
|
|
194
302
|
|
|
195
|
-
const rootCtx =
|
|
196
|
-
source: docSource,
|
|
197
|
-
config: this.config,
|
|
198
|
-
fields: new Map(),
|
|
199
|
-
pathCache: new Map(),
|
|
200
|
-
abortController,
|
|
201
|
-
executeSpan: span,
|
|
202
|
-
plugin: this,
|
|
203
|
-
// graphql.resolve variable tags: memoize the filtered result for the
|
|
204
|
-
// last-seen variableValues object (see #filterVariables).
|
|
205
|
-
filteredVariablesKey: NO_VARIABLES_CACHED,
|
|
206
|
-
filteredVariables: undefined,
|
|
207
|
-
}
|
|
303
|
+
const rootCtx = createRootContext(this, span, docSource, abortController, ctx.ddPlan, args.variableValues)
|
|
208
304
|
ctx.ddRootCtx = rootCtx
|
|
209
|
-
|
|
210
|
-
contexts.set(contextValue, rootCtx)
|
|
211
|
-
ctx.ddContextValue = contextValue
|
|
212
|
-
} else {
|
|
213
|
-
// Primitive / non-keyable contextValue: stash rootCtx on the
|
|
214
|
-
// orchestrion-scoped store that runStores enters for execute. Wrapped
|
|
215
|
-
// resolvers read it via legacyStorage.getStore() and it unwinds with the
|
|
216
|
-
// frame — no enterWith store that leaks past execute.
|
|
217
|
-
ctx.currentStore.graphqlRootCtx = rootCtx
|
|
218
|
-
}
|
|
305
|
+
this.storeRootContext(ctx, contextValue, rootCtx)
|
|
219
306
|
|
|
220
307
|
return ctx.currentStore
|
|
221
308
|
}
|
|
@@ -226,14 +313,14 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
226
313
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
227
314
|
if (!span) return
|
|
228
315
|
|
|
316
|
+
if (ctx.ddRootCtx && legacyStorage.getStore() !== ctx.currentStore) {
|
|
317
|
+
legacyStorage.enterWith(ctx.currentStore)
|
|
318
|
+
}
|
|
319
|
+
|
|
229
320
|
// Synchronous execute() throw (e.g. execute(null, doc)) — error handler
|
|
230
321
|
// already tagged the span.
|
|
231
322
|
if (ctx.error) {
|
|
232
|
-
|
|
233
|
-
span.finish()
|
|
234
|
-
} else {
|
|
235
|
-
this.#finishSpan(ctx, span)
|
|
236
|
-
}
|
|
323
|
+
this.#finishSpan(ctx, span)
|
|
237
324
|
return ctx.parentStore
|
|
238
325
|
}
|
|
239
326
|
|
|
@@ -242,7 +329,7 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
242
329
|
if (typeof result?.then === 'function') {
|
|
243
330
|
result.then(
|
|
244
331
|
(res) => this.#finishSpan(ctx, span, res),
|
|
245
|
-
(
|
|
332
|
+
(error) => this.#finishSpan(ctx, span, undefined, error)
|
|
246
333
|
)
|
|
247
334
|
} else {
|
|
248
335
|
this.#finishSpan(ctx, span, result)
|
|
@@ -252,9 +339,6 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
252
339
|
}
|
|
253
340
|
|
|
254
341
|
error (ctx) {
|
|
255
|
-
// Pre-execute WAF abort isn't an error condition — opSpan.error must
|
|
256
|
-
// stay 0 per master's contract.
|
|
257
|
-
if (ctx.ddAborted) return
|
|
258
342
|
const span = ctx?.currentStore?.span || this.activeSpan
|
|
259
343
|
if (span && ctx?.error) {
|
|
260
344
|
span.setTag('error', ctx.error)
|
|
@@ -266,17 +350,30 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
266
350
|
* @param {import('../../dd-trace/src/opentracing/span')} span
|
|
267
351
|
* @param {import('graphql').ExecutionResult} [res]
|
|
268
352
|
* @param {unknown} [error]
|
|
353
|
+
* @param {boolean} [incrementalComplete]
|
|
354
|
+
* @param {boolean} [finishPendingFields]
|
|
269
355
|
*/
|
|
270
|
-
#finishSpan (ctx, span, res, error) {
|
|
356
|
+
#finishSpan (ctx, span, res, error, incrementalComplete = false, finishPendingFields = false) {
|
|
357
|
+
if (!incrementalComplete && error === undefined && ctx.ddOperationType !== 'subscription') {
|
|
358
|
+
const iterator = getIncrementalIterator(res)
|
|
359
|
+
if (iterator) {
|
|
360
|
+
tagIncrementalResult(this.config, span, res?.initialResult)
|
|
361
|
+
observeIncrementalIterator(iterator, (result, iteratorError, iteratorCancelled, complete) => {
|
|
362
|
+
tagIncrementalResult(this.config, span, result)
|
|
363
|
+
if (complete) {
|
|
364
|
+
this.#finishSpan(ctx, span, res, iteratorError, true, iteratorCancelled)
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
271
371
|
if (error !== undefined) {
|
|
272
372
|
span.setTag('error', error)
|
|
273
373
|
}
|
|
274
374
|
|
|
275
375
|
if (res?.errors?.length) {
|
|
276
|
-
|
|
277
|
-
for (const err of res.errors) {
|
|
278
|
-
extractErrorIntoSpanEvent(this.config, span, err)
|
|
279
|
-
}
|
|
376
|
+
tagExecutionErrors(this.config, span, res.errors)
|
|
280
377
|
}
|
|
281
378
|
|
|
282
379
|
if (ctx.ddContextValue) {
|
|
@@ -286,18 +383,33 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
286
383
|
instrumentedArgs.delete(ctx.ddInstrumentedArgs)
|
|
287
384
|
}
|
|
288
385
|
|
|
386
|
+
const rootCtx = ctx.ddRootCtx
|
|
387
|
+
const releaseBeforeExecuteHook = rootCtx?.resolveHooksPending === true
|
|
388
|
+
if (releaseBeforeExecuteHook) {
|
|
389
|
+
releaseRootContext(rootCtx, finishPendingFields)
|
|
390
|
+
} else if (rootCtx?.jitResolveHooksPending === true) {
|
|
391
|
+
runResolveHooks(rootCtx)
|
|
392
|
+
}
|
|
289
393
|
this.config.hooks.execute(span, ctx.ddArgs, res)
|
|
290
|
-
|
|
394
|
+
if (!releaseBeforeExecuteHook) releaseRootContext(rootCtx, finishPendingFields)
|
|
291
395
|
span.finish()
|
|
292
396
|
}
|
|
293
397
|
|
|
294
398
|
// Public — called from wrapResolve (free function, crosses class boundary).
|
|
295
|
-
// Resolve-span creation
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
399
|
+
// Resolve-span creation stays inline so child fields can parent immediately.
|
|
400
|
+
/**
|
|
401
|
+
* @param {object} field
|
|
402
|
+
* @param {object} rootCtx
|
|
403
|
+
* @param {DatadogSpan} executeSpan
|
|
404
|
+
* @param {number} startTime
|
|
405
|
+
* @param {Record<string, unknown> | undefined} variableValues
|
|
406
|
+
* @param {object | null} [parentField]
|
|
407
|
+
* @returns {DatadogSpan}
|
|
408
|
+
*/
|
|
409
|
+
startResolveSpan (field, rootCtx, executeSpan, startTime, variableValues, parentField) {
|
|
410
|
+
const { fieldNode, fieldName, returnType, baseTypeName, collapsedKey } = field
|
|
299
411
|
|
|
300
|
-
const parent = getParentField(rootCtx, field)
|
|
412
|
+
const parent = parentField === undefined ? getParentField(rootCtx, field) : parentField
|
|
301
413
|
const childOf = parent?.span || executeSpan
|
|
302
414
|
|
|
303
415
|
const document = rootCtx.source
|
|
@@ -305,24 +417,29 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
305
417
|
const source = loc && document.slice(loc.start, loc.end)
|
|
306
418
|
|
|
307
419
|
// ctx form: startSpan sets field.currentStore = { ...activeStore, span }
|
|
308
|
-
// without entering it.
|
|
309
|
-
//
|
|
420
|
+
// without entering it. Collapsed siblings reuse that store until execute
|
|
421
|
+
// completes and finishes the shared span.
|
|
422
|
+
const meta = field.tags ?? {
|
|
423
|
+
'graphql.field.coordinates': `${field.parentTypeName}.${fieldName}`,
|
|
424
|
+
'graphql.field.name': fieldName,
|
|
425
|
+
'graphql.field.path': collapsedKey,
|
|
426
|
+
'graphql.field.type': baseTypeName,
|
|
427
|
+
'graphql.source': source,
|
|
428
|
+
}
|
|
310
429
|
const span = this.startSpan('graphql.resolve', {
|
|
311
430
|
service: this.config.service,
|
|
312
|
-
resource: `${fieldName}:${returnType}`,
|
|
431
|
+
resource: field.resource ?? `${fieldName}:${returnType}`,
|
|
313
432
|
childOf,
|
|
314
433
|
type: 'graphql',
|
|
315
434
|
startTime,
|
|
316
|
-
meta
|
|
317
|
-
'graphql.field.coordinates': `${field.parentTypeName}.${fieldName}`,
|
|
318
|
-
'graphql.field.name': fieldName,
|
|
319
|
-
'graphql.field.path': collapsedKey,
|
|
320
|
-
'graphql.field.type': baseTypeName,
|
|
321
|
-
'graphql.source': source,
|
|
322
|
-
},
|
|
435
|
+
meta,
|
|
323
436
|
}, field)
|
|
324
437
|
|
|
325
438
|
field.span = span
|
|
439
|
+
if (rootCtx.config.collapse) {
|
|
440
|
+
field.nextResolveField = rootCtx.resolveFields
|
|
441
|
+
rootCtx.resolveFields = field
|
|
442
|
+
}
|
|
326
443
|
|
|
327
444
|
if (fieldNode && this.config.variables && fieldNode.arguments) {
|
|
328
445
|
const variables = this.#filterVariables(rootCtx, variableValues)
|
|
@@ -358,30 +475,83 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
358
475
|
return filtered
|
|
359
476
|
}
|
|
360
477
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
478
|
+
/**
|
|
479
|
+
* @param {object} rootCtx
|
|
480
|
+
* @param {object} field
|
|
481
|
+
* @param {unknown} error
|
|
482
|
+
* @param {unknown} result
|
|
483
|
+
* @param {boolean} [failed]
|
|
484
|
+
*/
|
|
485
|
+
completeResolveSpan (rootCtx, field, error, result, failed = false) {
|
|
486
|
+
if (rootCtx.resolveFields !== undefined) {
|
|
487
|
+
if (field.endTime !== REUSED_FIELD_END_TIME) {
|
|
488
|
+
field.endTime = rootCtx.executeSpan._getTime()
|
|
489
|
+
}
|
|
490
|
+
if (field.jitPathKey === false) {
|
|
491
|
+
if (failed) recordResolveError(field, error)
|
|
492
|
+
if (this.config.hooks.resolve) {
|
|
493
|
+
field.resolveHookContext = createResolveHookContext(field, field.error, result)
|
|
494
|
+
rootCtx.resolveHooksPending = true
|
|
495
|
+
}
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
if (field.isSharedJitField) {
|
|
499
|
+
if (error) recordResolveError(field, error)
|
|
500
|
+
if (this.config.hooks.resolve) {
|
|
501
|
+
field.resolveHookContext = createResolveHookContext(field, field.error, result)
|
|
502
|
+
rootCtx.jitResolveHooksPending = true
|
|
503
|
+
}
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
this.#completeResolveSpan(field, error, result)
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this.#finishResolveSpan(field, error, result, rootCtx.executeSpan._getTime())
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* @param {object} field
|
|
515
|
+
* @param {unknown} error
|
|
516
|
+
* @param {unknown} result
|
|
517
|
+
* @param {number} endTime
|
|
518
|
+
*/
|
|
519
|
+
#finishResolveSpan (field, error, result, endTime) {
|
|
520
|
+
this.#completeResolveSpan(field, error, result)
|
|
521
|
+
field.span.finish(endTime)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* @param {object} field
|
|
526
|
+
* @param {unknown} error
|
|
527
|
+
* @param {unknown} result
|
|
528
|
+
*/
|
|
529
|
+
#completeResolveSpan (field, error, result) {
|
|
530
|
+
const { span } = field
|
|
364
531
|
if (error) span.setTag('error', error)
|
|
365
532
|
|
|
366
533
|
if (this.config.hooks.resolve) {
|
|
367
|
-
this.config.hooks.resolve(span,
|
|
368
|
-
fieldName: field.fieldName,
|
|
369
|
-
path: field.pathString,
|
|
370
|
-
error: error || null,
|
|
371
|
-
// Any thenable from any realm — keep undefined so the hook doesn't
|
|
372
|
-
// accidentally see the unresolved promise.
|
|
373
|
-
result: typeof result?.then === 'function' ? undefined : result,
|
|
374
|
-
})
|
|
534
|
+
this.config.hooks.resolve(span, createResolveHookContext(field, error, result))
|
|
375
535
|
}
|
|
376
|
-
|
|
377
|
-
span.finish(endTime)
|
|
378
536
|
}
|
|
379
537
|
}
|
|
380
538
|
|
|
381
539
|
// --- resolver wrapping --------------------------------------------------------
|
|
382
540
|
|
|
383
|
-
|
|
384
|
-
|
|
541
|
+
/**
|
|
542
|
+
* @param {GraphQLFieldResolver} resolve
|
|
543
|
+
* @param {boolean} [isJit]
|
|
544
|
+
*/
|
|
545
|
+
function wrapResolve (resolve, isJit = false) {
|
|
546
|
+
/* istanbul ignore next: future GraphQL versions may expose a non-callable resolver entry. */
|
|
547
|
+
if (typeof resolve !== 'function' || (!isJit && patchedResolvers.has(resolve))) return resolve
|
|
548
|
+
|
|
549
|
+
// Replace a schema wrapper with the execution-local JIT variant instead of nesting both.
|
|
550
|
+
resolve = unwrapResolve(resolve)
|
|
551
|
+
if (isJit) {
|
|
552
|
+
const jitResolver = jitResolvers.get(resolve)
|
|
553
|
+
if (jitResolver !== undefined) return jitResolver
|
|
554
|
+
}
|
|
385
555
|
|
|
386
556
|
function resolveAsync (source, args, contextValue, info) {
|
|
387
557
|
const hasIastSub = iastResolveCh.hasSubscribers
|
|
@@ -390,48 +560,57 @@ function wrapResolve (resolve) {
|
|
|
390
560
|
// Combined fast-path: depth=0 AND no IAST/AppSec subscriber means nothing
|
|
391
561
|
// to do — skip rootCtx lookup, path walk, publish gates.
|
|
392
562
|
if (depthDisabled && !hasIastSub && !hasResolverSub) {
|
|
393
|
-
return resolve
|
|
563
|
+
return invokeResolver(resolve, this, arguments, isJit)
|
|
394
564
|
}
|
|
395
565
|
|
|
396
|
-
const rootCtx =
|
|
397
|
-
|
|
566
|
+
const rootCtx = isJit
|
|
567
|
+
? legacyStorage.getStore()?.graphqlRootCtx
|
|
568
|
+
: contexts.get(contextValue) ?? legacyStorage.getStore()?.graphqlRootCtx
|
|
569
|
+
if (!rootCtx) return invokeResolver(resolve, this, arguments, isJit)
|
|
398
570
|
|
|
399
571
|
const infoPath = info?.path
|
|
400
572
|
const config = rootCtx.config
|
|
573
|
+
const traceResolver = !depthDisabled && shouldInstrumentNode(config, infoPath)
|
|
401
574
|
|
|
402
575
|
// pathString built incrementally off the parent's cached value
|
|
403
576
|
// (rootCtx.pathCache, keyed by path node) — avoids re-walking the whole
|
|
404
577
|
// path linked-list on every resolver call, which is O(depth) per call for
|
|
405
|
-
// deeply nested resolvers.
|
|
406
|
-
// record. Collapse-aware: list-index segments become '*'.
|
|
578
|
+
// deeply nested resolvers. Collapse-aware: list-index segments become '*'.
|
|
407
579
|
let pathString
|
|
408
580
|
let collapsedKey
|
|
409
|
-
if (infoPath) {
|
|
410
|
-
|
|
581
|
+
if (infoPath && traceResolver) {
|
|
582
|
+
const pathCache = rootCtx.pathCache ??= new Map()
|
|
583
|
+
pathString = buildCachedPathString(infoPath, pathCache, config.collapse)
|
|
411
584
|
if (config.collapse) collapsedKey = pathString
|
|
412
585
|
}
|
|
413
586
|
|
|
414
587
|
// IAST and AppSec subscribers see EVERY resolver call, regardless of
|
|
415
588
|
// depth or collapse. The depth knob caps span creation only.
|
|
416
589
|
if (hasIastSub) {
|
|
417
|
-
iastResolveCh.publish({ rootCtx, args
|
|
590
|
+
iastResolveCh.publish({ rootCtx, args })
|
|
418
591
|
}
|
|
419
592
|
if (hasResolverSub) {
|
|
420
593
|
resolverStartCh.publish({
|
|
421
594
|
abortController: rootCtx.abortController,
|
|
422
|
-
|
|
595
|
+
createResolverInfo,
|
|
596
|
+
info,
|
|
597
|
+
args,
|
|
423
598
|
})
|
|
424
599
|
}
|
|
425
600
|
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
601
|
+
if (rootCtx.abortController?.signal.aborted) {
|
|
602
|
+
throw new AbortError('Aborted')
|
|
603
|
+
}
|
|
430
604
|
|
|
431
|
-
|
|
605
|
+
if (!traceResolver) {
|
|
606
|
+
return invokeResolver(resolve, this, arguments, isJit)
|
|
432
607
|
}
|
|
433
608
|
|
|
434
|
-
|
|
609
|
+
// Compilations performed while tracing is disabled have no static JIT
|
|
610
|
+
// descriptor. Their resolver-info path nodes are recreated for each
|
|
611
|
+
// resolver, so use the stable path string to preserve span parenting.
|
|
612
|
+
const useStringKey = config.collapse || isJit
|
|
613
|
+
const fieldKey = useStringKey ? pathString : infoPath
|
|
435
614
|
const parentTypeName = info.parentType.name
|
|
436
615
|
let field = rootCtx.fields.get(fieldKey)
|
|
437
616
|
const collapsedField = field
|
|
@@ -457,14 +636,13 @@ function wrapResolve (resolve) {
|
|
|
457
636
|
parentTypeName,
|
|
458
637
|
returnType: info.returnType,
|
|
459
638
|
baseTypeName: getBaseTypeName(info.returnType),
|
|
460
|
-
|
|
461
|
-
args,
|
|
639
|
+
endTime: PENDING_FIELD_END_TIME,
|
|
462
640
|
infoPath,
|
|
641
|
+
jitPathKey: isJit,
|
|
463
642
|
pathString,
|
|
464
643
|
collapsedKey: collapsedKey ?? pathString,
|
|
465
644
|
span: null,
|
|
466
|
-
// Set by startResolveSpan;
|
|
467
|
-
// call only, siblings use parentStore (see the isFirst check below).
|
|
645
|
+
// Set by startResolveSpan; collapsed siblings reuse currentStore.
|
|
468
646
|
parentStore: null,
|
|
469
647
|
currentStore: null,
|
|
470
648
|
}
|
|
@@ -487,38 +665,355 @@ function wrapResolve (resolve) {
|
|
|
487
665
|
} else {
|
|
488
666
|
rootCtx.fields.set(fieldKey, field)
|
|
489
667
|
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (!isFirst) {
|
|
500
|
-
return callInAsyncScope(resolve, this, arguments, rootCtx.abortController, field.parentStore, (err) => {
|
|
501
|
-
if (updateFieldCh.hasSubscribers) {
|
|
502
|
-
updateFieldCh.publish({ rootCtx, field, error: err, pathString: field.pathString })
|
|
503
|
-
}
|
|
504
|
-
})
|
|
668
|
+
} else {
|
|
669
|
+
field.endTime = REUSED_FIELD_END_TIME
|
|
670
|
+
return callInCollapsedScope(
|
|
671
|
+
resolve,
|
|
672
|
+
this,
|
|
673
|
+
arguments,
|
|
674
|
+
field,
|
|
675
|
+
isJit
|
|
676
|
+
)
|
|
505
677
|
}
|
|
506
678
|
|
|
507
679
|
const executeSpan = rootCtx.executeSpan
|
|
508
680
|
const startTime = executeSpan._getTime()
|
|
509
|
-
|
|
681
|
+
rootCtx.plugin.startResolveSpan(field, rootCtx, executeSpan, startTime, info.variableValues)
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* @param {unknown} error
|
|
685
|
+
* @param {unknown} res
|
|
686
|
+
* @param {boolean} failed
|
|
687
|
+
*/
|
|
688
|
+
const finishField = (error, res, failed) => {
|
|
689
|
+
rootCtx.plugin?.completeResolveSpan(rootCtx, field, error, res, failed)
|
|
690
|
+
}
|
|
691
|
+
return callInAsyncScope(resolve, this, arguments, field.currentStore, finishField, isJit)
|
|
692
|
+
}
|
|
510
693
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
694
|
+
if (isJit) {
|
|
695
|
+
jitResolvers.set(resolve, resolveAsync)
|
|
696
|
+
} else {
|
|
697
|
+
patchedResolvers.add(resolveAsync)
|
|
698
|
+
}
|
|
699
|
+
originalResolvers.set(resolveAsync, resolve)
|
|
700
|
+
return resolveAsync
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* @param {GraphQLFieldResolver} resolve
|
|
705
|
+
* @returns {GraphQLFieldResolver}
|
|
706
|
+
*/
|
|
707
|
+
function unwrapResolve (resolve) {
|
|
708
|
+
return originalResolvers.get(resolve) ?? resolve
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @param {object} rootCtx
|
|
713
|
+
* @param {number} descriptorId
|
|
714
|
+
* @param {GraphQLFieldResolver} resolve
|
|
715
|
+
* @param {unknown} self
|
|
716
|
+
* @param {unknown} source
|
|
717
|
+
* @param {Record<string, unknown>} args
|
|
718
|
+
* @param {unknown} contextValue
|
|
719
|
+
* @param {import('graphql').GraphQLResolveInfo} info
|
|
720
|
+
* @returns {unknown}
|
|
721
|
+
*/
|
|
722
|
+
function resolveCompiledJitField (rootCtx, descriptorId, resolve, self, source, args, contextValue, info) {
|
|
723
|
+
const descriptor = rootCtx.jitPlan.fields[descriptorId]
|
|
724
|
+
const config = rootCtx.config
|
|
725
|
+
|
|
726
|
+
if (rootCtx.hasIastSub) {
|
|
727
|
+
iastResolveCh.publish({ rootCtx, args })
|
|
728
|
+
}
|
|
729
|
+
if (rootCtx.hasResolverSub) {
|
|
730
|
+
resolverStartCh.publish({
|
|
731
|
+
abortController: rootCtx.abortController,
|
|
732
|
+
createResolverInfo,
|
|
733
|
+
info,
|
|
734
|
+
args,
|
|
517
735
|
})
|
|
518
736
|
}
|
|
519
737
|
|
|
520
|
-
|
|
521
|
-
|
|
738
|
+
if (rootCtx.abortController?.signal.aborted) {
|
|
739
|
+
throw new AbortError('Aborted')
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const depth = config.countListIndices ? descriptor.pathDepth : descriptor.selectionDepth
|
|
743
|
+
if (rootCtx.depthDisabled || (config.depth >= 0 && config.depth < depth)) {
|
|
744
|
+
return resolve.call(self, source, args, contextValue, info)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const path = config.collapse ? undefined : pathToArray(info.path)
|
|
748
|
+
const pathString = path ? path.join('.') : descriptor.collapsedPath
|
|
749
|
+
let field
|
|
750
|
+
if (config.collapse) {
|
|
751
|
+
field = rootCtx.jitFields[descriptor.id]
|
|
752
|
+
} else {
|
|
753
|
+
const fieldKey = `${descriptor.id}:${pathString}`
|
|
754
|
+
field = rootCtx.jitFieldsByPath.get(fieldKey)
|
|
755
|
+
}
|
|
756
|
+
if (field) {
|
|
757
|
+
field.endTime = REUSED_FIELD_END_TIME
|
|
758
|
+
if (legacyStorage.getStore() !== field.currentStore) {
|
|
759
|
+
legacyStorage.enterWith(field.currentStore)
|
|
760
|
+
}
|
|
761
|
+
return resolve.call(self, source, args, contextValue, info)
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
field = startJitField(
|
|
765
|
+
rootCtx,
|
|
766
|
+
descriptor,
|
|
767
|
+
pathString,
|
|
768
|
+
path ?? descriptor.collapsedPathSegments,
|
|
769
|
+
info.variableValues,
|
|
770
|
+
info.path
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* @param {unknown} error
|
|
775
|
+
* @param {unknown} result
|
|
776
|
+
*/
|
|
777
|
+
const finishField = (error, result) => {
|
|
778
|
+
rootCtx.plugin?.completeResolveSpan(rootCtx, field, error, result)
|
|
779
|
+
}
|
|
780
|
+
return callCompiledJitInAsyncScope(
|
|
781
|
+
resolve,
|
|
782
|
+
self,
|
|
783
|
+
source,
|
|
784
|
+
args,
|
|
785
|
+
contextValue,
|
|
786
|
+
info,
|
|
787
|
+
field.currentStore,
|
|
788
|
+
finishField
|
|
789
|
+
)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* @param {object} rootCtx
|
|
794
|
+
* @param {JitDescriptor} descriptor
|
|
795
|
+
* @param {string} pathString
|
|
796
|
+
* @param {(string | number)[]} path
|
|
797
|
+
* @param {Record<string, unknown> | undefined} variableValues
|
|
798
|
+
* @param {object | undefined} [infoPath]
|
|
799
|
+
* @returns {object}
|
|
800
|
+
*/
|
|
801
|
+
function startJitField (rootCtx, descriptor, pathString, path, variableValues, infoPath) {
|
|
802
|
+
const field = {
|
|
803
|
+
fieldNode: descriptor.fieldNode,
|
|
804
|
+
fieldName: descriptor.fieldName,
|
|
805
|
+
parentTypeName: descriptor.parentTypeName,
|
|
806
|
+
returnType: descriptor.returnType,
|
|
807
|
+
baseTypeName: descriptor.baseTypeName,
|
|
808
|
+
endTime: PENDING_FIELD_END_TIME,
|
|
809
|
+
infoPath,
|
|
810
|
+
pathString,
|
|
811
|
+
collapsedKey: pathString,
|
|
812
|
+
resource: descriptor.resource,
|
|
813
|
+
span: null,
|
|
814
|
+
parentStore: null,
|
|
815
|
+
currentStore: null,
|
|
816
|
+
isSharedJitField: rootCtx.config.collapse && descriptor.pathDepth !== descriptor.selectionDepth,
|
|
817
|
+
tags: rootCtx.config.collapse && !rootCtx.config.source ? descriptor.tags : undefined,
|
|
818
|
+
}
|
|
819
|
+
if (rootCtx.config.collapse) {
|
|
820
|
+
rootCtx.jitFields[descriptor.id] = field
|
|
821
|
+
} else {
|
|
822
|
+
rootCtx.jitFieldsByPath.set(`${descriptor.id}:${pathString}`, field)
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
let parentField = null
|
|
826
|
+
if (descriptor.parentId !== undefined) {
|
|
827
|
+
if (rootCtx.config.collapse) {
|
|
828
|
+
parentField = rootCtx.jitFields[descriptor.parentId]
|
|
829
|
+
} else {
|
|
830
|
+
const parentPath = path.slice(0, -1)
|
|
831
|
+
while (typeof parentPath.at(-1) === 'number') parentPath.pop()
|
|
832
|
+
parentField = rootCtx.jitFieldsByPath.get(`${descriptor.parentId}:${parentPath.join('.')}`)
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const executeSpan = rootCtx.executeSpan
|
|
836
|
+
rootCtx.plugin.startResolveSpan(field, rootCtx, executeSpan, executeSpan._getTime(), variableValues, parentField)
|
|
837
|
+
return field
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* @param {object} rootCtx
|
|
842
|
+
* @param {number} descriptorId
|
|
843
|
+
* @param {unknown} error
|
|
844
|
+
*/
|
|
845
|
+
function recordJitResolverError (rootCtx, descriptorId, error) {
|
|
846
|
+
const field = rootCtx?.jitFields?.[descriptorId]
|
|
847
|
+
if (!field?.isSharedJitField) return
|
|
848
|
+
|
|
849
|
+
recordResolveError(field, error)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* @param {object} rootCtx
|
|
854
|
+
* @param {number} descriptorId
|
|
855
|
+
* @param {Record<string, unknown>} source
|
|
856
|
+
* @param {(string | number)[] | undefined} path
|
|
857
|
+
* @returns {unknown}
|
|
858
|
+
*/
|
|
859
|
+
function resolveJitDefault (rootCtx, descriptorId, source, path) {
|
|
860
|
+
const descriptor = rootCtx.jitPlan.fields[descriptorId]
|
|
861
|
+
const pathString = path ? path.join('.') : descriptor.collapsedPath
|
|
862
|
+
const field = startJitField(
|
|
863
|
+
rootCtx,
|
|
864
|
+
descriptor,
|
|
865
|
+
pathString,
|
|
866
|
+
path ?? descriptor.collapsedPathSegments,
|
|
867
|
+
rootCtx.variableValues
|
|
868
|
+
)
|
|
869
|
+
if (rootCtx.config.collapse && descriptor.pathDepth !== descriptor.selectionDepth) {
|
|
870
|
+
field.endTime = REUSED_FIELD_END_TIME
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
let result
|
|
874
|
+
try {
|
|
875
|
+
result = legacyStorage.run(field.currentStore, () => source[descriptor.fieldName])
|
|
876
|
+
} catch (error) {
|
|
877
|
+
rootCtx.plugin.completeResolveSpan(rootCtx, field, error)
|
|
878
|
+
throw error
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
rootCtx.plugin.completeResolveSpan(rootCtx, field, undefined, result)
|
|
882
|
+
return result
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* @param {object} rootCtx
|
|
887
|
+
* @param {number} descriptorId
|
|
888
|
+
* @param {Record<string, unknown>} source
|
|
889
|
+
* @param {(string | number)[] | undefined} path
|
|
890
|
+
* @returns {unknown}
|
|
891
|
+
*/
|
|
892
|
+
function readJitDefaultInScope (rootCtx, descriptorId, source, path) {
|
|
893
|
+
const descriptor = rootCtx.jitPlan.fields[descriptorId]
|
|
894
|
+
const field = rootCtx.config.collapse
|
|
895
|
+
? rootCtx.jitFields?.[descriptorId]
|
|
896
|
+
: rootCtx.jitFieldsByPath?.get(`${descriptorId}:${path.join('.')}`)
|
|
897
|
+
if (!field) return source[descriptor.fieldName]
|
|
898
|
+
|
|
899
|
+
if (legacyStorage.getStore() !== field.currentStore) {
|
|
900
|
+
legacyStorage.enterWith(field.currentStore)
|
|
901
|
+
}
|
|
902
|
+
return source[descriptor.fieldName]
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* @param {object} rootCtx
|
|
907
|
+
* @param {number} descriptorId
|
|
908
|
+
* @param {Record<string, unknown>} source
|
|
909
|
+
* @param {(string | number)[] | undefined} path
|
|
910
|
+
* @param {ArgumentFactory | undefined} argumentFactory
|
|
911
|
+
* @returns {unknown}
|
|
912
|
+
*/
|
|
913
|
+
function resolveJitDefaultInvocation (rootCtx, descriptorId, source, path, argumentFactory) {
|
|
914
|
+
const descriptor = rootCtx.jitPlan.fields[descriptorId]
|
|
915
|
+
if (rootCtx.hasIastSub || rootCtx.hasResolverSub) {
|
|
916
|
+
if (rootCtx.hasIastSub) {
|
|
917
|
+
iastResolveCh.publish({
|
|
918
|
+
rootCtx,
|
|
919
|
+
args: getJitDefaultArguments(rootCtx, descriptor, argumentFactory, true),
|
|
920
|
+
})
|
|
921
|
+
}
|
|
922
|
+
if (rootCtx.hasResolverSub) {
|
|
923
|
+
resolverStartCh.publish({
|
|
924
|
+
abortController: rootCtx.abortController,
|
|
925
|
+
createResolverInfo: createJitDefaultResolverInfo,
|
|
926
|
+
rootCtx,
|
|
927
|
+
descriptor,
|
|
928
|
+
argumentFactory,
|
|
929
|
+
})
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (rootCtx.abortController?.signal.aborted) {
|
|
933
|
+
throw new AbortError('Aborted')
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const depth = rootCtx.config.countListIndices ? descriptor.pathDepth : descriptor.selectionDepth
|
|
937
|
+
if (rootCtx.depthDisabled || (rootCtx.config.depth >= 0 && rootCtx.config.depth < depth)) {
|
|
938
|
+
if (!rootCtx.depthDisabled && rootCtx.config.collapse) rootCtx.jitFields[descriptorId] = false
|
|
939
|
+
return source[descriptor.fieldName]
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const pathString = path ? path.join('.') : descriptor.collapsedPath
|
|
943
|
+
const field = rootCtx.config.collapse
|
|
944
|
+
? rootCtx.jitFields[descriptorId]
|
|
945
|
+
: rootCtx.jitFieldsByPath.get(`${descriptorId}:${pathString}`)
|
|
946
|
+
if (field === undefined) {
|
|
947
|
+
return resolveJitDefault(rootCtx, descriptorId, source, path)
|
|
948
|
+
}
|
|
949
|
+
return readJitDefaultInScope(rootCtx, descriptorId, source, path)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* @param {object} rootCtx
|
|
954
|
+
* @param {JitDescriptor} descriptor
|
|
955
|
+
* @param {ArgumentFactory | undefined} argumentFactory
|
|
956
|
+
* @param {boolean} mutable
|
|
957
|
+
* @returns {Record<string, unknown> | undefined}
|
|
958
|
+
*/
|
|
959
|
+
function getJitDefaultArguments (rootCtx, descriptor, argumentFactory, mutable) {
|
|
960
|
+
if (mutable) {
|
|
961
|
+
return argumentFactory === undefined
|
|
962
|
+
? {}
|
|
963
|
+
: argumentFactory(rootCtx.variableValues, cloneArgumentValue)
|
|
964
|
+
}
|
|
965
|
+
if (descriptor.hasArgumentVariables) {
|
|
966
|
+
return argumentFactory(rootCtx.variableValues)
|
|
967
|
+
}
|
|
968
|
+
return descriptor.staticArguments
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* @param {unknown} value
|
|
973
|
+
* @param {WeakMap<object, object>} [clones]
|
|
974
|
+
* @returns {unknown}
|
|
975
|
+
*/
|
|
976
|
+
function cloneArgumentValue (value, clones) {
|
|
977
|
+
if (value === null || typeof value !== 'object') return value
|
|
978
|
+
|
|
979
|
+
// Lists get their own branch: Object.keys materializes the index strings and
|
|
980
|
+
// the clone is then filled through them instead of by index.
|
|
981
|
+
if (Array.isArray(value)) {
|
|
982
|
+
const { length } = value
|
|
983
|
+
if (length === 0) return value
|
|
984
|
+
|
|
985
|
+
clones ??= new WeakMap()
|
|
986
|
+
const cached = clones.get(value)
|
|
987
|
+
if (cached !== undefined) return cached
|
|
988
|
+
|
|
989
|
+
const clone = new Array(length)
|
|
990
|
+
clones.set(value, clone)
|
|
991
|
+
for (let index = 0; index < length; index++) {
|
|
992
|
+
clone[index] = cloneArgumentValue(value[index], clones)
|
|
993
|
+
}
|
|
994
|
+
return clone
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const keys = Object.keys(value)
|
|
998
|
+
if (keys.length === 0) return value
|
|
999
|
+
|
|
1000
|
+
clones ??= new WeakMap()
|
|
1001
|
+
const cached = clones.get(value)
|
|
1002
|
+
if (cached !== undefined) return cached
|
|
1003
|
+
|
|
1004
|
+
const clone = {}
|
|
1005
|
+
clones.set(value, clone)
|
|
1006
|
+
for (const name of keys) {
|
|
1007
|
+
clone[name] = cloneArgumentValue(value[name], clones)
|
|
1008
|
+
}
|
|
1009
|
+
return clone
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* @param {GraphQLFieldResolver} resolve
|
|
1014
|
+
*/
|
|
1015
|
+
function wrapJitResolve (resolve) {
|
|
1016
|
+
return wrapResolve(resolve, true)
|
|
522
1017
|
}
|
|
523
1018
|
|
|
524
1019
|
function wrapFields (type, schema) {
|
|
@@ -575,27 +1070,161 @@ function wrapFieldType (field, schema) {
|
|
|
575
1070
|
wrapFields(unwrapped, schema)
|
|
576
1071
|
}
|
|
577
1072
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* @param {GraphQLFieldResolver} resolve
|
|
1075
|
+
* @param {unknown} self
|
|
1076
|
+
* @param {ArgumentsList} args
|
|
1077
|
+
* @param {boolean} isJit
|
|
1078
|
+
* @returns {unknown}
|
|
1079
|
+
*/
|
|
1080
|
+
function invokeResolver (resolve, self, args, isJit) {
|
|
1081
|
+
return isJit
|
|
1082
|
+
? resolve.call(self, args[0], args[1], args[2], args[3])
|
|
1083
|
+
: resolve.apply(self, args)
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* @param {GraphQLFieldResolver} fn
|
|
1088
|
+
* @param {unknown} thisArg
|
|
1089
|
+
* @param {ArgumentsList} args
|
|
1090
|
+
* @param {object} field
|
|
1091
|
+
* @param {boolean} isJit
|
|
1092
|
+
* @returns {unknown}
|
|
1093
|
+
*/
|
|
1094
|
+
function callInCollapsedScope (fn, thisArg, args, field, isJit) {
|
|
1095
|
+
if (!isJit) {
|
|
1096
|
+
field.asyncResource ??= legacyStorage.run(field.currentStore, createResolverAsyncResource)
|
|
1097
|
+
return field.asyncResource.runInAsyncScope(invokeResolver, undefined, fn, thisArg, args, false)
|
|
584
1098
|
}
|
|
585
1099
|
|
|
1100
|
+
if (legacyStorage.getStore() !== field.currentStore) {
|
|
1101
|
+
legacyStorage.enterWith(field.currentStore)
|
|
1102
|
+
}
|
|
1103
|
+
return invokeResolver(fn, thisArg, args, isJit)
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function createResolverAsyncResource () {
|
|
1107
|
+
return new AsyncResource('dd-trace:graphql:resolve', { requireManualDestroy: true })
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* @param {GraphQLFieldResolver} resolve
|
|
1112
|
+
* @param {unknown} self
|
|
1113
|
+
* @param {unknown} source
|
|
1114
|
+
* @param {Record<string, unknown>} args
|
|
1115
|
+
* @param {unknown} contextValue
|
|
1116
|
+
* @param {import('graphql').GraphQLResolveInfo} info
|
|
1117
|
+
* @returns {unknown}
|
|
1118
|
+
*/
|
|
1119
|
+
function invokeCompiledJitResolver (resolve, self, source, args, contextValue, info) {
|
|
1120
|
+
return resolve.call(self, source, args, contextValue, info)
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* @param {GraphQLFieldResolver} resolve
|
|
1125
|
+
* @param {unknown} self
|
|
1126
|
+
* @param {unknown} source
|
|
1127
|
+
* @param {Record<string, unknown>} args
|
|
1128
|
+
* @param {unknown} contextValue
|
|
1129
|
+
* @param {import('graphql').GraphQLResolveInfo} info
|
|
1130
|
+
* @param {object} store
|
|
1131
|
+
* @param {(error: unknown, result?: unknown) => void} callback
|
|
1132
|
+
* @returns {unknown}
|
|
1133
|
+
*/
|
|
1134
|
+
function callCompiledJitInAsyncScope (resolve, self, source, args, contextValue, info, store, callback) {
|
|
586
1135
|
try {
|
|
587
|
-
const result = legacyStorage.run(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1136
|
+
const result = legacyStorage.run(
|
|
1137
|
+
store,
|
|
1138
|
+
invokeCompiledJitResolver,
|
|
1139
|
+
resolve,
|
|
1140
|
+
self,
|
|
1141
|
+
source,
|
|
1142
|
+
args,
|
|
1143
|
+
contextValue,
|
|
1144
|
+
info
|
|
1145
|
+
)
|
|
1146
|
+
if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
1147
|
+
return observeThenable(result, callback)
|
|
1148
|
+
}
|
|
1149
|
+
callback(null, result)
|
|
1150
|
+
return result
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
callback(error)
|
|
1153
|
+
throw error
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Runs the resolver inside `store`, including any code after an internal
|
|
1159
|
+
* `await`. A `.then()` the caller attaches afterward runs outside `store`.
|
|
1160
|
+
*
|
|
1161
|
+
* @param {GraphQLFieldResolver} fn
|
|
1162
|
+
* @param {unknown} thisArg
|
|
1163
|
+
* @param {ArgumentsList} args
|
|
1164
|
+
* @param {object} store
|
|
1165
|
+
* @param {(error: unknown, result?: unknown, failed?: boolean) => void} callback
|
|
1166
|
+
* @param {boolean} isJit
|
|
1167
|
+
* @returns {unknown}
|
|
1168
|
+
*/
|
|
1169
|
+
function callInAsyncScope (fn, thisArg, args, store, callback, isJit) {
|
|
1170
|
+
try {
|
|
1171
|
+
const result = legacyStorage.run(store, invokeResolver, fn, thisArg, args, isJit)
|
|
1172
|
+
if (typeof result?.then === 'function' && (!isJit || (result !== null && typeof result === 'object'))) {
|
|
1173
|
+
return observeThenable(result, callback)
|
|
593
1174
|
}
|
|
594
|
-
|
|
1175
|
+
callback(null, result)
|
|
595
1176
|
return result
|
|
596
|
-
} catch (
|
|
597
|
-
|
|
598
|
-
throw
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
callback(error, undefined, true)
|
|
1179
|
+
throw error
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Observes settlement without standing between the consumer and the resolver's
|
|
1185
|
+
* own `then`. Three constraints hold at once, each with its own regression test:
|
|
1186
|
+
* the foreign `then` runs exactly once (`should not re-execute thenables from
|
|
1187
|
+
* resolvers` — a Mongoose `Query` throws on the second call), the return value
|
|
1188
|
+
* stays then-able, and the consumer receives whatever the foreign `then` returned.
|
|
1189
|
+
*
|
|
1190
|
+
* `return thenable.then(onSettled)` satisfies the first two and breaks the third:
|
|
1191
|
+
* only `Promise.prototype.then` is guaranteed to return a promise, and
|
|
1192
|
+
* `await`/`Promise.resolve` discard that return value, so a thenable returning
|
|
1193
|
+
* `this` or a plain value is legal and invisible everywhere except here — see
|
|
1194
|
+
* `CustomThenableSuccess` in jit.spec.js, which resolves with 'actual' but returns
|
|
1195
|
+
* 'wrong'. Forwarding `then` lets the consumer make that single call itself and
|
|
1196
|
+
* get its real return value, while the settlement callbacks pass through us.
|
|
1197
|
+
*
|
|
1198
|
+
* @param {Thenable} thenable
|
|
1199
|
+
* @param {(error: unknown, result?: unknown, failed?: boolean) => void} callback
|
|
1200
|
+
* @returns {Thenable}
|
|
1201
|
+
*/
|
|
1202
|
+
function observeThenable (thenable, callback) {
|
|
1203
|
+
return {
|
|
1204
|
+
/**
|
|
1205
|
+
* @param {ThenableCallback} [onFulfilled]
|
|
1206
|
+
* @param {ThenableCallback} [onRejected]
|
|
1207
|
+
*/
|
|
1208
|
+
// eslint-disable-next-line unicorn/no-thenable -- graphql-jit intentionally consumes this adapter as a thenable.
|
|
1209
|
+
then (onFulfilled, onRejected) {
|
|
1210
|
+
return thenable.then(
|
|
1211
|
+
/**
|
|
1212
|
+
* @param {unknown} result
|
|
1213
|
+
*/
|
|
1214
|
+
result => {
|
|
1215
|
+
callback(null, result)
|
|
1216
|
+
return onFulfilled ? onFulfilled(result) : result
|
|
1217
|
+
},
|
|
1218
|
+
/**
|
|
1219
|
+
* @param {unknown} error
|
|
1220
|
+
*/
|
|
1221
|
+
error => {
|
|
1222
|
+
callback(error, undefined, true)
|
|
1223
|
+
if (onRejected) return onRejected(error)
|
|
1224
|
+
throw error
|
|
1225
|
+
}
|
|
1226
|
+
)
|
|
1227
|
+
},
|
|
599
1228
|
}
|
|
600
1229
|
}
|
|
601
1230
|
|
|
@@ -670,7 +1299,7 @@ function shouldInstrumentNode (config, path) {
|
|
|
670
1299
|
|
|
671
1300
|
function getParentField (rootCtx, field) {
|
|
672
1301
|
for (let curr = field.infoPath?.prev; curr; curr = curr.prev) {
|
|
673
|
-
const fieldKey = rootCtx.config.collapse ? rootCtx.pathCache.get(curr) : curr
|
|
1302
|
+
const fieldKey = rootCtx.config.collapse || field.jitPathKey ? rootCtx.pathCache.get(curr) : curr
|
|
674
1303
|
const innerField = rootCtx.fields.get(fieldKey)
|
|
675
1304
|
if (innerField) {
|
|
676
1305
|
if (curr.typename === undefined) {
|
|
@@ -691,15 +1320,40 @@ function getParentField (rootCtx, field) {
|
|
|
691
1320
|
return null
|
|
692
1321
|
}
|
|
693
1322
|
|
|
694
|
-
|
|
695
|
-
|
|
1323
|
+
/**
|
|
1324
|
+
* @param {{ info: import('graphql').GraphQLResolveInfo, args: Record<string, unknown> }} data
|
|
1325
|
+
* @returns {Record<string, Record<string, unknown>> | null}
|
|
1326
|
+
*/
|
|
1327
|
+
function createResolverInfo ({ info, args }) {
|
|
1328
|
+
return getResolverInfo(info, args)
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* @param {{
|
|
1333
|
+
* rootCtx: object,
|
|
1334
|
+
* descriptor: JitDescriptor,
|
|
1335
|
+
* argumentFactory: ArgumentFactory | undefined
|
|
1336
|
+
* }} data
|
|
1337
|
+
* @returns {Record<string, Record<string, unknown>> | null}
|
|
1338
|
+
*/
|
|
1339
|
+
function createJitDefaultResolverInfo ({ rootCtx, descriptor, argumentFactory }) {
|
|
1340
|
+
const args = getJitDefaultArguments(rootCtx, descriptor, argumentFactory, false)
|
|
1341
|
+
|
|
1342
|
+
return getResolverInfo(descriptor, args)
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* @param {{ fieldName: string, fieldNodes?: readonly import('graphql').FieldNode[] }} info
|
|
1347
|
+
* @param {Record<string, unknown> | undefined} args
|
|
1348
|
+
* @returns {Record<string, Record<string, unknown>> | null}
|
|
1349
|
+
*/
|
|
696
1350
|
function getResolverInfo (info, args) {
|
|
697
1351
|
let resolverVars = args ? { ...args } : undefined
|
|
698
1352
|
|
|
699
1353
|
const directives = info.fieldNodes?.[0]?.directives
|
|
700
1354
|
if (Array.isArray(directives)) {
|
|
701
1355
|
for (const directive of directives) {
|
|
702
|
-
if (directive.arguments
|
|
1356
|
+
if (!directive.arguments?.length) continue
|
|
703
1357
|
|
|
704
1358
|
const argList = {}
|
|
705
1359
|
for (const argument of directive.arguments) {
|
|
@@ -766,12 +1420,154 @@ function isWeakMapKey (value) {
|
|
|
766
1420
|
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
767
1421
|
}
|
|
768
1422
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return
|
|
1423
|
+
/**
|
|
1424
|
+
* @param {unknown} result
|
|
1425
|
+
* @returns {object | undefined}
|
|
1426
|
+
*/
|
|
1427
|
+
function getIncrementalIterator (result) {
|
|
1428
|
+
if (result === null || typeof result !== 'object') return
|
|
1429
|
+
|
|
1430
|
+
if ('initialResult' in result && typeof result.subsequentResults?.next === 'function') {
|
|
1431
|
+
return result.subsequentResults
|
|
1432
|
+
}
|
|
1433
|
+
if (typeof result.next === 'function' && typeof result[Symbol.asyncIterator] === 'function') {
|
|
1434
|
+
return result
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* @param {object} iterator
|
|
1440
|
+
* @param {(result?: unknown, error?: unknown, cancelled?: boolean, complete?: boolean) => void} observe
|
|
1441
|
+
*/
|
|
1442
|
+
function observeIncrementalIterator (iterator, observe) {
|
|
1443
|
+
let activeObserver = observe
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* @param {unknown} [result]
|
|
1447
|
+
* @param {unknown} [error]
|
|
1448
|
+
* @param {boolean} [cancelled]
|
|
1449
|
+
* @param {boolean} [complete]
|
|
1450
|
+
*/
|
|
1451
|
+
function notify (result, error, cancelled, complete) {
|
|
1452
|
+
if (!activeObserver) return
|
|
1453
|
+
|
|
1454
|
+
const callback = activeObserver
|
|
1455
|
+
if (complete) activeObserver = undefined
|
|
1456
|
+
callback(result, error, cancelled, complete)
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Orchestrion cannot reach Promise-resolved or nested iterators. Shimmer adds measurable work to each execution.
|
|
1460
|
+
wrapIteratorMethod(iterator, 'next', false, notify)
|
|
1461
|
+
wrapIteratorMethod(iterator, 'return', true, notify)
|
|
1462
|
+
wrapIteratorMethod(iterator, 'throw', true, notify)
|
|
1463
|
+
wrapIteratorMethod(iterator, asyncDisposeSymbol, true, notify)
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/**
|
|
1467
|
+
* @param {object} iterator
|
|
1468
|
+
* @param {string | symbol} name
|
|
1469
|
+
* @param {boolean} terminal
|
|
1470
|
+
* @param {(result?: unknown, error?: unknown, cancelled?: boolean, complete?: boolean) => void} notify
|
|
1471
|
+
*/
|
|
1472
|
+
function wrapIteratorMethod (iterator, name, terminal, notify) {
|
|
1473
|
+
const method = iterator[name]
|
|
1474
|
+
if (typeof method !== 'function') return
|
|
1475
|
+
|
|
1476
|
+
iterator[name] = function () {
|
|
1477
|
+
const result = method.apply(this, arguments)
|
|
1478
|
+
result.then((value) => {
|
|
1479
|
+
const complete = terminal || value?.done || value?.value?.hasNext === false
|
|
1480
|
+
notify(value?.value, undefined, terminal, complete)
|
|
1481
|
+
}, error => notify(undefined, error, true, true))
|
|
1482
|
+
return result
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* @param {object} config
|
|
1488
|
+
* @param {DatadogSpan} span
|
|
1489
|
+
* @param {unknown} result
|
|
1490
|
+
*/
|
|
1491
|
+
function tagIncrementalResult (config, span, result) {
|
|
1492
|
+
if (result === null || typeof result !== 'object') return
|
|
1493
|
+
|
|
1494
|
+
tagExecutionErrors(config, span, result.errors)
|
|
1495
|
+
if (result.incremental) {
|
|
1496
|
+
for (const entry of result.incremental) {
|
|
1497
|
+
tagExecutionErrors(config, span, entry.errors)
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* @param {object} config
|
|
1504
|
+
* @param {DatadogSpan} span
|
|
1505
|
+
* @param {unknown[] | undefined} errors
|
|
1506
|
+
*/
|
|
1507
|
+
function tagExecutionErrors (config, span, errors) {
|
|
1508
|
+
if (!errors?.length) return
|
|
1509
|
+
|
|
1510
|
+
if (!span.context().getTag('error')) span.setTag('error', errors[0])
|
|
1511
|
+
for (const error of errors) {
|
|
1512
|
+
extractErrorIntoSpanEvent(config, span, error)
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* @param {object} rootCtx
|
|
1518
|
+
* @param {boolean} [finishPendingFields]
|
|
1519
|
+
*/
|
|
1520
|
+
function releaseRootContext (rootCtx, finishPendingFields) {
|
|
1521
|
+
const endTime = rootCtx.executeSpan._getTime()
|
|
1522
|
+
if (rootCtx.resolveFields !== undefined) {
|
|
1523
|
+
if (rootCtx.resolveHooksPending || rootCtx.jitResolveHooksPending) {
|
|
1524
|
+
runResolveHooks(rootCtx)
|
|
1525
|
+
}
|
|
1526
|
+
for (let field = rootCtx.resolveFields; field; field = field.nextResolveField) {
|
|
1527
|
+
field.asyncResource?.emitDestroy()
|
|
1528
|
+
field.span.finish(field.endTime > PENDING_FIELD_END_TIME ? field.endTime : endTime)
|
|
1529
|
+
}
|
|
1530
|
+
} else if (finishPendingFields && rootCtx.fields !== undefined) {
|
|
1531
|
+
for (const field of rootCtx.fields.values()) {
|
|
1532
|
+
field.span.finish(endTime)
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Resolver-created async resources retain copied stores that all share this owner.
|
|
1537
|
+
for (const key of Object.keys(rootCtx)) {
|
|
1538
|
+
rootCtx[key] = undefined
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* @param {object} rootCtx
|
|
1544
|
+
*/
|
|
1545
|
+
function runResolveHooks (rootCtx) {
|
|
1546
|
+
rootCtx.resolveHooksPending = undefined
|
|
1547
|
+
rootCtx.jitResolveHooksPending = undefined
|
|
1548
|
+
|
|
1549
|
+
for (let field = rootCtx.resolveFields; field; field = field.nextResolveField) {
|
|
1550
|
+
if (field.resolveHookContext === undefined) continue
|
|
1551
|
+
|
|
1552
|
+
rootCtx.config.hooks.resolve(field.span, field.resolveHookContext)
|
|
1553
|
+
field.resolveHookContext = undefined
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* @param {object} field
|
|
1559
|
+
* @param {unknown} error
|
|
1560
|
+
* @param {unknown} result
|
|
1561
|
+
* @returns {{ fieldName: string, path: string, error: unknown, result: unknown }}
|
|
1562
|
+
*/
|
|
1563
|
+
function createResolveHookContext (field, error, result) {
|
|
1564
|
+
return {
|
|
1565
|
+
fieldName: field.fieldName,
|
|
1566
|
+
path: field.pathString,
|
|
1567
|
+
error: error || null,
|
|
1568
|
+
// Any thenable from any realm must stay undefined so the hook does not see an unresolved promise.
|
|
1569
|
+
result: error || typeof result?.then === 'function' ? undefined : result,
|
|
1570
|
+
}
|
|
775
1571
|
}
|
|
776
1572
|
|
|
777
1573
|
// Fallback resolver used when graphql.execute() is called without an explicit
|
|
@@ -801,3 +1597,9 @@ function addVariableTags (config, span, variableValues) {
|
|
|
801
1597
|
}
|
|
802
1598
|
|
|
803
1599
|
module.exports = GraphQLExecutePlugin
|
|
1600
|
+
module.exports.readJitDefaultInScope = readJitDefaultInScope
|
|
1601
|
+
module.exports.recordJitResolverError = recordJitResolverError
|
|
1602
|
+
module.exports.resolveCompiledJitField = resolveCompiledJitField
|
|
1603
|
+
module.exports.resolveJitDefaultInvocation = resolveJitDefaultInvocation
|
|
1604
|
+
module.exports.unwrapJitResolve = unwrapResolve
|
|
1605
|
+
module.exports.wrapJitResolve = wrapJitResolve
|