UNPKG

@subsquid/apollo-server-core

Version:
338 lines (326 loc) 15.9 kB
import type { GraphQLError, DocumentNode } from 'graphql'; import type { GraphQLRequestContextDidResolveOperation, GraphQLRequestContext, GraphQLRequestContextWillSendResponse, } from 'apollo-server-types'; import type { Logger } from '@apollo/utils.logger'; import type { fetch, RequestAgent } from 'apollo-server-env'; import type { Trace } from 'apollo-reporting-protobuf'; export interface ApolloServerPluginUsageReportingOptions<TContext> { //#region Configure exactly which data should be sent to Apollo. /** * By default, Apollo Server does not send the values of any GraphQL variables to Apollo's servers, because variable * values often contain the private data of your app's users. If you'd like variable values to be included in traces, set this option. * This option can take several forms: * - { none: true }: don't send any variable values (DEFAULT) * - { all: true}: send all variable values * - { transform: ... }: a custom function for modifying variable values. Keys added by the custom function will * be removed, and keys removed will be added back with an empty value. For security reasons, if an error occurs within this function, all variable values will be replaced with `[PREDICATE_FUNCTION_ERROR]`. * - { exceptNames: ... }: a case-sensitive list of names of variables whose values should not be sent to Apollo servers * - { onlyNames: ... }: A case-sensitive list of names of variables whose values will be sent to Apollo servers * * Defaults to not sending any variable values if both this parameter and * the deprecated `privateVariables` are not set. The report will * indicate each private variable key whose value was redacted by { none: true } or { exceptNames: [...] }. */ sendVariableValues?: VariableValueOptions; /** * By default, Apollo Server does not send the list of HTTP headers and values * to Apollo's servers, as these headers may contain your users' private data. * If you'd like this information included in traces, set this option. This * option can take several forms: * * - { none: true } to drop all HTTP request headers (DEFAULT) * - { all: true } to send the values of all HTTP request headers * - { exceptNames: Array<String> } A case-insensitive list of names of HTTP * headers whose values should not be sent to Apollo servers * - { onlyNames: Array<String> }: A case-insensitive list of names of HTTP * headers whose values will be sent to Apollo servers * * Unlike with sendVariableValues, names of dropped headers are not reported. * The headers 'authorization', 'cookie', and 'set-cookie' are never reported. */ sendHeaders?: SendValuesBaseOptions; /** * By default, all errors get reported to Apollo servers. You can specify * a filter function to exclude specific errors from being reported by * returning an explicit `null`, or you can mask certain details of the error * by modifying it and returning the modified error. */ rewriteError?: (err: GraphQLError) => GraphQLError | null; // We should strongly consider changing the default to false in AS4. /** * This option allows you to choose if Apollo Server should calculate detailed * per-field statistics for a particular request. It is only called for * executable operations: operations which parse and validate properly and * which do not have an unknown operation name. It is not called if an * `includeRequest` hook is provided and returns false. * * You can either pass an async function or a number. The function receives a * `GraphQLRequestContext`. (The effect of passing a number is described * later.) Your function can return a boolean or a number; returning false is * equivalent to returning 0 and returning true is equivalent to returning 1. * * Returning false (or 0) means that Apollo Server will only pay attention to * overall properties of the operation, like what GraphQL operation is * executing and how long the entire operation takes to execute, and not * anything about field-by-field execution. * * If you return false (or 0), this operation *will* still contribute to most * features of Studio, such as schema checks, the Operations page, and the * "referencing operations" statistic on the Fields page, etc. * * If you return false (or 0), this operation will *not* contribute to the * "field executions" statistic on the Fields page or to the execution timing * hints optionally displayed in Studio Explorer or in vscode-graphql. * Additionally, this operation will not produce a trace that can be viewed on * the Traces section of the Operations page. * * Returning false (or 0) for some or all operations can improve your server's * performance, as the overhead of calculating complete traces is not always * negligible. This is especially the case if this server is an Apollo * Gateway, as captured traces are transmitted from the subgraph to the * Gateway in-band inside the actual GraphQL response. * * Returning a positive number means that Apollo Server will track each field * execution and send Apollo Studio statistics on how many times each field * was executed and what the per-field performance was. Apollo Server sends * both a precise observed execution count and an estimated execution count. * The former is calculated by counting each field execution as 1, and the * latter is calculated by counting each field execution as the number * returned from this hook, which can be thought of as a weight. * * Passing a number `x` (which should be between 0 and 1 inclusive) for * `fieldLevelInstrumentation` is equivalent to passing the function `async () * => Math.random() < x ? 1/x : 0`. For example, if you pass 0.01, then 99% * of the time this function will return 0, and 1% of the time this function * will return 100. So 99% of the time Apollo Server will not track field * executions, and 1% of the time Apollo Server will track field executions * and send them to Apollo Studio both as an exact observed count and as an * "estimated" count which is 100 times higher. Generally, the weights you * return should be roughly the reciprocal of the probability that the * function returns non-zero; however, you're welcome to craft a more * sophisticated function, such as one that uses a higher probability for * rarer operations and a lower probability for more common operations. * * (Note that returning true here does *not* mean that the data derived from * field-level instrumentation must be transmitted to Apollo Studio's servers * in the form of a trace; it may still be aggregated locally to statistics. * But either way this operation will contribute to the "field executions" * statistic and timing hints.) * * The default `fieldLevelInstrumentation` is a function that always returns * true. */ fieldLevelInstrumentation?: | number | (( request: GraphQLRequestContextDidResolveOperation<TContext>, ) => Promise<number | boolean>); /** * This option allows you to choose if a particular request should be * represented in the usage reporting sent to Apollo servers. By default, all * requests are included. If this async predicate function is specified, its * return value will determine whether a given request is included. * * Note that returning false here means that the operation will be completely * ignored by all Apollo Studio features. If you merely want to improve * performance by skipping the field-level execution trace, set the * `fieldLevelInstrumentation` option instead of this one. * * The predicate function receives the request context. If validation and * parsing of the request succeeds, the function will receive the request * context in the * [`GraphQLRequestContextDidResolveOperation`](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#didresolveoperation) * phase, which permits tracing based on dynamic properties, e.g., HTTP * headers or the `operationName` (when available). Otherwise it will receive * the request context in the * [`GraphQLRequestContextWillSendResponse`](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#willsendresponse) * phase: * * (If you don't want any usage reporting at all, don't use this option: * instead, either avoid specifying an Apollo API key, or use * ApolloServerPluginUsageReportingDisabled to prevent this plugin from being * created by default.) * * **Example:** * * ```js * includeRequest(requestContext) { * // Always include `query HomeQuery { ... }`. * if (requestContext.operationName === "HomeQuery") return true; * * // Omit if the "report-to-apollo" header is set to "false". * if (requestContext.request.http?.headers?.get("report-to-apollo") === "false") { * return false; * } * * // Otherwise include. * return true; * }, * ``` * */ includeRequest?: ( request: | GraphQLRequestContextDidResolveOperation<TContext> | GraphQLRequestContextWillSendResponse<TContext>, ) => Promise<boolean>; /** * By default, this plugin associates client information such as name * and version with user requests based on HTTP headers starting with * `apollographql-client-`. If you have another way of communicating * client information to your server, tell the plugin how it works * with this option. */ generateClientInfo?: GenerateClientInfo<TContext>; /** * If you are using the `overrideReportedSchema` option to the schema * reporting plugin (`ApolloServerPluginSchemaReporting`), you should * pass the same value here as well, so that the schema ID associated * with requests in this plugin's usage reports matches the schema * ID that the other plugin reports. */ overrideReportedSchema?: string; /** * Whether to include the entire document in the trace if the operation * was a GraphQL parse or validation error (i.e. failed the GraphQL parse or * validation phases). This will be included as a separate field on the trace * and the operation name and signature will always be reported with a constant * identifier. Whether the operation was a parse failure or a validation * failure will be embedded within the stats report key itself. */ sendUnexecutableOperationDocuments?: boolean; /** * This plugin sends information about operations to Apollo's servers in two * forms: as detailed operation traces of single operations and as summarized * statistics of many operations. Each individual operation is described in * exactly one of those ways. This hook lets you select which operations are * sent as traces and which are sent as statistics. The default is a heuristic * that tries to send one trace for each rough duration bucket for each * operation each minute, plus more if the operations have errors. (Note that * Apollo's servers perform their own sampling on received traces; not all * traces sent to Apollo's servers can be later retrieved via the trace UI.) * * This option is highly experimental and may change or be removed in future * versions. */ experimental_sendOperationAsTrace?: ( trace: Trace, statsReportKey: string, ) => boolean; //#endregion //#region Configure the mechanics of communicating with Apollo's servers. /** * Sends a usage report after every request. This options is useful for * stateless environments like Amazon Lambda where processes handle only a * small number of requests before terminating. It defaults to true when * used with an ApolloServer subclass for a serverless framework (Amazon * Lambda, Google Cloud Functions, or Azure Functions), or false otherwise. * (Note that "immediately" does not mean synchronously with completing the * response, but "very soon", such as after a setImmediate call.) */ sendReportsImmediately?: boolean; /** * HTTP(s) agent to be used on the `fetch` call when sending reports to * Apollo. */ requestAgent?: RequestAgent | false; /** * Specifies which Fetch API implementation to use when sending usage reports. */ fetcher?: typeof fetch; /** * How often to send reports to Apollo. We'll also send reports when the * report gets big; see maxUncompressedReportSize. */ reportIntervalMs?: number; /** * We send a report when the report size will become bigger than this size in * bytes (default: 4MB). (This is a rough limit --- we ignore the size of the * report header and some other top level bytes. We just add up the lengths of * the serialized traces and signatures.) */ maxUncompressedReportSize?: number; /** * Reporting is retried with exponential backoff up to this many times * (including the original request). Defaults to 5. */ maxAttempts?: number; /** * Minimum back-off for retries. Defaults to 100ms. */ minimumRetryDelayMs?: number; /** * Timeout for each individual attempt to send a report to Apollo. (This is * for each HTTP POST, not for all potential retries.) Defaults to 30 seconds * (30000ms). */ requestTimeoutMs?: number; /** * A logger interface to be used for output and errors. When not provided * it will default to the server's own `logger` implementation and use * `console` when that is not available. */ logger?: Logger; /** * By default, if an error occurs when sending trace reports to Apollo * servers, its message will be sent to the `error` method on the logger * specified with the `logger` option to this plugin or to ApolloServer (or to * `console.error` by default). Specify this function to process errors in a * different way. (The difference between using this option and using a logger * is that this option receives the actual Error object whereas `logger.error` * only receives its message.) */ reportErrorFunction?: (err: Error) => void; //#endregion //#region Internal and non-recommended options /** * The URL base that we send reports to (not including the path). This option * only needs to be set for testing and Apollo-internal uses. */ endpointUrl?: string; /** * If set, prints all reports as JSON when they are sent. (Note that for * technical reasons, traces embedded in a report are printed separately when * they are added to a report.) */ debugPrintReports?: boolean; /** * Specify the function for creating a signature for a query. See signature.ts * for details. This option is not recommended, as Apollo's servers make assumptions * about how the signature relates to the operation you executed. */ calculateSignature?: (ast: DocumentNode, operationName: string) => string; /** * This option includes extra data in reports that helps Apollo validate the * stats generation code in this plugin. Do not set it; the only impact on * your app will be a decrease in performance. */ internal_includeTracesContributingToStats?: boolean; //#endregion } export type SendValuesBaseOptions = | { onlyNames: Array<String> } | { exceptNames: Array<String> } | { all: true } | { none: true }; type VariableValueTransformOptions = { variables: Record<string, any>; operationString?: string; }; export type VariableValueOptions = | { transform: ( options: VariableValueTransformOptions, ) => Record<string, any>; } | SendValuesBaseOptions; export interface ClientInfo { clientName?: string; clientVersion?: string; } export type GenerateClientInfo<TContext> = ( requestContext: GraphQLRequestContext<TContext>, ) => ClientInfo;