@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/esm/stats.js DELETED
@@ -1,539 +0,0 @@
1
- // Copied from https://github.com/apollographql/apollo-server/blob/8c6579e5b61276b62dc7e30e6fac9a4242e24daa/packages/server/src/plugin/usageReporting/stats.ts
2
- /* eslint-disable */
3
- import { Trace, } from '@apollo/usage-reporting-protobuf';
4
- // protobuf.js exports both a class and an interface (starting with I) for each
5
- // message type. The class is what it produces when it decodes the message; the
6
- // interface is what is accepted as input. We build up our messages using custom
7
- // types implementing the interfaces, so that we can take advantage of the
8
- // js_use_toArray option we added to our protobuf.js fork which allows us to use
9
- // classes like DurationHistogram to generate repeated fields. We end up
10
- // re-creating most of the report structure as custom classes (starting with
11
- // "Our"). TypeScript validates that we've properly listed all of the message
12
- // fields with the appropriate types (we use `Required` to ensure we implement
13
- // all message fields). Using our own classes has other advantages, like being
14
- // able to specify that nested messages are instances of the same class rather
15
- // than the interface type and thus that they have non-null fields (because the
16
- // interface type allows all fields to be optional, even though the protobuf
17
- // format doesn't differentiate between missing and falsey).
18
- export class SizeEstimator {
19
- bytes = 0;
20
- }
21
- export class OurReport {
22
- header;
23
- // Apollo Server includes each operation either as aggregated stats or as a
24
- // trace, but not both. Other reporting agents such as Apollo Router include
25
- // all operations in stats (even those that are sent as traces), and they set
26
- // this flag to true.
27
- tracesPreAggregated = false;
28
- constructor(header) {
29
- this.header = header;
30
- }
31
- tracesPerQuery = Object.create(null);
32
- endTime = null;
33
- operationCount = 0;
34
- // A rough estimate of the number of bytes currently in the report. We start
35
- // at zero and don't count `header` and `endTime`, which have the same size
36
- // for every report. This really is a rough estimate, so we don't stress too
37
- // much about counting bytes for the tags and string/message lengths, etc:
38
- // we mostly just count the lengths of strings plus some estimates for the
39
- // messages with a bunch of numbers in them.
40
- //
41
- // We store this in a class so we can pass it down as a reference to other
42
- // methods which increment it.
43
- sizeEstimator = new SizeEstimator();
44
- ensureCountsAreIntegers() {
45
- for (const tracesAndStats of Object.values(this.tracesPerQuery)) {
46
- tracesAndStats.ensureCountsAreIntegers();
47
- }
48
- }
49
- addTrace({ statsReportKey, trace, asTrace, referencedFieldsByType,
50
- // The max size a trace can be before it is sent as stats. Note that the
51
- // Apollo reporting ingress server will never store any traces over 10mb
52
- // anyway. They will still be converted to stats as we would do here.
53
- maxTraceBytes = 10 * 1024 * 1024, nonFtv1ErrorPaths, }) {
54
- const tracesAndStats = this.getTracesAndStats({
55
- statsReportKey,
56
- referencedFieldsByType,
57
- });
58
- if (asTrace) {
59
- const encodedTrace = Trace.encode(trace).finish();
60
- if (!isNaN(maxTraceBytes) && encodedTrace.length > maxTraceBytes) {
61
- tracesAndStats.statsWithContext.addTrace(trace, this.sizeEstimator, nonFtv1ErrorPaths);
62
- }
63
- else {
64
- tracesAndStats.trace.push(encodedTrace);
65
- this.sizeEstimator.bytes += 2 + encodedTrace.length;
66
- }
67
- }
68
- else {
69
- tracesAndStats.statsWithContext.addTrace(trace, this.sizeEstimator, nonFtv1ErrorPaths);
70
- }
71
- }
72
- getTracesAndStats({ statsReportKey, referencedFieldsByType, }) {
73
- const existing = this.tracesPerQuery[statsReportKey];
74
- if (existing) {
75
- return existing;
76
- }
77
- this.sizeEstimator.bytes += estimatedBytesForString(statsReportKey);
78
- // Update the size estimator for the referenced field structure.
79
- for (const [typeName, referencedFieldsForType] of Object.entries(referencedFieldsByType)) {
80
- // Two bytes each for the map entry and for the ReferencedFieldsForType,
81
- // and for the isInterface bool if it's set.
82
- this.sizeEstimator.bytes += 2 + 2;
83
- if (referencedFieldsForType.isInterface) {
84
- this.sizeEstimator.bytes += 2;
85
- }
86
- this.sizeEstimator.bytes += estimatedBytesForString(typeName);
87
- for (const fieldName of referencedFieldsForType.fieldNames) {
88
- this.sizeEstimator.bytes += estimatedBytesForString(fieldName);
89
- }
90
- }
91
- // Include the referenced fields map in the report. (In an ideal world we
92
- // could have a slightly more sophisticated protocol and ingestion pipeline
93
- // that allowed us to only have to send this data once for each
94
- // schema/operation pair.)
95
- return (this.tracesPerQuery[statsReportKey] = new OurTracesAndStats(referencedFieldsByType));
96
- }
97
- }
98
- class OurTracesAndStats {
99
- referencedFieldsByType;
100
- constructor(referencedFieldsByType) {
101
- this.referencedFieldsByType = referencedFieldsByType;
102
- }
103
- trace = [];
104
- statsWithContext = new StatsByContext();
105
- internalTracesContributingToStats = [];
106
- ensureCountsAreIntegers() {
107
- this.statsWithContext.ensureCountsAreIntegers();
108
- }
109
- }
110
- class StatsByContext {
111
- map = Object.create(null);
112
- /**
113
- * This function is used by the protobuf generator to convert this map into
114
- * an array of contextualized stats to serialize
115
- */
116
- toArray() {
117
- return Object.values(this.map);
118
- }
119
- ensureCountsAreIntegers() {
120
- for (const contextualizedStats of Object.values(this.map)) {
121
- contextualizedStats.ensureCountsAreIntegers();
122
- }
123
- }
124
- addTrace(trace, sizeEstimator, nonFtv1ErrorPaths) {
125
- this.getContextualizedStats(trace, sizeEstimator).addTrace(trace, sizeEstimator, nonFtv1ErrorPaths);
126
- }
127
- getContextualizedStats(trace, sizeEstimator) {
128
- const statsContext = {
129
- clientName: trace.clientName,
130
- clientVersion: trace.clientVersion,
131
- };
132
- const statsContextKey = JSON.stringify(statsContext);
133
- const existing = this.map[statsContextKey];
134
- if (existing) {
135
- return existing;
136
- }
137
- // Adding a ContextualizedStats means adding a StatsContext plus a
138
- // QueryLatencyStats. Let's guess about 20 bytes for a QueryLatencyStats;
139
- // it'll be more if more features are used (like cache, APQ, etc).
140
- sizeEstimator.bytes +=
141
- 20 + estimatedBytesForString(trace.clientName) + estimatedBytesForString(trace.clientVersion);
142
- const contextualizedStats = new OurContextualizedStats(statsContext);
143
- this.map[statsContextKey] = contextualizedStats;
144
- return contextualizedStats;
145
- }
146
- }
147
- export class OurContextualizedStats {
148
- context;
149
- queryLatencyStats = new OurQueryLatencyStats();
150
- perTypeStat = Object.create(null);
151
- constructor(context) {
152
- this.context = context;
153
- }
154
- ensureCountsAreIntegers() {
155
- for (const typeStat of Object.values(this.perTypeStat)) {
156
- typeStat.ensureCountsAreIntegers();
157
- }
158
- }
159
- // Extract statistics from the trace, and increment the estimated report size.
160
- // We only add to the estimate when adding whole sub-messages. If it really
161
- // mattered, we could do a lot more careful things like incrementing it
162
- // whenever a numeric field on queryLatencyStats gets incremented over 0.
163
- addTrace(trace, sizeEstimator, nonFtv1ErrorPaths = []) {
164
- const { fieldExecutionWeight } = trace;
165
- if (!fieldExecutionWeight) {
166
- this.queryLatencyStats.requestsWithoutFieldInstrumentation++;
167
- }
168
- this.queryLatencyStats.requestCount++;
169
- if (trace.fullQueryCacheHit) {
170
- this.queryLatencyStats.cacheLatencyCount.incrementDuration(trace.durationNs);
171
- this.queryLatencyStats.cacheHits++;
172
- }
173
- else {
174
- this.queryLatencyStats.latencyCount.incrementDuration(trace.durationNs);
175
- }
176
- // We only provide stats about cache TTLs on cache misses (ie, TTLs directly
177
- // calculated by the backend), not for cache hits. This matches the
178
- // behavior we've had for a while when converting traces into statistics
179
- // in Studio's servers.
180
- if (!trace.fullQueryCacheHit && trace.cachePolicy?.maxAgeNs != null) {
181
- switch (trace.cachePolicy.scope) {
182
- case Trace.CachePolicy.Scope.PRIVATE:
183
- this.queryLatencyStats.privateCacheTtlCount.incrementDuration(trace.cachePolicy.maxAgeNs);
184
- break;
185
- case Trace.CachePolicy.Scope.PUBLIC:
186
- this.queryLatencyStats.publicCacheTtlCount.incrementDuration(trace.cachePolicy.maxAgeNs);
187
- break;
188
- }
189
- }
190
- if (trace.persistedQueryHit) {
191
- this.queryLatencyStats.persistedQueryHits++;
192
- }
193
- if (trace.persistedQueryRegister) {
194
- this.queryLatencyStats.persistedQueryMisses++;
195
- }
196
- if (trace.forbiddenOperation) {
197
- this.queryLatencyStats.forbiddenOperationCount++;
198
- }
199
- if (trace.registeredOperation) {
200
- this.queryLatencyStats.registeredOperationCount++;
201
- }
202
- let hasError = false;
203
- const errorPathStats = new Set();
204
- const traceNodeStats = (node, path) => {
205
- // Generate error stats and error path information
206
- if (node.error?.length) {
207
- hasError = true;
208
- let currPathErrorStats = this.queryLatencyStats.rootErrorStats;
209
- path.toArray().forEach(subPath => {
210
- currPathErrorStats = currPathErrorStats.getChild(subPath, sizeEstimator);
211
- });
212
- errorPathStats.add(currPathErrorStats);
213
- currPathErrorStats.errorsCount += node.error.length;
214
- }
215
- if (fieldExecutionWeight) {
216
- // The actual field name behind the node; originalFieldName is set
217
- // if an alias was used, otherwise responseName. (This is falsey for
218
- // nodes that are not fields (root, array index, etc).)
219
- const fieldName = node.originalFieldName || node.responseName;
220
- // Protobuf doesn't really differentiate between "unset" and "falsey" so
221
- // we're mostly actually checking that these things are non-empty string /
222
- // non-zero numbers. The time fields represent the number of nanoseconds
223
- // since the beginning of the entire trace, so let's pretend for the
224
- // moment that it's plausible for a node to start or even end exactly when
225
- // the trace started (ie, for the time values to be 0). This is unlikely
226
- // in practice (everything should take at least 1ns). In practice we only
227
- // write `type` and `parentType` on a Node when we write `startTime`, so
228
- // the main thing we're looking out for by checking the time values is
229
- // whether we somehow failed to write `endTime` at the end of the field;
230
- // in this case, the `endTime >= startTime` check won't match.
231
- if (node.parentType &&
232
- fieldName &&
233
- node.type &&
234
- node.endTime != null &&
235
- node.startTime != null &&
236
- node.endTime >= node.startTime) {
237
- const typeStat = this.getTypeStat(node.parentType, sizeEstimator);
238
- const fieldStat = typeStat.getFieldStat(fieldName, node.type, sizeEstimator);
239
- fieldStat.errorsCount += node.error?.length ?? 0;
240
- fieldStat.observedExecutionCount++;
241
- fieldStat.estimatedExecutionCount += fieldExecutionWeight;
242
- // Note: this is actually counting the number of resolver calls for this
243
- // field that had at least one error, not the number of overall GraphQL
244
- // queries that had at least one error for this field. That doesn't seem
245
- // to match the name, but it does match the other implementations of this
246
- // logic.
247
- fieldStat.requestsWithErrorsCount += (node.error?.length ?? 0) > 0 ? 1 : 0;
248
- fieldStat.latencyCount.incrementDuration(node.endTime - node.startTime,
249
- // The latency histogram is always "estimated"; we don't track
250
- // "observed" and "estimated" separately.
251
- fieldExecutionWeight);
252
- }
253
- }
254
- return false;
255
- };
256
- iterateOverTrace(trace, traceNodeStats, true);
257
- // iterate over nonFtv1ErrorPaths, using some bits from traceNodeStats function
258
- for (const { subgraph, path } of nonFtv1ErrorPaths) {
259
- hasError = true;
260
- if (path) {
261
- let currPathErrorStats = this.queryLatencyStats.rootErrorStats.getChild(`service:${subgraph}`, sizeEstimator);
262
- path.forEach(subPath => {
263
- if (typeof subPath === 'string') {
264
- currPathErrorStats = currPathErrorStats.getChild(subPath, sizeEstimator);
265
- }
266
- });
267
- errorPathStats.add(currPathErrorStats);
268
- currPathErrorStats.errorsCount += 1;
269
- }
270
- }
271
- for (const errorPath of errorPathStats) {
272
- errorPath.requestsWithErrorsCount += 1;
273
- }
274
- if (hasError) {
275
- this.queryLatencyStats.requestsWithErrorsCount++;
276
- }
277
- }
278
- getTypeStat(parentType, sizeEstimator) {
279
- const existing = this.perTypeStat[parentType];
280
- if (existing) {
281
- return existing;
282
- }
283
- sizeEstimator.bytes += estimatedBytesForString(parentType);
284
- const typeStat = new OurTypeStat();
285
- this.perTypeStat[parentType] = typeStat;
286
- return typeStat;
287
- }
288
- }
289
- class OurQueryLatencyStats {
290
- latencyCount = new DurationHistogram();
291
- requestCount = 0;
292
- requestsWithoutFieldInstrumentation = 0;
293
- cacheHits = 0;
294
- persistedQueryHits = 0;
295
- persistedQueryMisses = 0;
296
- cacheLatencyCount = new DurationHistogram();
297
- rootErrorStats = new OurPathErrorStats();
298
- requestsWithErrorsCount = 0;
299
- publicCacheTtlCount = new DurationHistogram();
300
- privateCacheTtlCount = new DurationHistogram();
301
- registeredOperationCount = 0;
302
- forbiddenOperationCount = 0;
303
- }
304
- class OurPathErrorStats {
305
- children = Object.create(null);
306
- errorsCount = 0;
307
- requestsWithErrorsCount = 0;
308
- getChild(subPath, sizeEstimator) {
309
- const existing = this.children[subPath];
310
- if (existing) {
311
- return existing;
312
- }
313
- const child = new OurPathErrorStats();
314
- this.children[subPath] = child;
315
- // Include a few bytes in the estimate for the numbers etc.
316
- sizeEstimator.bytes += estimatedBytesForString(subPath) + 4;
317
- return child;
318
- }
319
- }
320
- class OurTypeStat {
321
- perFieldStat = Object.create(null);
322
- getFieldStat(fieldName, returnType, sizeEstimator) {
323
- const existing = this.perFieldStat[fieldName];
324
- if (existing) {
325
- return existing;
326
- }
327
- // Rough estimate of 10 bytes for the numbers in the FieldStat.
328
- sizeEstimator.bytes +=
329
- estimatedBytesForString(fieldName) + estimatedBytesForString(returnType) + 10;
330
- const fieldStat = new OurFieldStat(returnType);
331
- this.perFieldStat[fieldName] = fieldStat;
332
- return fieldStat;
333
- }
334
- ensureCountsAreIntegers() {
335
- for (const fieldStat of Object.values(this.perFieldStat)) {
336
- fieldStat.ensureCountsAreIntegers();
337
- }
338
- }
339
- }
340
- class OurFieldStat {
341
- returnType;
342
- errorsCount = 0;
343
- observedExecutionCount = 0;
344
- // Note that this number isn't necessarily an integer while it is being
345
- // aggregated. Before encoding as a protobuf we call ensureCountsAreIntegers
346
- // which floors it.
347
- estimatedExecutionCount = 0;
348
- requestsWithErrorsCount = 0;
349
- latencyCount = new DurationHistogram();
350
- constructor(returnType) {
351
- this.returnType = returnType;
352
- }
353
- ensureCountsAreIntegers() {
354
- // This is the only one that ever can receive non-integers.
355
- this.estimatedExecutionCount = Math.floor(this.estimatedExecutionCount);
356
- }
357
- }
358
- function estimatedBytesForString(s) {
359
- // 2 is for the tag (field ID + wire type) plus the encoded length. (The
360
- // encoded length takes up more than 1 byte for strings that are longer than
361
- // 127 bytes, but this is an estimate.)
362
- return 2 + Buffer.byteLength(s);
363
- }
364
- export class DurationHistogram {
365
- // Note that it's legal for the values in "buckets" to be non-integers; they
366
- // will be floored by toArray (which is called by the protobuf encoder).
367
- // (We take advantage of this for field latencies specifically, because
368
- // the ability to return a non-1 weight from fieldLevelInstrumentation
369
- // means we want to build up our histograms as floating-point rather than
370
- // rounding after every operation.)
371
- buckets;
372
- static BUCKET_COUNT = 384;
373
- static EXPONENT_LOG = Math.log(1.1);
374
- toArray() {
375
- let bufferedZeroes = 0;
376
- const outputArray = [];
377
- for (const value of this.buckets) {
378
- if (value === 0) {
379
- bufferedZeroes++;
380
- }
381
- else {
382
- if (bufferedZeroes === 1) {
383
- outputArray.push(0);
384
- }
385
- else if (bufferedZeroes !== 0) {
386
- outputArray.push(-bufferedZeroes);
387
- }
388
- outputArray.push(Math.floor(value));
389
- bufferedZeroes = 0;
390
- }
391
- }
392
- return outputArray;
393
- }
394
- static durationToBucket(durationNs) {
395
- const log = Math.log(durationNs / 1000.0);
396
- const unboundedBucket = Math.ceil(log / DurationHistogram.EXPONENT_LOG);
397
- // Compare <= 0 to catch -0 and -infinity
398
- return unboundedBucket <= 0 || Number.isNaN(unboundedBucket)
399
- ? 0
400
- : unboundedBucket >= DurationHistogram.BUCKET_COUNT
401
- ? DurationHistogram.BUCKET_COUNT - 1
402
- : unboundedBucket;
403
- }
404
- incrementDuration(durationNs, value = 1) {
405
- this.incrementBucket(DurationHistogram.durationToBucket(durationNs), value);
406
- return this;
407
- }
408
- incrementBucket(bucket, value = 1) {
409
- if (bucket >= DurationHistogram.BUCKET_COUNT) {
410
- // Since we don't have fixed size arrays I'd rather throw the error manually
411
- throw Error('Bucket is out of bounds of the buckets array');
412
- }
413
- // Extend the array if we haven't gotten it long enough to handle the new bucket
414
- if (bucket >= this.buckets.length) {
415
- const oldLength = this.buckets.length;
416
- this.buckets.length = bucket + 1;
417
- this.buckets.fill(0, oldLength);
418
- }
419
- this.buckets[bucket] += value; // ! is safe, we've already ensured the array is long enough
420
- }
421
- combine(otherHistogram) {
422
- for (let i = 0; i < otherHistogram.buckets.length; i++) {
423
- this.incrementBucket(i, otherHistogram.buckets[i]);
424
- }
425
- }
426
- constructor(options) {
427
- const initSize = options?.initSize || 74;
428
- const buckets = options?.buckets;
429
- const arrayInitSize = Math.max(buckets?.length || 0, initSize);
430
- this.buckets = Array(arrayInitSize).fill(0);
431
- if (buckets) {
432
- buckets.forEach((val, index) => (this.buckets[index] = val));
433
- }
434
- }
435
- }
436
- /**
437
- * Iterates over the entire trace, calling `f` on each Trace.Node found. It
438
- * looks under the "root" node as well as any inside the query plan. If any `f`
439
- * returns true, it stops walking the tree.
440
- *
441
- * Each call to `f` will receive an object that implements ResponseNamePath. If
442
- * `includePath` is true, `f` can call `toArray()` on it to convert the
443
- * linked-list representation to an array of the response name (field name)
444
- * nodes that you navigate to get to the node (including a "service:subgraph"
445
- * top-level node if this is a federated trace). Note that we don't add anything
446
- * to the path for index (list element) nodes. This is because the only use case
447
- * we have (error path statistics) does not care about list indexes (it's not
448
- * that interesting to know that sometimes an error was at foo.3.bar and
449
- * sometimes foo.5.bar, vs just generally foo.bar).
450
- *
451
- * If `includePath` is false, we don't bother to build up the linked lists, and
452
- * calling `toArray()` will throw.
453
- */
454
- export function iterateOverTrace(trace, f, includePath) {
455
- const rootPath = includePath
456
- ? new RootCollectingPathsResponseNamePath()
457
- : notCollectingPathsResponseNamePath;
458
- if (trace.root) {
459
- if (iterateOverTraceNode(trace.root, rootPath, f))
460
- return;
461
- }
462
- if (trace.queryPlan) {
463
- if (iterateOverQueryPlan(trace.queryPlan, rootPath, f))
464
- return;
465
- }
466
- }
467
- // Helper for iterateOverTrace; returns true to stop the overall walk.
468
- function iterateOverQueryPlan(node, rootPath, f) {
469
- if (!node)
470
- return false;
471
- if (node.fetch?.trace?.root && node.fetch.serviceName) {
472
- return iterateOverTraceNode(node.fetch.trace.root, rootPath.child(`service:${node.fetch.serviceName}`), f);
473
- }
474
- if (node.flatten?.node) {
475
- return iterateOverQueryPlan(node.flatten.node, rootPath, f);
476
- }
477
- if (node.parallel?.nodes) {
478
- // We want to stop as soon as some call returns true, which happens to be
479
- // exactly what 'some' does.
480
- return node.parallel.nodes.some(node => iterateOverQueryPlan(node, rootPath, f));
481
- }
482
- if (node.sequence?.nodes) {
483
- // We want to stop as soon as some call returns true, which happens to be
484
- // exactly what 'some' does.
485
- return node.sequence.nodes.some(node => iterateOverQueryPlan(node, rootPath, f));
486
- }
487
- return false;
488
- }
489
- // Helper for iterateOverTrace; returns true to stop the overall walk.
490
- function iterateOverTraceNode(node, path, f) {
491
- // Invoke the function; if it returns true, don't descend and tell callers to
492
- // stop walking.
493
- if (f(node, path)) {
494
- return true;
495
- }
496
- return (
497
- // We want to stop as soon as some call returns true, which happens to be
498
- // exactly what 'some' does.
499
- node.child?.some(child => {
500
- const childPath = child.responseName ? path.child(child.responseName) : path;
501
- return iterateOverTraceNode(child, childPath, f);
502
- }) ?? false);
503
- }
504
- const notCollectingPathsResponseNamePath = {
505
- toArray() {
506
- throw Error('not collecting paths!');
507
- },
508
- child() {
509
- return this;
510
- },
511
- };
512
- class RootCollectingPathsResponseNamePath {
513
- toArray() {
514
- return [];
515
- }
516
- child(responseName) {
517
- return new ChildCollectingPathsResponseNamePath(responseName, this);
518
- }
519
- }
520
- class ChildCollectingPathsResponseNamePath {
521
- responseName;
522
- prev;
523
- constructor(responseName, prev) {
524
- this.responseName = responseName;
525
- this.prev = prev;
526
- }
527
- toArray() {
528
- const out = [];
529
- let curr = this;
530
- while (curr instanceof ChildCollectingPathsResponseNamePath) {
531
- out.push(curr.responseName);
532
- curr = curr.prev;
533
- }
534
- return out.reverse();
535
- }
536
- child(responseName) {
537
- return new ChildCollectingPathsResponseNamePath(responseName, this);
538
- }
539
- }
@@ -1,122 +0,0 @@
1
- import { YogaLogger, YogaServer, type Maybe, type Plugin, type YogaInitialContext } from 'graphql-yoga';
2
- import { calculateReferencedFieldsByType } from '@apollo/utils.usagereporting';
3
- import { ApolloInlineGraphqlTraceContext, ApolloInlineRequestTraceContext, ApolloInlineTracePluginOptions } from '@graphql-yoga/plugin-apollo-inline-trace';
4
- import { Reporter } from './reporter.cjs';
5
- export type ApolloUsageReportOptions = ApolloInlineTracePluginOptions & {
6
- /**
7
- * The graph ref of the managed federation graph.
8
- * It is composed of the graph ID and the variant (`<YOUR_GRAPH_ID>@<VARIANT>`).
9
- *
10
- * If not provided, `APOLLO_GRAPH_REF` environment variable is used.
11
- *
12
- * You can find a a graph's ref at the top of its Schema Reference page in Apollo Studio.
13
- */
14
- graphRef?: string;
15
- /**
16
- * The API key to use to authenticate with the managed federation up link.
17
- * It needs at least the `service:read` permission.
18
- *
19
- * If not provided, `APOLLO_KEY` environment variable will be used instead.
20
- *
21
- * [Learn how to create an API key](https://www.apollographql.com/docs/federation/v1/managed-federation/setup#4-connect-the-gateway-to-studio)
22
- */
23
- apiKey?: string;
24
- /**
25
- * Usage report endpoint
26
- *
27
- * Defaults to GraphOS endpoint (https://usage-reporting.api.apollographql.com/api/ingress/traces)
28
- */
29
- endpoint?: string;
30
- /**
31
- * Agent Version to report to the usage reporting API
32
- */
33
- agentVersion?: string;
34
- /**
35
- * Client name to report to the usage reporting API
36
- */
37
- clientName?: StringFromRequestFn;
38
- /**
39
- * Client version to report to the usage reporting API
40
- */
41
- clientVersion?: StringFromRequestFn;
42
- /**
43
- * The version of the runtime (like 'node v23.7.0')
44
- * @default empty string.
45
- */
46
- runtimeVersion?: string;
47
- /**
48
- * The hostname of the machine running this server
49
- * @default $HOSTNAME environment variable
50
- */
51
- hostname?: string;
52
- /**
53
- * The OS identification string.
54
- * The format is `${os.platform()}, ${os.type()}, ${os.release()}, ${os.arch()})`
55
- * @default empty string
56
- */
57
- uname?: string;
58
- /**
59
- * The maximum estimated size of each traces in bytes. If the estimated size is higher than this threshold,
60
- * the complete trace will not be sent and will be reduced to aggregated stats.
61
- *
62
- * Note: GraphOS only allow for traces of 10mb maximum
63
- * @default 10 * 1024 * 1024 (10mb)
64
- */
65
- maxTraceSize?: number;
66
- /**
67
- * The maximum uncompressed size of a report in bytes.
68
- * The report will be sent once this threshold is reached, even if the delay between send is not
69
- * yet expired.
70
- *
71
- * @default 4Mb
72
- */
73
- maxBatchUncompressedSize?: number;
74
- /**
75
- * The maximum time in ms between reports.
76
- * @default 20s
77
- */
78
- maxBatchDelay?: number;
79
- /**
80
- * Control if traces should be always sent.
81
- * If false, the traces will be batched until a delay or size is reached.
82
- * Note: This is highly not recommended in a production environment
83
- *
84
- * @default false
85
- */
86
- alwaysSend?: boolean;
87
- /**
88
- * Timeout in ms of a trace export tentative
89
- * @default 30s
90
- */
91
- exportTimeout?: number;
92
- /**
93
- * The class to be used to keep track of traces and send them to the GraphOS endpoint
94
- * Note: This option is aimed to be used for testing purposes
95
- */
96
- reporter?: (options: ApolloUsageReportOptions, yoga: YogaServer<Record<string, unknown>, Record<string, unknown>>, logger: YogaLogger) => Reporter;
97
- /**
98
- * Called when all retry attempts to send a report to GraphOS endpoint failed.
99
- * By default, the error is logged.
100
- */
101
- onError?: (err: Error) => void;
102
- /**
103
- * If false, unexecutable operation (with parsing or validation error) will not be sent
104
- * @default false
105
- */
106
- sendUnexecutableOperationDocuments?: boolean;
107
- };
108
- export interface ApolloUsageReportRequestContext extends ApolloInlineRequestTraceContext {
109
- traces: Map<YogaInitialContext, ApolloUsageReportGraphqlContext>;
110
- }
111
- export interface ApolloUsageReportGraphqlContext extends ApolloInlineGraphqlTraceContext {
112
- referencedFieldsByType?: ReturnType<typeof calculateReferencedFieldsByType>;
113
- operationKey?: string;
114
- schemaId?: string;
115
- }
116
- type StringFromRequestFn = (req: Request) => Maybe<string>;
117
- export declare function useApolloUsageReport(options?: ApolloUsageReportOptions): Plugin;
118
- export declare function hashSHA256(text: string, api?: {
119
- crypto: Crypto;
120
- TextEncoder: (typeof globalThis)['TextEncoder'];
121
- }): Promise<string>;
122
- export {};