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) 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`. 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 } diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml index 2957511b0f5..d9b6dd3c34d 100644 --- a/docs/source/_sidebar.yaml +++ b/docs/source/_sidebar.yaml @@ -189,8 +189,14 @@ items: href: ./api/link/introduction - label: ApolloLink 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: 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-base-batch-http.mdx b/docs/source/api/link/apollo-link-base-batch-http.mdx new file mode 100644 index 00000000000..053e7583f41 --- /dev/null +++ b/docs/source/api/link/apollo-link-base-batch-http.mdx @@ -0,0 +1,36 @@ +--- +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 + + + + 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..b3d0e7b80bb --- /dev/null +++ b/docs/source/api/link/apollo-link-base-http.mdx @@ -0,0 +1,42 @@ +--- +title: BaseHttpLink +description: Get GraphQL results over a network using HTTP fetch +--- + + + +## 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/docs/source/api/link/apollo-link-batch.mdx b/docs/source/api/link/apollo-link-batch.mdx new file mode 100644 index 00000000000..fb8d145c07a --- /dev/null +++ b/docs/source/api/link/apollo-link-batch.mdx @@ -0,0 +1,25 @@ +--- +title: BatchLink +description: Core batching functionality for grouping multiple GraphQL operations +--- + + + +## Constructor signature + +```ts +constructor( + options?: BatchLink.Options +): BatchLink +``` + +## Types + + diff --git a/docs/source/api/link/apollo-link-client-awareness.mdx b/docs/source/api/link/apollo-link-client-awareness.mdx index 102354d09ff..7794964620f 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. @@ -88,6 +96,12 @@ function MyComponent() { ## Types + + -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 { SetContextLink } from "@apollo/client/link/context"; import { ErrorLink } 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/docs/source/api/link/apollo-link-http.mdx b/docs/source/api/link/apollo-link-http.mdx index 4b82b662c11..1a2c30e0c58 100644 --- a/docs/source/api/link/apollo-link-http.mdx +++ b/docs/source/api/link/apollo-link-http.mdx @@ -14,7 +14,7 @@ description: Get GraphQL results over a network using HTTP fetch. ```ts constructor( - options: HttpLink.Options & ClientAwarenessLink.Options = {} + options: HttpLink.Options = {} ): HttpLink ``` @@ -39,15 +39,3 @@ Provide a custom [`fetch` option](#options-fetch) to the `HttpLink` constructor headingLevel={3} displayName="HttpLink.ContextOptions" /> - - - - diff --git a/docs/source/api/link/apollo-link-remove-typename.mdx b/docs/source/api/link/apollo-link-remove-typename.mdx index ba228196a4c..eae7f9eacee 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,18 @@ const removeTypenameLink = removeTypenameFromVariables({ }); ``` -## Options - - - - - - - - - - - - - - + - -
Name /
Type
Description
- -###### `except` - -`KeepTypenameConfig` - - +## Types -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. +### `RemoveTypenameFromVariablesLink.KeepTypenameConfig` -
+ diff --git a/docs/source/api/link/apollo-link-retry.mdx b/docs/source/api/link/apollo-link-retry.mdx index 8b4ef10f08e..c7492ec8bc8 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 @@ -64,19 +26,53 @@ 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. -```js +```ts 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(); }, }); ``` + +## Types + + + + + + + + + + diff --git a/docs/source/api/link/apollo-link-schema.mdx b/docs/source/api/link/apollo-link-schema.mdx index 6024f6d9b6e..f9c6de34e7e 100644 --- a/docs/source/api/link/apollo-link-schema.mdx +++ b/docs/source/api/link/apollo-link-schema.mdx @@ -1,19 +1,22 @@ --- -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. +## Constructor signature -> 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` +```ts +constructor( + options: SchemaLink.Options +): SchemaLink +``` -## Usage +## Usage examples ### Server Side Rendering @@ -66,13 +69,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/docs/source/api/link/apollo-link-ws.mdx b/docs/source/api/link/apollo-link-ws.mdx index c175b8d06cf..94986beaa07 100644 --- a/docs/source/api/link/apollo-link-ws.mdx +++ b/docs/source/api/link/apollo-link-ws.mdx @@ -1,97 +1,41 @@ --- -title: WebSocket Link +title: WebSocketLink 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). -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. +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). -`WebSocketLink` requires the [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library. Install it in your project like so: + -```shell -npm install subscriptions-transport-ws -``` - -## Constructor + -```js -import { WebSocketLink } from "@apollo/client/link/ws"; -import { SubscriptionClient } from "subscriptions-transport-ws"; +## Constructor signature -const link = new WebSocketLink( - new SubscriptionClient("ws://localhost:4000/graphql", { - reconnect: true, - }) -); +```ts +constructor( + paramsOrClient: WebSocketLink.Configuration | SubscriptionClient +): WebSocketLink ``` -### 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`). - -
+## Installation -###### `options` +`WebSocketLink` requires the [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library. Install it in your project: -`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). - -
+```shell +npm install subscriptions-transport-ws +``` -## Usage +## Types -See [Subscriptions](../../data/subscriptions/). + diff --git a/docs/source/api/link/persisted-queries.mdx b/docs/source/api/link/persisted-queries.mdx index 47a869c80a0..c2707e887d4 100644 --- a/docs/source/api/link/persisted-queries.mdx +++ b/docs/source/api/link/persisted-queries.mdx @@ -1,8 +1,21 @@ --- -title: Persisted Queries Link +title: PersistedQueryLink 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. @@ -41,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: @@ -127,9 +148,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"), }) @@ -167,11 +188,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), @@ -180,26 +201,16 @@ 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. - `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 @@ -289,4 +300,43 @@ _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 + + + + + + + + + + + + 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"]} /> - - [!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 7b532803621..da844c691ea 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 { HttpLink } 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, - HttpLink.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 HttpLink.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 @@ -67,9 +51,7 @@ const backupFetch = maybe(() => fetch); * ``` */ 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), @@ -78,170 +60,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"; 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, diff --git a/src/link/client-awareness/ClientAwarenessLink.ts b/src/link/client-awareness/ClientAwarenessLink.ts index e5239cc7874..7661f7a237c 100644 --- a/src/link/client-awareness/ClientAwarenessLink.ts +++ b/src/link/client-awareness/ClientAwarenessLink.ts @@ -2,6 +2,21 @@ import { ApolloLink } from "@apollo/client/link"; import { compact } from "@apollo/client/utilities/internal"; export declare namespace ClientAwarenessLink { + /** + * Options passed to `ClientAwarenessLink` 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 `ClientAwarenessLink` for each operation. + * + * > [!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. @@ -94,7 +109,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 +123,7 @@ export class ClientAwarenessLink extends ApolloLink { } = compact( {}, clientOptions.clientAwareness, - constructorOptions.clientAwareness, + options.clientAwareness, context.clientAwareness ); @@ -131,7 +146,7 @@ export class ClientAwarenessLink extends ApolloLink { const { transport = "extensions" } = compact( {}, clientOptions.enhancedClientAwareness, - constructorOptions.enhancedClientAwareness + options.enhancedClientAwareness ); if (transport === "extensions") { operation.extensions = compact( diff --git a/src/link/context/index.ts b/src/link/context/index.ts index 69cdaa85bc2..86bd402e62b 100644 --- a/src/link/context/index.ts +++ b/src/link/context/index.ts @@ -3,20 +3,55 @@ 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: ApolloLink.OperationContext, - operation: SetContextOperation + prevContext: Readonly, + operation: SetContextLink.SetContextOperation ) => | Promise> | Partial; + /** + * @deprecated + * Use `ContextSetter` instead. This type is used by the deprecated + * `setContext` function. + */ export type LegacyContextSetter = ( - operation: SetContextOperation, - prevContext: ApolloLink.OperationContext + operation: SetContextLink.SetContextOperation, + prevContext: Readonly ) => | 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" @@ -40,6 +75,25 @@ 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 { + * credentials: "include", + * // ... + * }; + * }); + * ``` + */ export class SetContextLink extends ApolloLink { constructor(setter: SetContextLink.ContextSetter) { super((operation, forward) => { diff --git a/src/link/http/BaseHttpLink.ts b/src/link/http/BaseHttpLink.ts index 94ca367dc1b..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, @@ -26,8 +26,241 @@ import { selectURI } from "./selectURI.js"; const backupFetch = maybe(() => fetch); +export declare namespace BaseHttpLink { + /** + * 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. + * + * > [!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. + * + * > [!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 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; +} + +/** + * `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`. 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 + * + * ```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(linkOptions: HttpLink.Options = {}) { + constructor(options: BaseHttpLink.Options = {}) { let { uri = "/graphql", // use default global fetch if nothing passed in @@ -38,7 +271,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 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/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index f2cb411078d..334796ba26d 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -31,48 +31,191 @@ 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). + * + * > [!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` + */ 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 {} } @@ -124,6 +267,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: diff --git a/src/link/remove-typename/removeTypenameFromVariables.ts b/src/link/remove-typename/removeTypenameFromVariables.ts index 5204b238055..a59724794b8 100644 --- a/src/link/remove-typename/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/removeTypenameFromVariables.ts @@ -11,16 +11,110 @@ 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. + * + * 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`. + * + * @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 +129,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) => { diff --git a/src/link/retry/retryLink.ts b/src/link/retry/retryLink.ts index 7537720b8e2..33da92e5df5 100644 --- a/src/link/retry/retryLink.ts +++ b/src/link/retry/retryLink.ts @@ -14,21 +14,55 @@ 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; + /** + * 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. + * Note that if jittering is enabled, this is the average delay. * * @defaultValue `300` */ @@ -45,27 +79,34 @@ 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` */ 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; + /** + * 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. * * 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` */ @@ -85,6 +126,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. @@ -98,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; @@ -181,6 +222,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; 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"]; diff --git a/src/link/ws/index.ts b/src/link/ws/index.ts index 8455fd22f49..025c3704bb7 100644 --- a/src/link/ws/index.ts +++ b/src/link/ws/index.ts @@ -8,30 +8,79 @@ 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; } } -// 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 + * 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 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 || "",