From a75eae6ee2b3b35db5e5eb0bbd72833de340f405 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 26 Jul 2024 16:19:56 +0200 Subject: [PATCH] handle batched operations --- .../apollo-managed-federation/package.json | 1 + .../apollo-managed-federation/src/index.ts | 4 +- examples/hello-world copy/CHANGELOG.md | 405 ++++++++++++++++++ examples/hello-world copy/README.md | 93 ++++ examples/hello-world copy/index.ts | 27 ++ examples/hello-world copy/package.json | 20 + examples/hello-world copy/tsconfig.json | 9 + examples/hello-world/index.js | 3 + examples/hello-world/package.json | 1 + packages/graphql-yoga/package.json | 2 +- .../plugins/apollo-inline-trace/src/index.ts | 175 +++++--- .../plugins/apollo-usage-report/src/index.ts | 160 ++++--- pnpm-lock.yaml | 298 ++++++++++++- 13 files changed, 1067 insertions(+), 131 deletions(-) create mode 100644 examples/hello-world copy/CHANGELOG.md create mode 100644 examples/hello-world copy/README.md create mode 100644 examples/hello-world copy/index.ts create mode 100644 examples/hello-world copy/package.json create mode 100644 examples/hello-world copy/tsconfig.json diff --git a/examples/apollo-managed-federation/package.json b/examples/apollo-managed-federation/package.json index 8e328b9fd0..29396e1701 100644 --- a/examples/apollo-managed-federation/package.json +++ b/examples/apollo-managed-federation/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "@graphql-yoga/apollo-managed-federation": "workspace:^", + "@graphql-yoga/plugin-apollo-usage-report": "workspace:^", "graphql-yoga": "workspace:^", "tsx": "4.11.0" }, diff --git a/examples/apollo-managed-federation/src/index.ts b/examples/apollo-managed-federation/src/index.ts index 405bbe21f8..89f394a03a 100644 --- a/examples/apollo-managed-federation/src/index.ts +++ b/examples/apollo-managed-federation/src/index.ts @@ -1,9 +1,11 @@ import { createServer } from 'node:http'; import { createYoga } from 'graphql-yoga'; import { useManagedFederation } from '@graphql-yoga/apollo-managed-federation'; +import { useApolloUsageReport } from '@graphql-yoga/plugin-apollo-usage-report'; const yoga = createYoga({ - plugins: [useManagedFederation()], + plugins: [useManagedFederation(), useApolloUsageReport()], + logging: 'debug', }); const server = createServer(yoga); diff --git a/examples/hello-world copy/CHANGELOG.md b/examples/hello-world copy/CHANGELOG.md new file mode 100644 index 0000000000..133f76dfdc --- /dev/null +++ b/examples/hello-world copy/CHANGELOG.md @@ -0,0 +1,405 @@ +# hello-world + +## 0.13.11 + +### Patch Changes + +- Updated dependencies []: + - @graphql-yoga/node@2.13.11 + +## 0.13.10 + +### Patch Changes + +- Updated dependencies + [[`779b55ee`](https://github.com/dotansimha/graphql-yoga/commit/779b55eea843bd282f659e1012f255f62fd888b6)]: + - @graphql-yoga/node@2.13.10 + +## 0.13.9 + +### Patch Changes + +- Updated dependencies []: + - @graphql-yoga/node@2.13.9 + +## 0.13.8 + +### Patch Changes + +- Updated dependencies []: + - @graphql-yoga/node@2.13.8 + +## 0.13.7 + +### Patch Changes + +- Updated dependencies + [[`e4e8ade`](https://github.com/dotansimha/graphql-yoga/commit/e4e8ade526c2aec7ea28218ca7795e96b867fc6b), + [`94b41f3`](https://github.com/dotansimha/graphql-yoga/commit/94b41f30f598afb37db2438c736764e2a539cd10)]: + - @graphql-yoga/node@2.13.7 + +## 0.13.6 + +### Patch Changes + +- Updated dependencies [eecf24c] + - @graphql-yoga/node@2.13.6 + +## 0.13.5 + +### Patch Changes + +- Updated dependencies [c00dad3] + - @graphql-yoga/node@2.13.5 + +## 0.13.4 + +### Patch Changes + +- @graphql-yoga/node@2.13.4 + +## 0.13.3 + +### Patch Changes + +- Updated dependencies [639607d] + - @graphql-yoga/node@2.13.3 + +## 0.13.2 + +### Patch Changes + +- @graphql-yoga/node@2.13.2 + +## 0.13.1 + +### Patch Changes + +- @graphql-yoga/node@2.13.1 + +## 0.13.0 + +### Patch Changes + +- @graphql-yoga/node@2.13.0 + +## 0.12.0 + +### Patch Changes + +- @graphql-yoga/node@2.12.0 + +## 0.11.2 + +### Patch Changes + +- Updated dependencies [ca5f940] + - @graphql-yoga/node@2.11.2 + +## 0.11.1 + +### Patch Changes + +- Updated dependencies [9248df8] + - @graphql-yoga/node@2.11.1 + +## 0.11.0 + +### Patch Changes + +- Updated dependencies [8947657] + - @graphql-yoga/node@2.11.0 + +## 0.10.0 + +### Patch Changes + +- Updated dependencies [7de07cd] +- Updated dependencies [8922c3b] + - @graphql-yoga/node@2.10.0 + +## 0.9.2 + +### Patch Changes + +- @graphql-yoga/node@2.9.2 + +## 0.9.1 + +### Patch Changes + +- @graphql-yoga/node@2.9.1 + +## 0.9.0 + +### Patch Changes + +- Updated dependencies [06652c7] +- Updated dependencies [2d3c54c] + - @graphql-yoga/node@2.9.0 + +## 0.8.0 + +### Patch Changes + +- @graphql-yoga/node@2.8.0 + +## 0.7.0 + +### Patch Changes + +- @graphql-yoga/node@2.7.0 + +## 0.6.1 + +### Patch Changes + +- Updated dependencies [0224bf9] + - @graphql-yoga/node@2.6.1 + +## 0.6.0 + +### Patch Changes + +- @graphql-yoga/node@2.6.0 + +## 0.5.0 + +### Patch Changes + +- Updated dependencies [8b6d896] + - @graphql-yoga/node@2.5.0 + +## 0.4.1 + +### Patch Changes + +- @graphql-yoga/node@2.4.1 + +## 0.4.0 + +### Patch Changes + +- Updated dependencies [28e24c3] +- Updated dependencies [13f96db] + - @graphql-yoga/node@2.4.0 + +## 0.3.0 + +### Patch Changes + +- @graphql-yoga/node@2.3.0 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies [32e2e40] + - @graphql-yoga/node@2.2.1 + +## 0.2.0 + +### Patch Changes + +- Updated dependencies [1d4fe42] + - @graphql-yoga/node@2.2.0 + +## 0.1.0 + +### Patch Changes + +- Updated dependencies [4077773] +- Updated dependencies [2739db2] +- Updated dependencies [cd9394e] + - @graphql-yoga/node@2.1.0 + +## 0.0.1 + +### Patch Changes + +- de1693e: trigger release +- Updated dependencies [d414f95] +- Updated dependencies [133f8e9] +- Updated dependencies [14c93a7] +- Updated dependencies [ec777b1] +- Updated dependencies [dcaea56] +- Updated dependencies [b0b244b] +- Updated dependencies [cfec14b] +- Updated dependencies [433558f] +- Updated dependencies [3c82b57] +- Updated dependencies [f5f06f4] +- Updated dependencies [dcaea56] +- Updated dependencies [8ab60cf] +- Updated dependencies [433558f] +- Updated dependencies [5fba736] +- Updated dependencies [62e8c07] +- Updated dependencies [ce60a48] +- Updated dependencies [a8b619b] +- Updated dependencies [6d60ebf] +- Updated dependencies [44ad1b3] +- Updated dependencies [0424fe3] +- Updated dependencies [de1693e] +- Updated dependencies [d60f79f] +- Updated dependencies [dcaea56] +- Updated dependencies [daeea82] +- Updated dependencies [a10a16c] + - @graphql-yoga/node@0.1.0 + +## 0.0.1-beta.8 + +### Patch Changes + +- Updated dependencies [3c82b57] +- Updated dependencies [6d60ebf] +- Updated dependencies [0424fe3] +- Updated dependencies [d60f79f] + - @graphql-yoga/node@0.1.0-beta.8 + +## 0.0.1-beta.7 + +### Patch Changes + +- Updated dependencies [14c93a7] +- Updated dependencies [ec777b1] +- Updated dependencies [8ab60cf] + - @graphql-yoga/node@0.1.0-beta.7 + +## 0.0.1-beta.6 + +### Patch Changes + +- @graphql-yoga/node@0.1.0-beta.6 + +## 0.0.1-beta.5 + +### Patch Changes + +- Updated dependencies [cfec14b] +- Updated dependencies [5fba736] +- Updated dependencies [44ad1b3] + - @graphql-yoga/node@0.1.0-beta.5 + +## 0.0.1-beta.4 + +### Patch Changes + +- Updated dependencies [433558f] +- Updated dependencies [433558f] + - @graphql-yoga/node@0.1.0-beta.4 + +## 0.0.1-beta.3 + +### Patch Changes + +- Updated dependencies [62e8c07] + - @graphql-yoga/node@0.1.0-beta.3 + +## 0.0.1-beta.2 + +### Patch Changes + +- Updated dependencies [daeea82] + - @graphql-yoga/node@0.0.1-beta.2 + +## 0.0.1-beta.1 + +### Patch Changes + +- @graphql-yoga/node@0.0.1-beta.1 + +## 0.0.1-beta.0 + +### Patch Changes + +- de1693e: trigger release +- Updated dependencies [de1693e] + - @graphql-yoga/node@0.0.1-beta.0 + +## 0.0.1-alpha.11 + +### Patch Changes + +- Updated dependencies [133f8e9] +- Updated dependencies [dcaea56] +- Updated dependencies [f5f06f4] +- Updated dependencies [dcaea56] +- Updated dependencies [ce60a48] +- Updated dependencies [dcaea56] + - @graphql-yoga/node@0.1.0-alpha.4 + +## 0.0.1-alpha.10 + +### Patch Changes + +- @graphql-yoga/node@0.1.0-alpha.3 + +## 0.0.1-alpha.9 + +### Patch Changes + +- Updated dependencies [b0b244b] + - @graphql-yoga/node@0.1.0-alpha.2 + +## 0.0.1-alpha.8 + +### Patch Changes + +- @graphql-yoga/node@0.1.0-alpha.1 + +## 0.0.1-alpha.7 + +### Patch Changes + +- Updated dependencies [d414f95] +- Updated dependencies [a10a16c] + - @graphql-yoga/node@0.1.0-alpha.0 + +## 0.0.1-alpha.6 + +### Patch Changes + +- Updated dependencies [3d54829] + - graphql-yoga@2.0.0-alpha.7 + +## 0.0.1-alpha.5 + +### Patch Changes + +- Updated dependencies [36af58e] + - graphql-yoga@2.0.0-alpha.6 + +## 0.0.1-alpha.4 + +### Patch Changes + +- graphql-yoga@2.0.0-alpha.5 + +## 0.0.1-alpha.3 + +### Patch Changes + +- Updated dependencies [fb894da] + - graphql-yoga@2.0.0-alpha.4 + +## 0.0.1-alpha.2 + +### Patch Changes + +- Updated dependencies [0edf1f8] +- Updated dependencies [1a20e1e] +- Updated dependencies [9554f81] +- Updated dependencies [95e0ac0] + - graphql-yoga@2.0.0-alpha.3 + +## 0.0.1-alpha.1 + +### Patch Changes + +- graphql-yoga@2.0.0-alpha.2 + +## 0.0.1-alpha.0 + +### Patch Changes + +- Updated dependencies [d078e84] + - graphql-yoga@2.0.0-alpha.1 diff --git a/examples/hello-world copy/README.md b/examples/hello-world copy/README.md new file mode 100644 index 0000000000..211f691b93 --- /dev/null +++ b/examples/hello-world copy/README.md @@ -0,0 +1,93 @@ +# hello-world + +This directory contains a simple "Hello World" example based on `graphql-yoga`. + +## Get started + +**Clone the repository:** + +```sh +git clone https://github.com/graphcool/graphql-yoga.git +cd graphql-yoga/examples/hello-world +``` + +**Install dependencies and run the app:** + +```sh +pnpm install # or npm install +pnpm start # or npm start +``` + +## Testing + +Open your browser at [http://localhost:4000](http://localhost:4000) and start sending queries. + +**Query without `name` argument:** + +```graphql +query { + hello +} +``` + +The server returns the following response: + +```json +{ + "data": { + "hello": "Hello World" + } +} +``` + +**Query with `name` argument:** + +```graphql +query { + hello(name: "Sarah") +} +``` + +The server returns the following response: + +```json +{ + "data": { + "hello": "Hello Sarah" + } +} +``` + +## Implementation + +This is what the [implementation](./index.js) looks like: + +```js +import { createSchema, createServer } from 'http' +import { createYoga } from 'graphql-yoga' + +// ... or using `require()` +// const { createServer, createSchema } = require('graphql-yoga') + +const typeDefs = /* GraphQL */ ` + type Query { + hello(name: String): String! + } +` + +const resolvers = { + Query: { + hello: (_, { name }) => `Hello ${name || 'World'}` + } +} + +const yoga = createYoga({ + schema: createSchema({ + typeDefs, + resolvers + }) +}) + +const server = createServer(yoga) +server.listen(() => console.log('Server is running on localhost:4000')) +``` diff --git a/examples/hello-world copy/index.ts b/examples/hello-world copy/index.ts new file mode 100644 index 0000000000..9b6527ed03 --- /dev/null +++ b/examples/hello-world copy/index.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import { useEngine } from '@envelop/core'; +import { buildHTTPExecutor } from '@graphql-tools/executor-http'; +import { schemaFromExecutor } from '@graphql-tools/wrap'; + +const { createServer } = require('node:http'); +const { createYoga } = require('graphql-yoga'); + +const remoteExecutor = buildHTTPExecutor({ + endpoint: 'https://my.remote.service/graphql', +}); + +const yoga = createYoga({ + schema: await schemaFromExecutor(remoteExecutor), + + logging: 'debug', + plugins: [ + useEngine({ + execute: remoteExecutor, + }), + ], +}); + +const server = createServer(yoga); +server.listen(4000, () => { + console.log(`Server is running on http://localhost:4000${yoga.graphqlEndpoint}`); +}); diff --git a/examples/hello-world copy/package.json b/examples/hello-world copy/package.json new file mode 100644 index 0000000000..e30ee7c887 --- /dev/null +++ b/examples/hello-world copy/package.json @@ -0,0 +1,20 @@ +{ + "name": "example-hello-world-2", + "version": "0.13.11", + "private": true, + "scripts": { + "check": "exit 0", + "start": "tsx index.ts" + }, + "dependencies": { + "@envelop/core": "^5.0.0", + "@graphql-tools/executor-http": "^1.0.4", + "@graphql-tools/wrap": "^10.0.5", + "@graphql-yoga/plugin-apollo-usage-report": "workspace:^", + "@graphql-yoga/render-graphiql": "workspace:*", + "graphql": "16.6.0", + "graphql-yoga": "workspace:*", + "tsx": "^4.15.7", + "typescript": "^5.5.2" + } +} diff --git a/examples/hello-world copy/tsconfig.json b/examples/hello-world copy/tsconfig.json new file mode 100644 index 0000000000..2f080ed912 --- /dev/null +++ b/examples/hello-world copy/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "moduleResolution": "node", + "target": "esnext" + }, + "files": ["index.ts"] +} diff --git a/examples/hello-world/index.js b/examples/hello-world/index.js index 4e5ac514d0..9700546392 100644 --- a/examples/hello-world/index.js +++ b/examples/hello-world/index.js @@ -3,6 +3,7 @@ const { createServer } = require('node:http'); const { createYoga, createSchema } = require('graphql-yoga'); const { renderGraphiQL } = require('@graphql-yoga/render-graphiql'); +const { useApolloUsageReport } = require('@graphql-yoga/plugin-apollo-usage-report'); const yoga = createYoga({ schema: createSchema({ @@ -26,6 +27,8 @@ const yoga = createYoga({ `, }, renderGraphiQL, + logging: 'debug', + plugins: [useApolloUsageReport()], }); const server = createServer(yoga); diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index b1fbfa5468..7ab4c52f6b 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -7,6 +7,7 @@ "start": "node index.js" }, "dependencies": { + "@graphql-yoga/plugin-apollo-usage-report": "workspace:^", "@graphql-yoga/render-graphiql": "workspace:*", "graphql": "16.6.0", "graphql-yoga": "workspace:*" diff --git a/packages/graphql-yoga/package.json b/packages/graphql-yoga/package.json index 9834e81457..14b89f009a 100644 --- a/packages/graphql-yoga/package.json +++ b/packages/graphql-yoga/package.json @@ -56,7 +56,7 @@ "@graphql-yoga/logger": "workspace:^", "@graphql-yoga/subscription": "workspace:^", "@whatwg-node/fetch": "^0.9.18", - "@whatwg-node/server": "^0.9.41", + "@whatwg-node/server": "^0.9.44", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" diff --git a/packages/plugins/apollo-inline-trace/src/index.ts b/packages/plugins/apollo-inline-trace/src/index.ts index 7df398c8e9..9a322bd925 100644 --- a/packages/plugins/apollo-inline-trace/src/index.ts +++ b/packages/plugins/apollo-inline-trace/src/index.ts @@ -4,11 +4,11 @@ import { createGraphQLError, isAsyncIterable, Plugin, YogaInitialContext } from import { useOnResolve } from '@envelop/on-resolve'; import { btoa } from '@whatwg-node/fetch'; -export interface ApolloInlineTraceContext { +export interface ApolloInlineRequestTraceContext { startHrTime: [number, number]; - rootNode: ApolloReportingProtobuf.Trace.Node; - trace: ApolloReportingProtobuf.Trace; - nodes: Map; + traceStartTimestamp: ApolloReportingProtobuf.google.protobuf.Timestamp; + traces: Map; + /** * graphql-js can continue to execute more fields indefinitely after * `execute()` resolves. That's because parallelism on a selection set @@ -19,6 +19,12 @@ export interface ApolloInlineTraceContext { stopped: boolean; } +export interface ApolloInlineGraphqlTraceContext { + rootNode: ApolloReportingProtobuf.Trace.Node; + trace: ApolloReportingProtobuf.Trace; + nodes: Map; +} + export interface ApolloInlineTracePluginOptions { /** * Format errors before being sent for tracing. Beware that only the error @@ -60,24 +66,36 @@ export function useApolloInlineTrace( addPlugin(instrumentation); addPlugin({ onResultProcess({ request, result }) { - const ctx = ctxForReq.get(request); - if (!ctx) return; - // TODO: should handle streaming results? how? - if (isAsyncIterable(result)) return; + if (isAsyncIterable(result)) { + return; + } + + const reqCtx = ctxForReq.get(request); + if (!reqCtx) { + return; + } for (const singleResult of asArray(result)) { - // TODO: Probably broken in batched queries + const { ftv1_context, ...extensions } = singleResult.extensions || {}; + if (!ftv1_context) { + return; + } - if (singleResult.extensions?.ftv1 !== undefined) { + if (extensions?.ftv1 !== undefined) { throw new Error('The `ftv1` extension is already present'); } + const ctx = reqCtx.traces.get(ftv1_context); + if (!ctx) { + return; + } + const encodedUint8Array = ApolloReportingProtobuf.Trace.encode(ctx.trace).finish(); const base64 = btoa(String.fromCharCode(...encodedUint8Array)); singleResult.extensions = { - ...singleResult.extensions, + ...extensions, ftv1: base64, }; } @@ -95,66 +113,78 @@ export function useApolloInlineTrace( * @returns A tuple with the instrumentation plugin and a WeakMap containing the tracing data */ export function useApolloInstrumentation(options: ApolloInlineTracePluginOptions) { - const ctxForReq = new WeakMap(); + const ctxForReq = new WeakMap(); const plugin: Plugin = { onPluginInit: ({ addPlugin }) => { addPlugin( - useOnResolve(({ context: { request }, info }) => { - const ctx = ctxForReq.get(request); - if (!ctx) return; - + useOnResolve(({ context, info }) => { + const reqCtx = ctxForReq.get(context.request); + if (!reqCtx) return; // result was already shipped (see ApolloInlineTraceContext.stopped) - if (ctx.stopped) { - return () => { - // noop - }; + if (reqCtx.stopped) { + return; + } + + const ctx = reqCtx.traces.get(context); + if (!ctx) { + return; } const node = newTraceNode(ctx, info.path); node.type = info.returnType.toString(); node.parentType = info.parentType.toString(); - node.startTime = hrTimeToDurationInNanos(process.hrtime(ctx.startHrTime)); + node.startTime = hrTimeToDurationInNanos(process.hrtime(reqCtx.startHrTime)); if (typeof info.path.key === 'string' && info.path.key !== info.fieldName) { // field was aliased, send the original field name too node.originalFieldName = info.fieldName; } return () => { - node.endTime = hrTimeToDurationInNanos(process.hrtime(ctx.startHrTime)); + node.endTime = hrTimeToDurationInNanos(process.hrtime(reqCtx.startHrTime)); }; }), ); }, async onRequest({ request }) { - // must be ftv1 tracing protocol - if (await options.ignoreRequest?.(request)) { - return; - } + try { + // must be ftv1 tracing protocol + if (await options.ignoreRequest?.(request)) { + return; + } - const startHrTime = process.hrtime(); - const rootNode = new ApolloReportingProtobuf.Trace.Node(); - ctxForReq.set(request, { - startHrTime, - rootNode, - trace: new ApolloReportingProtobuf.Trace({ - root: rootNode, - fieldExecutionWeight: 1, // Why 1? See: https://github.com/apollographql/apollo-server/blob/9389da785567a56e989430962564afc71e93bd7f/packages/apollo-server-core/src/plugin/traceTreeBuilder.ts#L16-L23 - startTime: nowTimestamp(), - }), - nodes: new Map([[responsePathToString(), rootNode]]), - stopped: false, - }); + ctxForReq.set(request, { + startHrTime: process.hrtime(), + traceStartTimestamp: nowTimestamp(), + traces: new Map(), + stopped: false, + }); + } catch (err) { + console.error('Apollo inline error:', err); + } }, onParse() { - return ({ context: { request }, result }) => { - const ctx = ctxForReq.get(request); - if (!ctx) return; + return ({ context, result }) => { + const reqCtx = ctxForReq.get(context.request); + if (!reqCtx) return; + + const rootNode = new ApolloReportingProtobuf.Trace.Node(); + const ctx = { + rootNode, + trace: new ApolloReportingProtobuf.Trace({ + root: rootNode, + fieldExecutionWeight: 1, // Why 1? See: https://github.com/apollographql/apollo-server/blob/9389da785567a56e989430962564afc71e93bd7f/packages/apollo-server-core/src/plugin/traceTreeBuilder.ts#L16-L23 + startTime: reqCtx.traceStartTimestamp, + }), + nodes: new Map([[responsePathToString(), rootNode]]), + }; + reqCtx.traces.set(context, ctx); if (result instanceof GraphQLError) { - handleErrors(ctx, [result], options.rewriteError); + handleErrors(reqCtx, ctx, [result], options.rewriteError); } else if (result instanceof Error) { handleErrors( + reqCtx, ctx, [ createGraphQLError(result.message, { @@ -167,43 +197,53 @@ export function useApolloInstrumentation(options: ApolloInlineTracePluginOptions }; }, onValidate() { - return ({ context: { request }, result: errors }) => { + return ({ context, result: errors }) => { if (errors.length) { - const ctx = ctxForReq.get(request); - if (ctx) + const reqCtx = ctxForReq.get(context.request); + const ctx = reqCtx?.traces.get(context); + if (reqCtx && ctx) // Envelop doesn't give GraphQLError type since it is agnostic - handleErrors(ctx, errors as GraphQLError[], options.rewriteError); + handleErrors(reqCtx, ctx, errors as GraphQLError[], options.rewriteError); } }; }, onExecute() { return { - onExecuteDone({ - args: { - contextValue: { request }, - }, - result, - }) { + onExecuteDone({ args: { contextValue }, result }) { // TODO: should handle streaming results? how? - if (!isAsyncIterable(result) && result.errors?.length) { - const ctx = ctxForReq.get(request); - if (ctx) handleErrors(ctx, result.errors, options.rewriteError); + if (isAsyncIterable(result)) { + return; } + + const reqCtx = ctxForReq.get(contextValue.request); + const ctx = reqCtx?.traces.get(contextValue); + if (!reqCtx || !ctx || reqCtx.stopped) { + return; + } + + if (result.errors?.length && reqCtx && ctx) { + handleErrors(reqCtx, ctx, result.errors, options.rewriteError); + } + + result.extensions ||= {}; + result.extensions.ftv1_context = contextValue; }, }; }, onResultProcess({ request, result }) { - const ctx = ctxForReq.get(request); - if (!ctx) return; - // TODO: should handle streaming results? how? if (isAsyncIterable(result)) return; + const reqCtx = ctxForReq.get(request); + if (!reqCtx) return; // onResultProcess will be called only once since we disallow async iterables - if (ctx.stopped) throw new Error('Trace stopped multiple times'); - ctx.stopped = true; - ctx.trace.durationNs = hrTimeToDurationInNanos(process.hrtime(ctx.startHrTime)); - ctx.trace.endTime = nowTimestamp(); + if (reqCtx.stopped) throw new Error('Trace stopped multiple times'); + + reqCtx.stopped = true; + for (const ctx of reqCtx.traces.values()) { + ctx.trace.durationNs = hrTimeToDurationInNanos(process.hrtime(reqCtx.startHrTime)); + ctx.trace.endTime = nowTimestamp(); + } }, }; @@ -258,7 +298,7 @@ function responsePathToString(path?: ResponsePath): string { } function ensureParentTraceNode( - ctx: ApolloInlineTraceContext, + ctx: ApolloInlineGraphqlTraceContext, path: ResponsePath, ): ApolloReportingProtobuf.Trace.Node { const parentNode = ctx.nodes.get(responsePathToString(path.prev)); @@ -268,7 +308,7 @@ function ensureParentTraceNode( return newTraceNode(ctx, path.prev!); } -function newTraceNode(ctx: ApolloInlineTraceContext, path: ResponsePath) { +function newTraceNode(ctx: ApolloInlineGraphqlTraceContext, path: ResponsePath) { const node = new ApolloReportingProtobuf.Trace.Node(); const id = path.key; if (typeof id === 'number') { @@ -283,11 +323,12 @@ function newTraceNode(ctx: ApolloInlineTraceContext, path: ResponsePath) { } function handleErrors( - ctx: ApolloInlineTraceContext, + reqCtx: ApolloInlineRequestTraceContext, + ctx: ApolloInlineGraphqlTraceContext, errors: readonly GraphQLError[], rewriteError: ApolloInlineTracePluginOptions['rewriteError'], ) { - if (ctx.stopped) { + if (reqCtx.stopped) { throw new Error('Handling errors after tracing was stopped'); } diff --git a/packages/plugins/apollo-usage-report/src/index.ts b/packages/plugins/apollo-usage-report/src/index.ts index 2d04e01973..0013cec8f0 100644 --- a/packages/plugins/apollo-usage-report/src/index.ts +++ b/packages/plugins/apollo-usage-report/src/index.ts @@ -1,13 +1,19 @@ import { defaultUsageReportingSignature } from 'apollo-graphql'; import { Report } from 'apollo-reporting-protobuf'; import { printSchema } from 'graphql'; -import { isAsyncIterable, Plugin, YogaLogger } from 'graphql-yoga'; import { - ApolloInlineTraceContext, + isAsyncIterable, + Plugin, + YogaInitialContext, + YogaLogger, + type FetchAPI, +} from 'graphql-yoga'; +import { + ApolloInlineGraphqlTraceContext, + ApolloInlineRequestTraceContext, ApolloInlineTracePluginOptions, useApolloInstrumentation, } from '@graphql-yoga/plugin-apollo-inline-trace'; -import { fetch } from '@whatwg-node/fetch'; type ApolloUsageReportOptions = ApolloInlineTracePluginOptions & { /** @@ -36,55 +42,53 @@ type ApolloUsageReportOptions = ApolloInlineTracePluginOptions & { endpoint?: string; }; +export interface ApolloUsageReportRequestContext extends ApolloInlineRequestTraceContext { + traces: Map; +} + +export interface ApolloUsageReportGraphqlContext extends ApolloInlineGraphqlTraceContext { + operationKey?: string; + schemaId?: string; +} + const DEFAULT_REPORTING_ENDPOINT = 'https://usage-reporting.api.apollographql.com/api/ingress/traces'; export function useApolloUsageReport(options: ApolloUsageReportOptions = {}): Plugin { const [instrumentation, ctxForReq] = useApolloInstrumentation(options) as [ Plugin, - WeakMap< - Request, - ApolloInlineTraceContext & { - schemaId?: string; - operationKey?: string; - } - >, + WeakMap, ]; - const { - graphRef = process.env['APOLLO_GRAPH_REF'], - apiKey = process.env['APOLLO_KEY'], - endpoint = DEFAULT_REPORTING_ENDPOINT, - } = options; - let logger: YogaLogger; + let fetchAPI: FetchAPI; let schemaId: string; return { onPluginInit({ addPlugin }) { addPlugin(instrumentation); addPlugin({ - async onYogaInit(args) { + onYogaInit(args) { + fetchAPI = args.yoga.fetchAPI; logger = Object.fromEntries( - Object.entries(args.yoga.logger).map(([level, log]) => [ + (['error', 'warn', 'info', 'debug'] as const).map(level => [ level, - (...args: unknown[]) => log('[ApolloUsageReport]', ...args), + (...messages: unknown[]) => + args.yoga.logger[level]('[ApolloUsageReport]', ...messages), ]), ) as YogaLogger; - if (!apiKey) { + if (!options.apiKey && !process.env['APOLLO_KEY']) { throw new Error( `[ApolloUsageReport] Missing API key. Please provide one in plugin options or with 'APOLLO_KEY' environment variable.`, ); } - if (!graphRef) { + if (!options.graphRef && !process.env['APOLLO_GRAPH_REF']) { throw new Error( `[ApolloUsageReport] Missing Graph Ref. Please provide one in plugin options or with 'APOLLO_GRAPH_REF' environment variable.`, ); } - - logger.debug('using', { apiKey, graphRef }); }, async onSchemaChange({ schema }) { if (schema) { @@ -92,61 +96,52 @@ export function useApolloUsageReport(options: ApolloUsageReportOptions = {}): Pl } }, onParse() { - return ({ result, context: { request, params } }) => { - const ctx = ctxForReq.get(request); + return ({ result, context }) => { + const ctx = ctxForReq.get(context.request)?.traces.get(context); if (!ctx) { - logger.debug('ctx not found during parsing'); + logger.debug( + 'operation tracing context not found, this operation will not be traced.', + ); return; } - const { operationName } = params; + const { operationName } = context.params; const signature = defaultUsageReportingSignature(result, operationName || ''); ctx.operationKey = `# ${operationName || '-'}\n${signature}`; ctx.schemaId = schemaId; }; }, - async onResultProcess(args) { - const ctx = ctxForReq.get(args.request); - if (!ctx?.operationKey || !ctx?.schemaId) { - logger.debug('ctx not found during result processing:', ctx); - return; - } - + onResultProcess(args) { // TODO: Handle async iterables ? if (isAsyncIterable(args.result)) { logger.debug('async iterable results not implemented for now'); return; } - fetch(endpoint, { - method: 'POST', - headers: { - 'content-type': 'application/protobuf', - // The presence of the api key is already checked at Yoga initialization time - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - 'x-api-key': apiKey!, - accept: 'application/json', - }, - body: Report.encode({ - header: { - graphRef, - executableSchemaId: ctx.schemaId, - }, - operationCount: 1, - tracesPerQuery: { - [ctx.operationKey]: { - trace: [ctx.trace], - }, - }, - }).finish(), - }).then(async response => { - if (response.ok) { - logger.debug('Traces sent:', await response.text()); - } else { - logger.error('Failed to send trace:', await response.text()); + const reqCtx = ctxForReq.get(args.request); + if (!reqCtx) { + logger.debug('operation tracing context not found, this operation will not be traced.'); + return; + } + + // Each operation in a batched request can belongs to a different schema. + // Apollo doesn't allow to send batch queries for multiple schemas in the same batch + const tracesPerSchema: Record = {}; + for (const trace of reqCtx.traces.values()) { + if (!trace.schemaId || !trace.operationKey) { + throw new TypeError('Misformed trace, missing operation key or schema id'); } - }); + tracesPerSchema[trace.schemaId] ||= {}; + tracesPerSchema[trace.schemaId][trace.operationKey] ||= { trace: [] }; + tracesPerSchema[trace.schemaId][trace.operationKey].trace?.push(trace.trace); + } + + const tracesPromises = Object.entries(tracesPerSchema).map(([schemaId, tracesPerQuery]) => + sendTrace(options, logger, fetchAPI, schemaId, tracesPerQuery), + ); + + args.serverContext.waitUntil(Promise.all(tracesPromises)); }, }); }, @@ -170,3 +165,46 @@ export async function hashSHA256( } return hashHex; } + +async function sendTrace( + options: ApolloUsageReportOptions, + logger: YogaLogger, + { fetch }: FetchAPI, + schemaId: string, + tracesPerQuery: Report['tracesPerQuery'], +) { + const { + graphRef = process.env['APOLLO_GRAPH_REF'], + apiKey = process.env['APOLLO_KEY'], + endpoint = DEFAULT_REPORTING_ENDPOINT, + } = options; + + return fetch(endpoint, { + method: 'POST', + headers: { + 'content-type': 'application/protobuf', + // The presence of the api key is already checked at Yoga initialization time + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + 'x-api-key': apiKey!, + accept: 'application/json', + }, + body: Report.encode({ + header: { + graphRef, + executableSchemaId: schemaId, + }, + operationCount: 1, + tracesPerQuery, + }).finish(), + }) + .then(async response => { + if (response.ok) { + logger.debug('Traces sent:', await response.text()); + } else { + logger.error('Failed to send trace:', await response.text()); + } + }) + .catch(err => { + logger.error('Failed to send trace:', err); + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cac2ce112..169679c7e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -259,6 +259,9 @@ importers: '@graphql-yoga/apollo-managed-federation': specifier: workspace:^ version: link:../../packages/plugins/apollo-managed-federation/dist + '@graphql-yoga/plugin-apollo-usage-report': + specifier: workspace:^ + version: link:../../packages/plugins/apollo-usage-report/dist graphql-yoga: specifier: workspace:^ version: link:../../packages/graphql-yoga/dist @@ -802,6 +805,9 @@ importers: examples/hello-world: dependencies: + '@graphql-yoga/plugin-apollo-usage-report': + specifier: workspace:^ + version: link:../../packages/plugins/apollo-usage-report/dist '@graphql-yoga/render-graphiql': specifier: workspace:* version: link:../../packages/render-graphiql/dist @@ -812,6 +818,36 @@ importers: specifier: workspace:* version: link:../../packages/graphql-yoga/dist + examples/hello-world copy: + dependencies: + '@envelop/core': + specifier: 5.0.1 + version: 5.0.1 + '@graphql-tools/executor-http': + specifier: ^1.0.4 + version: 1.1.5(@types/node@20.14.12)(graphql@16.8.1) + '@graphql-tools/wrap': + specifier: ^10.0.5 + version: 10.0.5(graphql@16.8.1) + '@graphql-yoga/plugin-apollo-usage-report': + specifier: workspace:^ + version: link:../../packages/plugins/apollo-usage-report/dist + '@graphql-yoga/render-graphiql': + specifier: workspace:* + version: link:../../packages/render-graphiql/dist + graphql: + specifier: 16.8.1 + version: 16.8.1 + graphql-yoga: + specifier: workspace:* + version: link:../../packages/graphql-yoga/dist + tsx: + specifier: ^4.15.7 + version: 4.17.0 + typescript: + specifier: ^5.5.2 + version: 5.5.4 + examples/issue-template: dependencies: graphql: @@ -1602,7 +1638,7 @@ importers: specifier: ^0.9.18 version: 0.9.19 '@whatwg-node/server': - specifier: ^0.9.41 + specifier: ^0.9.44 version: 0.9.46 dset: specifier: ^3.1.1 @@ -3761,6 +3797,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.0': + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.17.19': resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -3803,6 +3845,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.0': + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.17.19': resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} engines: {node: '>=12'} @@ -3845,6 +3893,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.0': + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.17.19': resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -3887,6 +3941,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.0': + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.17.19': resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -3929,6 +3989,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.0': + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.17.19': resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -3971,6 +4037,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.0': + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.17.19': resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -4013,6 +4085,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.0': + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.17.19': resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -4055,6 +4133,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.0': + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.17.19': resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -4097,6 +4181,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.0': + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.17.19': resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -4139,6 +4229,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.0': + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.17.19': resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -4181,6 +4277,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.0': + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.17.19': resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} engines: {node: '>=12'} @@ -4223,6 +4325,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.0': + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.17.19': resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -4265,6 +4373,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.0': + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.17.19': resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -4307,6 +4421,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.0': + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.17.19': resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -4349,6 +4469,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.0': + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.17.19': resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -4391,6 +4517,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.0': + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.17.19': resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -4433,6 +4565,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.0': + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.17.19': resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -4475,6 +4613,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.0': + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.0': + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.17.19': resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -4517,6 +4667,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.0': + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.17.19': resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -4559,6 +4715,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.0': + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.17.19': resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -4601,6 +4763,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.0': + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.17.19': resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -4643,6 +4811,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.0': + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.17.19': resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -4685,6 +4859,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.0': + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@escape.tech/graphql-armor-block-field-suggestions@2.2.0': resolution: {integrity: sha512-a8E/mwDzlQsjv5WDxeG+cI7JprR+d0GbMKvwfNyuiS1f2yfSOiZjY45ZNbOhhPbIgvF8QtJysD+ZFax2cPcfUA==} engines: {node: '>=18.0.0'} @@ -10152,6 +10332,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -16378,6 +16563,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + engines: {node: '>=18.0.0'} + hasBin: true + tty-table@4.2.3: resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} engines: {node: '>=8.0.0'} @@ -19539,6 +19729,9 @@ snapshots: '@esbuild/aix-ppc64@0.21.4': optional: true + '@esbuild/aix-ppc64@0.23.0': + optional: true + '@esbuild/android-arm64@0.17.19': optional: true @@ -19560,6 +19753,9 @@ snapshots: '@esbuild/android-arm64@0.21.4': optional: true + '@esbuild/android-arm64@0.23.0': + optional: true + '@esbuild/android-arm@0.17.19': optional: true @@ -19581,6 +19777,9 @@ snapshots: '@esbuild/android-arm@0.21.4': optional: true + '@esbuild/android-arm@0.23.0': + optional: true + '@esbuild/android-x64@0.17.19': optional: true @@ -19602,6 +19801,9 @@ snapshots: '@esbuild/android-x64@0.21.4': optional: true + '@esbuild/android-x64@0.23.0': + optional: true + '@esbuild/darwin-arm64@0.17.19': optional: true @@ -19623,6 +19825,9 @@ snapshots: '@esbuild/darwin-arm64@0.21.4': optional: true + '@esbuild/darwin-arm64@0.23.0': + optional: true + '@esbuild/darwin-x64@0.17.19': optional: true @@ -19644,6 +19849,9 @@ snapshots: '@esbuild/darwin-x64@0.21.4': optional: true + '@esbuild/darwin-x64@0.23.0': + optional: true + '@esbuild/freebsd-arm64@0.17.19': optional: true @@ -19665,6 +19873,9 @@ snapshots: '@esbuild/freebsd-arm64@0.21.4': optional: true + '@esbuild/freebsd-arm64@0.23.0': + optional: true + '@esbuild/freebsd-x64@0.17.19': optional: true @@ -19686,6 +19897,9 @@ snapshots: '@esbuild/freebsd-x64@0.21.4': optional: true + '@esbuild/freebsd-x64@0.23.0': + optional: true + '@esbuild/linux-arm64@0.17.19': optional: true @@ -19707,6 +19921,9 @@ snapshots: '@esbuild/linux-arm64@0.21.4': optional: true + '@esbuild/linux-arm64@0.23.0': + optional: true + '@esbuild/linux-arm@0.17.19': optional: true @@ -19728,6 +19945,9 @@ snapshots: '@esbuild/linux-arm@0.21.4': optional: true + '@esbuild/linux-arm@0.23.0': + optional: true + '@esbuild/linux-ia32@0.17.19': optional: true @@ -19749,6 +19969,9 @@ snapshots: '@esbuild/linux-ia32@0.21.4': optional: true + '@esbuild/linux-ia32@0.23.0': + optional: true + '@esbuild/linux-loong64@0.17.19': optional: true @@ -19770,6 +19993,9 @@ snapshots: '@esbuild/linux-loong64@0.21.4': optional: true + '@esbuild/linux-loong64@0.23.0': + optional: true + '@esbuild/linux-mips64el@0.17.19': optional: true @@ -19791,6 +20017,9 @@ snapshots: '@esbuild/linux-mips64el@0.21.4': optional: true + '@esbuild/linux-mips64el@0.23.0': + optional: true + '@esbuild/linux-ppc64@0.17.19': optional: true @@ -19812,6 +20041,9 @@ snapshots: '@esbuild/linux-ppc64@0.21.4': optional: true + '@esbuild/linux-ppc64@0.23.0': + optional: true + '@esbuild/linux-riscv64@0.17.19': optional: true @@ -19833,6 +20065,9 @@ snapshots: '@esbuild/linux-riscv64@0.21.4': optional: true + '@esbuild/linux-riscv64@0.23.0': + optional: true + '@esbuild/linux-s390x@0.17.19': optional: true @@ -19854,6 +20089,9 @@ snapshots: '@esbuild/linux-s390x@0.21.4': optional: true + '@esbuild/linux-s390x@0.23.0': + optional: true + '@esbuild/linux-x64@0.17.19': optional: true @@ -19875,6 +20113,9 @@ snapshots: '@esbuild/linux-x64@0.21.4': optional: true + '@esbuild/linux-x64@0.23.0': + optional: true + '@esbuild/netbsd-x64@0.17.19': optional: true @@ -19896,6 +20137,12 @@ snapshots: '@esbuild/netbsd-x64@0.21.4': optional: true + '@esbuild/netbsd-x64@0.23.0': + optional: true + + '@esbuild/openbsd-arm64@0.23.0': + optional: true + '@esbuild/openbsd-x64@0.17.19': optional: true @@ -19917,6 +20164,9 @@ snapshots: '@esbuild/openbsd-x64@0.21.4': optional: true + '@esbuild/openbsd-x64@0.23.0': + optional: true + '@esbuild/sunos-x64@0.17.19': optional: true @@ -19938,6 +20188,9 @@ snapshots: '@esbuild/sunos-x64@0.21.4': optional: true + '@esbuild/sunos-x64@0.23.0': + optional: true + '@esbuild/win32-arm64@0.17.19': optional: true @@ -19959,6 +20212,9 @@ snapshots: '@esbuild/win32-arm64@0.21.4': optional: true + '@esbuild/win32-arm64@0.23.0': + optional: true + '@esbuild/win32-ia32@0.17.19': optional: true @@ -19980,6 +20236,9 @@ snapshots: '@esbuild/win32-ia32@0.21.4': optional: true + '@esbuild/win32-ia32@0.23.0': + optional: true + '@esbuild/win32-x64@0.17.19': optional: true @@ -20001,6 +20260,9 @@ snapshots: '@esbuild/win32-x64@0.21.4': optional: true + '@esbuild/win32-x64@0.23.0': + optional: true + '@escape.tech/graphql-armor-block-field-suggestions@2.2.0': dependencies: graphql: 16.8.1 @@ -27267,6 +27529,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.4 '@esbuild/win32-x64': 0.21.4 + esbuild@0.23.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.0 + '@esbuild/android-arm': 0.23.0 + '@esbuild/android-arm64': 0.23.0 + '@esbuild/android-x64': 0.23.0 + '@esbuild/darwin-arm64': 0.23.0 + '@esbuild/darwin-x64': 0.23.0 + '@esbuild/freebsd-arm64': 0.23.0 + '@esbuild/freebsd-x64': 0.23.0 + '@esbuild/linux-arm': 0.23.0 + '@esbuild/linux-arm64': 0.23.0 + '@esbuild/linux-ia32': 0.23.0 + '@esbuild/linux-loong64': 0.23.0 + '@esbuild/linux-mips64el': 0.23.0 + '@esbuild/linux-ppc64': 0.23.0 + '@esbuild/linux-riscv64': 0.23.0 + '@esbuild/linux-s390x': 0.23.0 + '@esbuild/linux-x64': 0.23.0 + '@esbuild/netbsd-x64': 0.23.0 + '@esbuild/openbsd-arm64': 0.23.0 + '@esbuild/openbsd-x64': 0.23.0 + '@esbuild/sunos-x64': 0.23.0 + '@esbuild/win32-arm64': 0.23.0 + '@esbuild/win32-ia32': 0.23.0 + '@esbuild/win32-x64': 0.23.0 + escalade@3.1.2: {} escape-goat@4.0.0: {} @@ -35539,6 +35828,13 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsx@4.17.0: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + tty-table@4.2.3: dependencies: chalk: 4.1.2