@subsquid/apollo-server-core
Version:
Core engine for Apollo GraphQL server
338 lines (326 loc) • 15.9 kB
text/typescript
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;