diff --git a/.changeset/@graphql-hive_gateway-1636-dependencies.md b/.changeset/@graphql-hive_gateway-1636-dependencies.md new file mode 100644 index 0000000000..4103f4ced0 --- /dev/null +++ b/.changeset/@graphql-hive_gateway-1636-dependencies.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/gateway': patch +--- + +dependencies updates: + +- Updated dependency [`graphql-yoga@^5.17.0` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/5.17.0) (from `^5.16.2`, in `dependencies`) diff --git a/.changeset/@graphql-hive_gateway-runtime-1636-dependencies.md b/.changeset/@graphql-hive_gateway-runtime-1636-dependencies.md new file mode 100644 index 0000000000..bbf6c5020c --- /dev/null +++ b/.changeset/@graphql-hive_gateway-runtime-1636-dependencies.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/gateway-runtime': patch +--- + +dependencies updates: + +- Updated dependency [`graphql-yoga@^5.17.0` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/5.17.0) (from `^5.16.2`, in `dependencies`) diff --git a/.changeset/@graphql-hive_gateway-testing-1636-dependencies.md b/.changeset/@graphql-hive_gateway-testing-1636-dependencies.md new file mode 100644 index 0000000000..f2b5d0ef18 --- /dev/null +++ b/.changeset/@graphql-hive_gateway-testing-1636-dependencies.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/gateway-testing': patch +--- + +dependencies updates: + +- Updated dependency [`graphql-yoga@^5.17.0` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/5.17.0) (from `^5.16.2`, in `dependencies`) diff --git a/.changeset/@graphql-hive_plugin-opentelemetry-1636-dependencies.md b/.changeset/@graphql-hive_plugin-opentelemetry-1636-dependencies.md new file mode 100644 index 0000000000..655af5d381 --- /dev/null +++ b/.changeset/@graphql-hive_plugin-opentelemetry-1636-dependencies.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/plugin-opentelemetry': patch +--- + +dependencies updates: + +- Added dependency [`@graphql-tools/executor@^1.4.9` ↗︎](https://www.npmjs.com/package/@graphql-tools/executor/v/1.4.9) (to `dependencies`) diff --git a/.changeset/@graphql-mesh_fusion-runtime-1636-dependencies.md b/.changeset/@graphql-mesh_fusion-runtime-1636-dependencies.md new file mode 100644 index 0000000000..d521e35535 --- /dev/null +++ b/.changeset/@graphql-mesh_fusion-runtime-1636-dependencies.md @@ -0,0 +1,7 @@ +--- +'@graphql-mesh/fusion-runtime': patch +--- + +dependencies updates: + +- Updated dependency [`graphql-yoga@^5.17.0` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/5.17.0) (from `^5.16.2`, in `dependencies`) diff --git a/.changeset/little-ties-jog.md b/.changeset/little-ties-jog.md new file mode 100644 index 0000000000..9fad7e90fc --- /dev/null +++ b/.changeset/little-ties-jog.md @@ -0,0 +1,22 @@ +--- +'@graphql-hive/plugin-opentelemetry': minor +'@graphql-hive/gateway-runtime': minor +--- + +Expose GraphQLError as OpenTelemetry Events. + +Errors contains in the result of a graphql operation are now reported as standalone OpenTelemetry +Events (name `graphql.error`) instead of OpenTelemetry Exceptions. + +This is aligned with the guidance of the Graphql OpenTelemetry working group. + +It allows to add more graphql specific attributes to errors reported in a response: + +- `message`: The error message +- `path`: The path in the operation document from which the error originated +- `locations`: The list of related locations in the document source +- `coordinate`: The schema coordinate of the resolver which is the source of the error + +This brings the experimental support of the `coordinate` error attribute in the Yoga Runtime. For +security reason, this attribute is purposefully not serialized, to avoid leaking schema information +to clients. diff --git a/e2e/auto-type-merging/package.json b/e2e/auto-type-merging/package.json index 123fe49b03..1be2d608fd 100644 --- a/e2e/auto-type-merging/package.json +++ b/e2e/auto-type-merging/package.json @@ -5,7 +5,7 @@ "@graphql-mesh/compose-cli": "^1.5.3", "@omnigraph/openapi": "^0.109.23", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/aws-sigv4/package.json b/e2e/aws-sigv4/package.json index 3d61feb72c..e78e0f5d15 100644 --- a/e2e/aws-sigv4/package.json +++ b/e2e/aws-sigv4/package.json @@ -6,7 +6,7 @@ "aws4-express": "^0.12.0", "express": "^5.0.0", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/demand-control/package.json b/e2e/demand-control/package.json index e56516c7ec..52a8dfbded 100644 --- a/e2e/demand-control/package.json +++ b/e2e/demand-control/package.json @@ -4,7 +4,7 @@ "dependencies": { "@apollo/subgraph": "^2.11.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/extra-fields/package.json b/e2e/extra-fields/package.json index 7be2f7897c..3a6fe82d6b 100644 --- a/e2e/extra-fields/package.json +++ b/e2e/extra-fields/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/graphos-polling/package.json b/e2e/graphos-polling/package.json index 2337c92d93..3fe112c401 100644 --- a/e2e/graphos-polling/package.json +++ b/e2e/graphos-polling/package.json @@ -6,7 +6,7 @@ "@apollo/subgraph": "^2.11.3", "fastify": "^5.6.2", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pino-pretty": "^13.1.2" } } diff --git a/e2e/hmac-auth-https/package.json b/e2e/hmac-auth-https/package.json index e36dd875ae..bfc262df52 100644 --- a/e2e/hmac-auth-https/package.json +++ b/e2e/hmac-auth-https/package.json @@ -14,6 +14,6 @@ "@graphql-mesh/hmac-upstream-signature": "workspace:^", "@graphql-mesh/plugin-jwt-auth": "workspace:^", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2" + "graphql-yoga": "^5.17.0" } } diff --git a/e2e/interface-additional-resolvers/package.json b/e2e/interface-additional-resolvers/package.json index f5161d4e4f..51e1f2bd6f 100644 --- a/e2e/interface-additional-resolvers/package.json +++ b/e2e/interface-additional-resolvers/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/opentelemetry/opentelemetry.e2e.ts b/e2e/opentelemetry/opentelemetry.e2e.ts index a508c8a651..d1dff3588d 100644 --- a/e2e/opentelemetry/opentelemetry.e2e.ts +++ b/e2e/opentelemetry/opentelemetry.e2e.ts @@ -55,11 +55,17 @@ type JaegerTraceResource = { tags: JaegerTraceTag[]; }; +type JaegerTraceLog = { + timestamp: number; + fields: JaegerTraceTag[]; +}; + type JaegerTraceSpan = { traceID: string; spanID: string; operationName: string; tags: Array; + logs: Array; references: Array<{ refType: string; spanID: string; traceID: string }>; }; @@ -402,6 +408,30 @@ describe('OpenTelemetry', () => { expect(relevantTrace?.spans).toContainEqual( expect.objectContaining({ operationName: 'POST /graphql' }), ); + + const operationSpan = relevantTrace!.spans.find( + (span) => span.operationName === 'graphql.operation', + ); + + expect(operationSpan?.logs).toContainEqual( + expect.objectContaining({ + fields: expect.arrayContaining([ + expect.objectContaining({ + key: 'event', + value: 'graphql.error', + }), + expect.objectContaining({ + key: 'hive.graphql.error.locations', + value: '["1:13"]', + }), + expect.objectContaining({ + key: 'hive.graphql.error.message', + value: 'Syntax Error: Expected Name, found .', + }), + ]), + }), + ); + expect(relevantTrace?.spans).toContainEqual( expect.objectContaining({ operationName: 'graphql.parse', @@ -416,7 +446,7 @@ describe('OpenTelemetry', () => { }), expect.objectContaining({ key: 'otel.status_description', - value: 'Syntax Error: Expected Name, found .', + value: 'GraphQL Parse Error', }), expect.objectContaining({ key: 'hive.graphql.error.count', @@ -477,6 +507,53 @@ describe('OpenTelemetry', () => { expect(relevantTrace?.spans).toContainEqual( expect.objectContaining({ operationName: 'POST /graphql' }), ); + expect(relevantTrace?.spans).toContainEqual( + expect.objectContaining({ + operationName: 'graphql.operation', + tags: expect.arrayContaining([ + expect.objectContaining({ + key: 'otel.status_code', + value: 'ERROR', + }), + expect.objectContaining({ + key: 'error', + value: true, + }), + expect.objectContaining({ + key: 'otel.status_description', + value: 'GraphQL Validation Error', + }), + expect.objectContaining({ + key: 'hive.graphql.error.count', + value: 1, + }), + ]), + }), + ); + const operationSpan = relevantTrace!.spans.find( + (span) => span.operationName === 'graphql.operation', + ); + + expect(operationSpan?.logs).toContainEqual( + expect.objectContaining({ + fields: expect.arrayContaining([ + expect.objectContaining({ + key: 'event', + value: 'graphql.error', + }), + expect.objectContaining({ + key: 'hive.graphql.error.locations', + value: '["1:9"]', + }), + expect.objectContaining({ + key: 'hive.graphql.error.message', + value: + 'Cannot query field "nonExistentField" on type "Query".', + }), + ]), + }), + ); + expect(relevantTrace?.spans).toContainEqual( expect.objectContaining({ operationName: 'graphql.parse' }), ); @@ -494,8 +571,7 @@ describe('OpenTelemetry', () => { }), expect.objectContaining({ key: 'otel.status_description', - value: - 'Cannot query field "nonExistentField" on type "Query".', + value: 'GraphQL Validation Error', }), expect.objectContaining({ key: 'hive.graphql.error.count', diff --git a/e2e/opentelemetry/package.json b/e2e/opentelemetry/package.json index 78d9a9f115..2ea9ed3679 100644 --- a/e2e/opentelemetry/package.json +++ b/e2e/opentelemetry/package.json @@ -6,7 +6,7 @@ "@apollo/subgraph": "^2.11.3", "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/polling/package.json b/e2e/polling/package.json index 0dfb3857a4..3f79717935 100644 --- a/e2e/polling/package.json +++ b/e2e/polling/package.json @@ -5,6 +5,6 @@ "@graphql-mesh/compose-cli": "^1.5.3", "express": "^5.0.0", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2" + "graphql-yoga": "^5.17.0" } } diff --git a/e2e/retry-timeout/package.json b/e2e/retry-timeout/package.json index cc4e6f853e..cdd4ec991e 100644 --- a/e2e/retry-timeout/package.json +++ b/e2e/retry-timeout/package.json @@ -5,7 +5,7 @@ "@apollo/subgraph": "^2.11.3", "@graphql-hive/gateway": "workspace:*", "graphql": "16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/subscriptions-with-transforms/package.json b/e2e/subscriptions-with-transforms/package.json index 4d7f8199f4..9c2898962b 100644 --- a/e2e/subscriptions-with-transforms/package.json +++ b/e2e/subscriptions-with-transforms/package.json @@ -5,7 +5,7 @@ "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "16.12.0", "graphql-sse": "^2.6.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/e2e/type-merging-batching/package.json b/e2e/type-merging-batching/package.json index 5841840721..2064115d41 100644 --- a/e2e/type-merging-batching/package.json +++ b/e2e/type-merging-batching/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" } } diff --git a/examples/extra-fields/example.tar.gz b/examples/extra-fields/example.tar.gz index 45fa709ed8..afb258f439 100644 Binary files a/examples/extra-fields/example.tar.gz and b/examples/extra-fields/example.tar.gz differ diff --git a/examples/extra-fields/package-lock.json b/examples/extra-fields/package-lock.json index 0594eb3161..e7b9dace30 100644 --- a/examples/extra-fields/package-lock.json +++ b/examples/extra-fields/package-lock.json @@ -9,7 +9,7 @@ "@graphql-hive/gateway": "^2.1.23", "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/examples/extra-fields/package.json b/examples/extra-fields/package.json index 82795b2766..92db25a393 100644 --- a/examples/extra-fields/package.json +++ b/examples/extra-fields/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1", "@graphql-hive/gateway": "^2.1.23" }, diff --git a/examples/hmac-auth-https/example.tar.gz b/examples/hmac-auth-https/example.tar.gz index acc4848a81..d6701781b2 100644 Binary files a/examples/hmac-auth-https/example.tar.gz and b/examples/hmac-auth-https/example.tar.gz differ diff --git a/examples/hmac-auth-https/package-lock.json b/examples/hmac-auth-https/package-lock.json index 4fe8f26370..058854a196 100644 --- a/examples/hmac-auth-https/package-lock.json +++ b/examples/hmac-auth-https/package-lock.json @@ -16,7 +16,7 @@ "@graphql-mesh/hmac-upstream-signature": "^2.0.8", "@graphql-mesh/plugin-jwt-auth": "^2.0.9", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2" + "graphql-yoga": "^5.17.0" }, "devDependencies": { "tsx": "^4.20.3" diff --git a/examples/hmac-auth-https/package.json b/examples/hmac-auth-https/package.json index 08840701af..f8929717cc 100644 --- a/examples/hmac-auth-https/package.json +++ b/examples/hmac-auth-https/package.json @@ -18,7 +18,7 @@ "@graphql-mesh/hmac-upstream-signature": "^2.0.8", "@graphql-mesh/plugin-jwt-auth": "^2.0.9", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2" + "graphql-yoga": "^5.17.0" }, "devDependencies": { "tsx": "^4.20.3" diff --git a/examples/interface-additional-resolvers/example.tar.gz b/examples/interface-additional-resolvers/example.tar.gz index 2a3786903c..a982568cf9 100644 Binary files a/examples/interface-additional-resolvers/example.tar.gz and b/examples/interface-additional-resolvers/example.tar.gz differ diff --git a/examples/interface-additional-resolvers/package-lock.json b/examples/interface-additional-resolvers/package-lock.json index d6815b4553..4bcf9e43e9 100644 --- a/examples/interface-additional-resolvers/package-lock.json +++ b/examples/interface-additional-resolvers/package-lock.json @@ -9,7 +9,7 @@ "@graphql-hive/gateway": "^2.1.23", "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/examples/interface-additional-resolvers/package.json b/examples/interface-additional-resolvers/package.json index 5dcfb1a040..95d6140e6b 100644 --- a/examples/interface-additional-resolvers/package.json +++ b/examples/interface-additional-resolvers/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1", "@graphql-hive/gateway": "^2.1.23" }, diff --git a/examples/subscriptions-with-transforms/example.tar.gz b/examples/subscriptions-with-transforms/example.tar.gz index 106350ef89..1bb5ad208d 100644 Binary files a/examples/subscriptions-with-transforms/example.tar.gz and b/examples/subscriptions-with-transforms/example.tar.gz differ diff --git a/examples/subscriptions-with-transforms/package-lock.json b/examples/subscriptions-with-transforms/package-lock.json index 6a945c0d16..58d5f33203 100644 --- a/examples/subscriptions-with-transforms/package-lock.json +++ b/examples/subscriptions-with-transforms/package-lock.json @@ -10,7 +10,7 @@ "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "16.12.0", "graphql-sse": "^2.6.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/examples/subscriptions-with-transforms/package.json b/examples/subscriptions-with-transforms/package.json index d940afbc18..a994bd4c26 100644 --- a/examples/subscriptions-with-transforms/package.json +++ b/examples/subscriptions-with-transforms/package.json @@ -5,7 +5,7 @@ "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "16.12.0", "graphql-sse": "^2.6.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1", "@graphql-hive/gateway": "^2.1.23" }, diff --git a/examples/type-merging-batching/example.tar.gz b/examples/type-merging-batching/example.tar.gz index 7dd34e5417..9bfefb2132 100644 Binary files a/examples/type-merging-batching/example.tar.gz and b/examples/type-merging-batching/example.tar.gz differ diff --git a/examples/type-merging-batching/package-lock.json b/examples/type-merging-batching/package-lock.json index 4d2b3bae3a..284513ecb5 100644 --- a/examples/type-merging-batching/package-lock.json +++ b/examples/type-merging-batching/package-lock.json @@ -9,7 +9,7 @@ "@graphql-hive/gateway": "^2.1.23", "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/examples/type-merging-batching/package.json b/examples/type-merging-batching/package.json index 960d879860..67d04305d7 100644 --- a/examples/type-merging-batching/package.json +++ b/examples/type-merging-batching/package.json @@ -4,7 +4,7 @@ "dependencies": { "@graphql-mesh/compose-cli": "^1.5.3", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1", "@graphql-hive/gateway": "^2.1.23" }, diff --git a/packages/batch-execute/src/mergeRequests.ts b/packages/batch-execute/src/mergeRequests.ts index 73c333ff11..8bc6bab2be 100644 --- a/packages/batch-execute/src/mergeRequests.ts +++ b/packages/batch-execute/src/mergeRequests.ts @@ -1,5 +1,6 @@ // adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js +import { abortSignalAll } from '@graphql-hive/signal'; import { ExecutionRequest, getOperationASTFromRequest, @@ -70,12 +71,19 @@ export function mergeRequests( const mergedSelections: Array = []; const mergedFragmentDefinitions: Array = []; let mergedExtensions: Record = Object.create(null); + let schemaCoordinateInErrors: boolean | undefined = false; + const signals: AbortSignal[] = []; for (let index = 0; index < requests.length; index++) { const request = requests[index]; if (request) { + schemaCoordinateInErrors ||= request.schemaCoordinateInErrors; const prefixedRequests = prefixRequest(createPrefix(index), request); + if (request.signal) { + signals.push(request.signal); + } + for (const def of prefixedRequests.document.definitions) { if (isOperationDefinition(def)) { mergedSelections.push(...def.selectionSet.selections); @@ -129,6 +137,8 @@ export function mergeRequests( info: firstRequest.info, operationType, rootValue: firstRequest.rootValue, + signal: abortSignalAll(signals), + schemaCoordinateInErrors, }; } diff --git a/packages/executors/http/package.json b/packages/executors/http/package.json index 1c26d83e4b..1f7bc8be9b 100644 --- a/packages/executors/http/package.json +++ b/packages/executors/http/package.json @@ -55,7 +55,7 @@ "@whatwg-node/disposablestack": "^0.0.6", "extract-files": "13.0.0", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pkgroll": "2.21.4" }, "sideEffects": false diff --git a/packages/fusion-runtime/package.json b/packages/fusion-runtime/package.json index fba57eab9e..3323732087 100644 --- a/packages/fusion-runtime/package.json +++ b/packages/fusion-runtime/package.json @@ -61,7 +61,7 @@ "@graphql-tools/wrap": "workspace:^", "@whatwg-node/disposablestack": "^0.0.6", "@whatwg-node/promise-helpers": "^1.3.2", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/packages/gateway/package.json b/packages/gateway/package.json index 591b12013f..2cfdd44456 100644 --- a/packages/gateway/package.json +++ b/packages/gateway/package.json @@ -128,7 +128,7 @@ "commander": "^14.0.2", "dotenv": "^17.2.3", "graphql-ws": "^6.0.6", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1", "ws": "^8.18.3" }, diff --git a/packages/plugins/aws-sigv4/package.json b/packages/plugins/aws-sigv4/package.json index 68baf5f10c..da295e1573 100644 --- a/packages/plugins/aws-sigv4/package.json +++ b/packages/plugins/aws-sigv4/package.json @@ -53,7 +53,7 @@ "@graphql-hive/gateway-runtime": "workspace:*", "@types/aws4": "^1.11.6", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pkgroll": "2.21.4" }, "sideEffects": false diff --git a/packages/plugins/hmac-upstream-signature/package.json b/packages/plugins/hmac-upstream-signature/package.json index fac6bd9e69..b6b710461b 100644 --- a/packages/plugins/hmac-upstream-signature/package.json +++ b/packages/plugins/hmac-upstream-signature/package.json @@ -54,7 +54,7 @@ "devDependencies": { "@graphql-hive/gateway-runtime": "workspace:*", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pkgroll": "2.21.4" }, "sideEffects": false diff --git a/packages/plugins/jwt-auth/package.json b/packages/plugins/jwt-auth/package.json index 11c8b2f3c4..630b928dfd 100644 --- a/packages/plugins/jwt-auth/package.json +++ b/packages/plugins/jwt-auth/package.json @@ -51,7 +51,7 @@ "@envelop/core": "^5.4.0", "@envelop/generic-auth": "^11.0.0", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "jsonwebtoken": "9.0.3", "pkgroll": "2.21.4" }, diff --git a/packages/plugins/opentelemetry/package.json b/packages/plugins/opentelemetry/package.json index 5d2303c877..05db90cee2 100644 --- a/packages/plugins/opentelemetry/package.json +++ b/packages/plugins/opentelemetry/package.json @@ -69,6 +69,7 @@ "@graphql-mesh/transport-common": "workspace:^", "@graphql-mesh/types": "^0.104.16", "@graphql-mesh/utils": "^0.104.16", + "@graphql-tools/executor": "^1.4.9", "@graphql-tools/utils": "^10.10.3", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", @@ -89,7 +90,7 @@ "devDependencies": { "@whatwg-node/server": "^0.10.17", "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pkgroll": "2.21.4", "rimraf": "^6.1.0", "rollup": "^4.53.2", diff --git a/packages/plugins/opentelemetry/src/attributes.ts b/packages/plugins/opentelemetry/src/attributes.ts index 3916ed89fd..489121c4d9 100644 --- a/packages/plugins/opentelemetry/src/attributes.ts +++ b/packages/plugins/opentelemetry/src/attributes.ts @@ -25,6 +25,15 @@ export const SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH = 'hive.graphql.operation.hash'; export const SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT = 'hive.graphql.error.count'; export const SEMATTRS_HIVE_GRAPHQL_ERROR_CODES = 'hive.graphql.error.codes'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATES = + 'hive.graphql.error.coordinates'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_CODE = 'hive.graphql.error.code'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATE = + 'hive.graphql.error.coordinate'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_PATH = 'hive.graphql.error.path'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_MESSAGE = 'hive.graphql.error.message'; +export const SEMATTRS_HIVE_GRAPHQL_ERROR_LOCATIONS = + 'hive.graphql.error.locations'; // Gateway-specific attributes export const SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME = diff --git a/packages/plugins/opentelemetry/src/hive-span-processor.ts b/packages/plugins/opentelemetry/src/hive-span-processor.ts index d0c5323073..66586a7671 100644 --- a/packages/plugins/opentelemetry/src/hive-span-processor.ts +++ b/packages/plugins/opentelemetry/src/hive-span-processor.ts @@ -9,11 +9,7 @@ import { } from '@opentelemetry/sdk-trace-base'; import type { SpanImpl } from '@opentelemetry/sdk-trace-base/build/src/Span'; import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; -import { - SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES, - SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, - SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, -} from './attributes'; +import { SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES } from './attributes'; export type HiveTracingSpanProcessorOptions = | { @@ -152,11 +148,6 @@ export class HiveTracingSpanProcessor implements SpanProcessor { return; } - if (SPANS_WITH_ERRORS.includes(span.name)) { - copyAttribute(span, operationSpan, SEMATTRS_HIVE_GRAPHQL_ERROR_CODES); - copyAttribute(span, operationSpan, SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT); - } - if (span.name === 'graphql.execute') { copyAttribute( span, @@ -208,9 +199,3 @@ function isOperationSpan(span: Span): boolean { const followingChar = span.name.at(17); return !followingChar || followingChar === ' '; } - -const SPANS_WITH_ERRORS = [ - 'graphql.parse', - 'graphql.validate', - 'graphql.execute', -]; diff --git a/packages/plugins/opentelemetry/src/plugin.ts b/packages/plugins/opentelemetry/src/plugin.ts index 701f1a5b2b..6b159b6a7d 100644 --- a/packages/plugins/opentelemetry/src/plugin.ts +++ b/packages/plugins/opentelemetry/src/plugin.ts @@ -8,6 +8,7 @@ import { import { getHeadersObj } from '@graphql-mesh/utils'; import { ExecutionRequest, fakePromise } from '@graphql-tools/utils'; import { unfakePromise } from '@whatwg-node/promise-helpers'; +import { useErrorCoordinate } from 'graphql-yoga'; import { context, hive, @@ -36,6 +37,7 @@ import { createSchemaLoadingSpan, createSubgraphExecuteSpan, createUpstreamHttpFetchSpan, + isGraphQLError, OperationHashingFn, recordCacheError, recordCacheEvent, @@ -501,7 +503,9 @@ export function useOpenTelemetry( try { wrapped(); } catch (err) { - registerException(forOperation.otel!.current, err); + if (err instanceof Error && !isGraphQLError(err)) { + registerException(forOperation.otel!.current, err); + } throw err; } finally { trace.getSpan(forOperation.otel!.current)?.end(); @@ -702,6 +706,11 @@ export function useOpenTelemetry( }, }, + onPluginInit({ addPlugin }) { + // @ts-expect-error Yoga plugin incompatible types with Gateway plugin. + addPlugin(useErrorCoordinate()); + }, + onYogaInit({ yoga }) { //TODO remove this when Yoga will also use the new Logger API pluginLogger ??= new Logger({ @@ -837,6 +846,7 @@ export function useOpenTelemetry( return ({ result }) => { setGraphQLParseAttributes({ ctx: getContext(state), + operationCtx: state.forOperation.otel!.root, operationName: gqlCtx.params.operationName, query: gqlCtx.params.query?.trim(), result, @@ -863,6 +873,7 @@ export function useOpenTelemetry( return ({ result }) => { setGraphQLValidateAttributes({ ctx: getContext(state), + operationCtx: state.forOperation.otel!.root, result, document: params.documentAST, operationName: gqlCtx.params.operationName, @@ -891,6 +902,7 @@ export function useOpenTelemetry( setGraphQLExecutionResultAttributes({ ctx, result, + operationCtx: state.forOperation.otel!.root, subgraphNames: state.forOperation.subgraphNames, }); }, diff --git a/packages/plugins/opentelemetry/src/setup.ts b/packages/plugins/opentelemetry/src/setup.ts index dfbdbea061..3eb76301d9 100644 --- a/packages/plugins/opentelemetry/src/setup.ts +++ b/packages/plugins/opentelemetry/src/setup.ts @@ -1,4 +1,4 @@ -import { Attributes, Logger } from '@graphql-hive/logger'; +import { Attributes, Logger, LogLevel } from '@graphql-hive/logger'; import { context, ContextManager, @@ -143,7 +143,7 @@ type BaseOptions = { * The Logger to be used by this utility. * A child of this logger will be used for OTEL diag API, unless `configureDiagLogger` is false */ - log?: Logger; + log?: Logger | false | LogLevel; /** * Configure Opentelemetry `diag` API to use Gateway's logger. * @@ -162,7 +162,10 @@ type OpentelemetrySetupOptions = TracingOptions & SamplingOptions & BaseOptions; let initialized: false | { name: string; source: string } = false; export function openTelemetrySetup(options: OpentelemetrySetupOptions) { - const log = options.log || new Logger(); + const log = + !options.log || typeof options.log === 'string' + ? new Logger({ level: options.log }) + : options.log; if (initialized) { log.error( @@ -311,7 +314,10 @@ export type HiveTracingSetupOptions = BaseOptions & TracerOptions; export function hiveTracingSetup(options: HiveTracingSetupOptions) { - const log = options.log || new Logger(); + const log = + !options.log || typeof options.log === 'string' + ? new Logger({ level: options.log }) + : options.log; options.target ??= getEnvStr('HIVE_TARGET'); if (!options.target) { diff --git a/packages/plugins/opentelemetry/src/spans.ts b/packages/plugins/opentelemetry/src/spans.ts index c96705aa40..2b0a3fd1d0 100644 --- a/packages/plugins/opentelemetry/src/spans.ts +++ b/packages/plugins/opentelemetry/src/spans.ts @@ -3,13 +3,16 @@ import { OnCacheGetHookEventPayload } from '@graphql-hive/gateway-runtime'; import { defaultPrintFn } from '@graphql-mesh/transport-common'; import { getOperationASTFromDocument, + getSchemaCoordinate, isAsyncIterable, type ExecutionRequest, type ExecutionResult, } from '@graphql-tools/utils'; import { + Attributes, context, ROOT_CONTEXT, + Span, SpanKind, SpanStatusCode, trace, @@ -17,12 +20,14 @@ import { type Tracer, } from '@opentelemetry/api'; import { + ATTR_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_MESSAGE, SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_TYPE, } from '@opentelemetry/semantic-conventions'; import { DocumentNode, + GraphQLError, GraphQLSchema, OperationDefinitionNode, printSchema, @@ -40,8 +45,14 @@ import { SEMATTRS_GRAPHQL_OPERATION_TYPE, SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES, SEMATTRS_HIVE_GATEWAY_UPSTREAM_SUBGRAPH_NAME, + SEMATTRS_HIVE_GRAPHQL_ERROR_CODE, SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, + SEMATTRS_HIVE_GRAPHQL_ERROR_LOCATIONS, + SEMATTRS_HIVE_GRAPHQL_ERROR_MESSAGE, + SEMATTRS_HIVE_GRAPHQL_ERROR_PATH, + SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATE, + SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATES, SEMATTRS_HIVE_GRAPHQL_OPERATION_HASH, SEMATTRS_HTTP_CLIENT_IP, SEMATTRS_HTTP_HOST, @@ -221,6 +232,7 @@ export function createGraphQLParseSpan(input: { export function setGraphQLParseAttributes(input: { ctx: Context; + operationCtx: Context; query?: string; operationName?: string; result: unknown; @@ -235,7 +247,28 @@ export function setGraphQLParseAttributes(input: { } if (input.result instanceof Error) { - span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, 1); + if (isGraphQLError(input.result)) { + span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, 1); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + const operationSpan = trace.getSpan(input.operationCtx); + if (operationSpan) { + recordGraphqlErrors( + operationSpan, + [input.result as GraphQLError], + 'GraphQL Parse Error', + ); + } + } else { + // It is a JS Exception + span.recordException(input.result); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: input.result.message, + }); + } } else { // result should be a document const document = input.result as DocumentNode; @@ -266,6 +299,7 @@ export function createGraphQLValidateSpan(input: { export function setGraphQLValidateAttributes(input: { ctx: Context; + operationCtx: Context; document: DocumentNode; operationName: string | undefined | null; result: any[] | readonly Error[]; @@ -285,28 +319,45 @@ export function setGraphQLValidateAttributes(input: { } } - const errors = Array.isArray(result) ? result : []; + const errors: (Error | GraphQLError)[] = Array.isArray(result) ? result : []; if (result instanceof Error) { errors.push(result); } - if (errors.length > 0) { + if (errors.length === 0) { + return; + } + + const graphqlErrors: GraphQLError[] = []; + const exceptions: Error[] = []; + for (const error of errors) { + (isGraphQLError(error) ? graphqlErrors : exceptions).push(error); + } + + if (graphqlErrors.length > 0) { span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, result.length); - span.setStatus({ - code: SpanStatusCode.ERROR, - message: result.map((e) => e.message).join(', '), - }); - const codes = []; - for (const error of result) { - if (error.extensions?.code) { - codes.push(`${error.extensions.code}`); - } - span.recordException(error); + const operationSpan = trace.getSpan(input.operationCtx); + if (operationSpan) { + recordGraphqlErrors( + operationSpan, + graphqlErrors, + 'GraphQL Validation Error', + ); } - span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, codes); } + + if (exceptions.length > 0) { + for (const exception of exceptions) { + span.recordException(exception); + } + } + + span.setStatus({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); } export function createGraphQLExecuteSpan(input: { @@ -362,43 +413,41 @@ export function setGraphQLExecutionAttributes(input: { export function setGraphQLExecutionResultAttributes(input: { ctx: Context; + operationCtx: Context; result: ExecutionResult | AsyncIterableIterator; subgraphNames?: string[]; }) { - const { ctx, result } = input; + const { ctx, operationCtx, result } = input; + const span = trace.getSpan(ctx); - if (!span) { - return; + if (span) { + if (input.subgraphNames?.length) { + span.setAttribute( + SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES, + input.subgraphNames, + ); + } } - if (input.subgraphNames?.length) { - span.setAttribute( - SEMATTRS_HIVE_GATEWAY_OPERATION_SUBGRAPH_NAMES, - input.subgraphNames, - ); - } + const operationSpan = trace.getSpan(operationCtx); if ( !isAsyncIterable(result) && // FIXME: Handle async iterable too result.errors && result.errors.length > 0 ) { - span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, result.errors.length); - span.setStatus({ + span?.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, result.errors.length); + span?.setStatus({ code: SpanStatusCode.ERROR, - message: result.errors.map((e) => e.message).join(', '), + message: 'GraphQL Execution Error', }); - const codes: string[] = []; - for (const error of result.errors) { - span.recordException(error); - if (error.extensions?.['code']) { - codes.push(`${error.extensions['code']}`); // Ensure string using string interpolation - } - } - - if (codes.length > 0) { - span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, codes); + if (operationSpan) { + recordGraphqlErrors( + operationSpan, + result.errors, + 'GraphQL Execution Error', + ); } } } @@ -608,3 +657,90 @@ export const getOperationFromDocument = ( operationNameMap.set(operationName ?? null, operation); return operation; }; + +function recordGraphqlErrors( + span: Span, + errors: readonly GraphQLError[], + message?: string, +): void { + const codes: string[] = []; + const schemaCoordinates: string[] = []; + + span.setStatus({ + code: SpanStatusCode.ERROR, + message: message ?? 'GraphQL Error', + }); + span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT, errors.length); + + for (const error of errors) { + const attributes = attributesFromGraphqlError(error); + if (attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_CODE]) { + codes.push(attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_CODE] as string); + } + if (attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATE]) { + schemaCoordinates.push( + attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATE] as string, + ); + } + + span.addEvent('graphql.error', attributes); + } + + if (codes.length > 0) { + span.setAttribute(SEMATTRS_HIVE_GRAPHQL_ERROR_CODES, codes); + } + + if (schemaCoordinates.length > 0) { + span.setAttribute( + SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATES, + schemaCoordinates, + ); + } +} + +function attributesFromGraphqlError(error: GraphQLError): Attributes { + const attributes: Attributes = { + [SEMATTRS_HIVE_GRAPHQL_ERROR_MESSAGE]: error.message, + }; + + if (error.path) { + attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_PATH] = error.path.map((p) => + p.toString(), + ); + } + + if (error.locations) { + attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_LOCATIONS] = error.locations.map( + ({ line, column }) => `${line}:${column}`, + ); + } + + if (error.extensions) { + const code = error.extensions?.['code']; + if (code) { + const codeStr = `${code}`; // Ensure string using string interpolation + attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_CODE] = codeStr; + } + + const schemaCoordinate = getSchemaCoordinate(error); + if (schemaCoordinate) { + attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_SCHEMA_COORDINATE] = + schemaCoordinate; + } + + const originalError: Error | undefined = error.extensions[ + 'originalError' + ] as Error; + if (originalError?.stack) { + attributes[ATTR_EXCEPTION_STACKTRACE] = originalError.stack; + } + } + + return attributes; +} + +export function isGraphQLError(error: Error): error is GraphQLError { + // It is probably a GraphQLError if there is no name. + // We can't use instanceof in case of multiple `graphql` deps. + return !error.name || error.name === 'GraphQLError'; +} diff --git a/packages/plugins/opentelemetry/tests/useOpenTelemetry.spec.ts b/packages/plugins/opentelemetry/tests/useOpenTelemetry.spec.ts index fe3cd126f2..ba6c6832fd 100644 --- a/packages/plugins/opentelemetry/tests/useOpenTelemetry.spec.ts +++ b/packages/plugins/opentelemetry/tests/useOpenTelemetry.spec.ts @@ -21,7 +21,9 @@ import { SEMATTRS_HTTP_URL, SEMATTRS_NET_HOST_NAME, } from '@graphql-hive/plugin-opentelemetry/setup'; +import { assertSingleExecutionValue } from '@internal/testing'; import { + Attributes, ROOT_CONTEXT, SpanStatusCode, TextMapPropagator, @@ -87,6 +89,7 @@ describe('useOpenTelemetry', () => { it('should setup OTEL with sain default', () => { openTelemetrySetup({ + log: false, contextManager: new AsyncLocalStorageContextManager(), traces: { exporter: new OTLPTraceExporter(), @@ -132,6 +135,7 @@ describe('useOpenTelemetry', () => { }; openTelemetrySetup({ + log: false, contextManager: null, traces: { tracerProvider, @@ -145,6 +149,7 @@ describe('useOpenTelemetry', () => { const before = getContextManager(); openTelemetrySetup({ + log: false, contextManager: null, }); @@ -153,6 +158,7 @@ describe('useOpenTelemetry', () => { it('should register a console exporter', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { console: true, @@ -168,6 +174,7 @@ describe('useOpenTelemetry', () => { it('should register a console exporter even if an exporter is given', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { exporter: new OTLPTraceExporter(), @@ -184,6 +191,7 @@ describe('useOpenTelemetry', () => { it('should register a console exporter even if a list of processors is given', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { processors: [new SimpleSpanProcessor(new OTLPTraceExporter())], @@ -200,6 +208,7 @@ describe('useOpenTelemetry', () => { it('should register a custom resource', () => { openTelemetrySetup({ + log: false, resource: resourceFromAttributes({ 'service.name': 'test-name', 'service.version': 'test-version', @@ -225,6 +234,7 @@ describe('useOpenTelemetry', () => { vi.stubEnv('OTEL_SERVICE_VERSION', 'test-version'); openTelemetrySetup({ + log: false, traces: { console: true }, contextManager: null, }); @@ -240,6 +250,7 @@ describe('useOpenTelemetry', () => { it('should allow to register a custom sampler', () => { openTelemetrySetup({ + log: false, traces: { console: true, }, @@ -252,6 +263,7 @@ describe('useOpenTelemetry', () => { it('should allow to configure a rate sampling strategy', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { console: true }, samplingRate: 0.1, @@ -271,6 +283,7 @@ describe('useOpenTelemetry', () => { it('should allow to disable batching', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { exporter: new OTLPTraceExporter(), @@ -284,6 +297,7 @@ describe('useOpenTelemetry', () => { it('should allow to configure batching', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { exporter: new OTLPTraceExporter(), @@ -309,6 +323,7 @@ describe('useOpenTelemetry', () => { it('should allow to manually define processor', () => { const processor = {} as SpanProcessor; openTelemetrySetup({ + log: false, contextManager: null, traces: { processors: [processor], @@ -323,6 +338,7 @@ describe('useOpenTelemetry', () => { it('should allow to customize propagators', () => { const propagator = {} as TextMapPropagator; openTelemetrySetup({ + log: false, contextManager: null, propagators: [propagator], }); @@ -334,6 +350,7 @@ describe('useOpenTelemetry', () => { const before = getPropagator(); openTelemetrySetup({ + log: false, contextManager: null, propagators: [], }); @@ -343,6 +360,7 @@ describe('useOpenTelemetry', () => { it('should allow to customize limits', () => { openTelemetrySetup({ + log: false, contextManager: null, traces: { console: true, @@ -381,6 +399,7 @@ describe('useOpenTelemetry', () => { it('should setup Hive Tracing', () => { hiveTracingSetup({ + log: false, contextManager: new AsyncLocalStorageContextManager(), target: 'target', accessToken: 'access-token', @@ -790,44 +809,311 @@ describe('useOpenTelemetry', () => { }); }); - it('should handle validation error with hive processor', async () => { - disableAll(); - const traceProvider = new BasicTracerProvider({ - spanProcessors: [ - new HiveTracingSpanProcessor({ - processor: new SimpleSpanProcessor(spanExporter), - }), - ], + describe('error reporting', () => { + it('should report execution errors on operation span', async () => { + await using gateway = await buildTestGatewayForCtx({ + fetch: () => async () => + new Response( + JSON.stringify({ + data: null, + errors: [ + { + message: 'Test Error', + path: ['hello'], + extensions: { code: 'TEST_ERROR' }, + }, + ], + }), + ), + }); + const result = await gateway.query({ shouldReturnErrors: true }); + assertSingleExecutionValue(result); + expect(result.errors?.[0]).toBeDefined(); + // By default, coordinate should not leak to client + expect(Object.keys(result.errors![0]!)).not.toContain('coordinate'); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Execution Error', + }); + const operationExpectedAttributes: Attributes = { + 'hive.graphql.error.count': 1, + 'hive.graphql.error.codes': ['TEST_ERROR'], + }; + + if (!usingHiveRouterRuntime()) { + operationExpectedAttributes['hive.graphql.error.coordinates'] = [ + 'Query.hello', + ]; + } + + expect(operationSpan.span.attributes).toMatchObject( + operationExpectedAttributes, + ); + + const executionSpan = operationSpan.expectChild('graphql.execute'); + expect(executionSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Execution Error', + }); + expect(executionSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + const errorExepectedAttributes: Attributes = { + 'hive.graphql.error.path': ['hello'], + 'hive.graphql.error.message': 'Test Error', + 'hive.graphql.error.code': 'TEST_ERROR', + }; + + if (!usingHiveRouterRuntime()) { + errorExepectedAttributes['hive.graphql.error.coordinate'] = + 'Query.hello'; + } + + expect(errorEvent?.attributes).toMatchObject( + errorExepectedAttributes, + ); }); - setupOtelForTests({ traceProvider }); - await using gateway = await buildTestGatewayForCtx({ - plugins: { - before: ({ fetch }) => { - return [ - { - onPluginInit() { - fetch('http://foo.bar', {}); - }, - }, - ]; - }, - }, + + it('should report validation errors on operation span', async () => { + await using gateway = await buildTestGatewayForCtx(); + await gateway.query({ + shouldReturnErrors: true, + body: { query: 'query test { unknown }' }, + }); + + const operationSpan = spanExporter.assertRoot( + 'graphql.operation test', + ); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + 'graphql.operation.type': 'query', + 'graphql.operation.name': 'test', + }); + + const validateSpan = operationSpan.expectChild('graphql.validate'); + expect(validateSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); + expect(validateSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:14'], + 'hive.graphql.error.message': + 'Cannot query field "unknown" on type "Query".', + }); }); - await gateway.query({ - body: { query: 'query test{ unknown }' }, - shouldReturnErrors: true, + + it('should report parsing errors on operation span', async () => { + await using gateway = await buildTestGatewayForCtx(); + await gateway.query({ + shouldReturnErrors: true, + body: { query: 'parse error' }, + }); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const parseSpan = operationSpan.expectChild('graphql.parse'); + expect(parseSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + expect(parseSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:1'], + 'hive.graphql.error.message': + 'Syntax Error: Unexpected Name "parse".', + }); }); - const operationSpan = spanExporter.assertRoot('graphql.operation test'); - expect(operationSpan.span.attributes['graphql.operation.name']).toBe( - 'test', - ); - expect(operationSpan.span.attributes['graphql.operation.type']).toBe( - 'query', - ); - expect( - operationSpan.span.attributes[SEMATTRS_HIVE_GRAPHQL_ERROR_COUNT], - ).toBe(1); + describe('hive processor', () => { + it('should handle validation error with hive processor', async () => { + disableAll(); + const traceProvider = new BasicTracerProvider({ + spanProcessors: [ + new HiveTracingSpanProcessor({ + processor: new SimpleSpanProcessor(spanExporter), + }), + ], + }); + setupOtelForTests({ traceProvider }); + await using gateway = await buildTestGatewayForCtx({ + plugins: { + before: ({ fetch }) => { + return [ + { + onPluginInit() { + fetch('http://foo.bar', {}); + }, + }, + ]; + }, + }, + }); + await gateway.query({ + body: { query: 'query test { unknown }' }, + shouldReturnErrors: true, + }); + + const operationSpan = spanExporter.assertRoot( + 'graphql.operation test', + ); + + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); + + expect(operationSpan.span.attributes).toMatchObject({ + 'graphql.operation.name': 'test', + 'graphql.operation.type': 'query', + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:14'], + 'hive.graphql.error.message': + 'Cannot query field "unknown" on type "Query".', + }); + }); + + it('should handle parsing error with hive processor', async () => { + disableAll(); + const traceProvider = new BasicTracerProvider({ + spanProcessors: [ + new HiveTracingSpanProcessor({ + processor: new SimpleSpanProcessor(spanExporter), + }), + ], + }); + setupOtelForTests({ traceProvider }); + await using gateway = await buildTestGatewayForCtx(); + await gateway.query({ + body: { query: 'parse error' }, + shouldReturnErrors: true, + }); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:1'], + 'hive.graphql.error.message': + 'Syntax Error: Unexpected Name "parse".', + }); + }); + + it('should handle execute error with hive processor', async () => { + disableAll(); + const traceProvider = new BasicTracerProvider({ + spanProcessors: [ + new HiveTracingSpanProcessor({ + processor: new SimpleSpanProcessor(spanExporter), + }), + ], + }); + setupOtelForTests({ traceProvider }); + await using gateway = await buildTestGatewayForCtx({ + fetch: () => async () => + new Response( + JSON.stringify({ + data: null, + errors: [ + { + message: 'Test Error', + path: ['hello'], + extensions: { code: 'TEST_ERROR' }, + }, + ], + }), + ), + }); + await gateway.query({ + shouldReturnErrors: true, + }); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Execution Error', + }); + + const operationExpectedAttributes: Attributes = { + 'hive.graphql.error.count': 1, + 'hive.graphql.error.codes': ['TEST_ERROR'], + }; + if (!usingHiveRouterRuntime()) { + operationExpectedAttributes['hive.graphql.error.coordinates'] = [ + 'Query.hello', + ]; + } + expect(operationSpan.span.attributes).toMatchObject( + operationExpectedAttributes, + ); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + const errorExpectedAttributes: Attributes = { + 'hive.graphql.error.path': ['hello'], + 'hive.graphql.error.message': 'Test Error', + 'hive.graphql.error.code': 'TEST_ERROR', + }; + if (!usingHiveRouterRuntime()) { + errorExpectedAttributes['hive.graphql.error.coordinate'] = + 'Query.hello'; + } + expect(errorEvent?.attributes).toMatchObject( + errorExpectedAttributes, + ); + }); + }); }); }); @@ -1209,6 +1495,7 @@ describe('useOpenTelemetry', () => { // Register testing OTEL api with a custom Span processor and an Async Context Manager disableAll(); hiveTracingSetup({ + log: false, target: 'test-target', contextManager: new AsyncLocalStorageContextManager(), processor: new SimpleSpanProcessor(spanExporter), diff --git a/packages/plugins/opentelemetry/tests/yoga.spec.ts b/packages/plugins/opentelemetry/tests/yoga.spec.ts index 9331c6ad3d..5b5c4a3f98 100644 --- a/packages/plugins/opentelemetry/tests/yoga.spec.ts +++ b/packages/plugins/opentelemetry/tests/yoga.spec.ts @@ -1,7 +1,12 @@ import { Logger } from '@graphql-hive/logger'; -import { createSchema, createYoga, Plugin as YogaPlugin } from 'graphql-yoga'; +import { + createGraphQLError, + createSchema, + createYoga, + Plugin as YogaPlugin, +} from 'graphql-yoga'; import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { hive } from '../src/api'; +import { hive, SpanStatusCode } from '../src/api'; import { ContextMatcher, useOpenTelemetry } from '../src/plugin'; import { disableAll, setupOtelForTests, spanExporter } from './utils'; @@ -31,11 +36,27 @@ describe('useOpenTelemetry', () => { typeDefs: /* GraphQL */ ` type Query { hello: String + withFailing: WithFailing + } + + type WithFailing { + failingField: String } `, resolvers: { Query: { hello: () => 'World', + withFailing: () => ({}), + }, + WithFailing: { + failingField: () => { + throw createGraphQLError('Test', { + extensions: { + code: 'TEST', + originalError: new Error('Test'), + }, + }); + }, }, }, }), @@ -51,20 +72,32 @@ describe('useOpenTelemetry', () => { }); return { - query: async () => { + query: async ( + queryOptions: { + query?: string; + shouldError?: boolean; + } = {}, + ) => { const response = await yoga.fetch('http://yoga/graphql', { method: 'POST', headers: { 'content-type': 'application/json', }, - body: JSON.stringify({ query: '{ hello }' }), + body: JSON.stringify({ + query: queryOptions.query ?? '{ hello }', + }), }); expect(response.status).toBe(200); const result = await response.json(); - if (result.errors) { - console.error('Graphql Errors:', result.errors); + if (queryOptions.shouldError) { + expect(result.errors?.length).toBeGreaterThan(0); + } else { + if (result.errors) { + console.error('Graphql Errors:', result.errors); + } + expect(result.errors).not.toBeDefined(); } - expect(result.errors).not.toBeDefined(); + return result; }, [Symbol.asyncDispose]: async () => { await yoga.dispose(); @@ -142,6 +175,127 @@ describe('useOpenTelemetry', () => { } }); }); + + describe('error reporting', () => { + it('should report execution errors on operation span', async () => { + await using gateway = buildTest(); + await gateway.query({ + query: '{ withFailing { failingField } }', + shouldError: true, + }); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Execution Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + 'hive.graphql.error.codes': ['TEST'], + 'hive.graphql.error.coordinates': ['WithFailing.failingField'], + }); + + const executionSpan = operationSpan.expectChild('graphql.execute'); + expect(executionSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Execution Error', + }); + expect(executionSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.message': 'Test', + 'hive.graphql.error.code': 'TEST', + 'hive.graphql.error.path': ['withFailing', 'failingField'], + 'hive.graphql.error.locations': ['1:17'], + 'hive.graphql.error.coordinate': 'WithFailing.failingField', + }); + expect(errorEvent?.attributes?.['exception.stacktrace']).toMatch( + /^Error: Test\n/, + ); + }); + + it('should report validation errors on operation span', async () => { + await using gateway = buildTest(); + await gateway.query({ + query: 'query test { unknown }', + shouldError: true, + }); + + const operationSpan = spanExporter.assertRoot( + 'graphql.operation test', + ); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + 'graphql.operation.type': 'query', + 'graphql.operation.name': 'test', + }); + + const validateSpan = operationSpan.expectChild('graphql.validate'); + expect(validateSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Validation Error', + }); + expect(validateSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:14'], + 'hive.graphql.error.message': + 'Cannot query field "unknown" on type "Query".', + }); + }); + + it('should report parsing errors on operation span', async () => { + await using gateway = buildTest(); + await gateway.query({ + query: 'parse error', + shouldError: true, + }); + + const operationSpan = spanExporter.assertRoot('graphql.operation'); + expect(operationSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + expect(operationSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const parseSpan = operationSpan.expectChild('graphql.parse'); + expect(parseSpan.span.status).toMatchObject({ + code: SpanStatusCode.ERROR, + message: 'GraphQL Parse Error', + }); + expect(parseSpan.span.attributes).toMatchObject({ + 'hive.graphql.error.count': 1, + }); + + const errorEvent = operationSpan.span.events.find( + (event) => event.name === 'graphql.error', + ); + + expect(errorEvent?.attributes).toMatchObject({ + 'hive.graphql.error.locations': ['1:1'], + 'hive.graphql.error.message': + 'Syntax Error: Unexpected Name "parse".', + }); + }); + }); }); }); }); diff --git a/packages/plugins/prometheus/package.json b/packages/plugins/prometheus/package.json index 7091d4d392..7d9c4edc31 100644 --- a/packages/plugins/prometheus/package.json +++ b/packages/plugins/prometheus/package.json @@ -55,7 +55,7 @@ }, "devDependencies": { "graphql": "^16.12.0", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "pkgroll": "2.21.4", "prom-client": "15.1.3" }, diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 5a6635adb2..f66df19985 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -79,7 +79,7 @@ "@whatwg-node/server": "^0.10.17", "@whatwg-node/server-plugin-cookies": "^1.0.5", "graphql-ws": "^6.0.6", - "graphql-yoga": "^5.16.2", + "graphql-yoga": "^5.17.0", "tslib": "^2.8.1" }, "devDependencies": { diff --git a/packages/runtime/src/utils.ts b/packages/runtime/src/utils.ts index 51b7d0dc70..1e6e56a931 100644 --- a/packages/runtime/src/utils.ts +++ b/packages/runtime/src/utils.ts @@ -104,6 +104,7 @@ export const getExecuteFnFromExecutor = memoize1( rootValue: args.rootValue, context: args.contextValue, signal: args.signal, + schemaCoordinateInErrors: args.schemaCoordinateInErrors, }); }; }, diff --git a/packages/signal/src/abortSignalAll.ts b/packages/signal/src/abortSignalAll.ts new file mode 100644 index 0000000000..15e6bbc382 --- /dev/null +++ b/packages/signal/src/abortSignalAll.ts @@ -0,0 +1,105 @@ +// Some runtimes doesn't implement FinalizationRegistry. For those, we don't handle GC of signals +// and only rely on signal abortion. It is ok because most of the time they are short live runtime, +// and it's any just an optimization. It will not break the resolution, only miss or delay abortion. +const allSignalRegistry = globalThis.FinalizationRegistry + ? new FinalizationRegistry<() => void>((cb) => cb()) + : null; + +const controllerInSignalSy = Symbol('CONTROLLER_IN_SIGNAL'); + +/** + * Memory safe AbortSignal merger. + * The resulting signal is aborted once all signals have aborted or GCed. + */ +export function abortSignalAll( + signals: AbortSignal[], +): AbortSignal | undefined { + if (signals.length === 0) { + // if no signals are passed, return undefined because the abortcontroller + // wouldnt ever be aborted (should be when GCd, but it's only a waste of memory) + // furthermore, the native AbortSignal.any will also never abort if receiving no signals + return undefined; + } + + if (signals.length === 1) { + // no need to waste resources by wrapping a single signal, simply return it + return signals[0]; + } + + for (const signal of signals) { + if (signal.aborted) { + // if any of the signals has already been aborted, return it immediately no need to continue at all + return signal; + } + } + + // we use weak refs for both the root controller and the passed signals + // because we want to make sure that signals are aborted and disposed of + // in both cases when GC-ed and actually aborted + + const ctrl = new AbortController(); + const ctrlRef = new WeakRef(ctrl); + + const eventListenerPairs: [WeakRef, () => void][] = []; + let retainedSignalsCount = signals.length; + + function removeSignal(signal: AbortSignal, abortListener: () => void) { + signal.removeEventListener('abort', abortListener); + allSignalRegistry?.unregister(signal); + --retainedSignalsCount; + } + + for (const signal of signals) { + const signalRef = new WeakRef(signal); + function onAbort() { + const signal = signalRef.deref(); + if (signal) { + removeSignal(signal, onAbort); + // abort when all of the signals have been GCed or aborted + if (retainedSignalsCount === 0) { + ctrlRef.deref()?.abort(signal.reason); + } + } + } + signal.addEventListener('abort', onAbort); + eventListenerPairs.push([signalRef, onAbort]); + allSignalRegistry?.register( + signal, + () => { + removeSignal(signal, onAbort); + // dispose when all of the signals have been GCed + if (retainedSignalsCount === 0) { + dispose(); + } + }, + signal, + ); + } + + function dispose() { + const ctrl = ctrlRef.deref(); + if (ctrl) { + allSignalRegistry?.unregister(ctrl.signal); + // @ts-expect-error + delete ctrl.signal[controllerInSignalSy]; + } + + for (const [signalRef, onAbort] of eventListenerPairs) { + const signal = signalRef.deref(); + if (signal) { + removeSignal(signal, onAbort); + } + } + } + + // cleanup when aborted + ctrl.signal.addEventListener('abort', dispose); + // cleanup when GCed + allSignalRegistry?.register(ctrl.signal, dispose, ctrl.signal); + + // keeping a strong reference of the controller binding it to the lifecycle of its signal + // @ts-expect-error + ctrl.signal[controllerInSignalSy] = ctrl; + + return ctrl.signal; +} diff --git a/packages/signal/src/index.ts b/packages/signal/src/index.ts index 317bb07ffc..d696f5fdb7 100644 --- a/packages/signal/src/index.ts +++ b/packages/signal/src/index.ts @@ -1 +1,2 @@ export * from './abortSignalAny'; +export * from './abortSignalAll'; diff --git a/packages/signal/tests/abortSignalAll.test.ts b/packages/signal/tests/abortSignalAll.test.ts new file mode 100644 index 0000000000..035ddd83dd --- /dev/null +++ b/packages/signal/tests/abortSignalAll.test.ts @@ -0,0 +1,158 @@ +import LeakDetector from 'jest-leak-detector'; +import { describe, expect, it } from 'vitest'; +import { abortSignalAll } from '../src/abortSignalAll'; + +describe.skipIf( + // doesn't report leaks locally, but does in the CI. + // we confirm that there is no leaks directly in tests below + // TODO: investigate why + process.env['LEAK_TEST'], +)('abortSignalAll', () => { + it('should return the single signal passed', () => { + const ctrl = new AbortController(); + + const signal = abortSignalAll([ctrl.signal]); + + expect(ctrl.signal).toBe(signal); + }); + + it('should return undefined if no signals have been passed', () => { + const signal = abortSignalAll([]); + + expect(signal).toBeUndefined(); + }); + + it('should not abort if none of the signals abort', () => { + const ctrl1 = new AbortController(); + const ctrl2 = new AbortController(); + + const signal = abortSignalAll([ctrl1.signal, ctrl2.signal]); + + expect(() => signal!.throwIfAborted()).not.toThrow(); + }); + + it('should not abort if only some signal aborts', async () => { + const ctrl1 = new AbortController(); + const ctrl2 = new AbortController(); + + const signal = abortSignalAll([ctrl1.signal, ctrl2.signal]); + ctrl1.abort('Test'); + + expect(signal).not.toBe(ctrl1.signal); + + expect(() => signal!.throwIfAborted()).not.toThrow('Test'); + }); + + it('should abort if all signal aborts', async () => { + const ctrl1 = new AbortController(); + const ctrl2 = new AbortController(); + + const signal = abortSignalAll([ctrl1.signal, ctrl2.signal]); + ctrl1.abort('Test'); + ctrl2.abort('Test'); + + expect(signal).not.toBe(ctrl1.signal); + + expect(() => signal!.throwIfAborted()).toThrow('Test'); + }); + + it('should return aborted signal if aborted before any', () => { + const ctrl1 = new AbortController(); + const ctrl2 = new AbortController(); + + ctrl1.abort('Test'); + ctrl2.abort('Test'); + const signal = abortSignalAll([ctrl1.signal, ctrl2.signal]); + + expect(signal).toBe(ctrl1.signal); + + expect(() => signal!.throwIfAborted()).toThrow('Test'); + }); + + it.skipIf(!!global.Bun)('should GC all signals after abort', async () => { + let ctrl1: AbortController | undefined = new AbortController(); + const ctrl1Detector = new LeakDetector(ctrl1); + const ctrl1SignalDetector = new LeakDetector(ctrl1.signal); + let ctrl2: AbortController | undefined = new AbortController(); + const ctrl2Detector = new LeakDetector(ctrl2); + const ctrl2SignalDetector = new LeakDetector(ctrl2.signal); + + let signal: AbortSignal | undefined = abortSignalAll([ + ctrl1.signal, + ctrl2.signal, + ]); + const signalDetector = new LeakDetector(signal); + + ctrl1.abort('Test'); + ctrl2.abort('Test'); + + expect(() => signal!.throwIfAborted()).toThrow('Test'); + + ctrl1 = undefined; + ctrl2 = undefined; + signal = undefined; + + await expect(ctrl1Detector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl1SignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl2Detector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl2SignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(signalDetector.isLeaking()).resolves.toBeFalsy(); + }); + + it.skipIf(!!global.Bun)('should GC all signals without abort', async () => { + let ctrl1: AbortController | undefined = new AbortController(); + const ctrl1Detector = new LeakDetector(ctrl1); + const ctrl1SignalDetector = new LeakDetector(ctrl1.signal); + let ctrl2: AbortController | undefined = new AbortController(); + const ctrl2Detector = new LeakDetector(ctrl2); + const ctrl2SignalDetector = new LeakDetector(ctrl2.signal); + + let signal: AbortSignal | undefined = abortSignalAll([ + ctrl1.signal, + ctrl2.signal, + ]); + const signalDetector = new LeakDetector(signal); + + // no abort + // ctrl1.abort('Test'); + + ctrl1 = undefined; + ctrl2 = undefined; + signal = undefined; + + await expect(ctrl1Detector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl1SignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl2Detector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl2SignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(signalDetector.isLeaking()).resolves.toBeFalsy(); + }); + + it.skipIf(!!global.Bun)( + 'should GC timeout signals without abort', + async () => { + let ctrl1: AbortController | undefined = new AbortController(); + const ctrl1Detector = new LeakDetector(ctrl1); + const ctrl1SignalDetector = new LeakDetector(ctrl1.signal); + let timeoutSignal: AbortSignal | undefined = AbortSignal.timeout(60_000); // longer than the test + const timeoutSignalDetector = new LeakDetector(timeoutSignal); + + let signal: AbortSignal | undefined = abortSignalAll([ + ctrl1.signal, + timeoutSignal, + ]); + const signalDetector = new LeakDetector(signal); + + // no abort + // ctrl1.abort('Test'); + + ctrl1 = undefined; + timeoutSignal = undefined; + signal = undefined; + + await expect(ctrl1Detector.isLeaking()).resolves.toBeFalsy(); + await expect(ctrl1SignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(timeoutSignalDetector.isLeaking()).resolves.toBeFalsy(); + await expect(signalDetector.isLeaking()).resolves.toBeFalsy(); + }, + ); +}); diff --git a/packages/testing/package.json b/packages/testing/package.json index 069a10da53..4b8c1a107c 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -47,7 +47,7 @@ "@graphql-mesh/fusion-composition": "^0.8.20", "@graphql-tools/executor-http": "workspace:^", "@graphql-tools/utils": "^10.10.3", - "graphql-yoga": "^5.16.2" + "graphql-yoga": "^5.17.0" }, "devDependencies": { "graphql": "^16.12.0", diff --git a/yarn.lock b/yarn.lock index a8cd1b7737..a1f1a285be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1116,7 +1116,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.28.5, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.26.10, @babel/core@npm:^7.27.4": +"@babel/core@npm:7.28.5": version: 7.28.5 resolution: "@babel/core@npm:7.28.5" dependencies: @@ -1139,6 +1139,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.26.10, @babel/core@npm:^7.27.4": + version: 7.28.4 + resolution: "@babel/core@npm:7.28.4" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.3" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.4" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.4" + "@babel/types": "npm:^7.28.4" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/ef5a6c3c6bf40d3589b5593f8118cfe2602ce737412629fb6e26d595be2fcbaae0807b43027a5c42ec4fba5b895ff65891f2503b5918c8a3ea3542ab44d4c278 + languageName: node + linkType: hard + "@babel/generator@npm:^7.26.2, @babel/generator@npm:^7.27.5, @babel/generator@npm:^7.28.5": version: 7.28.5 resolution: "@babel/generator@npm:7.28.5" @@ -1152,6 +1175,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" + dependencies: + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": version: 7.27.3 resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" @@ -1364,6 +1400,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/parser@npm:7.28.4" + dependencies: + "@babel/types": "npm:^7.28.4" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": version: 7.28.5 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" @@ -2255,6 +2302,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-typescript@npm:7.27.1" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/48f1db5de17a0f9fc365ff4fb046010aedc7aad813a7aa42fb73fcdab6442f9e700dde2cc0481086e01b0dae662ae4d3e965a52cde154f0f146d243a8ac68e93 + languageName: node + linkType: hard + "@babel/plugin-transform-typescript@npm:^7.28.5": version: 7.28.5 resolution: "@babel/plugin-transform-typescript@npm:7.28.5" @@ -2423,7 +2485,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:7.28.5, @babel/preset-typescript@npm:^7.24.7": +"@babel/preset-typescript@npm:7.28.5": version: 7.28.5 resolution: "@babel/preset-typescript@npm:7.28.5" dependencies: @@ -2438,6 +2500,21 @@ __metadata: languageName: node linkType: hard +"@babel/preset-typescript@npm:^7.24.7": + version: 7.27.1 + resolution: "@babel/preset-typescript@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-typescript": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/cba6ca793d915f8aff9fe2f13b0dfbf5fd3f2e9a17f17478ec9878e9af0d206dcfe93154b9fd353727f16c1dca7c7a3ceb4943f8d28b216235f106bc0fbbcaa3 + languageName: node + linkType: hard + "@babel/register@npm:^7.24.6": version: 7.27.1 resolution: "@babel/register@npm:7.27.1" @@ -2498,6 +2575,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.2": + version: 7.28.4 + resolution: "@babel/types@npm:7.28.4" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/ac6f909d6191319e08c80efbfac7bd9a25f80cc83b43cd6d82e7233f7a6b9d6e7b90236f3af7400a3f83b576895bcab9188a22b584eb0f224e80e6d4e95f4517 + languageName: node + linkType: hard + "@balena/dockerignore@npm:^1.0.2": version: 1.0.2 resolution: "@balena/dockerignore@npm:1.0.2" @@ -2936,7 +3023,7 @@ __metadata: "@graphql-mesh/compose-cli": "npm:^1.5.3" "@omnigraph/openapi": "npm:^0.109.23" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -2949,7 +3036,7 @@ __metadata: aws4-express: "npm:^0.12.0" express: "npm:^5.0.0" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -2988,7 +3075,7 @@ __metadata: dependencies: "@apollo/subgraph": "npm:^2.11.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3019,7 +3106,7 @@ __metadata: dependencies: "@graphql-mesh/compose-cli": "npm:^1.5.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3108,7 +3195,7 @@ __metadata: "@apollo/subgraph": "npm:^2.11.3" fastify: "npm:^5.6.2" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pino-pretty: "npm:^13.1.2" languageName: unknown linkType: soft @@ -3147,7 +3234,7 @@ __metadata: "@graphql-mesh/hmac-upstream-signature": "workspace:^" "@graphql-mesh/plugin-jwt-auth": "workspace:^" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" languageName: unknown linkType: soft @@ -3167,7 +3254,7 @@ __metadata: dependencies: "@graphql-mesh/compose-cli": "npm:^1.5.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3359,7 +3446,7 @@ __metadata: "@apollo/subgraph": "npm:^2.11.3" "@graphql-mesh/compose-cli": "npm:^1.5.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3382,7 +3469,7 @@ __metadata: "@graphql-mesh/compose-cli": "npm:^1.5.3" express: "npm:^5.0.0" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" languageName: unknown linkType: soft @@ -3426,7 +3513,7 @@ __metadata: "@apollo/subgraph": "npm:^2.11.3" "@graphql-hive/gateway": "workspace:*" graphql: "npm:16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3486,7 +3573,7 @@ __metadata: "@graphql-mesh/compose-cli": "npm:^1.5.3" graphql: "npm:16.12.0" graphql-sse: "npm:^2.6.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -3521,7 +3608,7 @@ __metadata: dependencies: "@graphql-mesh/compose-cli": "npm:^1.5.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" tslib: "npm:^2.8.1" languageName: unknown linkType: soft @@ -4189,7 +4276,7 @@ __metadata: graphql: "npm:^16.12.0" graphql-sse: "npm:^2.6.0" graphql-ws: "npm:^6.0.6" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" html-minifier-terser: "npm:7.2.0" pkgroll: "npm:2.21.4" react: "npm:^19.2.0" @@ -4210,7 +4297,7 @@ __metadata: "@graphql-tools/executor-http": "workspace:^" "@graphql-tools/utils": "npm:^10.10.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pkgroll: "npm:2.21.4" peerDependencies: "@graphql-hive/gateway-runtime": "workspace:^" @@ -4286,7 +4373,7 @@ __metadata: dotenv: "npm:^17.2.3" graphql: "npm:^16.12.0" graphql-ws: "npm:^6.0.6" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" parse-duration: "npm:^2.0.0" pkgroll: "npm:2.21.4" postject: "npm:^1.0.0-alpha.6" @@ -4382,7 +4469,7 @@ __metadata: "@whatwg-node/promise-helpers": "npm:^1.3.2" aws4: "npm:1.13.2" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pkgroll: "npm:2.21.4" tslib: "npm:^2.8.1" peerDependencies: @@ -4418,6 +4505,7 @@ __metadata: "@graphql-mesh/transport-common": "workspace:^" "@graphql-mesh/types": "npm:^0.104.16" "@graphql-mesh/utils": "npm:^0.104.16" + "@graphql-tools/executor": "npm:^1.4.9" "@graphql-tools/utils": "npm:^10.10.3" "@opentelemetry/api": "npm:^1.9.0" "@opentelemetry/api-logs": "npm:^0.208.0" @@ -4435,7 +4523,7 @@ __metadata: "@whatwg-node/promise-helpers": "npm:1.3.2" "@whatwg-node/server": "npm:^0.10.17" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pkgroll: "npm:2.21.4" rimraf: "npm:^6.1.0" rollup: "npm:^4.53.2" @@ -4649,7 +4737,19 @@ __metadata: languageName: node linkType: hard -"@graphql-mesh/cross-helpers@npm:^0.4.10, @graphql-mesh/cross-helpers@npm:^0.4.11": +"@graphql-mesh/cross-helpers@npm:^0.4.10": + version: 0.4.10 + resolution: "@graphql-mesh/cross-helpers@npm:0.4.10" + dependencies: + "@graphql-tools/utils": "npm:^10.8.0" + path-browserify: "npm:1.0.1" + peerDependencies: + graphql: "*" + checksum: 10c0/c0f336cfa176f75d7044286d2ec4363484577f2985246cb6c894f559538206c37e95afe371584d2b53caf64986d9a9871f6ae5821ad9605ab50ea930d2dd81bb + languageName: node + linkType: hard + +"@graphql-mesh/cross-helpers@npm:^0.4.11": version: 0.4.11 resolution: "@graphql-mesh/cross-helpers@npm:0.4.11" dependencies: @@ -4707,7 +4807,7 @@ __metadata: "@whatwg-node/promise-helpers": "npm:^1.3.2" change-case: "npm:^5.4.4" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pkgroll: "npm:2.21.4" tslib: "npm:^2.8.1" peerDependencies: @@ -4727,7 +4827,7 @@ __metadata: "@graphql-tools/utils": "npm:^10.10.3" "@whatwg-node/promise-helpers": "npm:^1.3.2" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" json-stable-stringify: "npm:^1.1.1" pkgroll: "npm:2.21.4" tslib: "npm:^2.8.1" @@ -4792,7 +4892,7 @@ __metadata: "@graphql-mesh/utils": "npm:^0.104.16" "@graphql-yoga/plugin-jwt": "npm:^3.10.2" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" jsonwebtoken: "npm:9.0.3" pkgroll: "npm:2.21.4" tslib: "npm:^2.4.0" @@ -4832,7 +4932,7 @@ __metadata: "@graphql-tools/utils": "npm:^10.10.3" "@graphql-yoga/plugin-prometheus": "npm:^6.11.3" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" pkgroll: "npm:2.21.4" prom-client: "npm:15.1.3" tslib: "npm:^2.8.1" @@ -5219,7 +5319,7 @@ __metadata: "@whatwg-node/promise-helpers": "npm:^1.3.2" extract-files: "npm:13.0.0" graphql: "npm:^16.12.0" - graphql-yoga: "npm:^5.16.2" + graphql-yoga: "npm:^5.17.0" meros: "npm:^1.3.2" pkgroll: "npm:2.21.4" tslib: "npm:^2.8.1" @@ -5228,7 +5328,7 @@ __metadata: languageName: unknown linkType: soft -"@graphql-tools/executor@npm:^1.3.6, @graphql-tools/executor@npm:^1.4.0, @graphql-tools/executor@npm:^1.4.13, @graphql-tools/executor@npm:^1.5.0": +"@graphql-tools/executor@npm:^1.3.6, @graphql-tools/executor@npm:^1.4.0, @graphql-tools/executor@npm:^1.4.13, @graphql-tools/executor@npm:^1.4.9, @graphql-tools/executor@npm:^1.5.0": version: 1.5.0 resolution: "@graphql-tools/executor@npm:1.5.0" dependencies: @@ -5347,7 +5447,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/merge@npm:^9.1.1, @graphql-tools/merge@npm:^9.1.5, @graphql-tools/merge@npm:^9.1.6": +"@graphql-tools/merge@npm:^9.1.1, @graphql-tools/merge@npm:^9.1.6": version: 9.1.6 resolution: "@graphql-tools/merge@npm:9.1.6" dependencies: @@ -5359,6 +5459,18 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/merge@npm:^9.1.5": + version: 9.1.5 + resolution: "@graphql-tools/merge@npm:9.1.5" + dependencies: + "@graphql-tools/utils": "npm:^10.10.3" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/2873105f78029832721f1943d44f6940617c97a34867936d7c5664d7679251e692bc2dda28fa8436f8eecbf56f1369b9059a2c1ef2d45f0e3682200597c5ecd1 + languageName: node + linkType: hard + "@graphql-tools/mock@npm:^9.1.3": version: 9.1.4 resolution: "@graphql-tools/mock@npm:9.1.4" @@ -5386,7 +5498,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.29, @graphql-tools/schema@npm:^10.0.30": +"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.30": version: 10.0.30 resolution: "@graphql-tools/schema@npm:10.0.30" dependencies: @@ -5399,6 +5511,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/schema@npm:^10.0.29": + version: 10.0.29 + resolution: "@graphql-tools/schema@npm:10.0.29" + dependencies: + "@graphql-tools/merge": "npm:^9.1.5" + "@graphql-tools/utils": "npm:^10.10.3" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/ec80dfbcb6a8a90fb3ca0b69747d75a94f8cb99eda127694a2d60e38bac7822c4f4bec4fcc2ba33513dd7457da3dfc583dfee645c1d41e1cd20e8fba34a02379 + languageName: node + linkType: hard + "@graphql-tools/stitch@workspace:^, @graphql-tools/stitch@workspace:packages/stitch": version: 0.0.0-use.local resolution: "@graphql-tools/stitch@workspace:packages/stitch" @@ -5580,7 +5705,7 @@ __metadata: languageName: node linkType: hard -"@graphql-yoga/plugin-persisted-operations@npm:^3.16.2, @graphql-yoga/plugin-persisted-operations@npm:^3.9.0": +"@graphql-yoga/plugin-persisted-operations@npm:^3.16.2": version: 3.17.1 resolution: "@graphql-yoga/plugin-persisted-operations@npm:3.17.1" dependencies: @@ -5592,6 +5717,18 @@ __metadata: languageName: node linkType: hard +"@graphql-yoga/plugin-persisted-operations@npm:^3.9.0": + version: 3.15.2 + resolution: "@graphql-yoga/plugin-persisted-operations@npm:3.15.2" + dependencies: + "@whatwg-node/promise-helpers": "npm:^1.2.4" + peerDependencies: + graphql: ^15.2.0 || ^16.0.0 + graphql-yoga: ^5.15.2 + checksum: 10c0/d77686aaf0cbdd2b796fe7a15ae2b8c999861a070d0901865389ebcd1e5875c8c4fcae0f354634d52f8f8e35810c6e08a1d99dea27fb148f00a13e4eb8b5dbe9 + languageName: node + linkType: hard + "@graphql-yoga/plugin-prometheus@npm:^6.11.3": version: 6.12.1 resolution: "@graphql-yoga/plugin-prometheus@npm:6.12.1" @@ -6058,13 +6195,20 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:1.4.0, @ioredis/commands@npm:^1.2.0": +"@ioredis/commands@npm:1.4.0": version: 1.4.0 resolution: "@ioredis/commands@npm:1.4.0" checksum: 10c0/99afe21fba794f84a2b84cceabcc370a7622e7b8b97a6589456c07c9fa62a15d54c5546f6f7214fb9a2458b1fa87579d5c531aaf48e06cc9be156d5923892c8d languageName: node linkType: hard +"@ioredis/commands@npm:^1.2.0": + version: 1.3.0 + resolution: "@ioredis/commands@npm:1.3.0" + checksum: 10c0/5ab990a8f69c20daf3d7d64307aa9f13ee727c92ab4c7664a6943bb500227667a0c368892e9c4913f06416377db47dba78d58627fe723da476d25f2c04a6d5aa + languageName: node + linkType: hard + "@isaacs/balanced-match@npm:^4.0.1": version: 4.0.1 resolution: "@isaacs/balanced-match@npm:4.0.1" @@ -7016,6 +7160,17 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/core@npm:2.1.0": + version: 2.1.0 + resolution: "@opentelemetry/core@npm:2.1.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/562c44c89150ef9cc7be4fcbfccfb62f71eb95d487de79b6a2716bb960da7d181f5e2ae3354ed6bd0bba0a3903fe5b7dad14b9a4a92fa90ab1b9172f11a3743d + languageName: node + linkType: hard + "@opentelemetry/core@npm:2.2.0, @opentelemetry/core@npm:^2.0.0, @opentelemetry/core@npm:^2.2.0": version: 2.2.0 resolution: "@opentelemetry/core@npm:2.2.0" @@ -7865,7 +8020,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resources@npm:2.2.0, @opentelemetry/resources@npm:^2.0.0, @opentelemetry/resources@npm:^2.2.0": +"@opentelemetry/resources@npm:2.2.0, @opentelemetry/resources@npm:^2.2.0": version: 2.2.0 resolution: "@opentelemetry/resources@npm:2.2.0" dependencies: @@ -7877,6 +8032,18 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/resources@npm:^2.0.0": + version: 2.1.0 + resolution: "@opentelemetry/resources@npm:2.1.0" + dependencies: + "@opentelemetry/core": "npm:2.1.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10c0/5e33f349b088a110e3492add63a131680760f87265fa81f269dfc3e7978ab82f3e43513f8fc3b2e168127919ca50f47ffef0146c076bc1434a30a6567e28cf3d + languageName: node + linkType: hard + "@opentelemetry/sampler-jaeger-remote@npm:^0.208.0": version: 0.208.0 resolution: "@opentelemetry/sampler-jaeger-remote@npm:0.208.0" @@ -8373,6 +8540,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.52.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" @@ -8380,6 +8554,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm64@npm:4.52.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-android-arm64@npm:4.53.3" @@ -8387,6 +8568,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-arm64@npm:4.52.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" @@ -8394,6 +8582,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-x64@npm:4.52.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" @@ -8401,6 +8596,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.52.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-arm64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" @@ -8408,6 +8610,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-x64@npm:4.52.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" @@ -8415,6 +8624,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" @@ -8422,6 +8638,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.52.5" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" @@ -8429,6 +8652,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.52.5" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" @@ -8436,6 +8666,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.52.5" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" @@ -8443,6 +8680,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-loong64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.5" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-loong64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" @@ -8450,6 +8694,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-ppc64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.52.5" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" @@ -8457,6 +8708,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.52.5" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" @@ -8464,6 +8722,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.52.5" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-musl@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" @@ -8471,6 +8736,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.52.5" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" @@ -8478,6 +8750,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.52.5" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" @@ -8485,6 +8764,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.52.5" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" @@ -8492,6 +8778,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-openharmony-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.52.5" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-openharmony-arm64@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" @@ -8499,6 +8792,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.52.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" @@ -8506,6 +8806,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.52.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" @@ -8513,6 +8820,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-gnu@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" @@ -8520,6 +8834,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.52.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.53.3": version: 4.53.3 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" @@ -10442,29 +10763,29 @@ __metadata: languageName: node linkType: hard -"@whatwg-node/server@npm:^0.10.0, @whatwg-node/server@npm:^0.10.14, @whatwg-node/server@npm:^0.10.5": - version: 0.10.15 - resolution: "@whatwg-node/server@npm:0.10.15" +"@whatwg-node/server@npm:^0.10.0, @whatwg-node/server@npm:^0.10.17, @whatwg-node/server@npm:^0.10.5": + version: 0.10.17 + resolution: "@whatwg-node/server@npm:0.10.17" dependencies: "@envelop/instrumentation": "npm:^1.0.0" "@whatwg-node/disposablestack": "npm:^0.0.6" - "@whatwg-node/fetch": "npm:^0.10.12" + "@whatwg-node/fetch": "npm:^0.10.13" "@whatwg-node/promise-helpers": "npm:^1.3.2" tslib: "npm:^2.6.3" - checksum: 10c0/6391cec0a829f436ba3d79dd761c9d473cc5b2892a6065d2e87f94aab5ffd1773b899f1d98b524f5e7ada80117ef0d7624cac9c2ee517f9261abad920528b270 + checksum: 10c0/b93fffa837745213f1fb9b913654978ec0e590de751f4efb4ff114ae990383f2c10a5d27615297e5812e174145e85386661b857b96979194c8a3efbffb5dac89 languageName: node linkType: hard -"@whatwg-node/server@npm:^0.10.17": - version: 0.10.17 - resolution: "@whatwg-node/server@npm:0.10.17" +"@whatwg-node/server@npm:^0.10.14": + version: 0.10.15 + resolution: "@whatwg-node/server@npm:0.10.15" dependencies: "@envelop/instrumentation": "npm:^1.0.0" "@whatwg-node/disposablestack": "npm:^0.0.6" - "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/fetch": "npm:^0.10.12" "@whatwg-node/promise-helpers": "npm:^1.3.2" tslib: "npm:^2.6.3" - checksum: 10c0/b93fffa837745213f1fb9b913654978ec0e590de751f4efb4ff114ae990383f2c10a5d27615297e5812e174145e85386661b857b96979194c8a3efbffb5dac89 + checksum: 10c0/6391cec0a829f436ba3d79dd761c9d473cc5b2892a6065d2e87f94aab5ffd1773b899f1d98b524f5e7ada80117ef0d7624cac9c2ee517f9261abad920528b270 languageName: node linkType: hard @@ -13177,7 +13498,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:17.2.3, dotenv@npm:^17.0.0, dotenv@npm:^17.2.3": +"dotenv@npm:17.2.3, dotenv@npm:^17.2.3": version: 17.2.3 resolution: "dotenv@npm:17.2.3" checksum: 10c0/c884403209f713214a1b64d4d1defa4934c2aa5b0002f5a670ae298a51e3c3ad3ba79dfee2f8df49f01ae74290fcd9acdb1ab1d09c7bfb42b539036108bb2ba0 @@ -13191,6 +13512,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^17.0.0": + version: 17.2.2 + resolution: "dotenv@npm:17.2.2" + checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b + languageName: node + linkType: hard + "dotenv@npm:^8.1.0": version: 8.6.0 resolution: "dotenv@npm:8.6.0" @@ -14816,7 +15144,7 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.13.0, get-tsconfig@npm:^4.7.5, get-tsconfig@npm:^4.7.6, get-tsconfig@npm:^4.8.1": +"get-tsconfig@npm:^4.13.0": version: 4.13.0 resolution: "get-tsconfig@npm:4.13.0" dependencies: @@ -14825,6 +15153,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5, get-tsconfig@npm:^4.7.6, get-tsconfig@npm:^4.8.1": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/7f8e3dabc6a49b747920a800fb88e1952fef871cdf51b79e98db48275a5de6cdaf499c55ee67df5fa6fe7ce65f0063e26de0f2e53049b408c585aa74d39ffa21 + languageName: node + linkType: hard + "git-up@npm:^7.0.0": version: 7.0.0 resolution: "git-up@npm:7.0.0" @@ -15158,7 +15495,7 @@ __metadata: languageName: node linkType: hard -"graphql-yoga@npm:5.17.1, graphql-yoga@npm:^5.16.2, graphql-yoga@npm:^5.17.1": +"graphql-yoga@npm:5.17.1, graphql-yoga@npm:^5.17.1": version: 5.17.1 resolution: "graphql-yoga@npm:5.17.1" dependencies: @@ -15180,6 +15517,28 @@ __metadata: languageName: node linkType: hard +"graphql-yoga@npm:^5.17.0": + version: 5.17.0 + resolution: "graphql-yoga@npm:5.17.0" + dependencies: + "@envelop/core": "npm:^5.3.0" + "@envelop/instrumentation": "npm:^1.0.0" + "@graphql-tools/executor": "npm:^1.5.0" + "@graphql-tools/schema": "npm:^10.0.11" + "@graphql-tools/utils": "npm:^10.11.0" + "@graphql-yoga/logger": "npm:^2.0.1" + "@graphql-yoga/subscription": "npm:^5.0.5" + "@whatwg-node/fetch": "npm:^0.10.6" + "@whatwg-node/promise-helpers": "npm:^1.2.4" + "@whatwg-node/server": "npm:^0.10.14" + lru-cache: "npm:^10.0.0" + tslib: "npm:^2.8.1" + peerDependencies: + graphql: ^15.2.0 || ^16.0.0 + checksum: 10c0/789a18df17baeb14c7f794d6f0a373c03bba7a0bec0f90b35356a789ea9d9363cec83435c10c2a4266e14af1b2dec7f14635f3985aefa1a20588e773173e0ef8 + languageName: node + linkType: hard + "graphql@npm:16.12.0": version: 16.12.0 resolution: "graphql@npm:16.12.0" @@ -18669,13 +19028,20 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:8.3.0, path-to-regexp@npm:^8.0.0": +"path-to-regexp@npm:8.3.0": version: 8.3.0 resolution: "path-to-regexp@npm:8.3.0" checksum: 10c0/ee1544a73a3f294a97a4c663b0ce71bbf1621d732d80c9c9ed201b3e911a86cb628ebad691b9d40f40a3742fe22011e5a059d8eed2cf63ec2cb94f6fb4efe67c languageName: node linkType: hard +"path-to-regexp@npm:^8.0.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -19688,7 +20054,7 @@ __metadata: languageName: node linkType: hard -"rollup@npm:4.53.3, rollup@npm:^4.18.1, rollup@npm:^4.43.0, rollup@npm:^4.53.2": +"rollup@npm:4.53.3, rollup@npm:^4.53.2": version: 4.53.3 resolution: "rollup@npm:4.53.3" dependencies: @@ -19769,6 +20135,87 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.18.1, rollup@npm:^4.43.0": + version: 4.52.5 + resolution: "rollup@npm:4.52.5" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.52.5" + "@rollup/rollup-android-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-x64": "npm:4.52.5" + "@rollup/rollup-freebsd-arm64": "npm:4.52.5" + "@rollup/rollup-freebsd-x64": "npm:4.52.5" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-arm64-musl": "npm:4.52.5" + "@rollup/rollup-linux-loong64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-musl": "npm:4.52.5" + "@rollup/rollup-linux-s390x-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-musl": "npm:4.52.5" + "@rollup/rollup-openharmony-arm64": "npm:4.52.5" + "@rollup/rollup-win32-arm64-msvc": "npm:4.52.5" + "@rollup/rollup-win32-ia32-msvc": "npm:4.52.5" + "@rollup/rollup-win32-x64-gnu": "npm:4.52.5" + "@rollup/rollup-win32-x64-msvc": "npm:4.52.5" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/faf1697b305d13a149bb64a2bb7378344becc7c8580f56225c4c00adbf493d82480a44b3e3b1cc82a3ac5d1d4cab6dfc89e6635443895a2dc488969075f5b94d + languageName: node + linkType: hard + "router@npm:^2.2.0": version: 2.2.0 resolution: "router@npm:2.2.0"