@graphql-yoga/plugin-apollo-usage-report 0.13.0 → 0.13.1-alpha-20260116132159-d18a95d04a1e11d197fdf672a8be4836ceed0818

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,152 @@
1
+ const require_reporter = require('./reporter.cjs');
2
+ let graphql = require("graphql");
3
+ let graphql_yoga = require("graphql-yoga");
4
+ let _apollo_utils_usagereporting = require("@apollo/utils.usagereporting");
5
+ let _graphql_tools_utils = require("@graphql-tools/utils");
6
+ let _graphql_yoga_plugin_apollo_inline_trace = require("@graphql-yoga/plugin-apollo-inline-trace");
7
+
8
+ //#region src/index.ts
9
+ function useApolloUsageReport(options = {}) {
10
+ const [instrumentation, ctxForReq] = (0, _graphql_yoga_plugin_apollo_inline_trace.useApolloInstrumentation)(options);
11
+ const makeReporter = options.reporter ?? ((...args) => new require_reporter.Reporter(...args));
12
+ let schemaIdSet$;
13
+ let currentSchema;
14
+ let yoga;
15
+ let reporter;
16
+ const setCurrentSchema = async (schema) => {
17
+ try {
18
+ currentSchema = {
19
+ id: await hashSHA256((0, _graphql_tools_utils.printSchemaWithDirectives)(schema), yoga.fetchAPI),
20
+ schema
21
+ };
22
+ } catch (error) {
23
+ logger.error("Failed to calculate schema hash: ", error);
24
+ }
25
+ schemaIdSet$ = void 0;
26
+ };
27
+ const logger = Object.fromEntries([
28
+ "error",
29
+ "warn",
30
+ "info",
31
+ "debug"
32
+ ].map((level) => [level, (...messages) => yoga.logger[level]("[ApolloUsageReport]", ...messages)]));
33
+ let clientNameFactory = (req) => req.headers.get("apollographql-client-name");
34
+ if (typeof options.clientName === "function") clientNameFactory = options.clientName;
35
+ let clientVersionFactory = (req) => req.headers.get("apollographql-client-version");
36
+ if (typeof options.clientVersion === "function") clientVersionFactory = options.clientVersion;
37
+ return { onPluginInit({ addPlugin }) {
38
+ addPlugin(instrumentation);
39
+ addPlugin({
40
+ onYogaInit(args) {
41
+ yoga = args.yoga;
42
+ reporter = makeReporter(options, yoga, logger);
43
+ if (!require_reporter.getEnvVar("APOLLO_KEY", options.apiKey)) throw new Error(`[ApolloUsageReport] Missing API key. Please provide one in plugin options or with 'APOLLO_KEY' environment variable.`);
44
+ if (!require_reporter.getEnvVar("APOLLO_GRAPH_REF", options.graphRef)) throw new Error(`[ApolloUsageReport] Missing Graph Ref. Please provide one in plugin options or with 'APOLLO_GRAPH_REF' environment variable.`);
45
+ if (!schemaIdSet$ && !currentSchema) {
46
+ const { schema } = yoga.getEnveloped();
47
+ if (schema) schemaIdSet$ = setCurrentSchema(schema);
48
+ }
49
+ },
50
+ onSchemaChange({ schema }) {
51
+ if (schema && yoga) schemaIdSet$ = setCurrentSchema(schema);
52
+ },
53
+ onRequestParse() {
54
+ return schemaIdSet$;
55
+ },
56
+ onParse() {
57
+ return function onParseEnd({ result, context }) {
58
+ const ctx = ctxForReq.get(context.request)?.traces.get(context);
59
+ if (!ctx) {
60
+ logger.debug("operation tracing context not found, this operation will not be traced.");
61
+ return;
62
+ }
63
+ ctx.schemaId = currentSchema.id;
64
+ if (isDocumentNode(result)) {
65
+ if ((0, graphql.getOperationAST)(result, context.params.operationName)) return;
66
+ ctx.operationKey = `## GraphQLUnknownOperationName\n`;
67
+ } else ctx.operationKey = "## GraphQLParseFailure\n";
68
+ if (!options.sendUnexecutableOperationDocuments) {
69
+ ctxForReq.delete(context.request);
70
+ return;
71
+ }
72
+ ctx.trace.unexecutedOperationName = context.params.operationName || "";
73
+ ctx.trace.unexecutedOperationBody = context.params.query || "";
74
+ };
75
+ },
76
+ onValidate({ params: { documentAST: document } }) {
77
+ return ({ valid, context }) => {
78
+ const ctx = ctxForReq.get(context.request)?.traces.get(context);
79
+ if (!ctx) {
80
+ logger.debug("operation tracing context not found, this operation will not be traced.");
81
+ return;
82
+ }
83
+ if (valid) {
84
+ if (!currentSchema) throw new Error("should not happen: schema doesn't exists");
85
+ const opName = (0, graphql.getOperationAST)(document, context.params.operationName)?.name?.value;
86
+ ctx.referencedFieldsByType = (0, _apollo_utils_usagereporting.calculateReferencedFieldsByType)({
87
+ document,
88
+ schema: currentSchema.schema,
89
+ resolvedOperationName: opName ?? null
90
+ });
91
+ ctx.operationKey = `# ${opName || "-"}\n${(0, _apollo_utils_usagereporting.usageReportingSignature)(document, opName ?? "")}`;
92
+ } else if (options.sendUnexecutableOperationDocuments) {
93
+ ctx.operationKey = "## GraphQLValidationFailure\n";
94
+ ctx.trace.unexecutedOperationName = context.params.operationName ?? "";
95
+ ctx.trace.unexecutedOperationBody = context.params.query ?? "";
96
+ } else ctxForReq.delete(context.request);
97
+ };
98
+ },
99
+ onResultProcess({ request, result, serverContext }) {
100
+ if ((0, graphql_yoga.isAsyncIterable)(result)) {
101
+ logger.debug("async iterable results not implemented for now");
102
+ return;
103
+ }
104
+ const reqCtx = ctxForReq.get(request);
105
+ if (!reqCtx) {
106
+ logger.debug("operation tracing context not found, this operation will not be traced.");
107
+ return;
108
+ }
109
+ for (const trace of reqCtx.traces.values()) {
110
+ if (!trace.schemaId || !trace.operationKey) {
111
+ logger.debug("Misformed trace, missing operation key or schema id");
112
+ continue;
113
+ }
114
+ const clientName = clientNameFactory(request);
115
+ if (clientName) trace.trace.clientName = clientName;
116
+ const clientVersion = clientVersionFactory(request);
117
+ if (clientVersion) trace.trace.clientVersion = clientVersion;
118
+ serverContext.waitUntil(reporter.addTrace(currentSchema.id, {
119
+ statsReportKey: trace.operationKey,
120
+ trace: trace.trace,
121
+ referencedFieldsByType: trace.referencedFieldsByType ?? {},
122
+ asTrace: true,
123
+ nonFtv1ErrorPaths: [],
124
+ maxTraceBytes: options.maxTraceSize
125
+ }));
126
+ }
127
+ },
128
+ async onDispose() {
129
+ await reporter?.flush();
130
+ }
131
+ });
132
+ } };
133
+ }
134
+ async function hashSHA256(text, api = globalThis) {
135
+ const inputUint8Array = new api.TextEncoder().encode(text);
136
+ const arrayBuf = await api.crypto.subtle.digest({ name: "SHA-256" }, inputUint8Array);
137
+ const outputUint8Array = new Uint8Array(arrayBuf);
138
+ let hash = "";
139
+ for (const byte of outputUint8Array) {
140
+ const hex = byte.toString(16);
141
+ hash += "00".slice(0, Math.max(0, 2 - hex.length)) + hex;
142
+ }
143
+ return hash;
144
+ }
145
+ function isDocumentNode(data) {
146
+ const isObject = (data$1) => !!data$1 && typeof data$1 === "object";
147
+ return isObject(data) && data["kind"] === graphql.Kind.DOCUMENT;
148
+ }
149
+
150
+ //#endregion
151
+ exports.hashSHA256 = hashSHA256;
152
+ exports.useApolloUsageReport = useApolloUsageReport;
@@ -0,0 +1,125 @@
1
+ import { Reporter } from "./reporter.cjs";
2
+ import { Maybe, Plugin, YogaInitialContext, YogaLogger, YogaServer } from "graphql-yoga";
3
+ import { calculateReferencedFieldsByType } from "@apollo/utils.usagereporting";
4
+ import { ApolloInlineGraphqlTraceContext, ApolloInlineRequestTraceContext, ApolloInlineTracePluginOptions } from "@graphql-yoga/plugin-apollo-inline-trace";
5
+
6
+ //#region src/index.d.ts
7
+ type ApolloUsageReportOptions = ApolloInlineTracePluginOptions & {
8
+ /**
9
+ * The graph ref of the managed federation graph.
10
+ * It is composed of the graph ID and the variant (`<YOUR_GRAPH_ID>@<VARIANT>`).
11
+ *
12
+ * If not provided, `APOLLO_GRAPH_REF` environment variable is used.
13
+ *
14
+ * You can find a a graph's ref at the top of its Schema Reference page in Apollo Studio.
15
+ */
16
+ graphRef?: string;
17
+ /**
18
+ * The API key to use to authenticate with the managed federation up link.
19
+ * It needs at least the `service:read` permission.
20
+ *
21
+ * If not provided, `APOLLO_KEY` environment variable will be used instead.
22
+ *
23
+ * [Learn how to create an API key](https://www.apollographql.com/docs/federation/v1/managed-federation/setup#4-connect-the-gateway-to-studio)
24
+ */
25
+ apiKey?: string;
26
+ /**
27
+ * Usage report endpoint
28
+ *
29
+ * Defaults to GraphOS endpoint (https://usage-reporting.api.apollographql.com/api/ingress/traces)
30
+ */
31
+ endpoint?: string;
32
+ /**
33
+ * Agent Version to report to the usage reporting API
34
+ */
35
+ agentVersion?: string;
36
+ /**
37
+ * Client name to report to the usage reporting API
38
+ */
39
+ clientName?: StringFromRequestFn;
40
+ /**
41
+ * Client version to report to the usage reporting API
42
+ */
43
+ clientVersion?: StringFromRequestFn;
44
+ /**
45
+ * The version of the runtime (like 'node v23.7.0')
46
+ * @default empty string.
47
+ */
48
+ runtimeVersion?: string;
49
+ /**
50
+ * The hostname of the machine running this server
51
+ * @default $HOSTNAME environment variable
52
+ */
53
+ hostname?: string;
54
+ /**
55
+ * The OS identification string.
56
+ * The format is `${os.platform()}, ${os.type()}, ${os.release()}, ${os.arch()})`
57
+ * @default empty string
58
+ */
59
+ uname?: string;
60
+ /**
61
+ * The maximum estimated size of each traces in bytes. If the estimated size is higher than this threshold,
62
+ * the complete trace will not be sent and will be reduced to aggregated stats.
63
+ *
64
+ * Note: GraphOS only allow for traces of 10mb maximum
65
+ * @default 10 * 1024 * 1024 (10mb)
66
+ */
67
+ maxTraceSize?: number;
68
+ /**
69
+ * The maximum uncompressed size of a report in bytes.
70
+ * The report will be sent once this threshold is reached, even if the delay between send is not
71
+ * yet expired.
72
+ *
73
+ * @default 4Mb
74
+ */
75
+ maxBatchUncompressedSize?: number;
76
+ /**
77
+ * The maximum time in ms between reports.
78
+ * @default 20s
79
+ */
80
+ maxBatchDelay?: number;
81
+ /**
82
+ * Control if traces should be always sent.
83
+ * If false, the traces will be batched until a delay or size is reached.
84
+ * Note: This is highly not recommended in a production environment
85
+ *
86
+ * @default false
87
+ */
88
+ alwaysSend?: boolean;
89
+ /**
90
+ * Timeout in ms of a trace export tentative
91
+ * @default 30s
92
+ */
93
+ exportTimeout?: number;
94
+ /**
95
+ * The class to be used to keep track of traces and send them to the GraphOS endpoint
96
+ * Note: This option is aimed to be used for testing purposes
97
+ */
98
+ reporter?: (options: ApolloUsageReportOptions, yoga: YogaServer<Record<string, unknown>, Record<string, unknown>>, logger: YogaLogger) => Reporter;
99
+ /**
100
+ * Called when all retry attempts to send a report to GraphOS endpoint failed.
101
+ * By default, the error is logged.
102
+ */
103
+ onError?: (err: Error) => void;
104
+ /**
105
+ * If false, unexecutable operation (with parsing or validation error) will not be sent
106
+ * @default false
107
+ */
108
+ sendUnexecutableOperationDocuments?: boolean;
109
+ };
110
+ interface ApolloUsageReportRequestContext extends ApolloInlineRequestTraceContext {
111
+ traces: Map<YogaInitialContext, ApolloUsageReportGraphqlContext>;
112
+ }
113
+ interface ApolloUsageReportGraphqlContext extends ApolloInlineGraphqlTraceContext {
114
+ referencedFieldsByType?: ReturnType<typeof calculateReferencedFieldsByType>;
115
+ operationKey?: string;
116
+ schemaId?: string;
117
+ }
118
+ type StringFromRequestFn = (req: Request) => Maybe<string>;
119
+ declare function useApolloUsageReport(options?: ApolloUsageReportOptions): Plugin;
120
+ declare function hashSHA256(text: string, api?: {
121
+ crypto: Crypto;
122
+ TextEncoder: (typeof globalThis)['TextEncoder'];
123
+ }): Promise<string>;
124
+ //#endregion
125
+ export { ApolloUsageReportGraphqlContext, ApolloUsageReportOptions, ApolloUsageReportRequestContext, hashSHA256, useApolloUsageReport };
@@ -0,0 +1,125 @@
1
+ import { Reporter } from "./reporter.mjs";
2
+ import { Maybe, Plugin, YogaInitialContext, YogaLogger, YogaServer } from "graphql-yoga";
3
+ import { calculateReferencedFieldsByType } from "@apollo/utils.usagereporting";
4
+ import { ApolloInlineGraphqlTraceContext, ApolloInlineRequestTraceContext, ApolloInlineTracePluginOptions } from "@graphql-yoga/plugin-apollo-inline-trace";
5
+
6
+ //#region src/index.d.ts
7
+ type ApolloUsageReportOptions = ApolloInlineTracePluginOptions & {
8
+ /**
9
+ * The graph ref of the managed federation graph.
10
+ * It is composed of the graph ID and the variant (`<YOUR_GRAPH_ID>@<VARIANT>`).
11
+ *
12
+ * If not provided, `APOLLO_GRAPH_REF` environment variable is used.
13
+ *
14
+ * You can find a a graph's ref at the top of its Schema Reference page in Apollo Studio.
15
+ */
16
+ graphRef?: string;
17
+ /**
18
+ * The API key to use to authenticate with the managed federation up link.
19
+ * It needs at least the `service:read` permission.
20
+ *
21
+ * If not provided, `APOLLO_KEY` environment variable will be used instead.
22
+ *
23
+ * [Learn how to create an API key](https://www.apollographql.com/docs/federation/v1/managed-federation/setup#4-connect-the-gateway-to-studio)
24
+ */
25
+ apiKey?: string;
26
+ /**
27
+ * Usage report endpoint
28
+ *
29
+ * Defaults to GraphOS endpoint (https://usage-reporting.api.apollographql.com/api/ingress/traces)
30
+ */
31
+ endpoint?: string;
32
+ /**
33
+ * Agent Version to report to the usage reporting API
34
+ */
35
+ agentVersion?: string;
36
+ /**
37
+ * Client name to report to the usage reporting API
38
+ */
39
+ clientName?: StringFromRequestFn;
40
+ /**
41
+ * Client version to report to the usage reporting API
42
+ */
43
+ clientVersion?: StringFromRequestFn;
44
+ /**
45
+ * The version of the runtime (like 'node v23.7.0')
46
+ * @default empty string.
47
+ */
48
+ runtimeVersion?: string;
49
+ /**
50
+ * The hostname of the machine running this server
51
+ * @default $HOSTNAME environment variable
52
+ */
53
+ hostname?: string;
54
+ /**
55
+ * The OS identification string.
56
+ * The format is `${os.platform()}, ${os.type()}, ${os.release()}, ${os.arch()})`
57
+ * @default empty string
58
+ */
59
+ uname?: string;
60
+ /**
61
+ * The maximum estimated size of each traces in bytes. If the estimated size is higher than this threshold,
62
+ * the complete trace will not be sent and will be reduced to aggregated stats.
63
+ *
64
+ * Note: GraphOS only allow for traces of 10mb maximum
65
+ * @default 10 * 1024 * 1024 (10mb)
66
+ */
67
+ maxTraceSize?: number;
68
+ /**
69
+ * The maximum uncompressed size of a report in bytes.
70
+ * The report will be sent once this threshold is reached, even if the delay between send is not
71
+ * yet expired.
72
+ *
73
+ * @default 4Mb
74
+ */
75
+ maxBatchUncompressedSize?: number;
76
+ /**
77
+ * The maximum time in ms between reports.
78
+ * @default 20s
79
+ */
80
+ maxBatchDelay?: number;
81
+ /**
82
+ * Control if traces should be always sent.
83
+ * If false, the traces will be batched until a delay or size is reached.
84
+ * Note: This is highly not recommended in a production environment
85
+ *
86
+ * @default false
87
+ */
88
+ alwaysSend?: boolean;
89
+ /**
90
+ * Timeout in ms of a trace export tentative
91
+ * @default 30s
92
+ */
93
+ exportTimeout?: number;
94
+ /**
95
+ * The class to be used to keep track of traces and send them to the GraphOS endpoint
96
+ * Note: This option is aimed to be used for testing purposes
97
+ */
98
+ reporter?: (options: ApolloUsageReportOptions, yoga: YogaServer<Record<string, unknown>, Record<string, unknown>>, logger: YogaLogger) => Reporter;
99
+ /**
100
+ * Called when all retry attempts to send a report to GraphOS endpoint failed.
101
+ * By default, the error is logged.
102
+ */
103
+ onError?: (err: Error) => void;
104
+ /**
105
+ * If false, unexecutable operation (with parsing or validation error) will not be sent
106
+ * @default false
107
+ */
108
+ sendUnexecutableOperationDocuments?: boolean;
109
+ };
110
+ interface ApolloUsageReportRequestContext extends ApolloInlineRequestTraceContext {
111
+ traces: Map<YogaInitialContext, ApolloUsageReportGraphqlContext>;
112
+ }
113
+ interface ApolloUsageReportGraphqlContext extends ApolloInlineGraphqlTraceContext {
114
+ referencedFieldsByType?: ReturnType<typeof calculateReferencedFieldsByType>;
115
+ operationKey?: string;
116
+ schemaId?: string;
117
+ }
118
+ type StringFromRequestFn = (req: Request) => Maybe<string>;
119
+ declare function useApolloUsageReport(options?: ApolloUsageReportOptions): Plugin;
120
+ declare function hashSHA256(text: string, api?: {
121
+ crypto: Crypto;
122
+ TextEncoder: (typeof globalThis)['TextEncoder'];
123
+ }): Promise<string>;
124
+ //#endregion
125
+ export { ApolloUsageReportGraphqlContext, ApolloUsageReportOptions, ApolloUsageReportRequestContext, hashSHA256, useApolloUsageReport };
package/dist/index.mjs ADDED
@@ -0,0 +1,151 @@
1
+ import { Reporter, getEnvVar } from "./reporter.mjs";
2
+ import { Kind, getOperationAST } from "graphql";
3
+ import { isAsyncIterable } from "graphql-yoga";
4
+ import { calculateReferencedFieldsByType, usageReportingSignature } from "@apollo/utils.usagereporting";
5
+ import { printSchemaWithDirectives } from "@graphql-tools/utils";
6
+ import { useApolloInstrumentation } from "@graphql-yoga/plugin-apollo-inline-trace";
7
+
8
+ //#region src/index.ts
9
+ function useApolloUsageReport(options = {}) {
10
+ const [instrumentation, ctxForReq] = useApolloInstrumentation(options);
11
+ const makeReporter = options.reporter ?? ((...args) => new Reporter(...args));
12
+ let schemaIdSet$;
13
+ let currentSchema;
14
+ let yoga;
15
+ let reporter;
16
+ const setCurrentSchema = async (schema) => {
17
+ try {
18
+ currentSchema = {
19
+ id: await hashSHA256(printSchemaWithDirectives(schema), yoga.fetchAPI),
20
+ schema
21
+ };
22
+ } catch (error) {
23
+ logger.error("Failed to calculate schema hash: ", error);
24
+ }
25
+ schemaIdSet$ = void 0;
26
+ };
27
+ const logger = Object.fromEntries([
28
+ "error",
29
+ "warn",
30
+ "info",
31
+ "debug"
32
+ ].map((level) => [level, (...messages) => yoga.logger[level]("[ApolloUsageReport]", ...messages)]));
33
+ let clientNameFactory = (req) => req.headers.get("apollographql-client-name");
34
+ if (typeof options.clientName === "function") clientNameFactory = options.clientName;
35
+ let clientVersionFactory = (req) => req.headers.get("apollographql-client-version");
36
+ if (typeof options.clientVersion === "function") clientVersionFactory = options.clientVersion;
37
+ return { onPluginInit({ addPlugin }) {
38
+ addPlugin(instrumentation);
39
+ addPlugin({
40
+ onYogaInit(args) {
41
+ yoga = args.yoga;
42
+ reporter = makeReporter(options, yoga, logger);
43
+ if (!getEnvVar("APOLLO_KEY", options.apiKey)) throw new Error(`[ApolloUsageReport] Missing API key. Please provide one in plugin options or with 'APOLLO_KEY' environment variable.`);
44
+ if (!getEnvVar("APOLLO_GRAPH_REF", options.graphRef)) throw new Error(`[ApolloUsageReport] Missing Graph Ref. Please provide one in plugin options or with 'APOLLO_GRAPH_REF' environment variable.`);
45
+ if (!schemaIdSet$ && !currentSchema) {
46
+ const { schema } = yoga.getEnveloped();
47
+ if (schema) schemaIdSet$ = setCurrentSchema(schema);
48
+ }
49
+ },
50
+ onSchemaChange({ schema }) {
51
+ if (schema && yoga) schemaIdSet$ = setCurrentSchema(schema);
52
+ },
53
+ onRequestParse() {
54
+ return schemaIdSet$;
55
+ },
56
+ onParse() {
57
+ return function onParseEnd({ result, context }) {
58
+ const ctx = ctxForReq.get(context.request)?.traces.get(context);
59
+ if (!ctx) {
60
+ logger.debug("operation tracing context not found, this operation will not be traced.");
61
+ return;
62
+ }
63
+ ctx.schemaId = currentSchema.id;
64
+ if (isDocumentNode(result)) {
65
+ if (getOperationAST(result, context.params.operationName)) return;
66
+ ctx.operationKey = `## GraphQLUnknownOperationName\n`;
67
+ } else ctx.operationKey = "## GraphQLParseFailure\n";
68
+ if (!options.sendUnexecutableOperationDocuments) {
69
+ ctxForReq.delete(context.request);
70
+ return;
71
+ }
72
+ ctx.trace.unexecutedOperationName = context.params.operationName || "";
73
+ ctx.trace.unexecutedOperationBody = context.params.query || "";
74
+ };
75
+ },
76
+ onValidate({ params: { documentAST: document } }) {
77
+ return ({ valid, context }) => {
78
+ const ctx = ctxForReq.get(context.request)?.traces.get(context);
79
+ if (!ctx) {
80
+ logger.debug("operation tracing context not found, this operation will not be traced.");
81
+ return;
82
+ }
83
+ if (valid) {
84
+ if (!currentSchema) throw new Error("should not happen: schema doesn't exists");
85
+ const opName = getOperationAST(document, context.params.operationName)?.name?.value;
86
+ ctx.referencedFieldsByType = calculateReferencedFieldsByType({
87
+ document,
88
+ schema: currentSchema.schema,
89
+ resolvedOperationName: opName ?? null
90
+ });
91
+ ctx.operationKey = `# ${opName || "-"}\n${usageReportingSignature(document, opName ?? "")}`;
92
+ } else if (options.sendUnexecutableOperationDocuments) {
93
+ ctx.operationKey = "## GraphQLValidationFailure\n";
94
+ ctx.trace.unexecutedOperationName = context.params.operationName ?? "";
95
+ ctx.trace.unexecutedOperationBody = context.params.query ?? "";
96
+ } else ctxForReq.delete(context.request);
97
+ };
98
+ },
99
+ onResultProcess({ request, result, serverContext }) {
100
+ if (isAsyncIterable(result)) {
101
+ logger.debug("async iterable results not implemented for now");
102
+ return;
103
+ }
104
+ const reqCtx = ctxForReq.get(request);
105
+ if (!reqCtx) {
106
+ logger.debug("operation tracing context not found, this operation will not be traced.");
107
+ return;
108
+ }
109
+ for (const trace of reqCtx.traces.values()) {
110
+ if (!trace.schemaId || !trace.operationKey) {
111
+ logger.debug("Misformed trace, missing operation key or schema id");
112
+ continue;
113
+ }
114
+ const clientName = clientNameFactory(request);
115
+ if (clientName) trace.trace.clientName = clientName;
116
+ const clientVersion = clientVersionFactory(request);
117
+ if (clientVersion) trace.trace.clientVersion = clientVersion;
118
+ serverContext.waitUntil(reporter.addTrace(currentSchema.id, {
119
+ statsReportKey: trace.operationKey,
120
+ trace: trace.trace,
121
+ referencedFieldsByType: trace.referencedFieldsByType ?? {},
122
+ asTrace: true,
123
+ nonFtv1ErrorPaths: [],
124
+ maxTraceBytes: options.maxTraceSize
125
+ }));
126
+ }
127
+ },
128
+ async onDispose() {
129
+ await reporter?.flush();
130
+ }
131
+ });
132
+ } };
133
+ }
134
+ async function hashSHA256(text, api = globalThis) {
135
+ const inputUint8Array = new api.TextEncoder().encode(text);
136
+ const arrayBuf = await api.crypto.subtle.digest({ name: "SHA-256" }, inputUint8Array);
137
+ const outputUint8Array = new Uint8Array(arrayBuf);
138
+ let hash = "";
139
+ for (const byte of outputUint8Array) {
140
+ const hex = byte.toString(16);
141
+ hash += "00".slice(0, Math.max(0, 2 - hex.length)) + hex;
142
+ }
143
+ return hash;
144
+ }
145
+ function isDocumentNode(data) {
146
+ const isObject = (data$1) => !!data$1 && typeof data$1 === "object";
147
+ return isObject(data) && data["kind"] === Kind.DOCUMENT;
148
+ }
149
+
150
+ //#endregion
151
+ export { hashSHA256, useApolloUsageReport };
@@ -0,0 +1,118 @@
1
+ const require_stats = require('./stats.cjs');
2
+ let _apollo_usage_reporting_protobuf = require("@apollo/usage-reporting-protobuf");
3
+
4
+ //#region src/reporter.ts
5
+ const DEFAULT_REPORTING_ENDPOINT = "https://usage-reporting.api.apollographql.com/api/ingress/traces";
6
+ var Reporter = class {
7
+ reportHeaders;
8
+ options;
9
+ reportsBySchema = {};
10
+ nextSendAfterDelay;
11
+ sending = [];
12
+ constructor(options, yoga, logger) {
13
+ this.yoga = yoga;
14
+ this.logger = logger;
15
+ this.options = {
16
+ ...options,
17
+ maxBatchDelay: options.maxBatchDelay ?? 2e4,
18
+ maxBatchUncompressedSize: options.maxBatchUncompressedSize ?? 4 * 1024 * 1024,
19
+ maxTraceSize: options.maxTraceSize ?? 10 * 1024 * 1024,
20
+ exportTimeout: options.exportTimeout ?? 3e4,
21
+ onError: options.onError ?? ((err) => this.logger.error("Failed to send report", err))
22
+ };
23
+ this.reportHeaders = {
24
+ graphRef: getGraphRef(options),
25
+ hostname: options.hostname ?? getEnvVar("HOSTNAME") ?? "",
26
+ uname: options.uname ?? "",
27
+ runtimeVersion: options.runtimeVersion ?? "",
28
+ agentVersion: options.agentVersion || `graphql-yoga@${yoga.version}`
29
+ };
30
+ }
31
+ addTrace(schemaId, options) {
32
+ const report = this.getReport(schemaId);
33
+ report.addTrace(options);
34
+ if (this.options.alwaysSend || report.sizeEstimator.bytes >= this.options.maxBatchUncompressedSize) return this._sendReport(schemaId);
35
+ this.nextSendAfterDelay ||= setTimeout(() => this.flush(), this.options.maxBatchDelay);
36
+ }
37
+ async flush() {
38
+ return Promise.allSettled([...this.sending, ...Object.keys(this.reportsBySchema).map((schemaId) => this._sendReport(schemaId))]);
39
+ }
40
+ async sendReport(schemaId) {
41
+ const sending = this._sendReport(schemaId);
42
+ this.sending.push(sending);
43
+ sending.finally(() => this.sending = this.sending?.filter((p) => p !== sending));
44
+ return sending;
45
+ }
46
+ async _sendReport(schemaId) {
47
+ const { fetchAPI: { fetch, CompressionStream, ReadableStream } } = this.yoga;
48
+ const report = this.reportsBySchema[schemaId];
49
+ if (!report) throw new Error(`No report to send for schema ${schemaId}`);
50
+ if (this.nextSendAfterDelay != null) {
51
+ clearTimeout(this.nextSendAfterDelay);
52
+ this.nextSendAfterDelay = void 0;
53
+ }
54
+ delete this.reportsBySchema[schemaId];
55
+ report.endTime = dateToProtoTimestamp(/* @__PURE__ */ new Date());
56
+ report.ensureCountsAreIntegers();
57
+ const validationError = _apollo_usage_reporting_protobuf.Report.verify(report);
58
+ if (validationError) throw new TypeError(`Invalid report: ${validationError}`);
59
+ const { apiKey = getEnvVar("APOLLO_KEY"), endpoint = DEFAULT_REPORTING_ENDPOINT } = this.options;
60
+ const encodedReport = _apollo_usage_reporting_protobuf.Report.encode(report).finish();
61
+ let lastError;
62
+ for (let tries = 0; tries < 5; tries++) try {
63
+ this.logger.debug(`Sending report (try ${tries}/5)`);
64
+ const response = await fetch(endpoint, {
65
+ method: "POST",
66
+ headers: {
67
+ "content-type": "application/protobuf",
68
+ "content-encoding": "gzip",
69
+ "x-api-key": apiKey,
70
+ accept: "application/json"
71
+ },
72
+ body: new ReadableStream({ start(controller) {
73
+ controller.enqueue(encodedReport);
74
+ controller.close();
75
+ } }).pipeThrough(new CompressionStream("gzip")),
76
+ signal: AbortSignal.timeout(this.options.exportTimeout)
77
+ });
78
+ const result = await response.text();
79
+ if (response.ok) {
80
+ this.logger.debug("Report sent:", result);
81
+ return;
82
+ }
83
+ throw result;
84
+ } catch (err) {
85
+ lastError = err;
86
+ this.logger.error("Failed to send report:", err);
87
+ }
88
+ this.options.onError(new Error("Failed to send traces after 5 tries", { cause: lastError }));
89
+ }
90
+ getReport(schemaId) {
91
+ const report = this.reportsBySchema[schemaId];
92
+ if (report) return report;
93
+ return this.reportsBySchema[schemaId] = new require_stats.OurReport(new _apollo_usage_reporting_protobuf.ReportHeader({
94
+ ...this.reportHeaders,
95
+ executableSchemaId: schemaId
96
+ }));
97
+ }
98
+ };
99
+ function getGraphRef(options) {
100
+ const graphRef = options.graphRef || getEnvVar("APOLLO_GRAPH_REF");
101
+ if (!graphRef) throw new Error("Missing GraphRef. Either provide `graphRef` option or `APOLLO_GRAPH_REF` environment variable");
102
+ return graphRef;
103
+ }
104
+ function getEnvVar(name, defaultValue) {
105
+ return globalThis.process?.env?.[name] || defaultValue || void 0;
106
+ }
107
+ function dateToProtoTimestamp(date) {
108
+ const totalMillis = date.getTime();
109
+ const millis = totalMillis % 1e3;
110
+ return new _apollo_usage_reporting_protobuf.google.protobuf.Timestamp({
111
+ seconds: (totalMillis - millis) / 1e3,
112
+ nanos: millis * 1e6
113
+ });
114
+ }
115
+
116
+ //#endregion
117
+ exports.Reporter = Reporter;
118
+ exports.getEnvVar = getEnvVar;