From e10061f454283859149266cc9e2e8a648b5f66d4 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 14:50:28 -0600 Subject: [PATCH 01/53] Add some doc blocks to retry link --- src/link/retry/retryLink.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index 7537720b8e2..5ddc3bd9c16 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -85,6 +85,9 @@ export declare namespace RetryLink { ) => boolean | Promise; } + /** + * Options provided to the `RetryLink` constructor. + */ export interface Options { /** * Configuration for the delay strategy to use, or a custom delay strategy. @@ -181,6 +184,31 @@ class RetryableOperation { } } +/** + * `RetryLink` is a non-terminating link that attempts to retry operations that + * fail due to network errors. It enables resilient GraphQL operations by + * automatically retrying failed requests with configurable delay and retry + * strategies. + * + * @remarks + * + * `RetryLink` is particularly useful for handling unreliable network conditions + * where you would rather wait longer than explicitly fail an operation. It + * provides exponential backoff and jitters delays between attempts by default. + * + * > [!NOTE] + * > This link does not handle retries for GraphQL errors in the response. Use + * > `ErrorLink` to retry an operation after a GraphQL error. For more + * > information, see the [Error handling documentation](https://apollographql.com/docs/react/data/error-handling#on-graphql-errors). + * + * @example + * + * ```ts + * import { RetryLink } from "@apollo/client/link/retry"; + * + * const link = new RetryLink(); + * ``` + */ export class RetryLink extends ApolloLink { private delayFor: RetryLink.DelayFunction; private retryIf: RetryLink.AttemptsFunction; From 271960fd2d3223c5047d637975de0ebeddc142dd Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 15:03:17 -0600 Subject: [PATCH 02/53] Update retry link --- docs/source/api/link/apollo-link-retry.mdx | 99 +++++++++------------- 1 file changed, 40 insertions(+), 59 deletions(-) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 8b4ef10f08e..9075f497abb 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -1,57 +1,19 @@ --- -title: Retry Link +title: RetryLink description: Attempt an operation multiple times if it fails due to network or server errors. --- -## Overview + -`@apollo/client/link/retry` can be used to retry an operation a certain amount of times. This comes in handy when dealing with unreliable communication situations, where you would rather wait longer than explicitly fail an operation. `@apollo/client/link/retry` provides exponential backoff, and jitters delays between attempts by default. - -> **Note:** It does not currently handle retries for GraphQL errors in the response, only for network errors; the `onError` link can be used to retry an operation after a GraphQL error. For more information, see the [Error handling documentation](/react/data/error-handling/#on-graphql-errors). - -An example use case is to hold on to a request while a network connection is offline, and retry until it comes back online. - -```js -import { RetryLink } from "@apollo/client/link/retry"; - -const link = new RetryLink(); -``` - -## Options - -The standard retry strategy provides exponential backoff with jittering, and takes the following options, grouped into `delay` and `attempt` strategies: - -### options.delay - -| Option | Description | -| --------------- | --------------------------------------------------------------------------- | -| `delay.initial` | The number of milliseconds to wait before attempting the first retry. | -| `delay.max` | The maximum number of milliseconds that the link should wait for any retry. | -| `delay.jitter` | Whether delays between attempts should be randomized. | - -### options.attempts - -| Option | Description | -| ------------------ | ---------------------------------------------------------------------------------------- | -| `attempts.max` | The max number of times to try a single operation before giving up. | -| `attempts.retryIf` | A predicate function that can determine whether a particular response should be retried. | - -### Default configuration - -The default configuration is equivalent to: +## Constructor signature ```ts -new RetryLink({ - delay: { - initial: 300, - max: Infinity, - jitter: true, - }, - attempts: { - max: 5, - retryIf: (error, _operation) => !!error, - }, -}); +constructor( + options?: RetryLink.Options +): RetryLink ``` ## Avoiding thundering herd @@ -68,15 +30,34 @@ Instead of the options object, you may pass a function for `delay` and/or `attem The `attempts` function should return a `boolean` (or a `Promise` which resolves to a `boolean`) indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by. -```js -import { RetryLink } from "@apollo/client/link/retry"; - -const link = new RetryLink({ - attempts: (count, operation, error) => { - return !!error && operation.operationName != "specialCase"; - }, - delay: (count, operation, error) => { - return count * 1000 * Math.random(); - }, -}); -``` +## Types + + + + + + + + + + From 5941059d4a44c7e6c7d932f63cc0b0d5c873d317 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 15:04:42 -0600 Subject: [PATCH 03/53] Update doc blocks for delay/retry functions --- src/link/retry/retryLink.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index 5ddc3bd9c16..9b7179688ac 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -20,13 +20,16 @@ export declare namespace RetryLink { error: ErrorLike ) => number; + /** + * Configuration options for the standard retry delay strategy. + */ export interface DelayOptions { /** * The number of milliseconds to wait before attempting the first retry. * * Delays will increase exponentially for each attempt. E.g. if this is * set to 100, subsequent retries will be delayed by 200, 400, 800, etc, - * until they reach maxDelay. + * until they reach the maximum delay. * * Note that if jittering is enabled, this is the _average_ delay. * @@ -45,8 +48,10 @@ export declare namespace RetryLink { /** * Whether delays between attempts should be randomized. * - * This helps avoid thundering herd type situations by better distributing - * load during major outages. + * This helps avoid [thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem) + * type situations by better distributing load during major outages. Without + * these strategies, when your server comes back up it will be hit by all + * of your clients at once, possibly causing it to go down again. * * @defaultValue `true` */ @@ -59,13 +64,18 @@ export declare namespace RetryLink { error: ErrorLike ) => boolean | Promise; + /** + * Configuration options for the standard retry attempt strategy. + */ export interface AttemptsOptions { /** - * The max number of times to try a single operation before giving up. Pass - * `Infinity` for infinite retries. + * The max number of times to try a single operation before giving up. * + * @remarks * Note that this INCLUDES the initial request as part of the count. - * E.g. maxTries of 1 indicates no retrying should occur. + * E.g. `max` of 1 indicates no retrying should occur. + * + * Pass `Infinity` for infinite retries. * * @defaultValue `5` */ From 0a63ad44df1e02c5541735a9168e63b3e2fb3969 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 15:06:19 -0600 Subject: [PATCH 04/53] Add back code example --- docs/source/api/link/apollo-link-retry.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 9075f497abb..6ce375ba51c 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -30,6 +30,19 @@ Instead of the options object, you may pass a function for `delay` and/or `attem The `attempts` function should return a `boolean` (or a `Promise` which resolves to a `boolean`) indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by. +```ts +import { RetryLink } from "@apollo/client/link/retry"; + +const link = new RetryLink({ + attempts: (count, operation, error) => { + return !!error && operation.operationName != "specialCase"; + }, + delay: (count, operation, error) => { + return count * 1000 * Math.random(); + }, +}); +``` + ## Types Date: Mon, 4 Aug 2025 17:33:29 -0600 Subject: [PATCH 05/53] Add doc blocks for RemoveTypenameFromVariablesLink --- .../removeTypenameFromVariables.ts | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/link/remove-typename/removeTypenameFromVariables.ts b/src/link/remove-typename/removeTypenameFromVariables.ts index 5204b238055..1bad19f8cb2 100644 --- a/src/link/remove-typename/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/removeTypenameFromVariables.ts @@ -11,16 +11,114 @@ import { isPlainObject } from "@apollo/client/utilities/internal"; import { defaultCacheSizes } from "../../utilities/caching/sizes.js"; +/** + * Sentinel value used to indicate that `__typename` fields should be kept + * for a specific field or input type. + * + * @remarks + * Use this value in the `except` configuration to preserve `__typename` + * fields in JSON scalar fields or other cases where you need to retain + * the typename information. + * + * @example + * + * ```ts + * import { + * removeTypenameFromVariables, + * KEEP, + * } from "@apollo/client/link/remove-typename"; + * + * const link = removeTypenameFromVariables({ + * except: { + * JSON: KEEP, // Keep __typename for all JSON scalar variables + * DashboardInput: { + * config: KEEP, // Keep __typename only for the config field + * }, + * }, + * }); + * ``` + */ export const KEEP = "__KEEP"; export declare namespace RemoveTypenameFromVariablesLink { + /** + * Configuration object that specifies which input types and fields should + * retain their `__typename` fields. + * + * @remarks + * This is a recursive configuration where: + * + * - Keys represent GraphQL input type names or field names + * - Values can be either the `KEEP` sentinel to preserve all `__typename` + * fields, or a nested `KeepTypenameConfig` to preserve `__typename` fields on + * a specific field name. + * + * @example + * + * ```ts + * const config: KeepTypenameConfig = { + * // Keep __typename for all JSON scalar variables + * JSON: KEEP, + * + * // For DashboardInput, only keep __typename on the config field + * DashboardInput: { + * config: KEEP, + * }, + * + * // Nested configuration for complex input types + * UserInput: { + * profile: { + * settings: KEEP, + * }, + * }, + * }; + * ``` + */ export interface KeepTypenameConfig { [key: string]: | typeof KEEP | RemoveTypenameFromVariablesLink.KeepTypenameConfig; } + /** + * Options for configuring the `RemoveTypenameFromVariablesLink`. + */ export interface Options { + /** + * Configuration that determines which input types should retain `__typename` + * fields. + * + * @remarks + * Maps GraphQL input type names to configurations. Each configuration can + * either be the `KEEP` sentinel, to preserve all `__typename` fields, or + * a nested object that specifies which fields should retain `__typename`. + * + * This is particularly useful for JSON scalar fields where `__typename` + * might be required, or when reusing query results as mutation inputs. + * + * @example + * + * ```ts + * { + * except: { + * // Keep __typename for all JSON scalar variables + * JSON: KEEP, + * + * // For DashboardInput, remove __typename except for config field + * DashboardInput: { + * config: KEEP, + * }, + * + * // Complex nested configuration + * UserProfileInput: { + * settings: { + * preferences: KEEP, + * }, + * }, + * }, + * } + * ``` + */ except?: RemoveTypenameFromVariablesLink.KeepTypenameConfig; } } @@ -35,6 +133,27 @@ export function removeTypenameFromVariables( return new RemoveTypenameFromVariablesLink(options); } +/** + * `RemoveTypenameFromVariablesLink` is a non-terminating link that automatically + * removes `__typename` fields from operation variables to prevent GraphQL + * validation errors. + * + * @remarks + * + * When reusing data from a query as input to another GraphQL operation, + * `__typename` fields can cause server-side validation errors because input + * types don't accept fields that start with double underscores (`__`). + * `RemoveTypenameFromVariablesLink` automatically strips these fields from all + * operation variables. + * + * @example + * + * ```ts + * import { RemoveTypenameFromVariablesLink } from "@apollo/client/link/remove-typename"; + * + * const link = new RemoveTypenameFromVariablesLink(); + * ``` + */ export class RemoveTypenameFromVariablesLink extends ApolloLink { constructor(options: RemoveTypenameFromVariablesLink.Options = {}) { super((operation, forward) => { From 80f251cf492f16d26ff055996f9602fe736a1579 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 17:50:29 -0600 Subject: [PATCH 06/53] Update RemoveTypenameFromVariablesLink doc --- .../api/link/apollo-link-remove-typename.mdx | 127 ++++++++---------- 1 file changed, 55 insertions(+), 72 deletions(-) diff --git a/docs/source/api/link/apollo-link-remove-typename.mdx b/docs/source/api/link/apollo-link-remove-typename.mdx index ba228196a4c..a0dd85d058b 100644 --- a/docs/source/api/link/apollo-link-remove-typename.mdx +++ b/docs/source/api/link/apollo-link-remove-typename.mdx @@ -1,13 +1,22 @@ --- -title: Remove Typename Link +title: RemoveTypenameFromVariablesLink description: Automatically remove __typename fields from variables. --- -## Overview + -When reusing data from a query as an argument to another GraphQL operation, `__typename` fields can cause errors. To avoid this, you can use the `removeTypenameFromVariables` link to automatically remove `__typename` fields from variables in operations. +## Constructor signature -## Remove `__typename` from all variables +```ts +constructor( + options?: RemoveTypenameFromVariablesLink.Options +): RemoveTypenameFromVariablesLink +``` + +## Use case As an example, take the following query. Apollo Client automatically adds `__typename` fields for each field selection set. @@ -51,25 +60,19 @@ await client.mutate({ }); ``` -Without the use of the `removeTypenameFromVariables` link, the server will return an error because `data.dashboard` still contains the `__typename` field. +Without the use of `RemoveTypenameFromVariablesLink`, the server will return an error because `data.dashboard` still contains the `__typename` field. ## Usage -You can import and instantiate it like so: - -```ts -import { removeTypenameFromVariables } from "@apollo/client/link/remove-typename"; - -const removeTypenameLink = removeTypenameFromVariables(); -``` - -Include `removeTypeNameLink` anywhere in your [link chain](./introduction/#your-first-link-chain) before your [terminating link](./introduction#the-terminating-link) +Include `RemoveTypenameFromVariablesLink` anywhere in your [link chain](./introduction/#composing-a-link-chain) before your [terminating link](./introduction#the-terminating-link) to remove `__typename` fields from variables for all operations. ```ts -import { from } from "@apollo/client"; +import { ApolloLink } from "@apollo/client"; +import { RemoveTypenameFromVariablesLink } from "@apollo/client/link/remove-typename"; -const link = from([removeTypenameLink, httpLink]); +const removeTypenameLink = new RemoveTypenameFromVariablesLink(); +const link = ApolloLink.from([removeTypenameLink, httpLink]); const client = new ApolloClient({ link, @@ -79,27 +82,24 @@ const client = new ApolloClient({ If you're using [directional composition](/react/api/link/introduction#directional-composition), for example, to [send a subscription to a websocket connection](/react/data/subscriptions#3-split-communication-by-operation-recommended), -place `removeTypenameLink` before `splitLink` to remove `__typename` from variables for all operations. +place `RemoveTypenameFromVariablesLink` before the split link to remove `__typename` from variables for all operations. ```ts -import { from, split } from "@apollo/client"; -import { removeTypenameFromVariables } from "@apollo/client/link/remove-typename"; - -const removeTypenameLink = removeTypenameFromVariables(); - -const splitLink = split( - ({ query }) => { - const definition = getMainDefinition(query); - return ( - definition.kind === "OperationDefinition" && - definition.operation === "subscription" - ); +import { OperationTypeNode } from "graphql"; +import { ApolloLink } from "@apollo/client"; +import { RemoveTypenameFromVariablesLink } from "@apollo/client/link/remove-typename"; + +const removeTypenameLink = new RemoveTypenameFromVariablesLink(); + +const splitLink = ApolloLink.split( + ({ operationType }) => { + return operationType === OperationTypeNode.SUBSCRIPTION; }, wsLink, httpLink ); -const link = from([removeTypenameLink, splitLink]); +const link = ApolloLink.from([removeTypenameLink, splitLink]); const client = new ApolloClient({ link, @@ -109,11 +109,11 @@ const client = new ApolloClient({ ## Keep `__typename` in JSON scalars -Sometimes, you may need to retain the `__typename` field from a query's response—for example, in the case of [JSON scalar](https://github.com/taion/graphql-type-json) input fields. +You may need to retain the `__typename` field from a query's response—for example, in the case of [JSON scalar](https://github.com/taion/graphql-type-json) input fields. -While the [GraphQL type validation spec](https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation) disallows input fields that begin with two underscores (`__`), this restriction doesn't apply when the input field is a [JSON scalar](https://github.com/taion/graphql-type-json). (A JSON scalar type accepts raw JSON as input.) You can configure the `removeTypenameFromVariables` link to retain `__typename` for certain `JSON` scalars. +While the [GraphQL type validation spec](https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation) disallows input fields that begin with two underscores (`__`), this restriction doesn't apply when the input field is a [JSON scalar](https://github.com/taion/graphql-type-json). (A JSON scalar type accepts raw JSON as input.) You can configure `RemoveTypenameFromVariablesLink` link to retain `__typename` for certain `JSON` scalars. -To do so, provide an `except` option when instantiating `removeTypenameFromVariables` and use the `KEEP` sentinel to denote which variables types should keep `__typename`. Each key in the `except` option should correspond to an input type in your GraphQL schema. +To do so, provide an `except` option when instantiating `RemoveTypenameFromVariablesLink` and use the `KEEP` sentinel to denote which variables types should keep `__typename`. Each key in the `except` option should correspond to an input type in your GraphQL schema. For example, suppose your schema includes a `ConfigureDashboardMutation` mutation that takes a `JSON` type variable named `$dashboardConfig`: @@ -125,24 +125,28 @@ mutation ConfigureDashboardMutation($dashboardConfig: JSON) { } ``` -You can tell the `removeTypenameFromVariables` link to keep all `__typename` fields for any variable declared as a `JSON` type. (Variable types are inferred from the GraphQL query.) +You tell `RemoveTypenameFromVariablesLink` to keep all `__typename` fields for any variable declared as a `JSON` type with the `KEEP` sentinel. Variable types are inferred from the GraphQL query. ```ts import { - removeTypenameFromVariables, + RemoveTypenameFromVariablesLink, KEEP, } from "@apollo/client/link/remove-typename"; -const removeTypenameLink = removeTypenameFromVariables({ +const removeTypenameLink = new RemoveTypenameFromVariablesLink({ except: { JSON: KEEP, }, }); ``` -> Note: the JSON scalar type does not need to be literally named `JSON` to be considered a JSON scalar. +When the query moves through `RemoveTypenameFromVariablesLink`, the `dashboardConfig` variable will be detected as a `JSON` scalar type and all `__typename` fields are kept intact. -When the query moves through the `removeTypenameFromVariables` link, the `dashboardConfig` variable will be detected as a `JSON` scalar type and all `__typename` fields are kept intact. + + +The JSON scalar type does not need to be literally named `JSON` to be considered a JSON scalar. + + ### Nested JSON scalar fields in input variables @@ -150,11 +154,11 @@ Not all top-level variables may map to a JSON scalar type. For more complex inpu ```ts import { - removeTypenameFromVariables, + RemoveTypenameFromVariablesLink, KEEP, } from "@apollo/client/link/remove-typename"; -const removeTypenameLink = removeTypenameFromVariables({ +const removeTypenameLink = new RemoveTypenameFromVariablesLink({ except: { DashboardInput: { config: KEEP, @@ -163,17 +167,17 @@ const removeTypenameLink = removeTypenameFromVariables({ }); ``` -Variables declared as type `DashboardInput` will have any top-level `__typename` fields removed, but keep `__typename` for the `config` field. +Variables declared as type `DashboardInput` will have all top-level `__typename` fields removed, but keep `__typename` for the `config` field. This nesting can be as deep as needed and include as many fields as necessary. Use the `KEEP` sentinel to determine where `__typename` should be kept. ```ts import { - removeTypenameFromVariables, + RemoveTypenameFromVariablesLink, KEEP, } from "@apollo/client/link/remove-typename"; -const removeTypenameLink = removeTypenameFromVariables({ +const removeTypenameLink = new RemoveTypenameFromVariablesLink({ except: { // Keep __typename for `bar` and `baz` fields on any variable // declared as a `FooInput` type @@ -208,11 +212,11 @@ To keep `__typename` for nested fields in arrays, use the same object notation a ```ts import { - removeTypenameFromVariables, + RemoveTypenameFromVariablesLink, KEEP, } from "@apollo/client/link/remove-typename"; -const removeTypenameLink = removeTypenameFromVariables({ +const removeTypenameLink = new RemoveTypenameFromVariablesLink({ except: { // Keep __typename on the `config` field for each widget // in the `widgets` array for variables declared as @@ -226,31 +230,10 @@ const removeTypenameLink = removeTypenameFromVariables({ }); ``` -## Options - - - - - - - - - - - - - - +## Types - -
Name /
Type
Description
- -###### `except` - -`KeepTypenameConfig` - - - -Determines which input types should retain `__typename`. This maps the input type to the config, which is either the `KEEP` sentinel or a nested config of fields. - -
+ From 17e6380d79553d28060942f8875bf0ee8e692835 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 17:51:59 -0600 Subject: [PATCH 07/53] More tweaks to doc blocks --- src/link/remove-typename/removeTypenameFromVariables.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/link/remove-typename/removeTypenameFromVariables.ts b/src/link/remove-typename/removeTypenameFromVariables.ts index 1bad19f8cb2..a59724794b8 100644 --- a/src/link/remove-typename/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/removeTypenameFromVariables.ts @@ -88,14 +88,10 @@ export declare namespace RemoveTypenameFromVariablesLink { * Configuration that determines which input types should retain `__typename` * fields. * - * @remarks * Maps GraphQL input type names to configurations. Each configuration can * either be the `KEEP` sentinel, to preserve all `__typename` fields, or * a nested object that specifies which fields should retain `__typename`. * - * This is particularly useful for JSON scalar fields where `__typename` - * might be required, or when reusing query results as mutation inputs. - * * @example * * ```ts From b1465ee72b940082806236dfc75d3848b070ac48 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:29:40 -0600 Subject: [PATCH 08/53] Add types to schema link doc --- docs/source/api/link/apollo-link-schema.mdx | 47 +++++---- src/link/schema/index.ts | 111 ++++++++++++++++++-- 2 files changed, 133 insertions(+), 25 deletions(-) diff --git a/docs/source/api/link/apollo-link-schema.mdx b/docs/source/api/link/apollo-link-schema.mdx index 6024f6d9b6e..0979d6a6edf 100644 --- a/docs/source/api/link/apollo-link-schema.mdx +++ b/docs/source/api/link/apollo-link-schema.mdx @@ -1,19 +1,14 @@ --- -title: Schema Link +title: SchemaLink description: Assists with mocking and server-side rendering --- -## Overview + -The schema link provides a [graphql execution environment](http://graphql.org/graphql-js/graphql/#graphql), which allows you to perform GraphQL operations on a provided schema. This type of behavior is commonly used for server-side rendering (SSR) to avoid network calls and mocking data. While the schema link could provide graphql results on the client, currently the graphql execution layer is [too heavy weight](https://bundlephobia.com/result?p=graphql) for practical application. - -> To unify your state management with client-side GraphQL operations, refer to Apollo Client's [local state management](../../local-state/local-state-management/) functionality. It integrates with the Apollo Client cache and is much more lightweight. - -## Installation - -`npm install @apollo/client --save` - -## Usage +## Usage examples ### Server Side Rendering @@ -66,13 +61,27 @@ const graphqlClient = new ApolloClient({ }); ``` -### Options +## Types + + -The `SchemaLink` constructor can be called with an object with the following properties: +### `SchemaLink.ResolverContext` + + + +#### Signature + +```ts +type ResolverContext = Record; +``` -| Option | Description | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `schema` | An executable graphql schema | -| `rootValue` | The root value that is passed to the resolvers (i.e. the first parameter for the [rootQuery](http://graphql.org/learn/execution/#root-fields-resolvers)) | -| `context` | An object passed to the resolvers, following the [graphql specification](http://graphql.org/learn/execution/#root-fields-resolvers) or a function that accepts the operation and returns the resolver context. The resolver context may contain all the data-fetching connectors for an operation. | -| `validate` | Enable validation of incoming queries against the local schema before execution, returning validation errors in `result.errors`, just like a non-local GraphQL endpoint typically would. | + diff --git a/src/link/schema/index.ts b/src/link/schema/index.ts index 65a4df113ce..01dddabf67a 100644 --- a/src/link/schema/index.ts +++ b/src/link/schema/index.ts @@ -5,35 +5,134 @@ import { Observable } from "rxjs"; import { ApolloLink } from "@apollo/client/link"; export declare namespace SchemaLink { + export namespace SchemaLinkDocumentationTypes { + /** + * A function that returns the resolver context for a given operation. + * + * This function is called for each operation and allows you to create + * operation-specific context. This is useful when you need to include + * information from the operation (like headers, variables, etc.) in the + * resolver context. + * + * @param operation - The Apollo Link operation + * @returns The resolver context object or a promise that resolves to the context + * + * @example + * + * ```ts + * const link = new SchemaLink({ + * schema, + * context: (operation) => { + * return { + * userId: operation.getContext().userId, + * dataSources: { + * userAPI: new UserAPI(), + * }, + * }; + * }, + * }); + * ``` + */ + export function ResolverContextFunction( + operation: ApolloLink.Operation + ): SchemaLink.ResolverContext | PromiseLike; + } + /** + * The resolver context object passed to GraphQL resolvers. + * + * This context object is passed as the third parameter to GraphQL resolvers + * and typically contains data-fetching connectors, authentication information, + * and other request-specific data. + */ export type ResolverContext = Record; + + /** {@inheritDoc @apollo/client/link/schema!SchemaLink.SchemaLinkDocumentationTypes.ResolverContextFunction:function(1)} */ export type ResolverContextFunction = ( operation: ApolloLink.Operation - ) => ResolverContext | PromiseLike; + ) => SchemaLink.ResolverContext | PromiseLike; + /** + * Options for configuring the `SchemaLink`. + */ export interface Options { /** - * The schema to generate responses from. + * An executable GraphQL schema to use for operation execution. + * + * @remarks + * + * This should be a complete, executable GraphQL schema created using + * tools like `makeExecutableSchema` from `@graphql-tools/schema` or + * `buildSchema` from `graphql`. + * + * @example + * + * ```ts + * import { makeExecutableSchema } from "@graphql-tools/schema"; + * + * const schema = makeExecutableSchema({ + * typeDefs, + * resolvers, + * }); + * + * const link = new SchemaLink({ schema }); + * ``` */ schema: GraphQLSchema; /** - * The root value to use when generating responses. + * The root value passed to root-level resolvers. It's typically not used in + * most schemas but can be useful for certain advanced patterns. */ rootValue?: any; /** - * A context to provide to resolvers declared within the schema. + * Context object or function that returns the context object to provide to + * resolvers. The context is passed as the third parameter to all GraphQL + * resolvers. + * + * - If a static object is provided, the same context will be used for all + * operations + * - If a function is provided, the function is called for each operation to + * generate operation-specific context */ context?: SchemaLink.ResolverContext | SchemaLink.ResolverContextFunction; /** - * Validate incoming queries against the given schema, returning - * validation errors as a GraphQL server would. + * Whether to validate incoming queries against the schema before execution. + * + * When enabled, queries will be validated against the schema before execution, + * and validation errors will be returned in the result's `errors` array, + * just like a remote GraphQL server would. + * + * This is useful for testing and development to catch query errors early, + * but may add overhead in production environments. + * + * @defaultValue false */ validate?: boolean; } } +/** + * `SchemaLink` is a terminating link that executes GraphQL operations against + * a local GraphQL schema instead of making network requests. This is commonly + * used for server-side rendering (SSR) and mocking dataa. + * + * > [!NOTE] + * > While `SchemaLink` can provide GraphQL results on the client, the GraphQL + * > execution layer is [quite large](https://bundlephobia.com/result?p=graphql) for practical client-side use. + * > For client-side state management, consider Apollo Client's [local state management](https://apollographql.com/docs/react/local-state/local-state-management/) + * > functionality instead, which integrates with the Apollo Client cache. + * + * @example + * + * ```ts + * import { SchemaLink } from "@apollo/client/link/schema"; + * import schema from "./path/to/your/schema"; + * + * const link = new SchemaLink({ schema }); + * ``` + */ export class SchemaLink extends ApolloLink { public schema: SchemaLink.Options["schema"]; public rootValue: SchemaLink.Options["rootValue"]; From b13c128afd46b30e20100e23cb529deb995ea81f Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:38:03 -0600 Subject: [PATCH 09/53] Set prevContext to readonly in SetContextLink --- src/link/context/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 69cdaa85bc2..74ed0edbc09 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -4,7 +4,7 @@ import { ApolloLink } from "@apollo/client/link"; export declare namespace SetContextLink { export type ContextSetter = ( - prevContext: ApolloLink.OperationContext, + prevContext: Readonly, operation: SetContextOperation ) => | Promise> @@ -12,7 +12,7 @@ export declare namespace SetContextLink { export type LegacyContextSetter = ( operation: SetContextOperation, - prevContext: ApolloLink.OperationContext + prevContext: Readonly ) => | Promise> | Partial; From 65a9e970914a552f93138c5f35c0fb658975f56b Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:39:42 -0600 Subject: [PATCH 10/53] Add constructor signature to SchemaLink doc --- docs/source/api/link/apollo-link-schema.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/api/link/apollo-link-schema.mdx b/docs/source/api/link/apollo-link-schema.mdx index 0979d6a6edf..f9c6de34e7e 100644 --- a/docs/source/api/link/apollo-link-schema.mdx +++ b/docs/source/api/link/apollo-link-schema.mdx @@ -8,6 +8,14 @@ description: Assists with mocking and server-side rendering customOrder={["summary", "remarks", "example"]} /> +## Constructor signature + +```ts +constructor( + options: SchemaLink.Options +): SchemaLink +``` + ## Usage examples ### Server Side Rendering From f6fd917603bd95a4a58df269a90256b21eec643d Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:41:39 -0600 Subject: [PATCH 11/53] Use full qualifier --- src/link/context/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 74ed0edbc09..87fb5e6cb8b 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -5,13 +5,13 @@ import { ApolloLink } from "@apollo/client/link"; export declare namespace SetContextLink { export type ContextSetter = ( prevContext: Readonly, - operation: SetContextOperation + operation: SetContextLink.SetContextOperation ) => | Promise> | Partial; export type LegacyContextSetter = ( - operation: SetContextOperation, + operation: SetContextLink.SetContextOperation, prevContext: Readonly ) => | Promise> From 5254d2d87f0dab8c89c74d2253763b3080323135 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:44:16 -0600 Subject: [PATCH 12/53] Deprecate the LegacyContextSetter type --- src/link/context/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 87fb5e6cb8b..45a8a2ed438 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -10,6 +10,11 @@ export declare namespace SetContextLink { | Promise> | Partial; + /** + * @deprecated + * Use `ContextSetter` instead. This type is used by the deprecated + * `setContext` function. + */ export type LegacyContextSetter = ( operation: SetContextLink.SetContextOperation, prevContext: Readonly From 45b8661426a1284dd6dc9e1470be53502b0e00e2 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 4 Aug 2025 18:58:58 -0600 Subject: [PATCH 13/53] Update SetContextLink --- docs/source/api/link/apollo-link-context.mdx | 114 ++++++++++++++----- src/link/context/index.ts | 48 ++++++++ 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/docs/source/api/link/apollo-link-context.mdx b/docs/source/api/link/apollo-link-context.mdx index 7155e8b8eee..d3087cbc51f 100644 --- a/docs/source/api/link/apollo-link-context.mdx +++ b/docs/source/api/link/apollo-link-context.mdx @@ -1,28 +1,55 @@ --- -title: Context Link +title: SetContextLink description: Easily set a context on your operation, which is used by other links further down the chain. --- -## Overview + -The `setContext` function accepts a function that returns either an object or a promise, which then returns an object to set the new context of a request. It receives two arguments: the GraphQL request being executed, and the previous context. This link makes it easy to perform the asynchronous lookup of things like authentication tokens and more. +## Constructor signature -```js -import { setContext } from "@apollo/client/link/context"; - -const setAuthorizationLink = setContext((request, previousContext) => ({ - headers: { authorization: "1234" }, -})); - -const asyncAuthLink = setContext( - (request) => - new Promise((success, fail) => { - // do some async lookup here - setTimeout(() => { - success({ token: "async found token" }); - }, 10); - }) -); +```ts +constructor( + setter: SetContextLink.ContextSetter +): SetContextLink +``` + +## Usage examples + +### Authentication + +The most common use case is adding authentication headers to requests: + +```ts +const authLink = new SetContextLink((prevContext, operation) => { + const token = getAuthToken(); + + return { + headers: { + ...prevContext.headers, + authorization: token ? `Bearer ${token}` : "", + }, + }; +}); +``` + +### Asynchronous token lookup + +You can also perform asynchronous operations to fetch tokens or other data: + +```ts +const asyncAuthLink = new SetContextLink(async (prevContext, operation) => { + const token = await fetchAuthToken(); + + return { + headers: { + ...prevContext.headers, + authorization: `Bearer ${token}`, + }, + }; +}); ``` ## Caching lookups @@ -33,19 +60,30 @@ Take for example a user auth token being found, cached, then removed on a 401 re ```js import { ServerError } from "@apollo/client"; -import { setContext } from "@apollo/client/link/context"; -import { ErrorLink } from "@apollo/client/link/error"; +import { SetContextLink } from "@apollo/client/link/context"; +import { onError } from "@apollo/client/link/error"; // cached storage for the user token let token; -const withToken = setContext(() => { +const withToken = new SetContextLink(async (prevContext, operation) => { // if you have a cached value, return it immediately - if (token) return { token }; + if (token) { + return { + headers: { + ...prevContext.headers, + authorization: `Bearer ${token}`, + }, + }; + } - return AsyncTokenLookup().then((userToken) => { - token = userToken; - return { token }; - }); + const userToken = await AsyncTokenLookup(); + token = userToken; + return { + headers: { + ...prevContext.headers, + authorization: `Bearer ${token}`, + }, + }; }); const resetToken = new ErrorLink(({ error }) => { @@ -57,3 +95,25 @@ const resetToken = new ErrorLink(({ error }) => { const authFlowLink = withToken.concat(resetToken); ``` + +## Types + + + +### `SetContextLink.SetContextOperation` + + + +#### Signature + +```ts +type SetContextOperation = Omit< + ApolloLink.Operation, + "getContext" | "setContext" +>; +``` diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 45a8a2ed438..5aa59fde678 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -3,6 +3,31 @@ import { Observable } from "rxjs"; import { ApolloLink } from "@apollo/client/link"; export declare namespace SetContextLink { + namespace SetContextLinkDocumentationTypes { + /** + * A function that returns an updated context object for an Apollo Link + * operation. + * + * The context setter function is called for each operation and allows you to + * modify the operation's context before it's passed to the next link in the + * chain. The returned context object is shallowly merged with the previous + * context object. + * + * @param prevContext - The previous context of the operation (e.g. the value + * of `operation.getContext()`) + * @param operation - The GraphQL operation being executed, without the + * `getContext` and `setContext` methods + * @returns A partial context object or a promise that resolves to a partial context object + */ + export function ContextSetter( + prevContext: Readonly, + operation: SetContextLink.SetContextOperation + ): + | Promise> + | Partial; + } + + /** {@inheritDoc @apollo/client/link/context!SetContextLink.SetContextLinkDocumentationTypes.ContextSetter:function(1)} */ export type ContextSetter = ( prevContext: Readonly, operation: SetContextLink.SetContextOperation @@ -22,6 +47,11 @@ export declare namespace SetContextLink { | Promise> | Partial; + /** + * An `ApolloLink.Operation` object without the `getContext` and `setContext` + * methods. This prevents context setters from directly manipulating the + * context during the setter function execution. + */ export type SetContextOperation = Omit< ApolloLink.Operation, "getContext" | "setContext" @@ -45,6 +75,24 @@ export function setContext(setter: SetContextLink.LegacyContextSetter) { setter(operation, prevContext) ); } +/** + * `SetContextLink` is a non-terminating link that allows you to modify the + * context of GraphQL operations before they're passed to the next link in the + * chain. This is commonly used for authentication, adding headers, and other + * request-time configuration. + * + * @example + * + * ```ts + * import { SetContextLink } from "@apollo/client/link/context"; + * + * const link = new SetContextLink((prevContext, operation) => { + * return { + * // ... + * }; + * }); + * ``` + */ export class SetContextLink extends ApolloLink { constructor(setter: SetContextLink.ContextSetter) { super((operation, forward) => { From 379849804a1378db2ac2fc1e3723b0a318377b1e Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:28:23 -0600 Subject: [PATCH 14/53] Update function docs --- docs/source/api/link/apollo-link-retry.mdx | 26 ++++++++-------- src/link/retry/retryLink.ts | 36 ++++++++++++++++++++-- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 6ce375ba51c..2b931cdf422 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -45,32 +45,34 @@ const link = new RetryLink({ ## Types - - diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index 9b7179688ac..179f2ba77aa 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -14,8 +14,39 @@ import { buildDelayFunction } from "./delayFunction.js"; import { buildRetryFunction } from "./retryFunction.js"; export declare namespace RetryLink { + namespace RetryLinkDocumentationTypes { + /** + * A function used to determine whether to retry the current operation. + * + * @param attempt - The current attempt number + * @param operation - The current `ApolloLink.Operation` for the request + * @param error - The error that triggered the retry attempt + * @returns A boolean to indicate whether to retry the current operation + */ + function AttemptsFunction( + attempt: number, + operation: ApolloLink.Operation, + error: ErrorLike + ): boolean | Promise; + + /** + * A function used to determine the delay for a retry attempt. + * + * @param attempt - The current attempt number + * @param operation - The current `ApolloLink.Operation` for the request + * @param error - The error that triggered the retry attempt + * @returns The delay in milliseconds before attempting the request again + */ + function DelayFunction( + attempt: number, + operation: ApolloLink.Operation, + error: ErrorLike + ): number; + } + + /** {@inheritDoc @apollo/client/link/retry!RetryLink.RetryLinkDocumentationTypes.DelayFunction:function(1)} */ export type DelayFunction = ( - count: number, + attempt: number, operation: ApolloLink.Operation, error: ErrorLike ) => number; @@ -58,8 +89,9 @@ export declare namespace RetryLink { jitter?: boolean; } + /** {@inheritDoc @apollo/client/link/retry!RetryLink.RetryLinkDocumentationTypes.AttemptsFunction:function(1)} */ export type AttemptsFunction = ( - count: number, + attempt: number, operation: ApolloLink.Operation, error: ErrorLike ) => boolean | Promise; From c988016d5e2e08edcb1c488b07965f270738ef90 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:31:13 -0600 Subject: [PATCH 15/53] Remove remarks --- src/link/retry/retryLink.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index 179f2ba77aa..e60992b1cb2 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -103,7 +103,6 @@ export declare namespace RetryLink { /** * The max number of times to try a single operation before giving up. * - * @remarks * Note that this INCLUDES the initial request as part of the count. * E.g. `max` of 1 indicates no retrying should occur. * From 130ca55f1ba5a92832bf86b3f588a50fc79a83fd Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:31:42 -0600 Subject: [PATCH 16/53] Remove italic --- src/link/retry/retryLink.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index e60992b1cb2..d17d24df2d4 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -62,7 +62,7 @@ export declare namespace RetryLink { * set to 100, subsequent retries will be delayed by 200, 400, 800, etc, * until they reach the maximum delay. * - * Note that if jittering is enabled, this is the _average_ delay. + * Note that if jittering is enabled, this is the average delay. * * @defaultValue `300` */ From f037d95baa66de494633301b18fa03b5611497ee Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:37:32 -0600 Subject: [PATCH 17/53] Remove unneeded doc block --- src/link/retry/retryLink.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index d17d24df2d4..33da92e5df5 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -142,9 +142,6 @@ export declare namespace RetryLink { } } -/** - * Tracking and management of operations that may be (or currently are) retried. - */ class RetryableOperation { private retryCount: number = 0; private currentSubscription: Subscription | null = null; From f4263b8f186a871cebb0933c4a17b09fbbf97485 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:38:26 -0600 Subject: [PATCH 18/53] Tweak doc --- docs/source/api/link/apollo-link-retry.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 2b931cdf422..3af6457d5a8 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -26,7 +26,7 @@ These two features are combined to help alleviate [the thundering herd problem]( ## Custom strategies -Instead of the options object, you may pass a function for `delay` and/or `attempts`, which implement custom strategies for each. In both cases the function is given the same arguments (`count`, `operation`, `error`). +Instead of the options object, you may pass a function for `delay` and/or `attempts`, which implement custom strategies for each. In both cases the function is given the same arguments (`attempt`, `operation`, `error`). The `attempts` function should return a `boolean` (or a `Promise` which resolves to a `boolean`) indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by. From 751720854edf6966ea389f9ddaed5d73eccaeb63 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:42:03 -0600 Subject: [PATCH 19/53] Fix typo --- docs/source/api/link/apollo-link-retry.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 3af6457d5a8..3e0604d05df 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -62,7 +62,7 @@ const link = new RetryLink({ canonicalReference="@apollo/client/link/retry!RetryLink.RetryLinkDocumentationTypes.DelayFunction:function(1)" headingLevel={3} result={false} - displayName="RetryLink.RetryFunction" + displayName="RetryLink.DelayFunction" /> Date: Tue, 5 Aug 2025 11:42:35 -0600 Subject: [PATCH 20/53] Update example --- docs/source/api/link/apollo-link-retry.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 3e0604d05df..c7492ec8bc8 100644 --- a/docs/source/api/link/apollo-link-retry.mdx +++ b/docs/source/api/link/apollo-link-retry.mdx @@ -34,11 +34,11 @@ The `attempts` function should return a `boolean` (or a `Promise` which resolves import { RetryLink } from "@apollo/client/link/retry"; const link = new RetryLink({ - attempts: (count, operation, error) => { + attempts: (attempt, operation, error) => { return !!error && operation.operationName != "specialCase"; }, - delay: (count, operation, error) => { - return count * 1000 * Math.random(); + delay: (attempt, operation, error) => { + return attempt * 1000 * Math.random(); }, }); ``` From 04bd14605ab198d38391306a2608086ae1b95d9b Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 11:48:44 -0600 Subject: [PATCH 21/53] Wrap in caution --- docs/source/api/link/apollo-link-ws.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/api/link/apollo-link-ws.mdx b/docs/source/api/link/apollo-link-ws.mdx index c175b8d06cf..f10e2a5a4eb 100644 --- a/docs/source/api/link/apollo-link-ws.mdx +++ b/docs/source/api/link/apollo-link-ws.mdx @@ -3,11 +3,13 @@ title: WebSocket Link description: Execute subscriptions (or other operations) over WebSocket with the subscriptions-transport-ws library --- -> ⚠️ **We no longer recommend using `WebSocketLink` or the `subscriptions-transport-ws` library**, because the library is not actively maintained. To execute subscriptions, We instead recommend using the newer `graphql-ws` library with the accompanying [`GraphQLWsLink`](./apollo-link-subscriptions). -> -> Whichever library you use, make sure you use the _same_ library in your server and any clients you support. For more information, see [Choosing a subscription library](../../data/subscriptions/#choosing-a-subscription-library). + -> We recommend reading [Apollo Link overview](./introduction/) before learning about individual links. +**We no longer recommend using `WebSocketLink` or the `subscriptions-transport-ws` library**, because the library is not actively maintained. To execute subscriptions, We instead recommend using the newer `graphql-ws` library with the accompanying [`GraphQLWsLink`](./apollo-link-subscriptions). + +Whichever library you use, make sure you use the _same_ library in your server and any clients you support. For more information, see [Choosing a subscription library](../../data/subscriptions/#choosing-a-subscription-library). + + The `WebSocketLink` is a [terminating link](./introduction/#the-terminating-link) that's used most commonly with GraphQL [subscriptions](../../data/subscriptions/) (which usually communicate over WebSocket), although you can send queries and mutations over WebSocket as well. From c5190a7144500711783c963256e9309509cc2684 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:09:26 -0600 Subject: [PATCH 22/53] Update doc blocks in WebSocketLink --- src/link/ws/index.ts | 60 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/link/ws/index.ts b/src/link/ws/index.ts index 8455fd22f49..99612850ffd 100644 --- a/src/link/ws/index.ts +++ b/src/link/ws/index.ts @@ -8,21 +8,55 @@ import { invariant } from "@apollo/client/utilities/invariant"; export declare namespace WebSocketLink { /** - * Configuration to use when constructing the subscription client (subscriptions-transport-ws). + * Configuration options for creating a `WebSocketLink` instance. + * + * @remarks + * + * These configuration options are used when creating a `WebSocketLink` without + * providing an existing `SubscriptionClient` instance. The options are passed + * directly to the `SubscriptionClient` constructor from the `subscriptions-transport-ws` + * library. */ export interface Configuration { /** - * The endpoint to connect to. + * The WebSocket endpoint URI to connect to. + * + * This should be a valid WebSocket URI (starting with `ws://` or `wss://`) + * that points to your GraphQL subscription endpoint. + * + * @example "ws://localhost:4000/subscriptions" + * @example "wss://api.example.com/graphql" */ uri: string; /** - * Options to pass when constructing the subscription client. + * Configuration options passed to the underlying `SubscriptionClient`. + * + * These options configure the WebSocket connection behavior, including + * reconnection settings, connection parameters, and event handlers. + * + * For a complete list of available options, see the + * [supported `subscriptions-transport-ws` options](https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L61-L71). */ options?: ClientOptions; /** - * A custom WebSocket implementation to use. + * A custom WebSocket implementation to use for the connection. + * + * This is useful in environments that don't have native WebSocket support. + * You can provide a WebSocket polyfill or implementation that conforms to + * the W3C WebSocket API. + * + * @example + * + * ```ts + * import WebSocket from "ws"; + * + * const wsLink = new WebSocketLink({ + * uri: "ws://localhost:4000/subscriptions", + * webSocketImpl: WebSocket, + * }); + * ``` */ webSocketImpl?: any; } @@ -32,6 +66,24 @@ export declare namespace WebSocketLink { export import WebSocketParams = WebSocketLink.Configuration; /** + * `WebSocketLink` is a terminating link that executes GraphQL operations over + * WebSocket connections using the `subscriptions-transport-ws` library. It's + * primarily used for GraphQL subscriptions but can also handle queries and + * mutations. + * + * @example + * + * ```ts + * import { WebSocketLink } from "@apollo/client/link/ws"; + * import { SubscriptionClient } from "subscriptions-transport-ws"; + * + * const wsLink = new WebSocketLink( + * new SubscriptionClient("ws://localhost:4000/subscriptions", { + * reconnect: true, + * }) + * ); + * ``` + * * @deprecated `WebSocketLink` uses the deprecated and unmaintained * `subscriptions-transport-ws` library. This link is no longer maintained and * will be removed in a future major version of Apollo Client. We recommend From 3949ec710a5ddb9cb7309fe2cb6f72471bb0a350 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:09:38 -0600 Subject: [PATCH 23/53] Update ws docs --- docs/source/api/link/apollo-link-ws.mdx | 98 +++++-------------------- 1 file changed, 20 insertions(+), 78 deletions(-) diff --git a/docs/source/api/link/apollo-link-ws.mdx b/docs/source/api/link/apollo-link-ws.mdx index f10e2a5a4eb..94986beaa07 100644 --- a/docs/source/api/link/apollo-link-ws.mdx +++ b/docs/source/api/link/apollo-link-ws.mdx @@ -1,5 +1,5 @@ --- -title: WebSocket Link +title: WebSocketLink description: Execute subscriptions (or other operations) over WebSocket with the subscriptions-transport-ws library --- @@ -11,89 +11,31 @@ Whichever library you use, make sure you use the _same_ library in your server a -The `WebSocketLink` is a [terminating link](./introduction/#the-terminating-link) that's used most commonly with GraphQL [subscriptions](../../data/subscriptions/) (which usually communicate over WebSocket), although you can send queries and mutations over WebSocket as well. + -`WebSocketLink` requires the [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library. Install it in your project like so: +## Constructor signature -```shell -npm install subscriptions-transport-ws +```ts +constructor( + paramsOrClient: WebSocketLink.Configuration | SubscriptionClient +): WebSocketLink ``` -## Constructor +## Installation -```js -import { WebSocketLink } from "@apollo/client/link/ws"; -import { SubscriptionClient } from "subscriptions-transport-ws"; +`WebSocketLink` requires the [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library. Install it in your project: -const link = new WebSocketLink( - new SubscriptionClient("ws://localhost:4000/graphql", { - reconnect: true, - }) -); +```shell +npm install subscriptions-transport-ws ``` -### Options - -The `WebSocketLink` constructor takes either a `SubscriptionClient` object or an options object with the following fields. (These options are passed directly to the `SubscriptionClient` constructor.) - - - - - - - - - - - - - - - - - - - - - - - - - -
Name /
Type
Description
- -###### `uri` - -`String` - - - -**Required.** The URL of the WebSocket endpoint to connect to (e.g., `ws://localhost:4000/subscriptions`). - -
- -###### `options` - -`Object` - - - -Options for configuring the WebSocket connection. - -[See supported options](https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L61-L71) - -
- -###### `webSocketImpl` - -`Object` - - - -A W3C-compliant WebSocket implementation to use. Provide this if your environment does not provide native WebSocket support (for example, in Node.js). - -
- -## Usage +## Types -See [Subscriptions](../../data/subscriptions/). + From 2d9a7134e95497dd0f4ed8c2159ae23f9a6f500b Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:12:15 -0600 Subject: [PATCH 24/53] Remove old type --- src/link/ws/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/link/ws/index.ts b/src/link/ws/index.ts index 99612850ffd..025c3704bb7 100644 --- a/src/link/ws/index.ts +++ b/src/link/ws/index.ts @@ -62,9 +62,6 @@ export declare namespace WebSocketLink { } } -// For backwards compatibility. -export import WebSocketParams = WebSocketLink.Configuration; - /** * `WebSocketLink` is a terminating link that executes GraphQL operations over * WebSocket connections using the `subscriptions-transport-ws` library. It's From c2aa5b030667eb124f0d791307fcf9170aad6526 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:16:28 -0600 Subject: [PATCH 25/53] Rename options type in ClientAwarenessLink --- src/link/client-awareness/ClientAwarenessLink.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/link/client-awareness/ClientAwarenessLink.ts b/src/link/client-awareness/ClientAwarenessLink.ts index e5239cc7874..f8eadf31018 100644 --- a/src/link/client-awareness/ClientAwarenessLink.ts +++ b/src/link/client-awareness/ClientAwarenessLink.ts @@ -94,7 +94,7 @@ export declare namespace ClientAwarenessLink { * ``` */ export class ClientAwarenessLink extends ApolloLink { - constructor(constructorOptions: ClientAwarenessLink.Options = {}) { + constructor(options: ClientAwarenessLink.Options = {}) { super((operation, forward) => { const client = operation.client; @@ -108,7 +108,7 @@ export class ClientAwarenessLink extends ApolloLink { } = compact( {}, clientOptions.clientAwareness, - constructorOptions.clientAwareness, + options.clientAwareness, context.clientAwareness ); @@ -131,7 +131,7 @@ export class ClientAwarenessLink extends ApolloLink { const { transport = "extensions" } = compact( {}, clientOptions.enhancedClientAwareness, - constructorOptions.enhancedClientAwareness + options.enhancedClientAwareness ); if (transport === "extensions") { operation.extensions = compact( From 7a3a2a4acc6e2763086695488cf699ea4bff3407 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:17:31 -0600 Subject: [PATCH 26/53] Add ClientAwarenessLink constructure signature --- docs/source/api/link/apollo-link-client-awareness.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/api/link/apollo-link-client-awareness.mdx b/docs/source/api/link/apollo-link-client-awareness.mdx index 102354d09ff..6530bd23615 100644 --- a/docs/source/api/link/apollo-link-client-awareness.mdx +++ b/docs/source/api/link/apollo-link-client-awareness.mdx @@ -8,6 +8,14 @@ description: API reference customOrder={["summary", "remarks", "example"]} /> +## Constructor signature + +```ts +constructor( + options?: ClientAwarenessLink.Options +): ClientAwarenessLink +``` + ## Configuring client awareness Client awareness can be configured in various ways in Apollo Client. From 4ea6856770df63f783530b7ad9bc4bf3e66a1002 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 12:58:00 -0600 Subject: [PATCH 27/53] Add doc block for intro --- docs/source/api/link/persisted-queries.mdx | 13 +++++++++++++ src/link/persisted-queries/index.ts | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 47a869c80a0..495cd7c0b2d 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -3,6 +3,19 @@ title: Persisted Queries Link description: Secure your graph while minimizing request latency. --- + + +## Constructor signature + +```ts +constructor( + options: PersistedQueryLink.Options +): PersistedQueryLink +``` + ## Problems to solve Unlike REST APIs that use a fixed URL to load data, GraphQL provides a rich query language that can be used to express the shape of application data requirements. This is a marvelous advancement in technology, but it comes at a cost: GraphQL query strings are often much longer than REST URLS—in some cases by many kilobytes. diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index f2cb411078d..15f27bb1052 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -124,6 +124,22 @@ function operationDefinesMutation(operation: ApolloLink.Operation) { export const createPersistedQueryLink = (options: PersistedQueryLink.Options) => new PersistedQueryLink(options); +/** + * `PersistedQueryLink` is a non-terminating link that enables the use of + * persisted queries, a technique that reduces bandwidth by sending query hashes + * instead of full query strings. + * + * @example + * + * ```ts + * import { PersistedQueryLink } from "@apollo/client/link/persisted-queries"; + * import { sha256 } from "crypto-hash"; + * + * const link = new PersistedQueryLink({ + * sha256: (queryString) => sha256(queryString), + * }); + * ``` + */ export class PersistedQueryLink extends ApolloLink { constructor(options: PersistedQueryLink.Options) { let hashesByQuery: From fe4aca169eea0879792889aef2b70379d1d87e57 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 16:54:21 -0600 Subject: [PATCH 28/53] Doc blocks for remaining options --- src/link/persisted-queries/index.ts | 142 ++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index 15f27bb1052..d74cba6a3e5 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -31,48 +31,190 @@ import { defaultCacheSizes } from "../../utilities/caching/sizes.js"; export const VERSION = 1; export declare namespace PersistedQueryLink { + namespace PersistedQueryLinkDocumentationTypes { + /** + * A SHA-256 hash function for hashing query strings. + * + * @param queryString - The query string to hash + * @returns The SHA-256 hash or a promise that resolves to the SHA-256 hash + * + * @example + * + * ```ts + * import { sha256 } from "crypto-hash"; + * + * const link = new PersistedQueryLink({ sha256 }); + * ``` + */ + function SHA256Function(queryString: string): string | PromiseLike; + + /** + * A function that generates a hash for a GraphQL document. + * + * @param document - The GraphQL document to hash + * @returns The hash string or a promise that resolves to the hash string + * + * @example + * + * ```ts + * import { print } from "graphql"; + * import { sha256 } from "crypto-hash"; + * + * const link = new PersistedQueryLink({ + * generateHash: async (document) => { + * const query = print(document); + * return sha256(query); + * }, + * }); + * ``` + */ + function GenerateHashFunction( + document: DocumentNode + ): string | PromiseLike; + } + namespace Base { + /** + * Base options shared between SHA256 and custom hash configurations. + */ interface Options { + /** + * A function to disable persisted queries for the current session. + * + * This function is called when an error occurs and determines whether + * to disable persisted queries for all future requests in this session. + * + * @defaultValue Disables on `PersistedQueryNotSupported` errors + */ disable?: (options: PersistedQueryLink.DisableFunctionOptions) => boolean; + + /** + * A function to determine whether to retry a request with the full query. + * + * When a persisted query fails, this function determines whether to + * retry the request with the full query text included. + * + * @defaultValue Retries on `PersistedQueryNotSupported` or `PersistedQueryNotFound` errors + */ retry?: (options: PersistedQueryLink.RetryFunctionOptions) => boolean; + + /** + * Whether to use HTTP GET for hashed queries (excluding mutations). + * + * When enabled, queries sent with just a hash will use GET requests. + * This can improve caching but is not compatible with batching. + * + * Note: Mutations always use POST regardless of this setting. + * + * @defaultValue `false` + */ useGETForHashedQueries?: boolean; } } + /** + * Metadata about persisted query errors extracted from the response. + */ export interface ErrorMeta { + /** + * Whether the server responded with a "PersistedQueryNotSupported" error. + * + * When `true`, indicates the server doesn't support persisted queries + * or has disabled them for this client. + */ persistedQueryNotSupported: boolean; + + /** + * Whether the server responded with a "PersistedQueryNotFound" error. + * + * When `true`, indicates the server doesn't recognize the query hash + * and needs the full query text. + */ persistedQueryNotFound: boolean; } + /** {@inheritDoc @apollo/client/link/persisted-queries!PersistedQueryLink.PersistedQueryLinkDocumentationTypes.GenerateHashFunction:function(1)} */ export type GenerateHashFunction = ( document: DocumentNode ) => string | PromiseLike; + /** {@inheritDoc @apollo/client/link/persisted-queries!PersistedQueryLink.PersistedQueryLinkDocumentationTypes.SHA256Function:function(1)} */ export type SHA256Function = ( queryString: string ) => string | PromiseLike; + /** + * Options for using SHA-256 hashing with persisted queries. + * + * Use this configuration when you want the link to handle query + * printing and hashing using a SHA-256 function. + */ export interface SHA256Options extends Base.Options { + /** + * The SHA-256 hash function to use for hashing queries. This function + * receives the printed query string and should return a SHA-256 hash. Can + * be synchronous or asynchronous. + */ sha256: PersistedQueryLink.SHA256Function; generateHash?: never; } + /** + * Options for using custom hash generation with persisted queries. + * + * Use this configuration when you need custom control over how + * query hashes are generated (e.g., using pre-computed hashes). + */ export interface GenerateHashOptions extends Base.Options { sha256?: never; + /** + * A custom function for generating query hashes. This function receives + * the GraphQL document and should return a hash. Useful for custom hashing + * strategies or when using build-time generated hashes. + */ generateHash: PersistedQueryLink.GenerateHashFunction; } + /** + * Configuration options for creating a `PersistedQueryLink`. + * + * You must provide either a `sha256` function or a custom `generateHash` + * function, but not both. + */ export type Options = | PersistedQueryLink.SHA256Options | PersistedQueryLink.GenerateHashOptions; + /** + * Options passed to the `retry` function when a persisted query request + * fails. + */ export interface RetryFunctionOptions { + /** + * The error that occurred during the request. + */ error: ErrorLike; + + /** + * The GraphQL operation that failed. + */ operation: ApolloLink.Operation; + + /** + * Metadata about the persisted query error. + */ meta: PersistedQueryLink.ErrorMeta; + + /** + * The GraphQL result, if available. + */ result?: FormattedExecutionResult; } + /** + * Options passed to the `disable` function when a persisted query request + * fails. + */ export interface DisableFunctionOptions extends PersistedQueryLink.RetryFunctionOptions {} } From b5ef8dcb2c259aa7b8c4e27d4529eaace51f6c44 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 16:54:41 -0600 Subject: [PATCH 29/53] Add codegen doc block types --- docs/source/api/link/persisted-queries.mdx | 41 +++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 495cd7c0b2d..0d7c66bdb9b 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -1,5 +1,5 @@ --- -title: Persisted Queries Link +title: PersistedQueryLink description: Secure your graph while minimizing request latency. --- @@ -303,3 +303,42 @@ _Missing hash path_ If you want to avoid hashing in the browser, you can use a build script to include the hash as part of the request, then pass a function to retrieve that hash when the operation is run. This works well with projects like [GraphQL Persisted Document Loader](https://github.com/leoasis/graphql-persisted-document-loader) which uses webpack to generate hashes at build time. If you use the above loader, you can pass `{ generateHash: ({ documentId }) => documentId }` to the `createPersistedQueryLink` call. + +## Types + + + + + + + + + + + + From 2289b1245abbfe62385d3e1204eef52d5946899d Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 16:54:54 -0600 Subject: [PATCH 30/53] Use class in code examples --- docs/source/api/link/persisted-queries.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 0d7c66bdb9b..31c5808e187 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -140,9 +140,9 @@ Finally, combine the link that `generatePersistedQueryIdsFromManifest` returns w ```js import { HttpLink, InMemoryCache, ApolloClient } from "@apollo/client"; import { generatePersistedQueryIdsFromManifest } from "@apollo/persisted-query-lists"; -import { createPersistedQueryLink } from "@apollo/client/link/persisted-queries"; +import { PersistedQueryLink } from "@apollo/client/link/persisted-queries"; -const persistedQueryLink = createPersistedQueryLink( +const persistedQueryLink = new PersistedQueryLink( generatePersistedQueryIdsFromManifest({ loadManifest: () => import("./path/to/persisted-query-manifest.json"), }) @@ -180,11 +180,11 @@ The link requires using `ApolloClient`'s `HttpLink`. The easiest way to use them ```js import { HttpLink, InMemoryCache, ApolloClient } from "@apollo/client"; -import { createPersistedQueryLink } from "@apollo/client/link/persisted-queries"; +import { PersistedQueryLink } from "@apollo/client/link/persisted-queries"; import { sha256 } from "crypto-hash"; const httpLink = new HttpLink({ uri: "/graphql" }); -const persistedQueriesLink = createPersistedQueryLink({ sha256 }); +const persistedQueriesLink = new PersistedQueryLink({ sha256 }); const client = new ApolloClient({ cache: new InMemoryCache(), link: persistedQueriesLink.concat(httpLink), From 368bd68ef063eff6a5479c58c2720f9da76799d5 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 16:58:39 -0600 Subject: [PATCH 31/53] Use notes --- docs/source/api/link/persisted-queries.mdx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 31c5808e187..e6b24bcd582 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -54,11 +54,19 @@ You can use APQ with the following versions of Apollo Client Web, Apollo Server, - [Apollo Server](/apollo-server/) (v1.0.0+) - [Apollo Router Core](/router) (v0.1.0+) -> **Note:** You can use _either_ Apollo Server _or_ Apollo Router Core for APQs. They don't need to be used together. + + +You can use _either_ Apollo Server _or_ Apollo Router Core for APQs. They don't need to be used together. + + ## 1. Generate operation manifests -> **This step is only required for persisted queries, not APQ.** + + +This step is only required for persisted queries, not APQ. + + An operation manifest acts as a safelist the [GraphOS Router](/router/) can check incoming requests against. You can generate the manifest using the [`@apollo/generate-persisted-query-manifest`](https://www.npmjs.com/package/@apollo/generate-persisted-query-manifest) package: From 6011f13aec8381df55b80c505f2db2cf81621256 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:06:52 -0600 Subject: [PATCH 32/53] Use the class name --- docs/source/api/link/persisted-queries.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index e6b24bcd582..4f74eab7ade 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -201,9 +201,9 @@ const client = new ApolloClient({ Thats it! By including the persisted queries link in your client instantiation, your client sends operation IDs instead of the full operation string. This results in improved network performance, but doesn't include the security benefits of operation safelisting that [persisted queries](#differences-between-persisted-queries-and-apq) provide. -#### `createPersistedQueryLink` Options +#### `PersistedQueryLink` options -The `createPersistedQueryLink` function takes a configuration object: +The `PersistedQueryLink` class takes a configuration object: - `sha256`: a SHA-256 hashing function. Can be sync or async. Providing a SHA-256 hashing function is required, unless you're defining a fully custom hashing approach via `generateHash`. - `generateHash`: an optional function that takes the query document and returns the hash. If provided this custom function will override the default hashing approach that uses the supplied `sha256` function. If not provided, the persisted queries link will use a fallback hashing approach leveraging the `sha256` function. @@ -310,7 +310,7 @@ _Missing hash path_ If you want to avoid hashing in the browser, you can use a build script to include the hash as part of the request, then pass a function to retrieve that hash when the operation is run. This works well with projects like [GraphQL Persisted Document Loader](https://github.com/leoasis/graphql-persisted-document-loader) which uses webpack to generate hashes at build time. -If you use the above loader, you can pass `{ generateHash: ({ documentId }) => documentId }` to the `createPersistedQueryLink` call. +If you use the above loader, you can pass `{ generateHash: ({ documentId }) => documentId }` to the `PersistedQueryLink` class. ## Types From 812bb113d038f7c9c92c9bb63c5382b930497ed4 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:17:30 -0600 Subject: [PATCH 33/53] Add note --- src/link/persisted-queries/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index d74cba6a3e5..334796ba26d 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -101,10 +101,11 @@ export declare namespace PersistedQueryLink { /** * Whether to use HTTP GET for hashed queries (excluding mutations). * - * When enabled, queries sent with just a hash will use GET requests. - * This can improve caching but is not compatible with batching. - * - * Note: Mutations always use POST regardless of this setting. + * > [!NOTE] + * > If you want to use `GET` for non-mutation queries whether or not they + * > are hashed, pass `useGETForQueries: true` option to `HttpLink` + * > instead. If you want to use GET for all requests, pass `fetchOptions: {method: 'GET'}` + * > to `HttpLink`. * * @defaultValue `false` */ From 5072235f4853f71116132bf0e813e1308f536477 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:17:35 -0600 Subject: [PATCH 34/53] Update options and remove object section --- docs/source/api/link/persisted-queries.mdx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 4f74eab7ade..c2707e887d4 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -209,18 +209,8 @@ The `PersistedQueryLink` class takes a configuration object: - `generateHash`: an optional function that takes the query document and returns the hash. If provided this custom function will override the default hashing approach that uses the supplied `sha256` function. If not provided, the persisted queries link will use a fallback hashing approach leveraging the `sha256` function. - `useGETForHashedQueries`: set to `true` to use the HTTP `GET` method when sending the hashed version of queries (but not for mutations). `GET` requests are not compatible with `@apollo/client/link/batch-http`. > If you want to use `GET` for non-mutation queries whether or not they are hashed, pass `useGETForQueries: true` option to `HttpLink` instead. If you want to use `GET` for all requests, pass `fetchOptions: {method: 'GET'}` to `HttpLink`. -- `disable`: a function which takes an `ErrorResponse` (see below) and returns a boolean to disable any future persisted queries for that session. This defaults to disabling on `PersistedQueryNotSupported` or a 400 or 500 http error. - -**ErrorResponse** - -The argument that the optional `disable` function is given is an object with the following keys: - -- `operation`: The Operation that encountered an error (contains `query`, `variables`, `operationName`, and `context`). -- `response`: The Execution of the response (contains `data` and `errors` as well `extensions` if sent from the server). -- `graphQLErrors`: An array of errors from the GraphQL endpoint. -- `networkError`: Any error during the link execution or server response. - -_Note_: `networkError` is the value from the downlink's `error` callback. In most cases, `graphQLErrors` is the `errors` field of the result from the last `next` call. A `networkError` can contain additional fields, such as a GraphQL object in the case of a failing HTTP status code from `@apollo/link/http`. In this situation, `graphQLErrors` is an alias for `networkError.result.errors` if the property exists. +- `disable`: a function which takes a [`PersistedQueryLink.DisableFunctionOptions`](#persistedquerylinkdisablefunctionoptions) object and returns a boolean to disable any future persisted queries for that session. This defaults to disabling on `PersistedQueryNotSupported` error. +- `retry`: a function which takes a [`PersistedQueryLink.RetryFunctionOptions`](#persistedquerylinkretryfunctionoptions) object and returns a boolean to retry the request with the full query text included. This defaults to `true` on `PersistedQueryNotSupported` or `PersistedQueryNotFound` errors. ## Apollo Studio From 407d1d12963692e63d423cb928edd25669a89ea9 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:23:04 -0600 Subject: [PATCH 35/53] fix canonical references in RemoveTypenameFromVariablesLink --- docs/source/api/link/apollo-link-remove-typename.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-remove-typename.mdx b/docs/source/api/link/apollo-link-remove-typename.mdx index a0dd85d058b..eae7f9eacee 100644 --- a/docs/source/api/link/apollo-link-remove-typename.mdx +++ b/docs/source/api/link/apollo-link-remove-typename.mdx @@ -232,8 +232,16 @@ const removeTypenameLink = new RemoveTypenameFromVariablesLink({ ## Types +### `RemoveTypenameFromVariablesLink.KeepTypenameConfig` + + + From 3d6733bb77dd1b3f8a6ab422b387a6d20fa8ff47 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:33:55 -0600 Subject: [PATCH 36/53] Rename option --- src/link/http/BaseHttpLink.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/http/BaseHttpLink.ts b/src/link/http/BaseHttpLink.ts index 94ca367dc1b..3e3a9ac72fc 100644 --- a/src/link/http/BaseHttpLink.ts +++ b/src/link/http/BaseHttpLink.ts @@ -27,7 +27,7 @@ import { selectURI } from "./selectURI.js"; const backupFetch = maybe(() => fetch); export class BaseHttpLink extends ApolloLink { - constructor(linkOptions: HttpLink.Options = {}) { + constructor(options: BaseHttpLink.Options = {}) { let { uri = "/graphql", // use default global fetch if nothing passed in @@ -38,7 +38,7 @@ export class BaseHttpLink extends ApolloLink { useGETForQueries, includeUnusedVariables = false, ...requestOptions - } = linkOptions; + } = options; if (__DEV__) { // Make sure at least one of preferredFetch, window.fetch, or backupFetch is From cc49f62762af85b62cfcdc0b5177e7ff85d8e8c6 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 17:37:05 -0600 Subject: [PATCH 37/53] Initial doc for `BaseHttpLink` --- docs/source/_sidebar.yaml | 2 + .../source/api/link/apollo-link-base-http.mdx | 31 ++++++++++++++ src/link/http/BaseHttpLink.ts | 42 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 docs/source/api/link/apollo-link-base-http.mdx diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml index 2957511b0f5..d21e75abe9c 100644 --- a/docs/source/_sidebar.yaml +++ b/docs/source/_sidebar.yaml @@ -189,6 +189,8 @@ items: href: ./api/link/introduction - label: ApolloLink href: ./api/link/apollo-link + - label: BaseHttpLink + href: ./api/link/apollo-link-base-http - label: BatchHttpLink href: ./api/link/apollo-link-batch-http - label: ClientAwarenessLink diff --git a/docs/source/api/link/apollo-link-base-http.mdx b/docs/source/api/link/apollo-link-base-http.mdx new file mode 100644 index 00000000000..97f8a481c28 --- /dev/null +++ b/docs/source/api/link/apollo-link-base-http.mdx @@ -0,0 +1,31 @@ +--- +title: BaseHttpLink +description: The foundational HTTP link for sending GraphQL operations over HTTP +--- + + + +## Constructor signature + +```ts +constructor( + options: BaseHttpLink.Options = {} +): BaseHttpLink +``` + +## Usage + +See the [`HttpLink` documentation](./apollo-link-http) for more information on +how to use `BaseHttpLink`. + +## Types + + + diff --git a/src/link/http/BaseHttpLink.ts b/src/link/http/BaseHttpLink.ts index 3e3a9ac72fc..8542bc27802 100644 --- a/src/link/http/BaseHttpLink.ts +++ b/src/link/http/BaseHttpLink.ts @@ -26,6 +26,48 @@ import { selectURI } from "./selectURI.js"; const backupFetch = maybe(() => fetch); +export declare namespace BaseHttpLink { + /** + * Configuration options for creating a `BaseHttpLink` instance. + * + * `BaseHttpLink` accepts the same options as `HttpLink`. The options + * allow you to configure the HTTP transport behavior, including the + * endpoint URI, headers, credentials, and fetch customization. + * + * Most options can be overridden on a per-operation basis using + * operation context. + */ + export interface Options extends HttpLink.Options {} +} + +/** + * `BaseHttpLink` is a terminating link that sends a GraphQL operation to a + * remote endpoint over HTTP. It serves as a base link to `HttpLink`. + * + * @remarks + * + * `BaseHttpLink` supports both POST and GET requests, and you can configure + * HTTP options on a per-operation basis. You can use these options for + * authentication, persisted queries, dynamic URIs, and other granular updates. + * + * > [!NOTE] + * > Prefer using `HttpLink` over `BaseHttpLink`. `BaseHttpLink` is useful when + * > you need to disable client awareness features and would like to tree-shake + * > the implementation of `ClientAwarenessLink` out of your app bundle. + * + * @example + * + * ```ts + * import { BaseHttpLink } from "@apollo/client/link/http"; + * + * const link = new BaseHttpLink({ + * uri: "http://localhost:4000/graphql", + * headers: { + * authorization: `Bearer ${token}`, + * }, + * }); + * ``` + */ export class BaseHttpLink extends ApolloLink { constructor(options: BaseHttpLink.Options = {}) { let { From 78ca3699f1557ad0e76e904c3acd55b7cf536d2a Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:07:47 -0600 Subject: [PATCH 38/53] Add ContextOptions type for ClientAwarenessLink --- .../api/link/apollo-link-client-awareness.mdx | 6 ++++++ src/link/client-awareness/ClientAwarenessLink.ts | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/source/api/link/apollo-link-client-awareness.mdx b/docs/source/api/link/apollo-link-client-awareness.mdx index 6530bd23615..7794964620f 100644 --- a/docs/source/api/link/apollo-link-client-awareness.mdx +++ b/docs/source/api/link/apollo-link-client-awareness.mdx @@ -96,6 +96,12 @@ function MyComponent() { ## Types + + [!NOTE] + * > Some of these values can also be provided to the `ClientAwarenessLink` + * > constructor. If a value is provided to both, the value in `context` takes + * > precedence. + */ + export interface ContextOptions { + /** {@inheritDoc @apollo/client/link/client-awareness!ClientAwarenessLink.Options#clientAwareness:member} */ + clientAwareness?: ClientAwarenessLink.ClientAwarenessOptions; + } + export interface ClientAwarenessOptions { /** * A custom name (e.g., `iOS`) that identifies this particular client among your set of clients. Apollo Server and Apollo Studio use this property as part of the [client awareness](https://www.apollographql.com/docs/apollo-server/monitoring/metrics#identifying-distinct-clients) feature. From b877f28e6e79ccec349fdd8e9b8eb1c2cd639162 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:21:08 -0600 Subject: [PATCH 39/53] Tweak doc block --- src/link/http/BaseHttpLink.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/http/BaseHttpLink.ts b/src/link/http/BaseHttpLink.ts index 8542bc27802..f5ba9727ee0 100644 --- a/src/link/http/BaseHttpLink.ts +++ b/src/link/http/BaseHttpLink.ts @@ -51,8 +51,8 @@ export declare namespace BaseHttpLink { * authentication, persisted queries, dynamic URIs, and other granular updates. * * > [!NOTE] - * > Prefer using `HttpLink` over `BaseHttpLink`. `BaseHttpLink` is useful when - * > you need to disable client awareness features and would like to tree-shake + * > Prefer using `HttpLink` over `BaseHttpLink`. Use `BaseHttpLink` when you + * > need to disable client awareness features and would like to tree-shake * > the implementation of `ClientAwarenessLink` out of your app bundle. * * @example From 5d60bdd6b83ef2e8165991840259db1073a0e9e7 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:22:45 -0600 Subject: [PATCH 40/53] Change description --- docs/source/api/link/apollo-link-base-http.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-base-http.mdx b/docs/source/api/link/apollo-link-base-http.mdx index 97f8a481c28..2d1aa8001cc 100644 --- a/docs/source/api/link/apollo-link-base-http.mdx +++ b/docs/source/api/link/apollo-link-base-http.mdx @@ -1,6 +1,6 @@ --- title: BaseHttpLink -description: The foundational HTTP link for sending GraphQL operations over HTTP +description: Get GraphQL results over a network using HTTP fetch --- Date: Tue, 5 Aug 2025 18:26:33 -0600 Subject: [PATCH 41/53] Move most HttpLink options to BaseHttpLink. Multi inherit --- .../source/api/link/apollo-link-base-http.mdx | 12 + docs/source/api/link/apollo-link-http.mdx | 14 +- src/link/batch-http/batchHttpLink.ts | 6 +- src/link/http/BaseHttpLink.ts | 207 +++++++++++++++++- src/link/http/HttpLink.ts | 197 +---------------- src/link/http/rewriteURIForGET.ts | 4 +- src/link/http/selectHttpOptionsAndBody.ts | 15 +- src/utilities/subscriptions/relay/index.ts | 4 +- 8 files changed, 234 insertions(+), 225 deletions(-) diff --git a/docs/source/api/link/apollo-link-base-http.mdx b/docs/source/api/link/apollo-link-base-http.mdx index 2d1aa8001cc..119ff3b79b3 100644 --- a/docs/source/api/link/apollo-link-base-http.mdx +++ b/docs/source/api/link/apollo-link-base-http.mdx @@ -23,6 +23,18 @@ how to use `BaseHttpLink`. ## Types + + + + - - - - diff --git a/src/link/batch-http/batchHttpLink.ts b/src/link/batch-http/batchHttpLink.ts index 7b532803621..7a640437b54 100644 --- a/src/link/batch-http/batchHttpLink.ts +++ b/src/link/batch-http/batchHttpLink.ts @@ -3,7 +3,7 @@ import { Observable, throwError } from "rxjs"; import { ApolloLink } from "@apollo/client/link"; import { BatchLink } from "@apollo/client/link/batch"; import { ClientAwarenessLink } from "@apollo/client/link/client-awareness"; -import type { HttpLink } from "@apollo/client/link/http"; +import type { BaseHttpLink } from "@apollo/client/link/http"; import { checkFetcher, defaultPrinter, @@ -23,7 +23,7 @@ export declare namespace BatchHttpLink { */ export interface Options extends BatchLink.Shared.Options, - HttpLink.Shared.Options { + BaseHttpLink.Shared.Options { /** {@inheritDoc @apollo/client/link/batch!BatchLink.Shared.Options#batchMax:member {"defaultValue": 10}} */ batchMax?: number; } @@ -37,7 +37,7 @@ export declare namespace BatchHttpLink { * > Some of these values can also be provided to the `BatchHttpLink` constructor. * > If a value is provided to both, the value in `context` takes precedence. */ - export interface ContextOptions extends HttpLink.ContextOptions {} + export interface ContextOptions extends BaseHttpLink.ContextOptions {} } const backupFetch = maybe(() => fetch); diff --git a/src/link/http/BaseHttpLink.ts b/src/link/http/BaseHttpLink.ts index f5ba9727ee0..f67422b0c0c 100644 --- a/src/link/http/BaseHttpLink.ts +++ b/src/link/http/BaseHttpLink.ts @@ -1,3 +1,4 @@ +import type { ASTNode, print } from "graphql"; import { Observable } from "rxjs"; import { ApolloLink } from "@apollo/client/link"; @@ -11,7 +12,6 @@ import { compact } from "@apollo/client/utilities/internal"; import { maybe } from "@apollo/client/utilities/internal/globals"; import { checkFetcher } from "./checkFetcher.js"; -import type { HttpLink } from "./HttpLink.js"; import { parseAndCheckHttpResponse, readMultipartBody, @@ -28,16 +28,207 @@ const backupFetch = maybe(() => fetch); export declare namespace BaseHttpLink { /** - * Configuration options for creating a `BaseHttpLink` instance. + * Options passed to `BaseHttpLink` through [request context](https://apollographql.com/docs/react/api/link/introduction#managing-context). Previous + * non-terminating links in the link chain also can set these values to + * customize the behavior of `BaseHttpLink` for each operation. * - * `BaseHttpLink` accepts the same options as `HttpLink`. The options - * allow you to configure the HTTP transport behavior, including the - * endpoint URI, headers, credentials, and fetch customization. + * > [!NOTE] + * > Some of these values can also be provided to the `HttpLink` constructor. + * > If a value is provided to both, the value in `context` takes precedence. + */ + interface ContextOptions { + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#uri:member} */ + uri?: string | BaseHttpLink.UriFunction; + + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#headers:member} */ + headers?: Record; + + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#credentials:member} */ + credentials?: RequestCredentials; + + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#fetchOptions:member} */ + fetchOptions?: RequestInit; + + /** + * An object that configures advanced functionality, such as support for + * persisted queries. + */ + http?: BaseHttpLink.HttpOptions; + } + + /** + * Options passed to `BaseHttpLink` through the `http` property of a request + * context. + */ + export interface HttpOptions { + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#includeExtensions:member} */ + includeExtensions?: boolean; + + /** + * If `false`, the GraphQL query string is not included in the request. Set + * this option if you're sending a request that uses a [persisted query](https://www.apollographql.com/docs/react/api/link/persisted-queries/). + * + * @defaultValue `true` + */ + includeQuery?: boolean; + + /** {@inheritDoc @apollo/client/link/http!BaseHttpLink.Shared.Options#preserveHeaderCase:member} */ + preserveHeaderCase?: boolean; + + /** + * A list of additional `accept` headers to include in the request, + * as defined in https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 + * + * @example + * + * ```json + * ["application/custom+json;q=1.0"] + * ``` + */ + accept?: string[]; + } + + export namespace Shared { + /** These options are shared between `BaseHttpLink` and `BaseBatchHttpLink` */ + export interface Options { + /** + * The URL of the GraphQL endpoint to send requests to. Can also be a + * function that accepts an `ApolloLink.Operation` object and returns the + * string URL to use for that operation. + * + * @defaultValue "/graphql" + */ + uri?: string | BaseHttpLink.UriFunction; + + /** + * If `true`, includes the `extensions` field in operations sent to your + * GraphQL endpoint. + * + * @defaultValue true + */ + includeExtensions?: boolean; + + /** + * A function to use instead of calling the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) directly + * when sending HTTP requests to your GraphQL endpoint. The function must + * conform to the signature of `fetch`. + * + * By default, the Fetch API is used unless it isn't available in your + * runtime environment. + * + * See [Customizing `fetch`](https://apollographql.com/docs/react/api/link/introduction#customizing-fetch). + */ + fetch?: typeof fetch; + + /** + * An object representing headers to include in every HTTP request. + * + * @example + * + * ```json + * { + * "Authorization": "Bearer 1234" + * } + * ``` + */ + headers?: Record; + + /** + * If `true`, header names won't be automatically normalized to lowercase. + * This allows for non-http-spec-compliant servers that might expect + * capitalized header names. + * + * @defaultValue false + */ + preserveHeaderCase?: boolean; + + /** + * The credentials policy to use for each `fetch` call. + */ + credentials?: RequestCredentials; + + /** + * Any overrides of the fetch options argument to pass to the fetch call. + * + * An object containing options to use for each call to `fetch`. If a + * particular option is not included in this object, the default value of + * that option is used. + * + * > [!NOTE] + * > If you set `fetchOptions.method` to `GET`, `HttpLink` follows [standard + * > GraphQL HTTP GET encoding](http://graphql.org/learn/serving-over-http/#get-request). + * + * See [available options](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) + */ + fetchOptions?: RequestInit; + + /** + * If `true`, unused variables from the operation will not be stripped from + * the request and will instead be sent to the GraphQL endpoint. + * + * @remarks + * Unused variables are likely to trigger server-side validation errors, + * per https://spec.graphql.org/draft/#sec-All-Variables-Used. + * `includeUnusedVariables` can be useful if your server deviates + * from the GraphQL specification by not strictly enforcing that rule. + * + * @defaultValue false + */ + includeUnusedVariables?: boolean; + /** + * A function to use when transforming a GraphQL document into a string. It + * accepts an `ASTNode` (typically a `DocumentNode`) and the original `print` + * function as arguments, and is expected to return a string. This option + * enables you to, for example, use `stripIgnoredCharacters` to remove + * whitespace from queries. + * + * By default the [GraphQL `print` function](https://graphql.org/graphql-js/language/#print) is used. + * + * @example + * + * ```ts + * import { stripIgnoredCharacters } from "graphql"; + * + * const httpLink = new HttpLink({ + * uri: "/graphql", + * print: (ast, originalPrint) => stripIgnoredCharacters(originalPrint(ast)), + * }); + * ``` + */ + print?: BaseHttpLink.Printer; + } + } + + /** + * Options provided to the `BaseHttpLink` constructor. * - * Most options can be overridden on a per-operation basis using - * operation context. + * > [!NOTE] + * > Some of these options are also available to override in [request context](https://apollographql.com/docs/react/api/link/introduction#managing-context). + * > Context options override the options passed to the constructor. Treat + * > these options as default values that are used when the request context + * > does not override the value. */ - export interface Options extends HttpLink.Options {} + interface Options extends Shared.Options { + /** + * If `true`, the link uses an HTTP `GET` request when sending query + * operations to your GraphQL endpoint. Mutation operations continue to use + * `POST` requests. If you want all operations to use `GET` requests, + * set `fetchOptions.method` instead. + * + * @defaultValue false + */ + useGETForQueries?: boolean; + } + + interface Body { + query?: string; + operationName?: string; + variables?: Record; + extensions?: Record; + } + + type Printer = (node: ASTNode, originalPrint: typeof print) => string; + type UriFunction = (operation: ApolloLink.Operation) => string; } /** diff --git a/src/link/http/HttpLink.ts b/src/link/http/HttpLink.ts index 2ac3504751f..1b9823694f2 100644 --- a/src/link/http/HttpLink.ts +++ b/src/link/http/HttpLink.ts @@ -1,8 +1,5 @@ -import type { ASTNode } from "graphql"; - import { ApolloLink } from "@apollo/client/link"; import { ClientAwarenessLink } from "@apollo/client/link/client-awareness"; -import type { print } from "@apollo/client/utilities"; import { BaseHttpLink } from "./BaseHttpLink.js"; @@ -16,168 +13,9 @@ export declare namespace HttpLink { * > Some of these values can also be provided to the `HttpLink` constructor. * > If a value is provided to both, the value in `context` takes precedence. */ - interface ContextOptions { - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#uri:member} */ - uri?: string | HttpLink.UriFunction; - - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#headers:member} */ - headers?: Record; - - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#credentials:member} */ - credentials?: RequestCredentials; - - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#fetchOptions:member} */ - fetchOptions?: RequestInit; - - /** - * An object that configures advanced `HttpLink` functionality, such as - * support for persisted queries. - */ - http?: HttpLink.HttpOptions; - } - - /** - * Options passed to `HttpLink` through the `http` property of a request - * context. - */ - export interface HttpOptions { - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#includeExtensions:member} */ - includeExtensions?: boolean; - - /** - * If `false`, the GraphQL query string is not included in the request. Set - * this option if you're sending a request that uses a [persisted query](https://www.apollographql.com/docs/react/api/link/persisted-queries/). - * - * @defaultValue true - */ - includeQuery?: boolean; - - /** {@inheritDoc @apollo/client/link/http!HttpLink.Shared.Options#preserveHeaderCase:member} */ - preserveHeaderCase?: boolean; - - /** - * A list of additional `accept` headers to include in the request, - * as defined in https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 - * - * @example - * - * ```json - * ["application/custom+json;q=1.0"] - * ``` - */ - accept?: string[]; - } - - export namespace Shared { - /** These options are shared between `HttpLink` and `BatchHttpLink` */ - export interface Options { - /** - * The URL of the GraphQL endpoint to send requests to. Can also be a - * function that accepts an `ApolloLink.Operation` object and returns the - * string URL to use for that operation. - * - * @defaultValue "/graphql" - */ - uri?: string | HttpLink.UriFunction; - - /** - * If `true`, includes the `extensions` field in operations sent to your - * GraphQL endpoint. - * - * @defaultValue true - */ - includeExtensions?: boolean; - - /** - * A function to use instead of calling the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) directly - * when sending HTTP requests to your GraphQL endpoint. The function must - * conform to the signature of `fetch`. - * - * By default, the Fetch API is used unless it isn't available in your - * runtime environment. - * - * See [Customizing `fetch`](https://apollographql.com/docs/react/api/link/introduction#customizing-fetch). - */ - fetch?: typeof fetch; - - /** - * An object representing headers to include in every HTTP request. - * - * @example - * - * ```json - * { - * "Authorization": "Bearer 1234" - * } - * ``` - */ - headers?: Record; - - /** - * If `true`, header names won't be automatically normalized to lowercase. - * This allows for non-http-spec-compliant servers that might expect - * capitalized header names. - * - * @defaultValue false - */ - preserveHeaderCase?: boolean; - - /** - * The credentials policy to use for each `fetch` call. - */ - credentials?: RequestCredentials; - - /** - * Any overrides of the fetch options argument to pass to the fetch call. - * - * An object containing options to use for each call to `fetch`. If a - * particular option is not included in this object, the default value of - * that option is used. - * - * > [!NOTE] - * > If you set `fetchOptions.method` to `GET`, `HttpLink` follows [standard - * > GraphQL HTTP GET encoding](http://graphql.org/learn/serving-over-http/#get-request). - * - * See [available options](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) - */ - fetchOptions?: RequestInit; - - /** - * If `true`, unused variables from the operation will not be stripped from - * the request and will instead be sent to the GraphQL endpoint. - * - * @remarks - * Unused variables are likely to trigger server-side validation errors, - * per https://spec.graphql.org/draft/#sec-All-Variables-Used. - * `includeUnusedVariables` can be useful if your server deviates - * from the GraphQL specification by not strictly enforcing that rule. - * - * @defaultValue false - */ - includeUnusedVariables?: boolean; - /** - * A function to use when transforming a GraphQL document into a string. It - * accepts an `ASTNode` (typically a `DocumentNode`) and the original `print` - * function as arguments, and is expected to return a string. This option - * enables you to, for example, use `stripIgnoredCharacters` to remove - * whitespace from queries. - * - * By default the [GraphQL `print` function](https://graphql.org/graphql-js/language/#print) is used. - * - * @example - * - * ```ts - * import { stripIgnoredCharacters } from "graphql"; - * - * const httpLink = new HttpLink({ - * uri: "/graphql", - * print: (ast, originalPrint) => stripIgnoredCharacters(originalPrint(ast)), - * }); - * ``` - */ - print?: HttpLink.Printer; - } - } + interface ContextOptions + extends BaseHttpLink.ContextOptions, + ClientAwarenessLink.ContextOptions {} /** * Options provided to the `HttpLink` constructor. @@ -188,27 +26,7 @@ export declare namespace HttpLink { * > these options as default values that are used when the request context * > does not override the value. */ - interface Options extends Shared.Options { - /** - * If `true`, the link uses an HTTP `GET` request when sending query - * operations to your GraphQL endpoint. Mutation operations continue to use - * `POST` requests. If you want all operations to use `GET` requests, - * set `fetchOptions.method` instead. - * - * @defaultValue false - */ - useGETForQueries?: boolean; - } - - interface Body { - query?: string; - operationName?: string; - variables?: Record; - extensions?: Record; - } - - type Printer = (node: ASTNode, originalPrint: typeof print) => string; - type UriFunction = (operation: ApolloLink.Operation) => string; + interface Options extends BaseHttpLink.Options, ClientAwarenessLink.Options {} } /** @@ -234,7 +52,7 @@ export declare namespace HttpLink { * ``` */ export class HttpLink extends ApolloLink { - constructor(options: HttpLink.Options & ClientAwarenessLink.Options = {}) { + constructor(options: HttpLink.Options = {}) { const { left, right, request } = ApolloLink.from([ new ClientAwarenessLink(options), new BaseHttpLink(options), @@ -248,6 +66,5 @@ export class HttpLink extends ApolloLink { * @deprecated * Use `HttpLink` from `@apollo/client/link/http` instead. */ -export const createHttpLink = ( - linkOptions: HttpLink.Options & ClientAwarenessLink.Options = {} -) => new HttpLink(linkOptions); +export const createHttpLink = (options: HttpLink.Options = {}) => + new HttpLink(options); diff --git a/src/link/http/rewriteURIForGET.ts b/src/link/http/rewriteURIForGET.ts index 1469abe983d..c42a7b07fe1 100644 --- a/src/link/http/rewriteURIForGET.ts +++ b/src/link/http/rewriteURIForGET.ts @@ -1,8 +1,8 @@ -import type { HttpLink } from "./HttpLink.js"; +import type { BaseHttpLink } from "./BaseHttpLink.js"; // For GET operations, returns the given URI rewritten with parameters, or a // parse error. -export function rewriteURIForGET(chosenURI: string, body: HttpLink.Body) { +export function rewriteURIForGET(chosenURI: string, body: BaseHttpLink.Body) { // Implement the standard HTTP GET serialization, plus 'extensions'. Note // the extra level of JSON serialization! const queryParams: string[] = []; diff --git a/src/link/http/selectHttpOptionsAndBody.ts b/src/link/http/selectHttpOptionsAndBody.ts index 04ea60ab13f..57223afbe53 100644 --- a/src/link/http/selectHttpOptionsAndBody.ts +++ b/src/link/http/selectHttpOptionsAndBody.ts @@ -1,16 +1,16 @@ import type { ApolloLink } from "@apollo/client/link"; import { print } from "@apollo/client/utilities"; -import type { HttpLink } from "./HttpLink.js"; +import type { BaseHttpLink } from "./BaseHttpLink.js"; interface HttpConfig { - http?: HttpLink.HttpOptions; + http?: BaseHttpLink.HttpOptions; options?: any; headers?: Record; credentials?: any; } -const defaultHttpOptions: HttpLink.HttpOptions = { +const defaultHttpOptions: BaseHttpLink.HttpOptions = { includeQuery: true, includeExtensions: true, preserveHeaderCase: false, @@ -44,7 +44,8 @@ export const fallbackHttpConfig = { options: defaultOptions, }; -export const defaultPrinter: HttpLink.Printer = (ast, printer) => printer(ast); +export const defaultPrinter: BaseHttpLink.Printer = (ast, printer) => + printer(ast); export function selectHttpOptionsAndBody( operation: ApolloLink.Operation, @@ -61,11 +62,11 @@ export function selectHttpOptionsAndBody( export function selectHttpOptionsAndBodyInternal( operation: ApolloLink.Operation, - printer: HttpLink.Printer, + printer: BaseHttpLink.Printer, ...configs: HttpConfig[] ) { let options = {} as HttpConfig & Record; - let http = {} as HttpLink.HttpOptions; + let http = {} as BaseHttpLink.HttpOptions; configs.forEach((config) => { options = { @@ -98,7 +99,7 @@ export function selectHttpOptionsAndBodyInternal( //The body depends on the http options const { operationName, extensions, variables, query } = operation; - const body: HttpLink.Body = { operationName, variables }; + const body: BaseHttpLink.Body = { operationName, variables }; if (http.includeExtensions && Object.keys(extensions || {}).length) (body as any).extensions = extensions; diff --git a/src/utilities/subscriptions/relay/index.ts b/src/utilities/subscriptions/relay/index.ts index f17533e5072..021de2e4d28 100644 --- a/src/utilities/subscriptions/relay/index.ts +++ b/src/utilities/subscriptions/relay/index.ts @@ -2,7 +2,7 @@ import type { GraphQLResponse, RequestParameters } from "relay-runtime"; import { Observable } from "relay-runtime"; import type { OperationVariables } from "@apollo/client"; -import type { HttpLink } from "@apollo/client/link/http"; +import type { BaseHttpLink } from "@apollo/client/link/http"; import { maybe } from "@apollo/client/utilities/internal/globals"; // eslint-disable-next-line local-rules/import-from-inside-other-export @@ -25,7 +25,7 @@ export function createFetchMultipartSubscription( operation: RequestParameters, variables: OperationVariables ): Observable { - const body: HttpLink.Body = { + const body: BaseHttpLink.Body = { operationName: operation.name, variables, query: operation.text || "", From ad3b31b44a8d2a6de35ab890ba0dadc187f18b96 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:28:34 -0600 Subject: [PATCH 42/53] Add changeset --- .changeset/brown-bobcats-joke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/brown-bobcats-joke.md diff --git a/.changeset/brown-bobcats-joke.md b/.changeset/brown-bobcats-joke.md new file mode 100644 index 00000000000..dc238334806 --- /dev/null +++ b/.changeset/brown-bobcats-joke.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Ensure `HttpLink.ContextOptions` and `BatchHttpLink.ContextOptions` include `ClientAwarenessLink.ContextOptions`. From f1147c38c1986de8c9eb46ba9b8a1580ed3338b5 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:48:00 -0600 Subject: [PATCH 43/53] Move BatchHttpLink.Options to BaseBatchHttpLink and add multi inheritance --- src/link/batch-http/BaseBatchHttpLink.ts | 240 +++++++++++++++++++++++ src/link/batch-http/batchHttpLink.ts | 197 +------------------ src/link/batch-http/index.ts | 3 +- 3 files changed, 249 insertions(+), 191 deletions(-) create mode 100644 src/link/batch-http/BaseBatchHttpLink.ts diff --git a/src/link/batch-http/BaseBatchHttpLink.ts b/src/link/batch-http/BaseBatchHttpLink.ts new file mode 100644 index 00000000000..edd1cd44949 --- /dev/null +++ b/src/link/batch-http/BaseBatchHttpLink.ts @@ -0,0 +1,240 @@ +import { Observable, throwError } from "rxjs"; + +import { ApolloLink } from "@apollo/client/link"; +import { BatchLink } from "@apollo/client/link/batch"; +import type { BaseHttpLink } from "@apollo/client/link/http"; +import { + checkFetcher, + defaultPrinter, + fallbackHttpConfig, + parseAndCheckHttpResponse, + selectHttpOptionsAndBodyInternal, + selectURI, +} from "@apollo/client/link/http"; +import { filterOperationVariables } from "@apollo/client/link/utils"; +import { __DEV__ } from "@apollo/client/utilities/environment"; +import { compact } from "@apollo/client/utilities/internal"; +import { maybe } from "@apollo/client/utilities/internal/globals"; + +export declare namespace BaseBatchHttpLink { + /** + * Options passed to `BaseBatchHttpLink` through [request context](https://apollographql.com/docs/react/api/link/introduction#managing-context). Previous + * non-terminating links in the link chain also can set these values to + * customize the behavior of `BatchHttpLink` for each operation. + * + * > [!NOTE] + * > Some of these values can also be provided to the `BaseBatchHttpLink` constructor. + * > If a value is provided to both, the value in `context` takes precedence. + */ + export interface ContextOptions extends BaseHttpLink.ContextOptions {} + /** + * Configuration options for creating a `BaseBatchHttpLink` instance. + * + * > [!NOTE] + * > Some of these options are also available to override in [request context](https://apollographql.com/docs/react/api/link/introduction#managing-context). + * > Context options override the options passed to the constructor. Treat + * > these options as default values that are used when the request context + * > does not override the value. + */ + interface Options + extends BatchLink.Shared.Options, + BaseHttpLink.Shared.Options { + /** {@inheritDoc @apollo/client/link/batch!BatchLink.Shared.Options#batchMax:member {"defaultValue": 10}} */ + batchMax?: number; + } +} + +const backupFetch = maybe(() => fetch); + +/** + * `BaseBatchHttpLink` is a terminating link that batches array of individual + * GraphQL operations into a single HTTP request that's sent to a single GraphQL + * endpoint. It serves as a base link to `BatchHttpLink`. + * + * @remarks + * + * > [!NOTE] + * > Prefer using `BatchHttpLink` over `BaseBatchHttpLink`. Use + * > `BaseBatchHttpLink` when you need to disable client awareness features and + * > would like to tree-shake the implementation of `ClientAwarenessLink` out + * > of your app bundle. + * + * @example + * + * ```ts + * import { BaseBatchHttpLink } from "@apollo/client/link/batch-http"; + * + * const link = new BaseBatchHttpLink({ + * uri: "http://localhost:4000/graphql", + * batchMax: 5, // No more than 5 operations per batch + * batchInterval: 20, // Wait no more than 20ms after first batched operation + * }); + * ``` + */ +export class BaseBatchHttpLink extends ApolloLink { + private batchDebounce?: boolean; + private batchInterval: number; + private batchMax: number; + private batcher: ApolloLink; + + constructor(options: BaseBatchHttpLink.Options = {}) { + super(); + + let { + uri = "/graphql", + // use default global fetch if nothing is passed in + fetch: preferredFetch, + print = defaultPrinter, + includeExtensions, + preserveHeaderCase, + batchInterval, + batchDebounce, + batchMax, + batchKey, + includeUnusedVariables = false, + ...requestOptions + } = options; + + if (__DEV__) { + // Make sure at least one of preferredFetch, window.fetch, or backupFetch + // is defined, so requests won't fail at runtime. + checkFetcher(preferredFetch || backupFetch); + } + + const linkConfig = { + http: compact({ includeExtensions, preserveHeaderCase }), + options: requestOptions.fetchOptions, + credentials: requestOptions.credentials, + headers: requestOptions.headers, + }; + + this.batchDebounce = batchDebounce; + this.batchInterval = batchInterval || 10; + this.batchMax = batchMax || 10; + + const batchHandler: BatchLink.BatchHandler = (operations) => { + const chosenURI = selectURI(operations[0], uri); + + const context = operations[0].getContext(); + + const contextConfig = { + http: context.http, + options: context.fetchOptions, + credentials: context.credentials, + headers: context.headers, + }; + + //uses fallback, link, and then context to build options + const optsAndBody = operations.map((operation) => { + const result = selectHttpOptionsAndBodyInternal( + operation, + print, + fallbackHttpConfig, + linkConfig, + contextConfig + ); + + if (result.body.variables && !includeUnusedVariables) { + result.body.variables = filterOperationVariables( + result.body.variables, + operation.query + ); + } + + return result; + }); + + const loadedBody = optsAndBody.map(({ body }) => body); + const options = optsAndBody[0].options; + + // There's no spec for using GET with batches. + if (options.method === "GET") { + return throwError( + () => + new Error("apollo-link-batch-http does not support GET requests") + ); + } + + try { + (options as any).body = JSON.stringify(loadedBody); + } catch (parseError) { + return throwError(() => parseError); + } + + let controller: AbortController | undefined; + if (!options.signal && typeof AbortController !== "undefined") { + controller = new AbortController(); + options.signal = controller.signal; + } + + return new Observable((observer) => { + // Prefer BatchHttpLink.Options.fetch (preferredFetch) if provided, and + // otherwise fall back to the *current* global window.fetch function + // (see issue #7832), or (if all else fails) the backupFetch function we + // saved when this module was first evaluated. This last option protects + // against the removal of window.fetch, which is unlikely but not + // impossible. + const currentFetch = + preferredFetch || maybe(() => fetch) || backupFetch; + + currentFetch!(chosenURI, options) + .then((response) => { + // Make the raw response available in the context. + operations.forEach((operation) => + operation.setContext({ response }) + ); + return response; + }) + .then(parseAndCheckHttpResponse(operations)) + .then((result) => { + controller = undefined; + // we have data and can send it to back up the link chain + observer.next(result); + observer.complete(); + return result; + }) + .catch((err) => { + controller = undefined; + observer.error(err); + }); + + return () => { + // XXX support canceling this request + // https://developers.google.com/web/updates/2017/09/abortable-fetch + if (controller) controller.abort(); + }; + }); + }; + + batchKey = + batchKey || + ((operation: ApolloLink.Operation) => { + const context = operation.getContext(); + + const contextConfig = { + http: context.http, + options: context.fetchOptions, + credentials: context.credentials, + headers: context.headers, + }; + + //may throw error if config not serializable + return selectURI(operation, uri) + JSON.stringify(contextConfig); + }); + + this.batcher = new BatchLink({ + batchDebounce: this.batchDebounce, + batchInterval: this.batchInterval, + batchMax: this.batchMax, + batchKey, + batchHandler, + }); + } + + public request( + operation: ApolloLink.Operation, + forward: ApolloLink.ForwardFunction + ): Observable { + return this.batcher.request(operation, forward); + } +} diff --git a/src/link/batch-http/batchHttpLink.ts b/src/link/batch-http/batchHttpLink.ts index 7a640437b54..5a88aa3161a 100644 --- a/src/link/batch-http/batchHttpLink.ts +++ b/src/link/batch-http/batchHttpLink.ts @@ -1,32 +1,16 @@ -import { Observable, throwError } from "rxjs"; - import { ApolloLink } from "@apollo/client/link"; -import { BatchLink } from "@apollo/client/link/batch"; import { ClientAwarenessLink } from "@apollo/client/link/client-awareness"; -import type { BaseHttpLink } from "@apollo/client/link/http"; -import { - checkFetcher, - defaultPrinter, - fallbackHttpConfig, - parseAndCheckHttpResponse, - selectHttpOptionsAndBodyInternal, - selectURI, -} from "@apollo/client/link/http"; -import { filterOperationVariables } from "@apollo/client/link/utils"; import { __DEV__ } from "@apollo/client/utilities/environment"; -import { compact } from "@apollo/client/utilities/internal"; -import { maybe } from "@apollo/client/utilities/internal/globals"; + +import { BaseBatchHttpLink } from "./BaseBatchHttpLink.js"; export declare namespace BatchHttpLink { /** * Options provided to the `BatchHttpLink` constructor. */ export interface Options - extends BatchLink.Shared.Options, - BaseHttpLink.Shared.Options { - /** {@inheritDoc @apollo/client/link/batch!BatchLink.Shared.Options#batchMax:member {"defaultValue": 10}} */ - batchMax?: number; - } + extends BaseBatchHttpLink.Options, + ClientAwarenessLink.Options {} /** * Options passed to `BatchHttpLink` through [request context](https://apollographql.com/docs/react/api/link/introduction#managing-context). Previous @@ -37,11 +21,11 @@ export declare namespace BatchHttpLink { * > Some of these values can also be provided to the `BatchHttpLink` constructor. * > If a value is provided to both, the value in `context` takes precedence. */ - export interface ContextOptions extends BaseHttpLink.ContextOptions {} + export interface ContextOptions + extends BaseBatchHttpLink.ContextOptions, + ClientAwarenessLink.ContextOptions {} } -const backupFetch = maybe(() => fetch); - /** * `BatchHttpLink` is a terminating link that batches array of individual * GraphQL operations into a single HTTP request that's sent to a single GraphQL @@ -78,170 +62,3 @@ export class BatchHttpLink extends ApolloLink { Object.assign(this, { left, right }); } } -export class BaseBatchHttpLink extends ApolloLink { - private batchDebounce?: boolean; - private batchInterval: number; - private batchMax: number; - private batcher: ApolloLink; - - constructor(fetchParams?: BatchHttpLink.Options) { - super(); - - let { - uri = "/graphql", - // use default global fetch if nothing is passed in - fetch: preferredFetch, - print = defaultPrinter, - includeExtensions, - preserveHeaderCase, - batchInterval, - batchDebounce, - batchMax, - batchKey, - includeUnusedVariables = false, - ...requestOptions - } = fetchParams || ({} as BatchHttpLink.Options); - - if (__DEV__) { - // Make sure at least one of preferredFetch, window.fetch, or backupFetch - // is defined, so requests won't fail at runtime. - checkFetcher(preferredFetch || backupFetch); - } - - const linkConfig = { - http: compact({ includeExtensions, preserveHeaderCase }), - options: requestOptions.fetchOptions, - credentials: requestOptions.credentials, - headers: requestOptions.headers, - }; - - this.batchDebounce = batchDebounce; - this.batchInterval = batchInterval || 10; - this.batchMax = batchMax || 10; - - const batchHandler: BatchLink.BatchHandler = (operations) => { - const chosenURI = selectURI(operations[0], uri); - - const context = operations[0].getContext(); - - const contextConfig = { - http: context.http, - options: context.fetchOptions, - credentials: context.credentials, - headers: context.headers, - }; - - //uses fallback, link, and then context to build options - const optsAndBody = operations.map((operation) => { - const result = selectHttpOptionsAndBodyInternal( - operation, - print, - fallbackHttpConfig, - linkConfig, - contextConfig - ); - - if (result.body.variables && !includeUnusedVariables) { - result.body.variables = filterOperationVariables( - result.body.variables, - operation.query - ); - } - - return result; - }); - - const loadedBody = optsAndBody.map(({ body }) => body); - const options = optsAndBody[0].options; - - // There's no spec for using GET with batches. - if (options.method === "GET") { - return throwError( - () => - new Error("apollo-link-batch-http does not support GET requests") - ); - } - - try { - (options as any).body = JSON.stringify(loadedBody); - } catch (parseError) { - return throwError(() => parseError); - } - - let controller: AbortController | undefined; - if (!options.signal && typeof AbortController !== "undefined") { - controller = new AbortController(); - options.signal = controller.signal; - } - - return new Observable((observer) => { - // Prefer BatchHttpLink.Options.fetch (preferredFetch) if provided, and - // otherwise fall back to the *current* global window.fetch function - // (see issue #7832), or (if all else fails) the backupFetch function we - // saved when this module was first evaluated. This last option protects - // against the removal of window.fetch, which is unlikely but not - // impossible. - const currentFetch = - preferredFetch || maybe(() => fetch) || backupFetch; - - currentFetch!(chosenURI, options) - .then((response) => { - // Make the raw response available in the context. - operations.forEach((operation) => - operation.setContext({ response }) - ); - return response; - }) - .then(parseAndCheckHttpResponse(operations)) - .then((result) => { - controller = undefined; - // we have data and can send it to back up the link chain - observer.next(result); - observer.complete(); - return result; - }) - .catch((err) => { - controller = undefined; - observer.error(err); - }); - - return () => { - // XXX support canceling this request - // https://developers.google.com/web/updates/2017/09/abortable-fetch - if (controller) controller.abort(); - }; - }); - }; - - batchKey = - batchKey || - ((operation: ApolloLink.Operation) => { - const context = operation.getContext(); - - const contextConfig = { - http: context.http, - options: context.fetchOptions, - credentials: context.credentials, - headers: context.headers, - }; - - //may throw error if config not serializable - return selectURI(operation, uri) + JSON.stringify(contextConfig); - }); - - this.batcher = new BatchLink({ - batchDebounce: this.batchDebounce, - batchInterval: this.batchInterval, - batchMax: this.batchMax, - batchKey, - batchHandler, - }); - } - - public request( - operation: ApolloLink.Operation, - forward: ApolloLink.ForwardFunction - ): Observable { - return this.batcher.request(operation, forward); - } -} diff --git a/src/link/batch-http/index.ts b/src/link/batch-http/index.ts index 78146899283..6f0cd8aff4f 100644 --- a/src/link/batch-http/index.ts +++ b/src/link/batch-http/index.ts @@ -1 +1,2 @@ -export { BaseBatchHttpLink, BatchHttpLink } from "./batchHttpLink.js"; +export { BaseBatchHttpLink } from "./BaseBatchHttpLink.js"; +export { BatchHttpLink } from "./batchHttpLink.js"; From 8d312f6bdb1103b8b3af73f6a43545bf4f88384a Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:48:19 -0600 Subject: [PATCH 44/53] Add docs page for base batch http link --- docs/source/_sidebar.yaml | 2 + .../api/link/apollo-link-base-batch-http.mdx | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 docs/source/api/link/apollo-link-base-batch-http.mdx diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml index d21e75abe9c..c5b49d95bcc 100644 --- a/docs/source/_sidebar.yaml +++ b/docs/source/_sidebar.yaml @@ -191,6 +191,8 @@ items: href: ./api/link/apollo-link - label: BaseHttpLink href: ./api/link/apollo-link-base-http + - label: BaseBatchHttpLink + href: ./api/link/apollo-link-base-batch-http - label: BatchHttpLink href: ./api/link/apollo-link-batch-http - label: ClientAwarenessLink diff --git a/docs/source/api/link/apollo-link-base-batch-http.mdx b/docs/source/api/link/apollo-link-base-batch-http.mdx new file mode 100644 index 00000000000..897bcc5cbbe --- /dev/null +++ b/docs/source/api/link/apollo-link-base-batch-http.mdx @@ -0,0 +1,37 @@ +--- +title: BaseBatchHttpLink +description: Batch multiple operations into a single HTTP request +--- + + + +## Constructor signature + +```ts +constructor( + options: BaseBatchHttpLink.Options = {} +): BaseBatchHttpLink +``` + +## Usage + +See the [`BatchHttpLink` documentation](./apollo-link-batch-http) for more information on +how to use `BaseBatchHttpLink`. + +## Types + + + + + From 3560310d16238b08ded961f37e6742ec8874ab4c Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 18:52:55 -0600 Subject: [PATCH 45/53] Update constructor type for BatchHttpLink --- src/link/batch-http/batchHttpLink.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/link/batch-http/batchHttpLink.ts b/src/link/batch-http/batchHttpLink.ts index 5a88aa3161a..da844c691ea 100644 --- a/src/link/batch-http/batchHttpLink.ts +++ b/src/link/batch-http/batchHttpLink.ts @@ -51,9 +51,7 @@ export declare namespace BatchHttpLink { * ``` */ export class BatchHttpLink extends ApolloLink { - constructor( - options: BatchHttpLink.Options & ClientAwarenessLink.Options = {} - ) { + constructor(options: BatchHttpLink.Options = {}) { const { left, right, request } = ApolloLink.from([ new ClientAwarenessLink(options), new BaseBatchHttpLink(options), From 6a07ebdc41af720aca2bf5e98d86e00867f6c77d Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:02:30 -0600 Subject: [PATCH 46/53] Tweak doc blocks --- src/link/batch/batchLink.ts | 73 +++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/link/batch/batchLink.ts b/src/link/batch/batchLink.ts index 56a5f7046fa..87a0bb68c72 100644 --- a/src/link/batch/batchLink.ts +++ b/src/link/batch/batchLink.ts @@ -37,14 +37,53 @@ export declare namespace BatchLink { } } + /** + * Function type for handling a batch of GraphQL operations. + * + * @remarks + * + * The batch handler is responsible for processing multiple operations together + * and returning their results. Each operation has a corresponding forward function + * that can be used to continue processing down the link chain. + * + * Results must be returned in the same order as the input operations to ensure + * proper correlation with the original requests. + * + * @param operations - Array of GraphQL operations to process + * @param forward - Array of forward functions, one per operation + * @returns Observable that emits an array of results in the same order as operations + */ export type BatchHandler = ( operations: ApolloLink.Operation[], forward: ApolloLink.ForwardFunction[] ) => Observable; + /** + * Configuration options for creating a `BatchLink` instance. + * + * @remarks + * + * `BatchLink` options control how operations are grouped into batches + * and when those batches are processed. The `batchHandler` function + * is responsible for actually processing the batched operations. + * + * Most batching behavior is configured through timing options: + * + * - `batchInterval`: How long to wait before processing a batch + * - `batchDebounce`: Whether to reset the timer on new operations + * - `batchMax`: Maximum operations per batch (0 = unlimited) + * + * Custom grouping logic can be implemented via `batchKey` function. + */ export interface Options extends Shared.Options { /** - * The handler that should execute a batch of operations. + * The handler that executes a batch of operations. + * + * @remarks + * + * This function receives an array of operations and their corresponding + * forward functions, and should return an Observable that emits the results + * for all operations in the batch. */ batchHandler?: BatchLink.BatchHandler; @@ -53,10 +92,38 @@ export declare namespace BatchLink { } } +/** + * `BatchLink` is a non-terminating link that provides the core batching + * functionality for grouping multiple GraphQL operations into batches based + * on configurable timing and key-based grouping strategies. It serves as a base + * link to `BatchHttpLink`. + * + * @remarks + * + * > [!NOTE] + * > You will not generally use `BatchLink` on your own unless you need to + * > provide batching capabilities to third-party terminating links. Prefer + * > using `BatchHttpLink` to batch GraphQL operations over HTTP. + * + * @example + * + * ```ts + * import { BatchLink } from "@apollo/client/link/batch"; + * + * const link = new BatchLink({ + * batchInterval: 20, + * batchMax: 5, + * batchHandler: (operations, forwards) => { + * // Custom logic to process batch of operations + * return handleBatch(operations, forwards); + * }, + * }); + * ``` + */ export class BatchLink extends ApolloLink { private batcher: OperationBatcher; - constructor(fetchParams?: BatchLink.Options) { + constructor(options?: BatchLink.Options) { super(); const { @@ -65,7 +132,7 @@ export class BatchLink extends ApolloLink { batchMax = 0, batchHandler = () => EMPTY, batchKey = () => "", - } = fetchParams || {}; + } = options || {}; this.batcher = new OperationBatcher({ batchDebounce, From be590b92eca842a08c1556c2adaedb01c187da17 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:02:37 -0600 Subject: [PATCH 47/53] Add batch link to docs --- docs/source/_sidebar.yaml | 2 ++ docs/source/api/link/apollo-link-batch.mdx | 26 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 docs/source/api/link/apollo-link-batch.mdx diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml index c5b49d95bcc..d9b6dd3c34d 100644 --- a/docs/source/_sidebar.yaml +++ b/docs/source/_sidebar.yaml @@ -195,6 +195,8 @@ items: href: ./api/link/apollo-link-base-batch-http - label: BatchHttpLink href: ./api/link/apollo-link-batch-http + - label: BatchLink + href: ./api/link/apollo-link-batch - label: ClientAwarenessLink href: ./api/link/apollo-link-client-awareness - label: ErrorLink diff --git a/docs/source/api/link/apollo-link-batch.mdx b/docs/source/api/link/apollo-link-batch.mdx new file mode 100644 index 00000000000..0b7e1539d98 --- /dev/null +++ b/docs/source/api/link/apollo-link-batch.mdx @@ -0,0 +1,26 @@ +--- +title: BatchLink +description: Core batching functionality for grouping multiple GraphQL operations +--- + + + +## Constructor signature + +```ts +constructor( + options?: BatchLink.Options +): BatchLink +``` + +## Types + + + From 0776261c25e9891f81bb0d478048e1fb55b519c1 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:04:42 -0600 Subject: [PATCH 48/53] Update api report --- .../api-report-link_batch-http.api.md | 20 ++- .api-reports/api-report-link_batch.api.md | 6 +- .../api-report-link_client-awareness.api.md | 5 +- .api-reports/api-report-link_context.api.md | 12 +- .api-reports/api-report-link_http.api.md | 116 ++++++++------- .../api-report-link_persisted-queries.api.md | 25 +--- .../api-report-link_remove-typename.api.md | 7 +- .api-reports/api-report-link_retry.api.md | 16 +-- .api-reports/api-report-link_schema.api.md | 9 +- .api-reports/api-report-link_ws.api.md | 10 +- .api-reports/api-report.api.md | 133 ++++++++++-------- 11 files changed, 184 insertions(+), 175 deletions(-) diff --git a/.api-reports/api-report-link_batch-http.api.md b/.api-reports/api-report-link_batch-http.api.md index e0a3636f9e4..d05550808ad 100644 --- a/.api-reports/api-report-link_batch-http.api.md +++ b/.api-reports/api-report-link_batch-http.api.md @@ -5,30 +5,38 @@ ```ts import { ApolloLink } from '@apollo/client/link'; +import type { BaseHttpLink } from '@apollo/client/link/http'; import { BatchLink } from '@apollo/client/link/batch'; import { ClientAwarenessLink } from '@apollo/client/link/client-awareness'; -import type { HttpLink } from '@apollo/client/link/http'; import { Observable } from 'rxjs'; // @public (undocumented) +export namespace BaseBatchHttpLink { + export interface ContextOptions extends BaseHttpLink.ContextOptions { + } + export interface Options extends BatchLink.Shared.Options, BaseHttpLink.Shared.Options { + batchMax?: number; + } +} + +// @public export class BaseBatchHttpLink extends ApolloLink { - constructor(fetchParams?: BatchHttpLink.Options); + constructor(options?: BaseBatchHttpLink.Options); // (undocumented) request(operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction): Observable; } // @public (undocumented) export namespace BatchHttpLink { - export interface ContextOptions extends HttpLink.ContextOptions { + export interface ContextOptions extends BaseBatchHttpLink.ContextOptions, ClientAwarenessLink.ContextOptions { } - export interface Options extends BatchLink.Shared.Options, HttpLink.Shared.Options { - batchMax?: number; + export interface Options extends BaseBatchHttpLink.Options, ClientAwarenessLink.Options { } } // @public export class BatchHttpLink extends ApolloLink { - constructor(options?: BatchHttpLink.Options & ClientAwarenessLink.Options); + constructor(options?: BatchHttpLink.Options); } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_batch.api.md b/.api-reports/api-report-link_batch.api.md index d6a791ed860..687f38fa98f 100644 --- a/.api-reports/api-report-link_batch.api.md +++ b/.api-reports/api-report-link_batch.api.md @@ -9,9 +9,7 @@ import type { Observable } from 'rxjs'; // @public (undocumented) export namespace BatchLink { - // (undocumented) export type BatchHandler = (operations: ApolloLink.Operation[], forward: ApolloLink.ForwardFunction[]) => Observable; - // (undocumented) export interface Options extends Shared.Options { batchHandler?: BatchLink.BatchHandler; batchMax?: number; @@ -27,9 +25,9 @@ export namespace BatchLink { } } -// @public (undocumented) +// @public export class BatchLink extends ApolloLink { - constructor(fetchParams?: BatchLink.Options); + constructor(options?: BatchLink.Options); // (undocumented) request(operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction): Observable; } diff --git a/.api-reports/api-report-link_client-awareness.api.md b/.api-reports/api-report-link_client-awareness.api.md index 2d88f8eb6dd..1df767023f0 100644 --- a/.api-reports/api-report-link_client-awareness.api.md +++ b/.api-reports/api-report-link_client-awareness.api.md @@ -14,6 +14,9 @@ export namespace ClientAwarenessLink { transport?: "headers" | false; version?: string; } + export interface ContextOptions { + clientAwareness?: ClientAwarenessLink.ClientAwarenessOptions; + } // (undocumented) export interface EnhancedClientAwarenessOptions { transport?: "extensions" | false; @@ -27,7 +30,7 @@ export namespace ClientAwarenessLink { // @public export class ClientAwarenessLink extends ApolloLink { - constructor(constructorOptions?: ClientAwarenessLink.Options); + constructor(options?: ClientAwarenessLink.Options); } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_context.api.md b/.api-reports/api-report-link_context.api.md index ccd565f9866..9ec268b13e1 100644 --- a/.api-reports/api-report-link_context.api.md +++ b/.api-reports/api-report-link_context.api.md @@ -11,15 +11,17 @@ export function setContext(setter: SetContextLink.LegacyContextSetter): SetConte // @public (undocumented) export namespace SetContextLink { + export type ContextSetter = (prevContext: Readonly, operation: SetContextLink.SetContextOperation) => Promise> | Partial; + // @deprecated (undocumented) + export type LegacyContextSetter = (operation: SetContextLink.SetContextOperation, prevContext: Readonly) => Promise> | Partial; // (undocumented) - export type ContextSetter = (prevContext: ApolloLink.OperationContext, operation: SetContextOperation) => Promise> | Partial; - // (undocumented) - export type LegacyContextSetter = (operation: SetContextOperation, prevContext: ApolloLink.OperationContext) => Promise> | Partial; - // (undocumented) + export namespace SetContextLinkDocumentationTypes { + export function ContextSetter(prevContext: Readonly, operation: SetContextLink.SetContextOperation): Promise> | Partial; + } export type SetContextOperation = Omit; } -// @public (undocumented) +// @public export class SetContextLink extends ApolloLink { constructor(setter: SetContextLink.ContextSetter); } diff --git a/.api-reports/api-report-link_http.api.md b/.api-reports/api-report-link_http.api.md index 92f4ea2d5ae..153f60ebe39 100644 --- a/.api-reports/api-report-link_http.api.md +++ b/.api-reports/api-report-link_http.api.md @@ -7,18 +7,67 @@ import { ApolloLink } from '@apollo/client/link'; import type { ASTNode } from 'graphql'; import { ClientAwarenessLink } from '@apollo/client/link/client-awareness'; -import type { print as print_2 } from '@apollo/client/utilities'; +import type { print as print_2 } from 'graphql'; // @public (undocumented) +export namespace BaseHttpLink { + // (undocumented) + export interface Body { + // (undocumented) + extensions?: Record; + // (undocumented) + operationName?: string; + // (undocumented) + query?: string; + // (undocumented) + variables?: Record; + } + export interface ContextOptions { + credentials?: RequestCredentials; + fetchOptions?: RequestInit; + headers?: Record; + http?: BaseHttpLink.HttpOptions; + uri?: string | BaseHttpLink.UriFunction; + } + export interface HttpOptions { + accept?: string[]; + includeExtensions?: boolean; + includeQuery?: boolean; + preserveHeaderCase?: boolean; + } + export interface Options extends Shared.Options { + useGETForQueries?: boolean; + } + // (undocumented) + export type Printer = (node: ASTNode, originalPrint: typeof print_2) => string; + // (undocumented) + export namespace Shared { + export interface Options { + credentials?: RequestCredentials; + fetch?: typeof fetch; + fetchOptions?: RequestInit; + headers?: Record; + includeExtensions?: boolean; + includeUnusedVariables?: boolean; + preserveHeaderCase?: boolean; + print?: BaseHttpLink.Printer; + uri?: string | BaseHttpLink.UriFunction; + } + } + // (undocumented) + export type UriFunction = (operation: ApolloLink.Operation) => string; +} + +// @public export class BaseHttpLink extends ApolloLink { - constructor(linkOptions?: HttpLink.Options); + constructor(options?: BaseHttpLink.Options); } // @public (undocumented) export const checkFetcher: (fetcher: typeof fetch | undefined) => void; // @public @deprecated (undocumented) -export const createHttpLink: (linkOptions?: HttpLink.Options & ClientAwarenessLink.Options) => HttpLink; +export const createHttpLink: (options?: HttpLink.Options) => HttpLink; // @public @deprecated (undocumented) export const createSignalIfSupported: () => { @@ -30,11 +79,11 @@ export const createSignalIfSupported: () => { }; // @public (undocumented) -export const defaultPrinter: HttpLink.Printer; +export const defaultPrinter: BaseHttpLink.Printer; // @public (undocumented) export const fallbackHttpConfig: { - http: HttpLink.HttpOptions; + http: BaseHttpLink.HttpOptions; headers: { accept: string; "content-type": string; @@ -51,70 +100,29 @@ interface HttpConfig { // (undocumented) headers?: Record; // (undocumented) - http?: HttpLink.HttpOptions; + http?: BaseHttpLink.HttpOptions; // (undocumented) options?: any; } // @public (undocumented) export namespace HttpLink { - // (undocumented) - export interface Body { - // (undocumented) - extensions?: Record; - // (undocumented) - operationName?: string; - // (undocumented) - query?: string; - // (undocumented) - variables?: Record; + export interface ContextOptions extends BaseHttpLink.ContextOptions, ClientAwarenessLink.ContextOptions { } - export interface ContextOptions { - credentials?: RequestCredentials; - fetchOptions?: RequestInit; - headers?: Record; - http?: HttpLink.HttpOptions; - uri?: string | HttpLink.UriFunction; + export interface Options extends BaseHttpLink.Options, ClientAwarenessLink.Options { } - export interface HttpOptions { - accept?: string[]; - includeExtensions?: boolean; - includeQuery?: boolean; - preserveHeaderCase?: boolean; - } - export interface Options extends Shared.Options { - useGETForQueries?: boolean; - } - // (undocumented) - export type Printer = (node: ASTNode, originalPrint: typeof print_2) => string; - // (undocumented) - export namespace Shared { - export interface Options { - credentials?: RequestCredentials; - fetch?: typeof fetch; - fetchOptions?: RequestInit; - headers?: Record; - includeExtensions?: boolean; - includeUnusedVariables?: boolean; - preserveHeaderCase?: boolean; - print?: HttpLink.Printer; - uri?: string | HttpLink.UriFunction; - } - } - // (undocumented) - export type UriFunction = (operation: ApolloLink.Operation) => string; } // @public export class HttpLink extends ApolloLink { - constructor(options?: HttpLink.Options & ClientAwarenessLink.Options); + constructor(options?: HttpLink.Options); } // @public (undocumented) export function parseAndCheckHttpResponse(operations: ApolloLink.Operation | ApolloLink.Operation[]): (response: Response) => Promise; // @public (undocumented) -export function rewriteURIForGET(chosenURI: string, body: HttpLink.Body): { +export function rewriteURIForGET(chosenURI: string, body: BaseHttpLink.Body): { parseError: unknown; newURI?: undefined; } | { @@ -127,13 +135,13 @@ export function rewriteURIForGET(chosenURI: string, body: HttpLink.Body): { // @public (undocumented) export function selectHttpOptionsAndBody(operation: ApolloLink.Operation, fallbackConfig: HttpConfig, ...configs: Array): { options: HttpConfig & Record; - body: HttpLink.Body; + body: BaseHttpLink.Body; }; // @public (undocumented) -export function selectHttpOptionsAndBodyInternal(operation: ApolloLink.Operation, printer: HttpLink.Printer, ...configs: HttpConfig[]): { +export function selectHttpOptionsAndBodyInternal(operation: ApolloLink.Operation, printer: BaseHttpLink.Printer, ...configs: HttpConfig[]): { options: HttpConfig & Record; - body: HttpLink.Body; + body: BaseHttpLink.Body; }; // @public (undocumented) diff --git a/.api-reports/api-report-link_persisted-queries.api.md b/.api-reports/api-report-link_persisted-queries.api.md index 5f45237ce8c..341a25a9bfe 100644 --- a/.api-reports/api-report-link_persisted-queries.api.md +++ b/.api-reports/api-report-link_persisted-queries.api.md @@ -16,60 +16,45 @@ export const createPersistedQueryLink: (options: PersistedQueryLink.Options) => export namespace PersistedQueryLink { // (undocumented) export namespace Base { - // (undocumented) export interface Options { - // (undocumented) disable?: (options: PersistedQueryLink.DisableFunctionOptions) => boolean; - // (undocumented) retry?: (options: PersistedQueryLink.RetryFunctionOptions) => boolean; - // (undocumented) useGETForHashedQueries?: boolean; } } - // (undocumented) export interface DisableFunctionOptions extends PersistedQueryLink.RetryFunctionOptions { } - // (undocumented) export interface ErrorMeta { - // (undocumented) persistedQueryNotFound: boolean; - // (undocumented) persistedQueryNotSupported: boolean; } - // (undocumented) export type GenerateHashFunction = (document: DocumentNode) => string | PromiseLike; - // (undocumented) export interface GenerateHashOptions extends Base.Options { - // (undocumented) generateHash: PersistedQueryLink.GenerateHashFunction; // (undocumented) sha256?: never; } - // (undocumented) export type Options = PersistedQueryLink.SHA256Options | PersistedQueryLink.GenerateHashOptions; // (undocumented) + export namespace PersistedQueryLinkDocumentationTypes { + export function GenerateHashFunction(document: DocumentNode): string | PromiseLike; + export function SHA256Function(queryString: string): string | PromiseLike; + } export interface RetryFunctionOptions { - // (undocumented) error: ErrorLike; - // (undocumented) meta: PersistedQueryLink.ErrorMeta; - // (undocumented) operation: ApolloLink.Operation; - // (undocumented) result?: FormattedExecutionResult; } - // (undocumented) export type SHA256Function = (queryString: string) => string | PromiseLike; - // (undocumented) export interface SHA256Options extends Base.Options { // (undocumented) generateHash?: never; - // (undocumented) sha256: PersistedQueryLink.SHA256Function; } } -// @public (undocumented) +// @public export class PersistedQueryLink extends ApolloLink { constructor(options: PersistedQueryLink.Options); // (undocumented) diff --git a/.api-reports/api-report-link_remove-typename.api.md b/.api-reports/api-report-link_remove-typename.api.md index fc36249eba3..10a9a29634b 100644 --- a/.api-reports/api-report-link_remove-typename.api.md +++ b/.api-reports/api-report-link_remove-typename.api.md @@ -6,7 +6,7 @@ import { ApolloLink } from '@apollo/client/link'; -// @public (undocumented) +// @public export const KEEP = "__KEEP"; // @public @deprecated (undocumented) @@ -14,19 +14,16 @@ export function removeTypenameFromVariables(options?: RemoveTypenameFromVariable // @public (undocumented) export namespace RemoveTypenameFromVariablesLink { - // (undocumented) export interface KeepTypenameConfig { // (undocumented) [key: string]: typeof KEEP | RemoveTypenameFromVariablesLink.KeepTypenameConfig; } - // (undocumented) export interface Options { - // (undocumented) except?: RemoveTypenameFromVariablesLink.KeepTypenameConfig; } } -// @public (undocumented) +// @public export class RemoveTypenameFromVariablesLink extends ApolloLink { constructor(options?: RemoveTypenameFromVariablesLink.Options); } diff --git a/.api-reports/api-report-link_retry.api.md b/.api-reports/api-report-link_retry.api.md index f41f5a1cce2..4262eefa0c0 100644 --- a/.api-reports/api-report-link_retry.api.md +++ b/.api-reports/api-report-link_retry.api.md @@ -10,29 +10,29 @@ import { Observable } from 'rxjs'; // @public (undocumented) export namespace RetryLink { - // (undocumented) - export type AttemptsFunction = (count: number, operation: ApolloLink.Operation, error: ErrorLike) => boolean | Promise; - // (undocumented) + export type AttemptsFunction = (attempt: number, operation: ApolloLink.Operation, error: ErrorLike) => boolean | Promise; export interface AttemptsOptions { max?: number; retryIf?: (error: ErrorLike, operation: ApolloLink.Operation) => boolean | Promise; } - // (undocumented) - export type DelayFunction = (count: number, operation: ApolloLink.Operation, error: ErrorLike) => number; - // (undocumented) + export type DelayFunction = (attempt: number, operation: ApolloLink.Operation, error: ErrorLike) => number; export interface DelayOptions { initial?: number; jitter?: boolean; max?: number; } - // (undocumented) export interface Options { attempts?: RetryLink.AttemptsOptions | RetryLink.AttemptsFunction; delay?: RetryLink.DelayOptions | RetryLink.DelayFunction; } + // (undocumented) + export namespace RetryLinkDocumentationTypes { + export function AttemptsFunction(attempt: number, operation: ApolloLink.Operation, error: ErrorLike): boolean | Promise; + export function DelayFunction(attempt: number, operation: ApolloLink.Operation, error: ErrorLike): number; + } } -// @public (undocumented) +// @public export class RetryLink extends ApolloLink { constructor(options?: RetryLink.Options); // (undocumented) diff --git a/.api-reports/api-report-link_schema.api.md b/.api-reports/api-report-link_schema.api.md index 9beafc4c4ac..dd12280f67f 100644 --- a/.api-reports/api-report-link_schema.api.md +++ b/.api-reports/api-report-link_schema.api.md @@ -10,20 +10,21 @@ import { Observable } from 'rxjs'; // @public (undocumented) export namespace SchemaLink { - // (undocumented) export interface Options { context?: SchemaLink.ResolverContext | SchemaLink.ResolverContextFunction; rootValue?: any; schema: GraphQLSchema; validate?: boolean; } - // (undocumented) export type ResolverContext = Record; + export type ResolverContextFunction = (operation: ApolloLink.Operation) => SchemaLink.ResolverContext | PromiseLike; // (undocumented) - export type ResolverContextFunction = (operation: ApolloLink.Operation) => ResolverContext | PromiseLike; + export namespace SchemaLinkDocumentationTypes { + export function ResolverContextFunction(operation: ApolloLink.Operation): SchemaLink.ResolverContext | PromiseLike; + } } -// @public (undocumented) +// @public export class SchemaLink extends ApolloLink { constructor(options: SchemaLink.Options); // (undocumented) diff --git a/.api-reports/api-report-link_ws.api.md b/.api-reports/api-report-link_ws.api.md index c698cc33897..89b7036af6f 100644 --- a/.api-reports/api-report-link_ws.api.md +++ b/.api-reports/api-report-link_ws.api.md @@ -11,26 +11,20 @@ import { SubscriptionClient } from 'subscriptions-transport-ws'; // @public (undocumented) export namespace WebSocketLink { - export interface WebSocketParams { + export interface Configuration { options?: ClientOptions; uri: string; webSocketImpl?: any; } } -// @public @deprecated (undocumented) +// @public @deprecated export class WebSocketLink extends ApolloLink { constructor(paramsOrClient: WebSocketLink.Configuration | SubscriptionClient); // (undocumented) request(operation: ApolloLink.Operation): Observable; } -export interface WebSocketParams { - options?: ClientOptions; - uri: string; - webSocketImpl?: any; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/.api-reports/api-report.api.md b/.api-reports/api-report.api.md index 35a378f35c3..6e6af958af2 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -21,6 +21,7 @@ import { Observable } from 'rxjs'; import type { ObservableNotification } from 'rxjs'; import type { Observer } from 'rxjs'; import { OperationTypeNode } from 'graphql'; +import type { print as print_2 } from 'graphql'; import { resetCaches } from 'graphql-tag'; import type { SelectionSetNode } from 'graphql'; import type { Subscribable } from 'rxjs'; @@ -485,6 +486,61 @@ type AsStoreObject; + // (undocumented) + operationName?: string; + // (undocumented) + query?: string; + // (undocumented) + variables?: Record; + } + interface ContextOptions { + credentials?: RequestCredentials; + fetchOptions?: RequestInit; + headers?: Record; + http?: BaseHttpLink.HttpOptions; + uri?: string | BaseHttpLink.UriFunction; + } + interface HttpOptions { + accept?: string[]; + includeExtensions?: boolean; + includeQuery?: boolean; + preserveHeaderCase?: boolean; + } + // Warning: (ae-forgotten-export) The symbol "BaseHttpLink" needs to be exported by the entry point index.d.ts + interface Options extends Shared.Options { + useGETForQueries?: boolean; + } + // (undocumented) + type Printer = (node: ASTNode, originalPrint: typeof print_2) => string; + // (undocumented) + namespace Shared { + interface Options { + credentials?: RequestCredentials; + fetch?: typeof fetch; + fetchOptions?: RequestInit; + headers?: Record; + includeExtensions?: boolean; + includeUnusedVariables?: boolean; + preserveHeaderCase?: boolean; + print?: BaseHttpLink.Printer; + uri?: string | BaseHttpLink.UriFunction; + } + } + // (undocumented) + type UriFunction = (operation: ApolloLink.Operation) => string; +} + +// @public +class BaseHttpLink extends ApolloLink { + constructor(options?: BaseHttpLink.Options); +} + // @public (undocumented) type BroadcastOptions = Pick, "optimistic" | "onWatchUpdated">; @@ -661,6 +717,9 @@ namespace ClientAwarenessLink { transport?: "headers" | false; version?: string; } + interface ContextOptions { + clientAwareness?: ClientAwarenessLink.ClientAwarenessOptions; + } // (undocumented) interface EnhancedClientAwarenessOptions { transport?: "extensions" | false; @@ -674,7 +733,7 @@ namespace ClientAwarenessLink { // @public class ClientAwarenessLink extends ApolloLink { - constructor(constructorOptions?: ClientAwarenessLink.Options); + constructor(options?: ClientAwarenessLink.Options); } // Warning: (ae-forgotten-export) The symbol "Prettify" needs to be exported by the entry point index.d.ts @@ -765,7 +824,7 @@ export const concat: typeof ApolloLink.concat; type ContainsFragmentsRefs = true extends (IsAny) ? false : TData extends object ? Exact extends Seen ? false : " $fragmentRefs" extends keyof RemoveIndexSignature ? true : ContainsFragmentsRefs> : false; // @public @deprecated (undocumented) -export const createHttpLink: (linkOptions?: HttpLink.Options & ClientAwarenessLink.Options) => HttpLink; +export const createHttpLink: (options?: HttpLink.Options) => HttpLink; // @public @deprecated (undocumented) export const createSignalIfSupported: () => { @@ -858,8 +917,10 @@ type DefaultImplementation = GraphQLCodegenDataMasking.Implementation; // @public @deprecated (undocumented) export type DefaultOptions = ApolloClient.DefaultOptions; +// Warning: (ae-forgotten-export) The symbol "BaseHttpLink" needs to be exported by the entry point index.d.ts +// // @public (undocumented) -export const defaultPrinter: HttpLink.Printer; +export const defaultPrinter: BaseHttpLink.Printer; // @public (undocumented) interface DeleteModifier { @@ -1032,7 +1093,7 @@ type ExtractByMatchingTypeNames; // (undocumented) - http?: HttpLink.HttpOptions; + http?: BaseHttpLink.HttpOptions; // (undocumented) options?: any; } // @public (undocumented) export namespace HttpLink { - // (undocumented) - export interface Body { - // (undocumented) - extensions?: Record; - // (undocumented) - operationName?: string; - // (undocumented) - query?: string; - // (undocumented) - variables?: Record; - } - export interface ContextOptions { - credentials?: RequestCredentials; - fetchOptions?: RequestInit; - headers?: Record; - http?: HttpLink.HttpOptions; - uri?: string | HttpLink.UriFunction; + export interface ContextOptions extends BaseHttpLink.ContextOptions, ClientAwarenessLink.ContextOptions { } - export interface HttpOptions { - accept?: string[]; - includeExtensions?: boolean; - includeQuery?: boolean; - preserveHeaderCase?: boolean; + export interface Options extends BaseHttpLink.Options, ClientAwarenessLink.Options { } - export interface Options extends Shared.Options { - useGETForQueries?: boolean; - } - // Warning: (ae-forgotten-export) The symbol "print_2" needs to be exported by the entry point index.d.ts - // - // (undocumented) - export type Printer = (node: ASTNode, originalPrint: typeof print_2) => string; - // (undocumented) - export namespace Shared { - export interface Options { - credentials?: RequestCredentials; - fetch?: typeof fetch; - fetchOptions?: RequestInit; - headers?: Record; - includeExtensions?: boolean; - includeUnusedVariables?: boolean; - preserveHeaderCase?: boolean; - print?: HttpLink.Printer; - uri?: string | HttpLink.UriFunction; - } - } - // (undocumented) - export type UriFunction = (operation: ApolloLink.Operation) => string; } // @public export class HttpLink extends ApolloLink { - constructor(options?: HttpLink.Options & ClientAwarenessLink.Options); + constructor(options?: HttpLink.Options); } // @public (undocumented) @@ -2233,11 +2251,6 @@ type Prettify = { // @internal @deprecated (undocumented) type Primitive = null | undefined | string | number | boolean | symbol | bigint; -// @public -const print_2: ((ast: ASTNode) => string) & { - reset(): void; -}; - // @public (undocumented) class QueryManager { // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts @@ -2487,7 +2500,7 @@ export type RequestHandler = ApolloLink.RequestHandler; export { resetCaches } // @public (undocumented) -export function rewriteURIForGET(chosenURI: string, body: HttpLink.Body): { +export function rewriteURIForGET(chosenURI: string, body: BaseHttpLink.Body): { parseError: unknown; newURI?: undefined; } | { @@ -2524,13 +2537,13 @@ type SafeReadonly = T extends object ? Readonly : T; // @public (undocumented) export function selectHttpOptionsAndBody(operation: ApolloLink.Operation, fallbackConfig: HttpConfig, ...configs: Array): { options: HttpConfig & Record; - body: HttpLink.Body; + body: BaseHttpLink.Body; }; // @public (undocumented) -export function selectHttpOptionsAndBodyInternal(operation: ApolloLink.Operation, printer: HttpLink.Printer, ...configs: HttpConfig[]): { +export function selectHttpOptionsAndBodyInternal(operation: ApolloLink.Operation, printer: BaseHttpLink.Printer, ...configs: HttpConfig[]): { options: HttpConfig & Record; - body: HttpLink.Body; + body: BaseHttpLink.Body; }; // @public (undocumented) From 6006c0cef25aad973238a7524f8b7bac80915f8d Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:06:06 -0600 Subject: [PATCH 49/53] Update size limits --- .size-limits.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 86a6a779747..f04cd95e727 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,6 +1,6 @@ { - "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43798, - "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38629, - "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33543, - "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27562 + "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43904, + "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38553, + "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33555, + "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27554 } From 8e4ea3354ad7335f14f6ef6d3922e7b90ab3f242 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:06:41 -0600 Subject: [PATCH 50/53] Formatting --- docs/source/api/link/apollo-link-base-batch-http.mdx | 1 - docs/source/api/link/apollo-link-base-http.mdx | 1 - docs/source/api/link/apollo-link-batch.mdx | 1 - 3 files changed, 3 deletions(-) diff --git a/docs/source/api/link/apollo-link-base-batch-http.mdx b/docs/source/api/link/apollo-link-base-batch-http.mdx index 897bcc5cbbe..053e7583f41 100644 --- a/docs/source/api/link/apollo-link-base-batch-http.mdx +++ b/docs/source/api/link/apollo-link-base-batch-http.mdx @@ -34,4 +34,3 @@ how to use `BaseBatchHttpLink`. headingLevel={3} displayName="BaseBatchHttpLink.Options" /> - diff --git a/docs/source/api/link/apollo-link-base-http.mdx b/docs/source/api/link/apollo-link-base-http.mdx index 119ff3b79b3..b3d0e7b80bb 100644 --- a/docs/source/api/link/apollo-link-base-http.mdx +++ b/docs/source/api/link/apollo-link-base-http.mdx @@ -40,4 +40,3 @@ how to use `BaseHttpLink`. headingLevel={3} displayName="BaseHttpLink.Options" /> - diff --git a/docs/source/api/link/apollo-link-batch.mdx b/docs/source/api/link/apollo-link-batch.mdx index 0b7e1539d98..fb8d145c07a 100644 --- a/docs/source/api/link/apollo-link-batch.mdx +++ b/docs/source/api/link/apollo-link-batch.mdx @@ -23,4 +23,3 @@ constructor( headingLevel={3} displayName="BatchLink.Options" /> - From 4d00299ac6fa428e607d7714c39ea42c40024b58 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 5 Aug 2025 19:10:51 -0600 Subject: [PATCH 51/53] Remove HttpOptions from basic networking page --- docs/source/networking/basic-http-networking.mdx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/source/networking/basic-http-networking.mdx b/docs/source/networking/basic-http-networking.mdx index 0e6963df7d7..12d7ca7cfc3 100644 --- a/docs/source/networking/basic-http-networking.mdx +++ b/docs/source/networking/basic-http-networking.mdx @@ -75,12 +75,6 @@ const client = new ApolloClient({ customPropertyOrder={["uri", "credentials", "headers", "http"]} /> - - Date: Wed, 6 Aug 2025 08:58:38 -0600 Subject: [PATCH 52/53] Fix reference to old class Co-authored-by: Lenz Weber-Tronic --- docs/source/api/link/apollo-link-context.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-context.mdx b/docs/source/api/link/apollo-link-context.mdx index d3087cbc51f..ff470a103b7 100644 --- a/docs/source/api/link/apollo-link-context.mdx +++ b/docs/source/api/link/apollo-link-context.mdx @@ -61,7 +61,7 @@ Take for example a user auth token being found, cached, then removed on a 401 re ```js import { ServerError } from "@apollo/client"; import { SetContextLink } from "@apollo/client/link/context"; -import { onError } from "@apollo/client/link/error"; +import { ErrorLink } from "@apollo/client/link/error"; // cached storage for the user token let token; From d6a21a9c06e16d0692408a338c2416e43aaac1e6 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 6 Aug 2025 08:59:57 -0600 Subject: [PATCH 53/53] Add key to example --- src/link/context/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 5aa59fde678..86bd402e62b 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -88,6 +88,7 @@ export function setContext(setter: SetContextLink.LegacyContextSetter) { * * const link = new SetContextLink((prevContext, operation) => { * return { + * credentials: "include", * // ... * }; * });