diff --git a/.api-reports/api-report-cache.api.md b/.api-reports/api-report-cache.api.md index 30ec666b306..3434c4bbb06 100644 --- a/.api-reports/api-report-cache.api.md +++ b/.api-reports/api-report-cache.api.md @@ -976,8 +976,6 @@ export type TypePolicy = { // @public export interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index de8b71f9153..6e411da8cfb 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -9,12 +9,12 @@ import { disableExperimentalFragmentVariables } from 'graphql-tag'; import { disableFragmentWarnings } from 'graphql-tag'; import type { DocumentNode } from 'graphql'; import { enableExperimentalFragmentVariables } from 'graphql-tag'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import { gql } from 'graphql-tag'; -import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import type { InlineFragmentNode } from 'graphql'; import { InvariantError } from 'ts-invariant'; import { Observable } from 'zen-observable-ts'; @@ -94,7 +94,7 @@ export class ApolloClient implements DataProxy { __actionHookForDevTools(cb: () => any): void; constructor(options: ApolloClientOptions); // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; addResolvers(resolvers: Resolvers | Resolvers[]): void; // (undocumented) cache: ApolloCache; @@ -103,6 +103,10 @@ export class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; get documentTransform(): DocumentTransform; @@ -144,12 +148,14 @@ export class ApolloClient implements DataProxy { export interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -172,14 +178,16 @@ export interface ApolloClientOptions { export class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -202,7 +210,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -256,7 +264,7 @@ export interface ApolloQueryResult { // (undocumented) data: T; error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // (undocumented) @@ -589,6 +597,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) export type DiffQueryAgainstStoreOptions = ReadQueryOptions & { returnPartialData?: boolean; @@ -760,7 +774,7 @@ export interface ExecutionPatchInitialResult, TExten // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -977,9 +991,6 @@ const getInMemoryCacheMemoryInternals: (() => { export { gql } -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) export interface GraphQLRequest> { // (undocumented) @@ -1064,7 +1075,7 @@ export interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1380,9 +1391,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; update?: MutationUpdaterFunction; updateQueries?: MutationQueryReducersMap; @@ -1696,7 +1708,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1750,19 +1762,8 @@ export type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1791,6 +1792,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1855,7 +1858,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1867,6 +1870,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -2073,11 +2102,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -2127,6 +2160,7 @@ export type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -2215,8 +2249,6 @@ export interface UriFunction { // @public export interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2285,11 +2317,9 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-errors.api.md b/.api-reports/api-report-errors.api.md index 205b170bf0f..96c8bafc999 100644 --- a/.api-reports/api-report-errors.api.md +++ b/.api-reports/api-report-errors.api.md @@ -4,19 +4,23 @@ ```ts -import type { ExecutionResult } from 'graphql'; import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; // @public (undocumented) export class ApolloError extends Error { constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -42,7 +46,7 @@ export interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -77,7 +81,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -109,7 +113,7 @@ type FetchResultWithSymbolExtensions = FetchResult & { extensions: Record; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type GraphQLErrors = ReadonlyArray; // Warning: (ae-forgotten-export) The symbol "FetchResultWithSymbolExtensions" needs to be exported by the entry point index.d.ts @@ -122,7 +126,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -162,11 +166,15 @@ type ServerParseError = Error & { // Warning: (ae-forgotten-export) The symbol "DefaultContext" needs to be exported by the entry point index.d.ts // // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_batch-http.api.md b/.api-reports/api-report-link_batch-http.api.md index 89008cc6985..f212ce55c05 100644 --- a/.api-reports/api-report-link_batch-http.api.md +++ b/.api-reports/api-report-link_batch-http.api.md @@ -6,8 +6,7 @@ import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -117,7 +116,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -179,7 +178,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -232,11 +231,15 @@ interface Printer { type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) diff --git a/.api-reports/api-report-link_batch.api.md b/.api-reports/api-report-link_batch.api.md index 6f6464edbbd..f7e4426ef61 100644 --- a/.api-reports/api-report-link_batch.api.md +++ b/.api-reports/api-report-link_batch.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -107,7 +106,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -153,7 +152,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -208,11 +207,15 @@ type Path = ReadonlyArray; type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (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 76a7c4e5344..cbf51621b32 100644 --- a/.api-reports/api-report-link_context.api.md +++ b/.api-reports/api-report-link_context.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -80,7 +79,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -124,7 +123,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -169,11 +168,15 @@ type RequestHandler = (operation: Operation, forward: NextLink) => Observable, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_core.api.md b/.api-reports/api-report-link_core.api.md index ce472253f3c..32f6e56f608 100644 --- a/.api-reports/api-report-link_core.api.md +++ b/.api-reports/api-report-link_core.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -83,7 +82,7 @@ export interface ExecutionPatchInitialResult, TExten // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -126,7 +125,7 @@ export interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -164,11 +163,15 @@ export type Path = ReadonlyArray; export type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) diff --git a/.api-reports/api-report-link_error.api.md b/.api-reports/api-report-link_error.api.md index 245cc7946c9..92cbae62eb5 100644 --- a/.api-reports/api-report-link_error.api.md +++ b/.api-reports/api-report-link_error.api.md @@ -5,8 +5,8 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -80,10 +80,8 @@ export class ErrorLink extends ApolloLink { export interface ErrorResponse { // (undocumented) forward: NextLink; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors?: GraphQLErrors; + graphQLErrors?: ReadonlyArray; // Warning: (ae-forgotten-export) The symbol "NetworkError" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -91,7 +89,7 @@ export interface ErrorResponse { // (undocumented) operation: Operation; // (undocumented) - response?: ExecutionResult; + response?: FormattedExecutionResult; } // Warning: (ae-forgotten-export) The symbol "ExecutionPatchResultBase" needs to be exported by the entry point index.d.ts @@ -115,7 +113,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -140,9 +138,6 @@ interface ExecutionPatchResultBase { // @public (undocumented) type FetchResult, TContext = Record, TExtensions = Record> = SingleExecutionResult | ExecutionPatchResult; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // Warning: (ae-forgotten-export) The symbol "DefaultContext" needs to be exported by the entry point index.d.ts @@ -164,7 +159,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -227,11 +222,15 @@ type ServerParseError = Error & { }; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_http.api.md b/.api-reports/api-report-link_http.api.md index 6e74d1fd378..dd7cf6778c2 100644 --- a/.api-reports/api-report-link_http.api.md +++ b/.api-reports/api-report-link_http.api.md @@ -6,8 +6,7 @@ import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { InvariantError } from 'ts-invariant'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -116,7 +115,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -217,7 +216,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -311,11 +310,15 @@ export type ServerParseError = Error & { }; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @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 7a977f4ce55..0e4fa0e4639 100644 --- a/.api-reports/api-report-link_persisted-queries.api.md +++ b/.api-reports/api-report-link_persisted-queries.api.md @@ -5,8 +5,8 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -88,7 +88,7 @@ type ErrorMeta = { // @public (undocumented) export interface ErrorResponse { // (undocumented) - graphQLErrors?: readonly GraphQLError[]; + graphQLErrors?: ReadonlyArray; // Warning: (ae-forgotten-export) The symbol "ErrorMeta" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -100,7 +100,7 @@ export interface ErrorResponse { // (undocumented) operation: Operation; // (undocumented) - response?: ExecutionResult; + response?: FormattedExecutionResult; } // Warning: (ae-forgotten-export) The symbol "ExecutionPatchResultBase" needs to be exported by the entry point index.d.ts @@ -124,7 +124,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -173,7 +173,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -263,11 +263,15 @@ type ServerParseError = Error & { type SHA256Function = (...args: any[]) => string | PromiseLike; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) diff --git a/.api-reports/api-report-link_remove-typename.api.md b/.api-reports/api-report-link_remove-typename.api.md index 05dcca3dac0..7d088615a2b 100644 --- a/.api-reports/api-report-link_remove-typename.api.md +++ b/.api-reports/api-report-link_remove-typename.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -75,7 +74,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -121,7 +120,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -191,11 +190,15 @@ export interface RemoveTypenameFromVariablesOptions { type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_retry.api.md b/.api-reports/api-report-link_retry.api.md index 173a281dd67..843ff5f1654 100644 --- a/.api-reports/api-report-link_retry.api.md +++ b/.api-reports/api-report-link_retry.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -88,7 +87,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -134,7 +133,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -208,11 +207,15 @@ export class RetryLink extends ApolloLink { } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_schema.api.md b/.api-reports/api-report-link_schema.api.md index 14459f745e9..817398b7594 100644 --- a/.api-reports/api-report-link_schema.api.md +++ b/.api-reports/api-report-link_schema.api.md @@ -5,8 +5,7 @@ ```ts import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import type { GraphQLSchema } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -76,7 +75,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -122,7 +121,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -194,11 +193,15 @@ export class SchemaLink extends ApolloLink { } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_subscriptions.api.md b/.api-reports/api-report-link_subscriptions.api.md index a67c5415721..b7fcb443f86 100644 --- a/.api-reports/api-report-link_subscriptions.api.md +++ b/.api-reports/api-report-link_subscriptions.api.md @@ -6,8 +6,7 @@ import type { Client } from 'graphql-ws'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; @@ -76,7 +75,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -133,7 +132,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -173,11 +172,15 @@ type Path = ReadonlyArray; type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-link_ws.api.md b/.api-reports/api-report-link_ws.api.md index 7969b0cdbc1..ec666d00c71 100644 --- a/.api-reports/api-report-link_ws.api.md +++ b/.api-reports/api-report-link_ws.api.md @@ -6,8 +6,7 @@ import type { ClientOptions } from 'subscriptions-transport-ws'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; -import type { GraphQLError } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import { SubscriptionClient } from 'subscriptions-transport-ws'; @@ -77,7 +76,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -123,7 +122,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -163,11 +162,15 @@ type Path = ReadonlyArray; type RequestHandler = (operation: Operation, forward: NextLink) => Observable | null; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) diff --git a/.api-reports/api-report-react.api.md b/.api-reports/api-report-react.api.md index 74721b26f76..69378231855 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -6,11 +6,11 @@ import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; -import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import type * as ReactTypes from 'react'; @@ -102,7 +102,7 @@ class ApolloClient implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -114,6 +114,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -171,12 +175,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -223,14 +229,16 @@ export interface ApolloContextValue { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -256,7 +264,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -322,7 +330,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -382,8 +390,12 @@ export interface BaseQueryOptions { client?: ApolloClient; context?: Context; + // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts + errorPolicy?: ErrorPolicy; + extensions?: Record; // Warning: (ae-forgotten-export) The symbol "FetchPolicy" needs to be exported by the entry point index.d.ts fetchPolicy?: FetchPolicy; + ignoreResults?: boolean; onComplete?: () => void; onData?: (options: OnDataOptions) => any; onError?: (error: ApolloError) => void; @@ -673,6 +685,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -741,7 +759,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -855,9 +873,6 @@ const getApolloClientMemoryInternals: (() => { // @public (undocumented) export function getApolloContext(): ReactTypes.Context; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -896,7 +911,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -989,7 +1004,6 @@ export interface LoadableQueryHookOptions { canonizeResults?: boolean; client?: ApolloClient; context?: Context; - // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts errorPolicy?: ErrorPolicy; fetchPolicy?: LoadableQueryHookFetchPolicy; queryKey?: string | number | any[]; @@ -1105,9 +1119,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1462,7 +1477,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1524,19 +1539,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1567,6 +1571,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1634,7 +1640,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1646,6 +1652,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1680,6 +1712,8 @@ export interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1860,11 +1894,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = Context, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = Context, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1915,7 +1953,7 @@ export interface SubscriptionCurrentObservable { subscription?: Subscription; } -// @public (undocumented) +// @public @deprecated (undocumented) export interface SubscriptionDataOptions extends BaseSubscriptionOptions { // (undocumented) children?: null | ((result: SubscriptionResult) => ReactTypes.ReactNode); @@ -1931,6 +1969,7 @@ export interface SubscriptionHookOptions { context?: Context; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -2089,6 +2128,7 @@ UseBackgroundQueryResult // @public (undocumented) export type UseBackgroundQueryResult = { + subscribeToMore: SubscribeToMoreFunction; fetchMore: FetchMoreFunction; refetch: RefetchFunction; }; @@ -2148,6 +2188,7 @@ queryRef: QueryRef | null, handlers: { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; reset: ResetFunction; } ]; @@ -2165,6 +2206,7 @@ export function useQueryRefHandlers { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; } // Warning: (ae-forgotten-export) The symbol "ReactiveVar" needs to be exported by the entry point index.d.ts @@ -2183,7 +2225,13 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // @public (undocumented) export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; @@ -2240,8 +2288,6 @@ export interface UseSuspenseQueryResult; - // Warning: (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts - // // (undocumented) subscribeToMore: SubscribeToMoreFunction; } @@ -2259,8 +2305,6 @@ TVariables // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2298,16 +2342,15 @@ interface WatchQueryOptions implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -114,6 +114,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -172,12 +176,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -201,14 +207,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -234,7 +242,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -287,7 +295,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -330,8 +338,12 @@ interface BaseQueryOptions { client?: ApolloClient; context?: DefaultContext; + // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts + errorPolicy?: ErrorPolicy; + extensions?: Record; // Warning: (ae-forgotten-export) The symbol "FetchPolicy" needs to be exported by the entry point index.d.ts fetchPolicy?: FetchPolicy; + ignoreResults?: boolean; onComplete?: () => void; // Warning: (ae-forgotten-export) The symbol "OnDataOptions" needs to be exported by the entry point index.d.ts onData?: (options: OnDataOptions) => any; @@ -616,6 +628,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -673,7 +691,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -774,9 +792,6 @@ const getApolloClientMemoryInternals: (() => { }; }) | undefined; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -805,7 +820,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -977,13 +992,13 @@ export interface Mutation { interface MutationBaseOptions = ApolloCache> { awaitRefetchQueries?: boolean; context?: TContext; - // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts errorPolicy?: ErrorPolicy; // Warning: (ae-forgotten-export) The symbol "OnQueryUpdated" needs to be exported by the entry point index.d.ts onQueryUpdated?: OnQueryUpdated; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1282,7 +1297,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1338,19 +1353,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1381,6 +1385,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1448,7 +1454,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1460,6 +1466,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1484,6 +1516,8 @@ interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1613,11 +1647,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1679,6 +1717,7 @@ export interface SubscriptionComponentOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1745,8 +1784,6 @@ interface UriFunction { // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1784,13 +1821,11 @@ interface WatchQueryOptions implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -113,6 +113,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -171,12 +175,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -221,14 +227,16 @@ export interface ApolloContextValue { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -254,7 +262,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -318,7 +326,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -614,6 +622,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -671,7 +685,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -775,9 +789,6 @@ const getApolloClientMemoryInternals: (() => { // @public (undocumented) export function getApolloContext(): ReactTypes.Context; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -806,7 +817,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -967,9 +978,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1213,7 +1225,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1269,19 +1281,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1312,6 +1313,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1379,7 +1382,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1391,6 +1394,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1415,6 +1444,8 @@ interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1569,11 +1600,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1611,6 +1646,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1668,8 +1704,6 @@ interface UriFunction { // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1707,13 +1741,11 @@ interface WatchQueryOptions implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -113,6 +113,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -171,12 +175,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -200,14 +206,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -233,7 +241,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -286,7 +294,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -612,6 +620,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -669,7 +683,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -782,9 +796,6 @@ const getApolloClientMemoryInternals: (() => { // @public @deprecated (undocumented) export function graphql> & Partial>>(document: DocumentNode, operationOptions?: OperationOption): (WrappedComponent: ReactTypes.ComponentType) => ReactTypes.ComponentClass; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -813,7 +824,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -986,9 +997,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1258,7 +1270,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1314,19 +1326,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1357,6 +1358,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1424,7 +1427,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1436,6 +1439,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1575,11 +1604,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1617,6 +1650,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1680,8 +1714,6 @@ interface UriFunction { // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1736,13 +1768,11 @@ export function withSubscription implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -112,6 +112,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -170,12 +174,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -199,14 +205,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -232,7 +240,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -285,7 +293,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -353,8 +361,12 @@ interface BaseQueryOptions { client?: ApolloClient; context?: DefaultContext; + // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts + errorPolicy?: ErrorPolicy; + extensions?: Record; // Warning: (ae-forgotten-export) The symbol "FetchPolicy" needs to be exported by the entry point index.d.ts fetchPolicy?: FetchPolicy; + ignoreResults?: boolean; onComplete?: () => void; // Warning: (ae-forgotten-export) The symbol "OnDataOptions" needs to be exported by the entry point index.d.ts onData?: (options: OnDataOptions) => any; @@ -639,6 +651,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -696,7 +714,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -807,9 +825,6 @@ const getApolloClientMemoryInternals: (() => { }; }) | undefined; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -838,7 +853,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -937,7 +952,6 @@ interface LoadableQueryHookOptions { canonizeResults?: boolean; client?: ApolloClient; context?: DefaultContext; - // Warning: (ae-forgotten-export) The symbol "ErrorPolicy" needs to be exported by the entry point index.d.ts errorPolicy?: ErrorPolicy; // Warning: (ae-forgotten-export) The symbol "LoadableQueryHookFetchPolicy" needs to be exported by the entry point index.d.ts fetchPolicy?: LoadableQueryHookFetchPolicy; @@ -1054,9 +1068,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1337,7 +1352,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1393,19 +1408,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1436,6 +1440,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1503,7 +1509,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1515,6 +1521,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1545,6 +1577,8 @@ interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1696,11 +1730,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1753,6 +1791,7 @@ interface SubscriptionHookOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1913,6 +1952,7 @@ UseBackgroundQueryResult // @public (undocumented) export type UseBackgroundQueryResult = { + subscribeToMore: SubscribeToMoreFunction; fetchMore: FetchMoreFunction; refetch: RefetchFunction; }; @@ -1976,6 +2016,7 @@ queryRef: QueryRef | null, handlers: { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; reset: ResetFunction; } ]; @@ -1996,6 +2037,7 @@ export function useQueryRefHandlers { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; } // Warning: (ae-forgotten-export) The symbol "ReactiveVar" needs to be exported by the entry point index.d.ts @@ -2016,7 +2058,13 @@ export interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "SubscriptionHookOptions" needs to be exported by the entry point index.d.ts // // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts // @@ -2075,16 +2123,12 @@ export interface UseSuspenseQueryResult; - // Warning: (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts - // // (undocumented) subscribeToMore: SubscribeToMoreFunction; } // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2122,16 +2166,15 @@ interface WatchQueryOptions implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -112,6 +112,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -170,12 +174,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -199,14 +205,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -232,7 +240,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -285,7 +293,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -489,6 +497,11 @@ class Concast extends Observable { // @public (undocumented) type ConcastSourcesIterable = Iterable>; +// Warning: (ae-forgotten-export) The symbol "PreloadQueryFunction" needs to be exported by the entry point index.d.ts +// +// @public +function createQueryPreloader(client: ApolloClient): PreloadQueryFunction; + // @public (undocumented) namespace DataProxy { // (undocumented) @@ -622,6 +635,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -679,7 +698,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -813,9 +832,6 @@ export function getSuspenseCache(client: ApolloClient & { // @public (undocumented) export function getWrappedPromise(queryRef: WrappedQueryRef): QueryRefPromise; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -851,7 +867,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1063,9 +1079,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1248,6 +1265,11 @@ const OBSERVED_CHANGED_OPTIONS: readonly ["canonizeResults", "context", "errorPo // @public (undocumented) type ObservedOptions = Pick; +// @public +type OnlyRequiredProperties = { + [K in keyof T as {} extends Pick ? never : K]: T[K]; +}; + // @public (undocumented) type OnQueryUpdated = (observableQuery: ObservableQuery, diff: Cache_2.DiffResult, lastDiff: Cache_2.DiffResult | undefined) => boolean | TResult; @@ -1287,6 +1309,50 @@ export interface PreloadedQueryRef extend toPromise(): Promise>; } +// @public (undocumented) +type PreloadQueryFetchPolicy = Extract; + +// @public +interface PreloadQueryFunction { + // Warning: (ae-forgotten-export) The symbol "PreloadQueryOptions" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "PreloadQueryOptionsArg" needs to be exported by the entry point index.d.ts + >(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): PreloadedQueryRef | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + returnPartialData: true; + errorPolicy: "ignore" | "all"; + }): PreloadedQueryRef | undefined, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + errorPolicy: "ignore" | "all"; + }): PreloadedQueryRef; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + returnPartialData: true; + }): PreloadedQueryRef, TVariables>; + (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): PreloadedQueryRef; +} + +// Warning: (ae-forgotten-export) The symbol "VariablesOption" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type PreloadQueryOptions = { + canonizeResults?: boolean; + context?: DefaultContext; + errorPolicy?: ErrorPolicy; + fetchPolicy?: PreloadQueryFetchPolicy; + returnPartialData?: boolean; + refetchWritePolicy?: RefetchWritePolicy; +} & VariablesOption; + +// Warning: (ae-forgotten-export) The symbol "OnlyRequiredProperties" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type PreloadQueryOptionsArg = [TVariables] extends [never] ? [ +options?: PreloadQueryOptions & TOptions +] : {} extends OnlyRequiredProperties ? [ +options?: PreloadQueryOptions> & Omit +] : [ +options: PreloadQueryOptions> & Omit +]; + // @public (undocumented) type Primitive = null | undefined | string | number | boolean | symbol | bigint; @@ -1331,7 +1397,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1393,19 +1459,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1436,6 +1491,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1503,7 +1560,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1515,6 +1572,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1556,6 +1639,8 @@ interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1688,7 +1773,6 @@ interface SharedWatchQueryOptions // @deprecated partialRefetch?: boolean; pollInterval?: number; - // Warning: (ae-forgotten-export) The symbol "RefetchWritePolicy" needs to be exported by the entry point index.d.ts refetchWritePolicy?: RefetchWritePolicy; returnPartialData?: boolean; skipPollAttempt?: () => boolean; @@ -1696,11 +1780,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1747,6 +1835,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1923,6 +2012,7 @@ UseBackgroundQueryResult // @public (undocumented) type UseBackgroundQueryResult = { + subscribeToMore: SubscribeToMoreFunction; fetchMore: FetchMoreFunction; refetch: RefetchFunction; }; @@ -1967,6 +2057,7 @@ function useQueryRefHandlers { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; } // Warning: (ae-forgotten-export) The symbol "UseReadQueryResult" needs to be exported by the entry point index.d.ts @@ -2039,16 +2130,23 @@ interface UseSuspenseQueryResult; - // Warning: (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts - // // (undocumented) subscribeToMore: SubscribeToMoreFunction; } +// @public (undocumented) +type VariablesOption = [ +TVariables +] extends [never] ? { + variables?: Record; +} : {} extends OnlyRequiredProperties ? { + variables?: TVariables; +} : { + variables: TVariables; +}; + // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2077,6 +2175,10 @@ interface WatchQueryOptions(inter // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:29:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:38:3 - (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:54:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:78:4 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/react/query-preloader/createQueryPreloader.ts:145:3 - (ae-forgotten-export) The symbol "PreloadQueryFetchPolicy" needs to be exported by the entry point index.d.ts +// src/react/query-preloader/createQueryPreloader.ts:167:5 - (ae-forgotten-export) The symbol "RefetchWritePolicy" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-react_ssr.api.md b/.api-reports/api-report-react_ssr.api.md index f4ca55a6daf..dc35a946fb7 100644 --- a/.api-reports/api-report-react_ssr.api.md +++ b/.api-reports/api-report-react_ssr.api.md @@ -6,11 +6,11 @@ import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; -import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import type * as ReactTypes from 'react'; @@ -101,7 +101,7 @@ class ApolloClient implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -113,6 +113,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -171,12 +175,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -200,14 +206,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -233,7 +241,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -286,7 +294,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -583,6 +591,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -640,7 +654,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -760,9 +774,6 @@ type GetMarkupFromTreeOptions = { renderFunction?: (tree: ReactTypes.ReactElement) => string | PromiseLike; }; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -791,7 +802,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -952,9 +963,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1198,7 +1210,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1254,19 +1266,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1297,6 +1298,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1364,7 +1367,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1376,6 +1379,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1400,6 +1429,8 @@ interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -1554,11 +1585,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1596,6 +1631,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1653,8 +1689,6 @@ interface UriFunction { // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1692,13 +1726,11 @@ interface WatchQueryOptions implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -113,6 +113,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -171,12 +175,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -200,14 +206,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -233,7 +241,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -286,7 +294,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -584,6 +592,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -641,7 +655,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -742,9 +756,6 @@ const getApolloClientMemoryInternals: (() => { }; }) | undefined; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -773,7 +784,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1073,9 +1084,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1281,7 +1293,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1337,19 +1349,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1378,6 +1379,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1445,7 +1448,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1457,6 +1460,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1601,11 +1630,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1646,6 +1679,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1712,8 +1746,6 @@ export function wait(ms: number): Promise; // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1762,13 +1794,11 @@ export function withWarningSpy(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-testing_core.api.md b/.api-reports/api-report-testing_core.api.md index 1cbfc54605f..3bc699063cb 100644 --- a/.api-reports/api-report-testing_core.api.md +++ b/.api-reports/api-report-testing_core.api.md @@ -6,11 +6,11 @@ import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; -import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import type { Subscriber } from 'zen-observable-ts'; @@ -100,7 +100,7 @@ class ApolloClient implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -112,6 +112,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; // Warning: (ae-forgotten-export) The symbol "DocumentTransform" needs to be exported by the entry point index.d.ts @@ -170,12 +174,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -199,14 +205,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -232,7 +240,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -285,7 +293,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -583,6 +591,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) class DocumentTransform { // Warning: (ae-forgotten-export) The symbol "TransformFn" needs to be exported by the entry point index.d.ts @@ -640,7 +654,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -741,9 +755,6 @@ const getApolloClientMemoryInternals: (() => { }; }) | undefined; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -772,7 +783,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1028,9 +1039,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -1236,7 +1248,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -1292,19 +1304,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -1335,6 +1336,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -1402,7 +1405,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -1414,6 +1417,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -1558,11 +1587,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -1603,6 +1636,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -1669,8 +1703,6 @@ export function wait(ms: number): Promise; // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -1719,13 +1751,11 @@ export function withWarningSpy(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.api-reports/api-report-testing_experimental.api.md b/.api-reports/api-report-testing_experimental.api.md index 97af4a043cf..330f543fc64 100644 --- a/.api-reports/api-report-testing_experimental.api.md +++ b/.api-reports/api-report-testing_experimental.api.md @@ -8,7 +8,7 @@ import type { FieldNode } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import type { GraphQLSchema } from 'graphql'; -// @alpha +// @alpha @deprecated export const createSchemaFetch: (schema: GraphQLSchema, mockFetchOpts?: { validate?: boolean; delay?: { @@ -24,7 +24,7 @@ export const createSchemaFetch: (schema: GraphQLSchema, mockFetchOpts?: { // Warning: (ae-forgotten-export) The symbol "TestSchemaOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts // -// @alpha +// @alpha @deprecated export const createTestSchema: (schemaWithTypeDefs: GraphQLSchema, options: TestSchemaOptions) => ProxiedSchema; // @public diff --git a/.api-reports/api-report-utilities.api.md b/.api-reports/api-report-utilities.api.md index c53a249f699..8788d7cf02d 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -8,12 +8,12 @@ import type { ArgumentNode } from 'graphql'; import type { ASTNode } from 'graphql'; import type { DirectiveNode } from 'graphql'; import type { DocumentNode } from 'graphql'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import type { FragmentSpreadNode } from 'graphql'; -import { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import { GraphQLFormattedError } from 'graphql'; import type { InlineFragmentNode } from 'graphql'; import type { NameNode } from 'graphql'; import { Observable } from 'zen-observable-ts'; @@ -114,7 +114,7 @@ class ApolloClient implements DataProxy { // Warning: (ae-forgotten-export) The symbol "GraphQLRequest" needs to be exported by the entry point index.d.ts // // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; // Warning: (ae-forgotten-export) The symbol "Resolvers" needs to be exported by the entry point index.d.ts addResolvers(resolvers: Resolvers | Resolvers[]): void; // Warning: (ae-forgotten-export) The symbol "ApolloCache" needs to be exported by the entry point index.d.ts @@ -126,6 +126,10 @@ class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; get documentTransform(): DocumentTransform; @@ -183,12 +187,14 @@ class ApolloClient implements DataProxy { interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -212,14 +218,16 @@ interface ApolloClientOptions { class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -245,7 +253,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -309,7 +317,7 @@ interface ApolloQueryResult { data: T; // Warning: (ae-forgotten-export) The symbol "ApolloError" needs to be exported by the entry point index.d.ts error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // Warning: (ae-forgotten-export) The symbol "NetworkStatus" needs to be exported by the entry point index.d.ts @@ -775,6 +783,12 @@ const _deleteModifier: unique symbol; // @public @deprecated (undocumented) export const DEV: boolean; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) export type DirectiveInfo = { [fieldName: string]: { @@ -947,7 +961,7 @@ interface ExecutionPatchInitialResult, TExtensions = // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1153,7 +1167,7 @@ export function getFragmentQueryDocument(document: DocumentNode, fragmentName?: export type GetFragmentSpreadConfig = GetNodeConfig; // @public (undocumented) -export function getGraphQLErrorsFromResult(result: FetchResult): GraphQLError[]; +export function getGraphQLErrorsFromResult(result: FetchResult): GraphQLFormattedError[]; // @public (undocumented) export function getInclusionDirectives(directives: ReadonlyArray): InclusionDirectives; @@ -1204,9 +1218,6 @@ export const getStoreKeyName: ((fieldName: string, args?: Record | // @public (undocumented) export function getTypenameFromResult(result: Record, selectionSet: SelectionSetNode, fragmentMap?: FragmentMap): string | undefined; -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) interface GraphQLRequest> { // (undocumented) @@ -1271,7 +1282,7 @@ interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1676,9 +1687,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; // Warning: (ae-forgotten-export) The symbol "MutationUpdaterFunction" needs to be exported by the entry point index.d.ts update?: MutationUpdaterFunction; @@ -2000,7 +2012,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -2056,19 +2068,8 @@ type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -2099,6 +2100,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -2166,7 +2169,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -2178,6 +2181,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -2402,11 +2431,15 @@ interface SharedWatchQueryOptions export function shouldInclude({ directives }: SelectionNode, variables?: Record): boolean; // @public (undocumented) -interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -2463,6 +2496,7 @@ type SubscribeToMoreOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -2574,8 +2608,6 @@ export type VariableValue = (node: VariableNode) => any; // @public interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2650,13 +2682,11 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/LocalState.ts:71:3 - (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/utilities/graphql/storeUtils.ts:226:12 - (ae-forgotten-export) The symbol "storeKeyNameStringify" needs to be exported by the entry point index.d.ts // src/utilities/policies/pagination.ts:76:3 - (ae-forgotten-export) The symbol "TRelayEdge" needs to be exported by the entry point index.d.ts // src/utilities/policies/pagination.ts:77:3 - (ae-forgotten-export) The symbol "TRelayPageInfo" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report.api.md b/.api-reports/api-report.api.md index 95cc0a4694b..69307a44778 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -9,12 +9,12 @@ import { disableExperimentalFragmentVariables } from 'graphql-tag'; import { disableFragmentWarnings } from 'graphql-tag'; import type { DocumentNode } from 'graphql'; import { enableExperimentalFragmentVariables } from 'graphql-tag'; -import type { ExecutionResult } from 'graphql'; import type { FieldNode } from 'graphql'; +import type { FormattedExecutionResult } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import { gql } from 'graphql-tag'; -import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLFormattedError } from 'graphql'; import type { InlineFragmentNode } from 'graphql'; import { InvariantError } from 'ts-invariant'; import { Observable } from 'zen-observable-ts'; @@ -96,7 +96,7 @@ export class ApolloClient implements DataProxy { __actionHookForDevTools(cb: () => any): void; constructor(options: ApolloClientOptions); // (undocumented) - __requestRaw(payload: GraphQLRequest): Observable; + __requestRaw(payload: GraphQLRequest): Observable; addResolvers(resolvers: Resolvers | Resolvers[]): void; // (undocumented) cache: ApolloCache; @@ -105,6 +105,10 @@ export class ApolloClient implements DataProxy { get defaultContext(): Partial; // (undocumented) defaultOptions: DefaultOptions; + // Warning: (ae-forgotten-export) The symbol "DevtoolsOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly devtoolsConfig: DevtoolsOptions; // (undocumented) disableNetworkFetches: boolean; get documentTransform(): DocumentTransform; @@ -146,12 +150,14 @@ export class ApolloClient implements DataProxy { export interface ApolloClientOptions { assumeImmutableResults?: boolean; cache: ApolloCache; + // @deprecated connectToDevTools?: boolean; // (undocumented) credentials?: string; // (undocumented) defaultContext?: Partial; defaultOptions?: DefaultOptions; + devtools?: DevtoolsOptions; // (undocumented) documentTransform?: DocumentTransform; // (undocumented) @@ -195,14 +201,16 @@ export interface ApolloContextValue { export class ApolloError extends Error { // Warning: (ae-forgotten-export) The symbol "ApolloErrorOptions" needs to be exported by the entry point index.d.ts constructor({ graphQLErrors, protocolErrors, clientErrors, networkError, errorMessage, extraInfo, }: ApolloErrorOptions); + cause: ({ + readonly message: string; + extensions?: GraphQLErrorExtensions[] | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) | null; // (undocumented) clientErrors: ReadonlyArray; // (undocumented) extraInfo: any; - // Warning: (ae-forgotten-export) The symbol "GraphQLErrors" needs to be exported by the entry point index.d.ts - // // (undocumented) - graphQLErrors: GraphQLErrors; + graphQLErrors: ReadonlyArray; // (undocumented) message: string; // (undocumented) @@ -225,7 +233,7 @@ interface ApolloErrorOptions { // (undocumented) extraInfo?: any; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) networkError?: Error | ServerParseError | ServerError | null; // (undocumented) @@ -292,7 +300,7 @@ export interface ApolloQueryResult { // (undocumented) data: T; error?: ApolloError; - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) loading: boolean; // (undocumented) @@ -354,7 +362,10 @@ export interface BaseQueryOptions { client?: ApolloClient; context?: DefaultContext; + errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; + ignoreResults?: boolean; onComplete?: () => void; onData?: (options: OnDataOptions) => any; onError?: (error: ApolloError) => void; @@ -688,6 +699,12 @@ interface DeleteModifier { // @public (undocumented) const _deleteModifier: unique symbol; +// @public (undocumented) +interface DevtoolsOptions { + enabled?: boolean; + name?: string; +} + // @public (undocumented) export type DiffQueryAgainstStoreOptions = ReadQueryOptions & { returnPartialData?: boolean; @@ -870,7 +887,7 @@ export interface ExecutionPatchInitialResult, TExten // (undocumented) data: TData | null | undefined; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1098,9 +1115,6 @@ const getInMemoryCacheMemoryInternals: (() => { export { gql } -// @public (undocumented) -type GraphQLErrors = ReadonlyArray; - // @public (undocumented) export interface GraphQLRequest> { // (undocumented) @@ -1195,7 +1209,7 @@ export interface IncrementalPayload { // (undocumented) data: TData | null; // (undocumented) - errors?: ReadonlyArray; + errors?: ReadonlyArray; // (undocumented) extensions?: TExtensions; // (undocumented) @@ -1558,9 +1572,10 @@ interface MutationBaseOptions; + // Warning: (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts optimisticResponse?: TData | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier; - }) => TData); + }) => TData | IgnoreModifier); refetchQueries?: ((result: FetchResult) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude; update?: MutationUpdaterFunction; updateQueries?: MutationQueryReducersMap; @@ -2038,7 +2053,7 @@ class QueryInfo { // (undocumented) getDiff(): Cache_2.DiffResult; // (undocumented) - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; // (undocumented) init(query: { document: DocumentNode; @@ -2098,19 +2113,8 @@ export type QueryListener = (queryInfo: QueryInfo) => void; // @public (undocumented) class QueryManager { - constructor({ cache, link, defaultOptions, documentTransform, queryDeduplication, onBroadcast, ssrMode, clientAwareness, localState, assumeImmutableResults, defaultContext, }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }); + // Warning: (ae-forgotten-export) The symbol "QueryManagerOptions" needs to be exported by the entry point index.d.ts + constructor(options: QueryManagerOptions); // (undocumented) readonly assumeImmutableResults: boolean; // (undocumented) @@ -2139,6 +2143,8 @@ class QueryManager { // // (undocumented) getDocumentInfo(document: DocumentNode): TransformCacheEntry; + // Warning: (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts + // // (undocumented) getLocalState(): LocalState; // (undocumented) @@ -2203,7 +2209,7 @@ class QueryManager { // (undocumented) readonly ssrMode: boolean; // (undocumented) - startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, }: SubscriptionOptions): Observable>; + startGraphQLSubscription({ query, fetchPolicy, errorPolicy, variables, context, extensions, }: SubscriptionOptions): Observable>; stop(): void; // (undocumented) stopQuery(queryId: string): void; @@ -2215,6 +2221,32 @@ class QueryManager { watchQuery(options: WatchQueryOptions): ObservableQuery; } +// @public (undocumented) +interface QueryManagerOptions { + // (undocumented) + assumeImmutableResults: boolean; + // (undocumented) + cache: ApolloCache; + // (undocumented) + clientAwareness: Record; + // (undocumented) + defaultContext: Partial | undefined; + // (undocumented) + defaultOptions: DefaultOptions; + // (undocumented) + documentTransform: DocumentTransform | null | undefined; + // (undocumented) + link: ApolloLink; + // (undocumented) + localState: LocalState; + // (undocumented) + onBroadcast: undefined | (() => void); + // (undocumented) + queryDeduplication: boolean; + // (undocumented) + ssrMode: boolean; +} + // @public interface QueryOptions { // @deprecated @@ -2251,6 +2283,8 @@ export interface QueryResult; data: TData | undefined; error?: ApolloError; + // @deprecated (undocumented) + errors?: ReadonlyArray; loading: boolean; networkStatus: NetworkStatus; observable: ObservableQuery; @@ -2480,11 +2514,15 @@ interface SharedWatchQueryOptions } // @public (undocumented) -export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> extends ExecutionResult { +export interface SingleExecutionResult, TContext = DefaultContext, TExtensions = Record> { // (undocumented) context?: TContext; // (undocumented) data?: TData | null; + // (undocumented) + errors?: ReadonlyArray; + // (undocumented) + extensions?: TExtensions; } // @public (undocumented) @@ -2547,7 +2585,7 @@ export interface SubscriptionCurrentObservable { subscription?: ObservableSubscription; } -// @public (undocumented) +// @public @deprecated (undocumented) export interface SubscriptionDataOptions extends BaseSubscriptionOptions { // (undocumented) children?: null | ((result: SubscriptionResult) => ReactTypes.ReactNode); @@ -2563,6 +2601,7 @@ export interface SubscriptionHookOptions { context?: DefaultContext; errorPolicy?: ErrorPolicy; + extensions?: Record; fetchPolicy?: FetchPolicy; query: DocumentNode | TypedDocumentNode; variables?: TVariables; @@ -2752,6 +2791,7 @@ UseBackgroundQueryResult // @public (undocumented) export type UseBackgroundQueryResult = { + subscribeToMore: SubscribeToMoreFunction; fetchMore: FetchMoreFunction; refetch: RefetchFunction; }; @@ -2811,6 +2851,7 @@ queryRef: QueryRef | null, handlers: { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; reset: ResetFunction; } ]; @@ -2828,6 +2869,7 @@ export function useQueryRefHandlers { fetchMore: FetchMoreFunction; refetch: RefetchFunction; + subscribeToMore: SubscribeToMoreFunction; } // @public @@ -2844,7 +2886,13 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // @public (undocumented) export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; @@ -2901,8 +2949,6 @@ export interface UseSuspenseQueryResult; - // Warning: (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts - // // (undocumented) subscribeToMore: SubscribeToMoreFunction; } @@ -2920,8 +2966,6 @@ TVariables // @public export interface WatchFragmentOptions { - // @deprecated (undocumented) - canonizeResults?: boolean; fragment: DocumentNode | TypedDocumentNode; fragmentName?: string; from: StoreObject | Reference | string; @@ -2988,15 +3032,14 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:124:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:158:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:390:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts -// src/core/watchQueryOptions.ts:269:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:138:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:382:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/watchQueryOptions.ts:275:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:29:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useBackgroundQuery.ts:30:3 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts -// src/react/hooks/useLoadableQuery.ts:107:1 - (ae-forgotten-export) The symbol "ResetFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:38:3 - (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:54:3 - (ae-forgotten-export) The symbol "FetchMoreFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useBackgroundQuery.ts:78:4 - (ae-forgotten-export) The symbol "RefetchFunction" needs to be exported by the entry point index.d.ts +// src/react/hooks/useLoadableQuery.ts:120:9 - (ae-forgotten-export) The symbol "ResetFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.changeset/angry-ravens-mate.md b/.changeset/angry-ravens-mate.md new file mode 100644 index 00000000000..3072009aff1 --- /dev/null +++ b/.changeset/angry-ravens-mate.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Add support for `subscribeToMore` function to `useQueryRefHandlers`. diff --git a/.changeset/angry-seals-jog.md b/.changeset/angry-seals-jog.md new file mode 100644 index 00000000000..9ba52ae2f3c --- /dev/null +++ b/.changeset/angry-seals-jog.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Support extensions in useSubscription diff --git a/.changeset/breezy-deers-dream.md b/.changeset/breezy-deers-dream.md new file mode 100644 index 00000000000..71d8b128201 --- /dev/null +++ b/.changeset/breezy-deers-dream.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +add React 19 RC to `peerDependencies` diff --git a/.changeset/chilly-dots-shake.md b/.changeset/chilly-dots-shake.md new file mode 100644 index 00000000000..0bbb1de7e58 --- /dev/null +++ b/.changeset/chilly-dots-shake.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Add support for `subscribeToMore` function to `useLoadableQuery`. diff --git a/.changeset/clever-bikes-admire.md b/.changeset/clever-bikes-admire.md new file mode 100644 index 00000000000..36b9ba5de3a --- /dev/null +++ b/.changeset/clever-bikes-admire.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Add `restart` function to `useSubscription`. diff --git a/.changeset/curly-vans-draw.md b/.changeset/curly-vans-draw.md new file mode 100644 index 00000000000..e9096914b90 --- /dev/null +++ b/.changeset/curly-vans-draw.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Remove deprecated `watchFragment` option, `canonizeResults` diff --git a/.changeset/early-tips-vanish.md b/.changeset/early-tips-vanish.md new file mode 100644 index 00000000000..3c794284f8e --- /dev/null +++ b/.changeset/early-tips-vanish.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +`createSchemaFetch`: simulate serialized errors instead of an `ApolloError` instance diff --git a/.changeset/flat-onions-guess.md b/.changeset/flat-onions-guess.md new file mode 100644 index 00000000000..ce7dc67887a --- /dev/null +++ b/.changeset/flat-onions-guess.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Add `cause` field to `ApolloError`. diff --git a/.changeset/fluffy-badgers-rush.md b/.changeset/fluffy-badgers-rush.md new file mode 100644 index 00000000000..62f55a2b0f0 --- /dev/null +++ b/.changeset/fluffy-badgers-rush.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +MockLink: add query default variables if not specified in mock request diff --git a/.changeset/good-suns-happen.md b/.changeset/good-suns-happen.md new file mode 100644 index 00000000000..e184b2c1d09 --- /dev/null +++ b/.changeset/good-suns-happen.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +`watchFragment`: forward additional options to `diffOptions` diff --git a/.changeset/hungry-rings-help.md b/.changeset/hungry-rings-help.md new file mode 100644 index 00000000000..d5fa17d2f35 --- /dev/null +++ b/.changeset/hungry-rings-help.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Fix issue where mutations were not accessible by Apollo Client Devtools in 3.11.0-rc.0. diff --git a/.changeset/little-suits-return.md b/.changeset/little-suits-return.md new file mode 100644 index 00000000000..6232f48b7fd --- /dev/null +++ b/.changeset/little-suits-return.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Reimplement `useSubscription` to fix rules of React violations. diff --git a/.changeset/nasty-olives-act.md b/.changeset/nasty-olives-act.md new file mode 100644 index 00000000000..02fadec8cae --- /dev/null +++ b/.changeset/nasty-olives-act.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Rewrite big parts of `useQuery` and `useLazyQuery` to be more compliant with the Rules of React and React Compiler diff --git a/.changeset/pink-ants-remember.md b/.changeset/pink-ants-remember.md new file mode 100644 index 00000000000..b5d5506d5b7 --- /dev/null +++ b/.changeset/pink-ants-remember.md @@ -0,0 +1,16 @@ +--- +"@apollo/client": minor +--- + +Add the ability to specify a name for the client instance for use with Apollo Client Devtools. This is useful when instantiating multiple clients to identify the client instance more easily. This deprecates the `connectToDevtools` option in favor of a new `devtools` configuration. + +```ts +new ApolloClient({ + devtools: { + enabled: true, + name: "Test Client", + }, +}); +``` + +This option is backwards-compatible with `connectToDevtools` and will be used in the absense of a `devtools` option. diff --git a/.changeset/pink-flowers-switch.md b/.changeset/pink-flowers-switch.md new file mode 100644 index 00000000000..c85a3e59376 --- /dev/null +++ b/.changeset/pink-flowers-switch.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Allow `IgnoreModifier` to be returned from a `optimisticResponse` function when inferring from a `TypedDocumentNode` when used with a generic argument. diff --git a/.changeset/proud-humans-begin.md b/.changeset/proud-humans-begin.md new file mode 100644 index 00000000000..059cffa65fc --- /dev/null +++ b/.changeset/proud-humans-begin.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Document (and deprecate) the previously undocumented `errors` property on the `useQuery` `QueryResult` type. diff --git a/.changeset/slimy-balloons-cheat.md b/.changeset/slimy-balloons-cheat.md new file mode 100644 index 00000000000..72291902106 --- /dev/null +++ b/.changeset/slimy-balloons-cheat.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Add support for `subscribeToMore` function to `useBackgroundQuery`. diff --git a/.changeset/slimy-berries-yawn.md b/.changeset/slimy-berries-yawn.md new file mode 100644 index 00000000000..ef7d7ec671c --- /dev/null +++ b/.changeset/slimy-berries-yawn.md @@ -0,0 +1,14 @@ +--- +"@apollo/client": minor +--- + +Changes usages of the `GraphQLError` type to `GraphQLFormattedError`. + +This was a type bug - these errors were never `GraphQLError` instances +to begin with, and the `GraphQLError` class has additional properties that can +never be correctly rehydrated from a GraphQL result. +The correct type to use here is `GraphQLFormattedError`. + +Similarly, please ensure to use the type `FormattedExecutionResult` +instead of `ExecutionResult` - the non-"Formatted" versions of these types +are for use on the server only, but don't get transported over the network. diff --git a/.changeset/tasty-chairs-dress.md b/.changeset/tasty-chairs-dress.md new file mode 100644 index 00000000000..459c72bd44b --- /dev/null +++ b/.changeset/tasty-chairs-dress.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Call `nextFetchPolicy` with "variables-changed" even if there is a `fetchPolicy` specified. (fixes #11365) diff --git a/.changeset/thin-lies-begin.md b/.changeset/thin-lies-begin.md new file mode 100644 index 00000000000..bc258c65cdf --- /dev/null +++ b/.changeset/thin-lies-begin.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Allow wrapping `createQueryPreloader` diff --git a/.changeset/unlucky-birds-press.md b/.changeset/unlucky-birds-press.md new file mode 100644 index 00000000000..5696787576d --- /dev/null +++ b/.changeset/unlucky-birds-press.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +add `ignoreResults` option to `useSubscription` diff --git a/.changeset/weak-ads-develop.md b/.changeset/weak-ads-develop.md new file mode 100644 index 00000000000..2499c7cb871 --- /dev/null +++ b/.changeset/weak-ads-develop.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Deprecates experimental schema testing utilities introduced in 3.10 in favor of recommending [`@apollo/graphql-testing-library`](https://github.com/apollographql/graphql-testing-library). diff --git a/.eslintrc b/.eslintrc index b4d6d6f5363..e8abca31af1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -40,6 +40,22 @@ ], "@typescript-eslint/consistent-type-exports": ["error"], "@typescript-eslint/no-import-type-side-effects": "error", + "@typescript-eslint/ban-types": [ + "error", + { + "types": { + "GraphQLError": { + "message": "Use GraphQLFormattedError instead", + "fixWith": "GraphQLFormattedError" + }, + "ExecutionResult": { + "message": "Use FormattedExecutionResult instead", + "fixWith": "FormattedExecutionResult" + } + }, + "extendDefaults": false + } + ], "no-restricted-syntax": [ "error", { diff --git a/.size-limits.json b/.size-limits.json index 09bf55362fa..3b2a83ce94c 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39604, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32852 + "dist/apollo-client.min.cjs": 40179, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32990 } diff --git a/CHANGELOG.md b/CHANGELOG.md index cea036ef5be..c3ecbd0181c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,83 @@ # @apollo/client +## 3.11.0-rc.2 + +### Patch Changes + +- [#11951](https://github.com/apollographql/apollo-client/pull/11951) [`0de03af`](https://github.com/apollographql/apollo-client/commit/0de03af912a76c4e0111f21b4f90a073317b63b6) Thanks [@phryneas](https://github.com/phryneas)! - add React 19 RC to `peerDependencies` + +- [#11937](https://github.com/apollographql/apollo-client/pull/11937) [`78332be`](https://github.com/apollographql/apollo-client/commit/78332be32a9af0da33eb3e4100e7a76c3eac2496) Thanks [@phryneas](https://github.com/phryneas)! - `createSchemaFetch`: simulate serialized errors instead of an `ApolloError` instance + +- [#11944](https://github.com/apollographql/apollo-client/pull/11944) [`8f3d7eb`](https://github.com/apollographql/apollo-client/commit/8f3d7eb3bc2e0c2d79c5b1856655abe829390742) Thanks [@sneyderdev](https://github.com/sneyderdev)! - Allow `IgnoreModifier` to be returned from a `optimisticResponse` function when inferring from a `TypedDocumentNode` when used with a generic argument. + +- [#11954](https://github.com/apollographql/apollo-client/pull/11954) [`4a6e86a`](https://github.com/apollographql/apollo-client/commit/4a6e86aeaf6685abf0dd23110784848c8b085735) Thanks [@phryneas](https://github.com/phryneas)! - Document (and deprecate) the previously undocumented `errors` property on the `useQuery` `QueryResult` type. + +## 3.11.0-rc.1 + +### Patch Changes + +- [#11949](https://github.com/apollographql/apollo-client/pull/11949) [`4528918`](https://github.com/apollographql/apollo-client/commit/45289186bcaaa33dfe904913eb6df31e2541c219) Thanks [@alessbell](https://github.com/alessbell)! - Remove deprecated `watchFragment` option, `canonizeResults` + +- [#11926](https://github.com/apollographql/apollo-client/pull/11926) [`3dd6432`](https://github.com/apollographql/apollo-client/commit/3dd64324dc5156450cead27f8141ea93315ffe65) Thanks [@phryneas](https://github.com/phryneas)! - `watchFragment`: forward additional options to `diffOptions` + +- [#11946](https://github.com/apollographql/apollo-client/pull/11946) [`7d833b8`](https://github.com/apollographql/apollo-client/commit/7d833b80119a991e6d2eb58f2c71074d697b8e63) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Fix issue where mutations were not accessible by Apollo Client Devtools in 3.11.0-rc.0. + +## 3.11.0-rc.0 + +### Minor Changes + +- [#11923](https://github.com/apollographql/apollo-client/pull/11923) [`d88c7f8`](https://github.com/apollographql/apollo-client/commit/d88c7f8909e3cb31532e8b1fc7dd06be12f35591) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Add support for `subscribeToMore` function to `useQueryRefHandlers`. + +- [#11854](https://github.com/apollographql/apollo-client/pull/11854) [`3812800`](https://github.com/apollographql/apollo-client/commit/3812800c6e4e5e3e64f473543babdba35ce100c2) Thanks [@jcostello-atlassian](https://github.com/jcostello-atlassian)! - Support extensions in useSubscription + +- [#11923](https://github.com/apollographql/apollo-client/pull/11923) [`d88c7f8`](https://github.com/apollographql/apollo-client/commit/d88c7f8909e3cb31532e8b1fc7dd06be12f35591) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Add support for `subscribeToMore` function to `useLoadableQuery`. + +- [#11863](https://github.com/apollographql/apollo-client/pull/11863) [`98e44f7`](https://github.com/apollographql/apollo-client/commit/98e44f74cb7c7e93a81bdc7492c9218bf4a2dcd4) Thanks [@phryneas](https://github.com/phryneas)! - Reimplement `useSubscription` to fix rules of React violations. + +- [#11869](https://github.com/apollographql/apollo-client/pull/11869) [`a69327c`](https://github.com/apollographql/apollo-client/commit/a69327cce1b36e8855258e9b19427511e0af8748) Thanks [@phryneas](https://github.com/phryneas)! - Rewrite big parts of `useQuery` and `useLazyQuery` to be more compliant with the Rules of React and React Compiler + +- [#11936](https://github.com/apollographql/apollo-client/pull/11936) [`1b23337`](https://github.com/apollographql/apollo-client/commit/1b23337e5a9eec4ce3ed69531ca4f4afe8e897a6) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Add the ability to specify a name for the client instance for use with Apollo Client Devtools. This is useful when instantiating multiple clients to identify the client instance more easily. This deprecates the `connectToDevtools` option in favor of a new `devtools` configuration. + + ```ts + new ApolloClient({ + devtools: { + enabled: true, + name: "Test Client", + }, + }); + ``` + + This option is backwards-compatible with `connectToDevtools` and will be used in the absense of a `devtools` option. + +- [#11923](https://github.com/apollographql/apollo-client/pull/11923) [`d88c7f8`](https://github.com/apollographql/apollo-client/commit/d88c7f8909e3cb31532e8b1fc7dd06be12f35591) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Add support for `subscribeToMore` function to `useBackgroundQuery`. + +- [#11789](https://github.com/apollographql/apollo-client/pull/11789) [`5793301`](https://github.com/apollographql/apollo-client/commit/579330147d6bd6f7167a35413a33746103e375cb) Thanks [@phryneas](https://github.com/phryneas)! - Changes usages of the `GraphQLError` type to `GraphQLFormattedError`. + + This was a type bug - these errors were never `GraphQLError` instances + to begin with, and the `GraphQLError` class has additional properties that can + never be correctly rehydrated from a GraphQL result. + The correct type to use here is `GraphQLFormattedError`. + + Similarly, please ensure to use the type `FormattedExecutionResult` + instead of `ExecutionResult` - the non-"Formatted" versions of these types + are for use on the server only, but don't get transported over the network. + +- [#11930](https://github.com/apollographql/apollo-client/pull/11930) [`a768575`](https://github.com/apollographql/apollo-client/commit/a768575ac1454587208aad63abc811b6a966fe72) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Deprecates experimental schema testing utilities introduced in 3.10 in favor of recommending [`@apollo/graphql-testing-library`](https://github.com/apollographql/graphql-testing-library). + +### Patch Changes + +- [#11927](https://github.com/apollographql/apollo-client/pull/11927) [`2941824`](https://github.com/apollographql/apollo-client/commit/2941824dd66cdd20eee5f2293373ad7a9cf991a4) Thanks [@phryneas](https://github.com/phryneas)! - Add `restart` function to `useSubscription`. + +- [#11902](https://github.com/apollographql/apollo-client/pull/11902) [`96422ce`](https://github.com/apollographql/apollo-client/commit/96422ce95b923b560321a88acd2eec35cf2a1c18) Thanks [@phryneas](https://github.com/phryneas)! - Add `cause` field to `ApolloError`. + +- [#11806](https://github.com/apollographql/apollo-client/pull/11806) [`8df6013`](https://github.com/apollographql/apollo-client/commit/8df6013b6b45452ec058fab3e068b5b6d6c493f7) Thanks [@phryneas](https://github.com/phryneas)! - MockLink: add query default variables if not specified in mock request + +- [#11626](https://github.com/apollographql/apollo-client/pull/11626) [`228429a`](https://github.com/apollographql/apollo-client/commit/228429a1d36eae691473b24fb641ec3cd84c8a3d) Thanks [@phryneas](https://github.com/phryneas)! - Call `nextFetchPolicy` with "variables-changed" even if there is a `fetchPolicy` specified. (fixes #11365) + +- [#11719](https://github.com/apollographql/apollo-client/pull/11719) [`09a6677`](https://github.com/apollographql/apollo-client/commit/09a6677ec1a0cffedeecb2cbac5cd3a3c8aa0fa1) Thanks [@phryneas](https://github.com/phryneas)! - Allow wrapping `createQueryPreloader` + +- [#11921](https://github.com/apollographql/apollo-client/pull/11921) [`70406bf`](https://github.com/apollographql/apollo-client/commit/70406bfd2b9a645d781638569853d9b435e047df) Thanks [@phryneas](https://github.com/phryneas)! - add `ignoreResults` option to `useSubscription` + ## 3.10.8 ### Patch Changes @@ -457,7 +535,7 @@ import { Environment, Network, RecordSource, Store } from "relay-runtime"; const fetchMultipartSubs = createFetchMultipartSubscription( - "http://localhost:4000", + "http://localhost:4000" ); const network = Network.create(fetchQuery, fetchMultipartSubs); @@ -810,7 +888,7 @@ return data.breeds.map(({ characteristics }) => characteristics.map((characteristic) => (
{characteristic}
- )), + )) ); } ``` @@ -861,7 +939,7 @@ const { data } = useSuspenseQuery( query, - id ? { variables: { id } } : skipToken, + id ? { variables: { id } } : skipToken ); ``` @@ -2816,7 +2894,7 @@ In upcoming v3.6.x and v3.7 (beta) releases, we will be completely overhauling o fields: { comments(comments: Reference[], { readField }) { return comments.filter( - (comment) => idToRemove !== readField("id", comment), + (comment) => idToRemove !== readField("id", comment) ); }, }, diff --git a/package-lock.json b/package-lock.json index 2b243ee79bd..44c82bb973e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,10 +84,10 @@ "prettier": "3.1.1", "react": "18.3.1", "react-17": "npm:react@^17", - "react-19": "npm:react@19.0.0-rc-fb9a90fa48-20240614", + "react-19": "npm:react@19.0.0-rc-378b305958-20240710", "react-dom": "18.3.1", "react-dom-17": "npm:react-dom@^17", - "react-dom-19": "npm:react-dom@19.0.0-rc-fb9a90fa48-20240614", + "react-dom-19": "npm:react-dom@19.0.0-rc-378b305958-20240710", "react-error-boundary": "4.0.13", "recast": "0.23.9", "resolve": "1.22.8", @@ -116,8 +116,8 @@ "peerDependencies": { "graphql": "^15.0.0 || ^16.0.0", "graphql-ws": "^5.5.5", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" }, "peerDependenciesMeta": { @@ -2233,38 +2233,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -3578,38 +3546,6 @@ "pretty-format": "^29.0.0" } }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/@types/jsdom": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", @@ -8203,18 +8139,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-circus/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8230,26 +8154,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -8328,18 +8232,6 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8360,26 +8252,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -8395,38 +8267,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -8455,38 +8295,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", @@ -8593,38 +8401,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -8640,38 +8416,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -8692,38 +8436,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -8938,38 +8650,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", @@ -9016,18 +8696,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -9040,26 +8708,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -10530,17 +10178,17 @@ } }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -10556,9 +10204,9 @@ } }, "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "node_modules/prompts": { @@ -10694,11 +10342,10 @@ }, "node_modules/react-19": { "name": "react", - "version": "19.0.0-rc-fb9a90fa48-20240614", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-fb9a90fa48-20240614.tgz", - "integrity": "sha512-nvE3Gy+IOIfH/DXhkyxFVQSrITarFcQz4+shzC/McxQXEUSonpw2oDy/Wi9hdDtV3hlP12VYuDL95iiBREedNQ==", + "version": "19.0.0-rc-378b305958-20240710", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-378b305958-20240710.tgz", + "integrity": "sha512-z+c5SdYuX74FSlyDcBWXMmR7KN30rpak804OtidcgxvYoyU43YCT0GWHubH6UvnNDvaLsr5nl66AKmC2y7VwYA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10743,24 +10390,22 @@ }, "node_modules/react-dom-19": { "name": "react-dom", - "version": "19.0.0-rc-fb9a90fa48-20240614", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-fb9a90fa48-20240614.tgz", - "integrity": "sha512-PoEsPe32F7KPLYOBvZfjylEI1B67N44PwY3lyvpmBkhlluLnLz0jH8q2Wg9YidAi6z0k3iUnNRm5x10wurzt9Q==", + "version": "19.0.0-rc-378b305958-20240710", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-378b305958-20240710.tgz", + "integrity": "sha512-WpE0+4pnFMyuC9WxJGj7+XysxNChbwnCcFS7INR428/uUoECSTRmlQt05hTGFIyfpYBO1zGlXfMBAkrFmqh3wg==", "dev": true, - "license": "MIT", "dependencies": { - "scheduler": "0.25.0-rc-fb9a90fa48-20240614" + "scheduler": "0.25.0-rc-378b305958-20240710" }, "peerDependencies": { - "react": "19.0.0-rc-fb9a90fa48-20240614" + "react": "19.0.0-rc-378b305958-20240710" } }, "node_modules/react-dom-19/node_modules/scheduler": { - "version": "0.25.0-rc-fb9a90fa48-20240614", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-fb9a90fa48-20240614.tgz", - "integrity": "sha512-HHqQ/SqbeiDfXXVKgNxTpbQTD4n7IUb4hZATvHjp03jr3TF7igehCyHdOjeYTrzIseLO93cTTfSb5f4qWcirMQ==", - "dev": true, - "license": "MIT" + "version": "0.25.0-rc-378b305958-20240710", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-378b305958-20240710.tgz", + "integrity": "sha512-j6dzH6LeNjhKFSpxUiZT2E8tFvFTCQOn339mmzYA3QBLvIkgAtHzSrpdReKhFzw9x4hxazBjgE/4YwBLQBUNlw==", + "dev": true }, "node_modules/react-error-boundary": { "version": "4.0.13", diff --git a/package.json b/package.json index d82684919fc..6f8e2508b1d 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,8 @@ "peerDependencies": { "graphql": "^15.0.0 || ^16.0.0", "graphql-ws": "^5.5.5", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0", "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" }, "peerDependenciesMeta": { @@ -165,10 +165,10 @@ "prettier": "3.1.1", "react": "18.3.1", "react-17": "npm:react@^17", - "react-19": "npm:react@19.0.0-rc-fb9a90fa48-20240614", + "react-19": "npm:react@19.0.0-rc-378b305958-20240710", "react-dom": "18.3.1", "react-dom-17": "npm:react-dom@^17", - "react-dom-19": "npm:react-dom@19.0.0-rc-fb9a90fa48-20240614", + "react-dom-19": "npm:react-dom@19.0.0-rc-378b305958-20240710", "react-error-boundary": "4.0.13", "recast": "0.23.9", "resolve": "1.22.8", @@ -204,5 +204,8 @@ "**/*.js.map", "**/*.d.ts", "**/*.json" - ] + ], + "overrides": { + "pretty-format": "^29.7.0" + } } diff --git a/patches/pretty-format+29.7.0.patch b/patches/pretty-format+29.7.0.patch new file mode 100644 index 00000000000..d3d733f80fb --- /dev/null +++ b/patches/pretty-format+29.7.0.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/pretty-format/build/index.js b/node_modules/pretty-format/build/index.js +index 8d3a562..879eed7 100644 +--- a/node_modules/pretty-format/build/index.js ++++ b/node_modules/pretty-format/build/index.js +@@ -103,6 +103,9 @@ function printBasicValue(val, printFunctionName, escapeRegex, escapeString) { + if (val === null) { + return 'null'; + } ++ if (val.name === 'ApolloError' || val.name === 'GraphQLError') { ++ return null ++ } + const typeOf = typeof val; + if (typeOf === 'number') { + return printNumber(val); diff --git a/patches/react-dom-19+19.0.0-rc-378b305958-20240710.patch b/patches/react-dom-19+19.0.0-rc-378b305958-20240710.patch new file mode 100644 index 00000000000..fc7597f276a --- /dev/null +++ b/patches/react-dom-19+19.0.0-rc-378b305958-20240710.patch @@ -0,0 +1,88 @@ +diff --git a/node_modules/react-dom-19/cjs/react-dom-client.development.js b/node_modules/react-dom-19/cjs/react-dom-client.development.js +index f9ae214..c29f983 100644 +--- a/node_modules/react-dom-19/cjs/react-dom-client.development.js ++++ b/node_modules/react-dom-19/cjs/react-dom-client.development.js +@@ -14401,7 +14401,7 @@ + (lanes & RetryLanes) === lanes && + ((didTimeout = + globalMostRecentFallbackTime + +- FALLBACK_THROTTLE_MS - ++ (globalThis.REACT_FALLBACK_THROTTLE_MS || FALLBACK_THROTTLE_MS) - + now$1()), + 10 < didTimeout) + ) { +@@ -15599,7 +15599,7 @@ + (workInProgressRootExitStatus === RootSuspended && + (workInProgressRootRenderLanes & RetryLanes) === + workInProgressRootRenderLanes && +- now$1() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) ++ now$1() - globalMostRecentFallbackTime < (globalThis.REACT_FALLBACK_THROTTLE_MS || FALLBACK_THROTTLE_MS)) + ? (executionContext & RenderContext) === NoContext && + prepareFreshStack(root, 0) + : (workInProgressRootPingedLanes |= pingedLanes)); +diff --git a/node_modules/react-dom-19/cjs/react-dom-client.production.js b/node_modules/react-dom-19/cjs/react-dom-client.production.js +index b93642c..66bb184 100644 +--- a/node_modules/react-dom-19/cjs/react-dom-client.production.js ++++ b/node_modules/react-dom-19/cjs/react-dom-client.production.js +@@ -10071,7 +10071,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) { + } + if ( + (lanes & 62914560) === lanes && +- ((didTimeout = globalMostRecentFallbackTime + 300 - now()), ++ ((didTimeout = globalMostRecentFallbackTime + (globalThis.REACT_FALLBACK_THROTTLE_MS || 300) - now()), + 10 < didTimeout) + ) { + markRootSuspended( +@@ -10936,7 +10936,7 @@ function pingSuspendedRoot(root, wakeable, pingedLanes) { + (3 === workInProgressRootExitStatus && + (workInProgressRootRenderLanes & 62914560) === + workInProgressRootRenderLanes && +- 300 > now() - globalMostRecentFallbackTime) ++ (globalThis.REACT_FALLBACK_THROTTLE_MS || 300) > now() - globalMostRecentFallbackTime) + ? 0 === (executionContext & 2) && prepareFreshStack(root, 0) + : (workInProgressRootPingedLanes |= pingedLanes)); + ensureRootIsScheduled(root); +diff --git a/node_modules/react-dom-19/cjs/react-dom-profiling.development.js b/node_modules/react-dom-19/cjs/react-dom-profiling.development.js +index 9e4fe6a..a4bd41e 100644 +--- a/node_modules/react-dom-19/cjs/react-dom-profiling.development.js ++++ b/node_modules/react-dom-19/cjs/react-dom-profiling.development.js +@@ -14409,7 +14409,7 @@ + (lanes & RetryLanes) === lanes && + ((didTimeout = + globalMostRecentFallbackTime + +- FALLBACK_THROTTLE_MS - ++ (globalThis.REACT_FALLBACK_THROTTLE_MS || FALLBACK_THROTTLE_MS) - + now$1()), + 10 < didTimeout) + ) { +@@ -15611,7 +15611,7 @@ + (workInProgressRootExitStatus === RootSuspended && + (workInProgressRootRenderLanes & RetryLanes) === + workInProgressRootRenderLanes && +- now$1() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) ++ now$1() - globalMostRecentFallbackTime < (globalThis.REACT_FALLBACK_THROTTLE_MS || FALLBACK_THROTTLE_MS)) + ? (executionContext & RenderContext) === NoContext && + prepareFreshStack(root, 0) + : (workInProgressRootPingedLanes |= pingedLanes)); +diff --git a/node_modules/react-dom-19/cjs/react-dom-profiling.profiling.js b/node_modules/react-dom-19/cjs/react-dom-profiling.profiling.js +index 683c9d9..000bb78 100644 +--- a/node_modules/react-dom-19/cjs/react-dom-profiling.profiling.js ++++ b/node_modules/react-dom-19/cjs/react-dom-profiling.profiling.js +@@ -10600,7 +10600,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) { + } + if ( + (lanes & 62914560) === lanes && +- ((didTimeout = globalMostRecentFallbackTime + 300 - now$1()), ++ ((didTimeout = globalMostRecentFallbackTime + (globalThis.REACT_FALLBACK_THROTTLE_MS || 300) - now$1()), + 10 < didTimeout) + ) { + markRootSuspended( +@@ -11621,7 +11621,7 @@ function pingSuspendedRoot(root, wakeable, pingedLanes) { + (3 === workInProgressRootExitStatus && + (workInProgressRootRenderLanes & 62914560) === + workInProgressRootRenderLanes && +- 300 > now$1() - globalMostRecentFallbackTime) ++ (globalThis.REACT_FALLBACK_THROTTLE_MS || 300) > now$1() - globalMostRecentFallbackTime) + ? 0 === (executionContext & 2) && prepareFreshStack(root, 0) + : (workInProgressRootPingedLanes |= pingedLanes)); + ensureRootIsScheduled(root); diff --git a/src/__tests__/ApolloClient.ts b/src/__tests__/ApolloClient.ts index 8ba8ccf7ab5..bcf9978ef90 100644 --- a/src/__tests__/ApolloClient.ts +++ b/src/__tests__/ApolloClient.ts @@ -2419,6 +2419,102 @@ describe("ApolloClient", () => { new Error("Timeout waiting for next event") ); }); + it("works with `variables`", async () => { + const cache = new InMemoryCache(); + const client = new ApolloClient({ + cache, + link: ApolloLink.empty(), + }); + const ItemFragment = gql` + fragment ItemFragment on Item { + id + text(language: $language) + } + `; + + cache.writeFragment({ + fragment: ItemFragment, + data: { + __typename: "Item", + id: 5, + text: "Item #5", + }, + variables: { language: "Esperanto" }, + }); + + const observable = client.watchFragment({ + fragment: ItemFragment, + from: { __typename: "Item", id: 5 }, + variables: { language: "Esperanto" }, + }); + + const stream = new ObservableStream(observable); + + { + const result = await stream.takeNext(); + + expect(result).toStrictEqual({ + data: { + __typename: "Item", + id: 5, + text: "Item #5", + }, + complete: true, + }); + } + }); + it("supports the @includes directive with `variables`", async () => { + const cache = new InMemoryCache(); + const client = new ApolloClient({ + cache, + link: ApolloLink.empty(), + }); + const ItemFragment = gql` + fragment ItemFragment on Item { + id + text @include(if: $withText) + } + `; + + cache.writeFragment({ + fragment: ItemFragment, + data: { + __typename: "Item", + id: 5, + text: "Item #5", + }, + variables: { withText: true }, + }); + cache.writeFragment({ + fragment: ItemFragment, + data: { + __typename: "Item", + id: 5, + }, + variables: { withText: false }, + }); + + const observable = client.watchFragment({ + fragment: ItemFragment, + from: { __typename: "Item", id: 5 }, + variables: { withText: true }, + }); + + const stream = new ObservableStream(observable); + + { + const result = await stream.takeNext(); + + expect(result).toStrictEqual({ + data: { + __typename: "Item", + id: 5, + text: "Item #5", + }, + complete: true, + }); + } + }); }); describe("defaultOptions", () => { @@ -2639,7 +2735,9 @@ describe("ApolloClient", () => { expect(invariantDebugSpy).toHaveBeenCalledTimes(1); expect(invariantDebugSpy).toHaveBeenCalledWith( "In client.refetchQueries, Promise.all promise rejected with error %o", - new ApolloError({ errorMessage: "refetch failed" }) + new ApolloError({ + networkError: new Error("refetch failed"), + }) ); resolve(); } catch (err) { diff --git a/src/__tests__/__snapshots__/graphqlSubscriptions.ts.snap b/src/__tests__/__snapshots__/graphqlSubscriptions.ts.snap index c32d90c0954..cec1ed33bf9 100644 --- a/src/__tests__/__snapshots__/graphqlSubscriptions.ts.snap +++ b/src/__tests__/__snapshots__/graphqlSubscriptions.ts.snap @@ -1,5 +1,67 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GraphQL Subscriptions should throw an error if the result has errors on it 1`] = `[ApolloError: This is an error]`; +exports[`GraphQL Subscriptions should throw an error if the result has errors on it 1`] = ` +ApolloError { + "cause": Object { + "locations": Array [ + Object { + "column": 3, + "line": 2, + }, + ], + "message": "This is an error", + "path": Array [ + "result", + ], + }, + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [ + Object { + "locations": Array [ + Object { + "column": 3, + "line": 2, + }, + ], + "message": "This is an error", + "path": Array [ + "result", + ], + }, + ], + "message": "This is an error", + "name": "ApolloError", + "networkError": null, + "protocolErrors": Array [], +} +`; -exports[`GraphQL Subscriptions should throw an error if the result has protocolErrors on it 1`] = `[ApolloError: cannot read message from websocket]`; +exports[`GraphQL Subscriptions should throw an error if the result has protocolErrors on it 1`] = ` +ApolloError { + "cause": Object { + "extensions": Array [ + Object { + "code": "WEBSOCKET_MESSAGE_ERROR", + }, + ], + "message": "cannot read message from websocket", + }, + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "cannot read message from websocket", + "name": "ApolloError", + "networkError": null, + "protocolErrors": Array [ + Object { + "extensions": Array [ + Object { + "code": "WEBSOCKET_MESSAGE_ERROR", + }, + ], + "message": "cannot read message from websocket", + }, + ], +} +`; diff --git a/src/__tests__/client.ts b/src/__tests__/client.ts index cf8df19c268..fd49358b6d4 100644 --- a/src/__tests__/client.ts +++ b/src/__tests__/client.ts @@ -1,11 +1,11 @@ import { cloneDeep, assign } from "lodash"; import { GraphQLError, - ExecutionResult, DocumentNode, Kind, print, visit, + FormattedExecutionResult, } from "graphql"; import gql from "graphql-tag"; @@ -752,7 +752,7 @@ describe("client", () => { cache: new InMemoryCache({ addTypename: false }), }); - return client.query({ query }).then((result: ExecutionResult) => { + return client.query({ query }).then((result: FormattedExecutionResult) => { expect(result.data).toEqual(data); }); }); @@ -2855,7 +2855,7 @@ describe("client", () => { const lastError = observable.getLastError(); expect(lastError).toBeInstanceOf(ApolloError); - expect(lastError!.networkError).toEqual(error); + expect(lastError!.networkError).toEqual((error as any).networkError); const lastResult = observable.getLastResult(); expect(lastResult).toBeTruthy(); @@ -6411,7 +6411,7 @@ function clientRoundtrip( resolve: (result: any) => any, reject: (reason: any) => any, query: DocumentNode, - data: ExecutionResult, + data: FormattedExecutionResult, variables?: any, possibleTypes?: PossibleTypesMap ) { diff --git a/src/__tests__/graphqlSubscriptions.ts b/src/__tests__/graphqlSubscriptions.ts index 2f4d66228d9..d666ed22e65 100644 --- a/src/__tests__/graphqlSubscriptions.ts +++ b/src/__tests__/graphqlSubscriptions.ts @@ -7,6 +7,7 @@ import { QueryManager } from "../core/QueryManager"; import { itAsync, mockObservableLink } from "../testing"; import { GraphQLError } from "graphql"; import { spyOnConsole } from "../testing/internal"; +import { getDefaultOptionsForQueryManagerTests } from "../testing/core/mocking/mockQueryManager"; describe("GraphQL Subscriptions", () => { const results = [ @@ -103,10 +104,12 @@ describe("GraphQL Subscriptions", () => { itAsync("should multiplex subscriptions", (resolve, reject) => { const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); const obs = queryManager.startGraphQLSubscription(options); @@ -143,10 +146,12 @@ describe("GraphQL Subscriptions", () => { (resolve, reject) => { const link = mockObservableLink(); let numResults = 0; - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); // tslint:disable-next-line queryManager.startGraphQLSubscription(options).subscribe({ @@ -192,10 +197,12 @@ describe("GraphQL Subscriptions", () => { it("should throw an error if the result has errors on it", () => { const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); const obs = queryManager.startGraphQLSubscription(options); @@ -242,10 +249,12 @@ describe("GraphQL Subscriptions", () => { } `; const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache(), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache(), + }) + ); const obs = queryManager.startGraphQLSubscription({ query, @@ -289,10 +298,12 @@ describe("GraphQL Subscriptions", () => { } `; const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache(), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache(), + }) + ); const obs = queryManager.startGraphQLSubscription({ query, @@ -358,10 +369,12 @@ describe("GraphQL Subscriptions", () => { } `; const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache(), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache(), + }) + ); const obs = queryManager.startGraphQLSubscription({ query, @@ -400,10 +413,12 @@ describe("GraphQL Subscriptions", () => { } `; const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache(), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache(), + }) + ); const obs = queryManager.startGraphQLSubscription({ query, @@ -501,10 +516,12 @@ describe("GraphQL Subscriptions", () => { it("should throw an error if the result has protocolErrors on it", async () => { const link = mockObservableLink(); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); const obs = queryManager.startGraphQLSubscription(options); diff --git a/src/__tests__/optimistic.ts b/src/__tests__/optimistic.ts index 3cc984868ed..ed53dc8cf9c 100644 --- a/src/__tests__/optimistic.ts +++ b/src/__tests__/optimistic.ts @@ -9,6 +9,7 @@ import { ApolloLink, ApolloCache, MutationQueryReducersMap, + TypedDocumentNode, } from "../core"; import { QueryManager } from "../core/QueryManager"; @@ -1089,6 +1090,25 @@ describe("optimistic mutation results", () => { resolve(); } ); + + it("allows IgnoreModifier as return value when inferring from a TypedDocumentNode mutation", () => { + const mutation: TypedDocumentNode<{ bar: string }> = gql` + mutation foo { + foo { + bar + } + } + `; + + const client = new ApolloClient({ + cache: new InMemoryCache(), + }); + + client.mutate({ + mutation, + optimisticResponse: (vars, { IGNORE }) => IGNORE, + }); + }); }); describe("optimistic updates using `updateQueries`", () => { diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index d25c5928332..cb953152c45 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -69,17 +69,6 @@ export interface WatchFragmentOptions { * @docGroup 2. Cache options */ optimistic?: boolean; - /** - * @deprecated - * Using `canonizeResults` can result in memory leaks so we generally do not - * recommend using this option anymore. - * A future version of Apollo Client will contain a similar feature. - * - * Whether to canonize cache results before returning them. Canonization - * takes some extra time, but it speeds up future deep equality comparisons. - * Defaults to false. - */ - canonizeResults?: boolean; } /** @@ -228,10 +217,17 @@ export abstract class ApolloCache implements DataProxy { public watchFragment( options: WatchFragmentOptions ): Observable> { - const { fragment, fragmentName, from, optimistic = true } = options; + const { + fragment, + fragmentName, + from, + optimistic = true, + ...otherOptions + } = options; const query = this.getFragmentDoc(fragment, fragmentName); const diffOptions: Cache.DiffOptions = { + ...otherOptions, returnPartialData: true, id: typeof from === "string" ? from : this.identify(from), query, diff --git a/src/config/jest/areApolloErrorsEqual.ts b/src/config/jest/areApolloErrorsEqual.ts new file mode 100644 index 00000000000..0724c023986 --- /dev/null +++ b/src/config/jest/areApolloErrorsEqual.ts @@ -0,0 +1,26 @@ +import type { ApolloError } from "../../errors/index.js"; +import type { Tester } from "@jest/expect-utils"; +function isApolloError(e: any): e is ApolloError { + return e instanceof Error && e.name == "ApolloError"; +} + +export const areApolloErrorsEqual: Tester = function (a, b, customTesters) { + const isAApolloError = isApolloError(a); + const isBApolloError = isApolloError(b); + + if (isAApolloError && isBApolloError) { + return ( + a.message === b.message && + this.equals(a.graphQLErrors, b.graphQLErrors, customTesters) && + this.equals(a.protocolErrors, b.protocolErrors, customTesters) && + this.equals(a.clientErrors, b.clientErrors, customTesters) && + this.equals(a.networkError, b.networkError, customTesters) && + this.equals(a.cause, b.cause, customTesters) && + this.equals(a.extraInfo, b.extraInfo, customTesters) + ); + } else if (isAApolloError === isBApolloError) { + return undefined; + } else { + return false; + } +}; diff --git a/src/config/jest/areGraphQlErrorsEqual.ts b/src/config/jest/areGraphQlErrorsEqual.ts new file mode 100644 index 00000000000..44b681228ed --- /dev/null +++ b/src/config/jest/areGraphQlErrorsEqual.ts @@ -0,0 +1,12 @@ +import { GraphQLError } from "graphql"; +import type { Tester } from "@jest/expect-utils"; + +export const areGraphQLErrorsEqual: Tester = function (a, b, customTesters) { + if (a instanceof GraphQLError || b instanceof GraphQLError) { + return this.equals( + a instanceof GraphQLError ? a.toJSON() : a, + b instanceof GraphQLError ? b.toJSON() : b, + customTesters + ); + } +}; diff --git a/src/config/jest/setup.ts b/src/config/jest/setup.ts index f5ad519835f..e6bbe2c43c0 100644 --- a/src/config/jest/setup.ts +++ b/src/config/jest/setup.ts @@ -2,6 +2,8 @@ import gql from "graphql-tag"; import "@testing-library/jest-dom"; import { loadErrorMessageHandler } from "../../dev/loadErrorMessageHandler.js"; import "../../testing/matchers/index.js"; +import { areApolloErrorsEqual } from "./areApolloErrorsEqual.js"; +import { areGraphQLErrorsEqual } from "./areGraphQlErrorsEqual.js"; // Turn off warnings for repeated fragment names gql.disableFragmentWarnings(); @@ -27,3 +29,9 @@ if (!Symbol.asyncDispose) { value: Symbol("asyncDispose"), }); } + +// @ts-ignore +expect.addEqualityTesters([areApolloErrorsEqual, areGraphQLErrorsEqual]); + +// @ts-ignore +globalThis.REACT_FALLBACK_THROTTLE_MS = 10; diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index 05e713d520a..b9c5c7883fc 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -1,6 +1,6 @@ import { invariant, newInvariantError } from "../utilities/globals/index.js"; -import type { ExecutionResult, DocumentNode } from "graphql"; +import type { DocumentNode, FormattedExecutionResult } from "graphql"; import type { FetchResult, GraphQLRequest } from "../link/core/index.js"; import { ApolloLink, execute } from "../link/core/index.js"; @@ -41,6 +41,22 @@ export interface DefaultOptions { mutate?: Partial>; } +export interface DevtoolsOptions { + /** + * If `true`, the [Apollo Client Devtools](https://www.apollographql.com/docs/react/development-testing/developer-tooling/#apollo-client-devtools) browser extension can connect to this `ApolloClient` instance. + * + * The default value is `false` in production and `true` in development if there is a `window` object. + */ + enabled?: boolean; + + /** + * Optional name for this `ApolloClient` instance in the devtools. This is + * useful when you instantiate multiple clients and want to be able to + * identify them by name. + */ + name?: string; +} + let hasSuggestedDevtools = false; export interface ApolloClientOptions { @@ -85,6 +101,7 @@ export interface ApolloClientOptions { * If `true`, the [Apollo Client Devtools](https://www.apollographql.com/docs/react/development-testing/developer-tooling/#apollo-client-devtools) browser extension can connect to Apollo Client. * * The default value is `false` in production and `true` in development (if there is a `window` object). + * @deprecated Please use the `devtools.enabled` option. */ connectToDevTools?: boolean; /** @@ -120,6 +137,13 @@ export interface ApolloClientOptions { */ version?: string; documentTransform?: DocumentTransform; + + /** + * Configuration used by the [Apollo Client Devtools extension](https://www.apollographql.com/docs/react/development-testing/developer-tooling/#apollo-client-devtools) for this client. + * + * @since 3.11.0 + */ + devtools?: DevtoolsOptions; } // Though mergeOptions now resides in @apollo/client/utilities, it was @@ -148,6 +172,7 @@ export class ApolloClient implements DataProxy { public queryDeduplication: boolean; public defaultOptions: DefaultOptions; public readonly typeDefs: ApolloClientOptions["typeDefs"]; + public readonly devtoolsConfig: DevtoolsOptions; private queryManager: QueryManager; private devToolsHookCb?: Function; @@ -201,9 +226,7 @@ export class ApolloClient implements DataProxy { // Expose the client instance as window.__APOLLO_CLIENT__ and call // onBroadcast in queryManager.broadcastQueries to enable browser // devtools, but disable them by default in production. - connectToDevTools = typeof window === "object" && - !(window as any).__APOLLO_CLIENT__ && - __DEV__, + connectToDevTools, queryDeduplication = true, defaultOptions, defaultContext, @@ -213,6 +236,7 @@ export class ApolloClient implements DataProxy { fragmentMatcher, name: clientAwarenessName, version: clientAwarenessVersion, + devtools, } = options; let { link } = options; @@ -228,6 +252,17 @@ export class ApolloClient implements DataProxy { this.queryDeduplication = queryDeduplication; this.defaultOptions = defaultOptions || Object.create(null); this.typeDefs = typeDefs; + this.devtoolsConfig = { + ...devtools, + enabled: devtools?.enabled || connectToDevTools, + }; + + if (this.devtoolsConfig.enabled === undefined) { + this.devtoolsConfig.enabled = + typeof window === "object" && + (window as any).__APOLLO_CLIENT__ && + __DEV__; + } if (ssrForceFetchDelay) { setTimeout( @@ -267,7 +302,7 @@ export class ApolloClient implements DataProxy { localState: this.localState, assumeImmutableResults, onBroadcast: - connectToDevTools ? + this.devtoolsConfig.enabled ? () => { if (this.devToolsHookCb) { this.devToolsHookCb({ @@ -283,7 +318,7 @@ export class ApolloClient implements DataProxy { : void 0, }); - if (connectToDevTools) this.connectToDevTools(); + if (this.devtoolsConfig.enabled) this.connectToDevTools(); } private connectToDevTools() { @@ -571,7 +606,9 @@ export class ApolloClient implements DataProxy { this.devToolsHookCb = cb; } - public __requestRaw(payload: GraphQLRequest): Observable { + public __requestRaw( + payload: GraphQLRequest + ): Observable { return execute(this.link, payload); } diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 47deef22483..7a419ff078e 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -163,6 +163,8 @@ export class ObservableQuery< this.waitForOwnResult = skipCacheDataFor(options.fetchPolicy); this.isTornDown = false; + this.subscribeToMore = this.subscribeToMore.bind(this); + const { watchQuery: { fetchPolicy: defaultFetchPolicy = "cache-first" } = {}, } = queryManager.defaultOptions; @@ -908,7 +910,10 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, options.fetchPolicy !== "standby" && // If we're changing the fetchPolicy anyway, don't try to change it here // using applyNextFetchPolicy. The explicit options.fetchPolicy wins. - options.fetchPolicy === oldFetchPolicy + (options.fetchPolicy === oldFetchPolicy || + // A `nextFetchPolicy` function has even higher priority, though, + // so in that case `applyNextFetchPolicy` must be called. + typeof options.nextFetchPolicy === "function") ) { this.applyNextFetchPolicy("variables-changed", options); if (newNetworkStatus === void 0) { diff --git a/src/core/QueryInfo.ts b/src/core/QueryInfo.ts index 2e8c149eedd..d4058bae2af 100644 --- a/src/core/QueryInfo.ts +++ b/src/core/QueryInfo.ts @@ -1,4 +1,4 @@ -import type { DocumentNode, GraphQLError } from "graphql"; +import type { DocumentNode, GraphQLFormattedError } from "graphql"; import { equal } from "@wry/equality"; import type { Cache, ApolloCache } from "../cache/index.js"; @@ -82,7 +82,7 @@ export class QueryInfo { variables?: Record; networkStatus?: NetworkStatus; networkError?: Error | null; - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; stopped = false; private cache: ApolloCache; diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 92029d9a6f1..6ba645285c8 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -64,7 +64,7 @@ import type { InternalRefetchQueriesMap, DefaultContext, } from "./types.js"; -import { LocalState } from "./LocalState.js"; +import type { LocalState } from "./LocalState.js"; import type { QueryStoreValue } from "./QueryInfo.js"; import { @@ -105,6 +105,20 @@ import type { DefaultOptions } from "./ApolloClient.js"; import { Trie } from "@wry/trie"; import { AutoCleanedWeakCache, cacheSizes } from "../utilities/index.js"; +export interface QueryManagerOptions { + cache: ApolloCache; + link: ApolloLink; + defaultOptions: DefaultOptions; + documentTransform: DocumentTransform | null | undefined; + queryDeduplication: boolean; + onBroadcast: undefined | (() => void); + ssrMode: boolean; + clientAwareness: Record; + localState: LocalState; + assumeImmutableResults: boolean; + defaultContext: Partial | undefined; +} + export class QueryManager { public cache: ApolloCache; public link: ApolloLink; @@ -134,45 +148,22 @@ export class QueryManager { // @apollo/experimental-nextjs-app-support can access type info. protected fetchCancelFns = new Map any>(); - constructor({ - cache, - link, - defaultOptions, - documentTransform, - queryDeduplication = false, - onBroadcast, - ssrMode = false, - clientAwareness = {}, - localState, - assumeImmutableResults = !!cache.assumeImmutableResults, - defaultContext, - }: { - cache: ApolloCache; - link: ApolloLink; - defaultOptions?: DefaultOptions; - documentTransform?: DocumentTransform; - queryDeduplication?: boolean; - onBroadcast?: () => void; - ssrMode?: boolean; - clientAwareness?: Record; - localState?: LocalState; - assumeImmutableResults?: boolean; - defaultContext?: Partial; - }) { + constructor(options: QueryManagerOptions) { const defaultDocumentTransform = new DocumentTransform( (document) => this.cache.transformDocument(document), // Allow the apollo cache to manage its own transform caches { cache: false } ); - this.cache = cache; - this.link = link; - this.defaultOptions = defaultOptions || Object.create(null); - this.queryDeduplication = queryDeduplication; - this.clientAwareness = clientAwareness; - this.localState = localState || new LocalState({ cache }); - this.ssrMode = ssrMode; - this.assumeImmutableResults = assumeImmutableResults; + this.cache = options.cache; + this.link = options.link; + this.defaultOptions = options.defaultOptions; + this.queryDeduplication = options.queryDeduplication; + this.clientAwareness = options.clientAwareness; + this.localState = options.localState; + this.ssrMode = options.ssrMode; + this.assumeImmutableResults = options.assumeImmutableResults; + const documentTransform = options.documentTransform; this.documentTransform = documentTransform ? defaultDocumentTransform @@ -183,9 +174,9 @@ export class QueryManager { // selections and fragments from the fragment registry. .concat(defaultDocumentTransform) : defaultDocumentTransform; - this.defaultContext = defaultContext || Object.create(null); + this.defaultContext = options.defaultContext || Object.create(null); - if ((this.onBroadcast = onBroadcast)) { + if ((this.onBroadcast = options.onBroadcast)) { this.mutationStore = Object.create(null); } } @@ -293,6 +284,7 @@ export class QueryManager { optimisticResponse: isOptimistic ? optimisticResponse : void 0, }, variables, + {}, false ), @@ -981,52 +973,55 @@ export class QueryManager { errorPolicy = "none", variables, context = {}, + extensions = {}, }: SubscriptionOptions): Observable> { query = this.transform(query); variables = this.getVariables(query, variables); const makeObservable = (variables: OperationVariables) => - this.getObservableFromLink(query, context, variables).map((result) => { - if (fetchPolicy !== "no-cache") { - // the subscription interface should handle not sending us results we no longer subscribe to. - // XXX I don't think we ever send in an object with errors, but we might in the future... - if (shouldWriteResult(result, errorPolicy)) { - this.cache.write({ - query, - result: result.data, - dataId: "ROOT_SUBSCRIPTION", - variables: variables, - }); + this.getObservableFromLink(query, context, variables, extensions).map( + (result) => { + if (fetchPolicy !== "no-cache") { + // the subscription interface should handle not sending us results we no longer subscribe to. + // XXX I don't think we ever send in an object with errors, but we might in the future... + if (shouldWriteResult(result, errorPolicy)) { + this.cache.write({ + query, + result: result.data, + dataId: "ROOT_SUBSCRIPTION", + variables: variables, + }); + } + + this.broadcastQueries(); } - this.broadcastQueries(); - } + const hasErrors = graphQLResultHasError(result); + const hasProtocolErrors = graphQLResultHasProtocolErrors(result); + if (hasErrors || hasProtocolErrors) { + const errors: ApolloErrorOptions = {}; + if (hasErrors) { + errors.graphQLErrors = result.errors; + } + if (hasProtocolErrors) { + errors.protocolErrors = result.extensions[PROTOCOL_ERRORS_SYMBOL]; + } - const hasErrors = graphQLResultHasError(result); - const hasProtocolErrors = graphQLResultHasProtocolErrors(result); - if (hasErrors || hasProtocolErrors) { - const errors: ApolloErrorOptions = {}; - if (hasErrors) { - errors.graphQLErrors = result.errors; - } - if (hasProtocolErrors) { - errors.protocolErrors = result.extensions[PROTOCOL_ERRORS_SYMBOL]; + // `errorPolicy` is a mechanism for handling GraphQL errors, according + // to our documentation, so we throw protocol errors regardless of the + // set error policy. + if (errorPolicy === "none" || hasProtocolErrors) { + throw new ApolloError(errors); + } } - // `errorPolicy` is a mechanism for handling GraphQL errors, according - // to our documentation, so we throw protocol errors regardless of the - // set error policy. - if (errorPolicy === "none" || hasProtocolErrors) { - throw new ApolloError(errors); + if (errorPolicy === "ignore") { + delete result.errors; } - } - if (errorPolicy === "ignore") { - delete result.errors; + return result; } - - return result; - }); + ); if (this.getDocumentInfo(query).hasClientExports) { const observablePromise = this.localState @@ -1088,6 +1083,7 @@ export class QueryManager { query: DocumentNode, context: any, variables?: OperationVariables, + extensions?: Record, // Prefer context.queryDeduplication if specified. deduplication: boolean = context?.queryDeduplication ?? this.queryDeduplication @@ -1106,6 +1102,7 @@ export class QueryManager { ...context, forceFetch: !deduplication, }), + extensions, }; context = operation.context; diff --git a/src/core/__tests__/ObservableQuery.ts b/src/core/__tests__/ObservableQuery.ts index 98c1f735026..3b639e14b58 100644 --- a/src/core/__tests__/ObservableQuery.ts +++ b/src/core/__tests__/ObservableQuery.ts @@ -28,7 +28,9 @@ import { subscribeAndCount, wait, } from "../../testing"; -import mockQueryManager from "../../testing/core/mocking/mockQueryManager"; +import mockQueryManager, { + getDefaultOptionsForQueryManagerTests, +} from "../../testing/core/mocking/mockQueryManager"; import mockWatchQuery from "../../testing/core/mocking/mockWatchQuery"; import wrap from "../../testing/core/wrap"; @@ -92,15 +94,20 @@ describe("ObservableQuery", () => { const error = new GraphQLError("is offline.", undefined, null, null, [ "people_one", ]); + const wrappedError = new ApolloError({ + graphQLErrors: [error], + }); const createQueryManager = ({ link }: { link: ApolloLink }) => { - return new QueryManager({ - link, - assumeImmutableResults: true, - cache: new InMemoryCache({ - addTypename: false, - }), - }); + return new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + assumeImmutableResults: true, + cache: new InMemoryCache({ + addTypename: false, + }), + }) + ); }; describe("setOptions", () => { @@ -1090,14 +1097,16 @@ describe("ObservableQuery", () => { it("calling refetch with different variables before the query itself resolved will only yield the result for the new variables", async () => { const observers: SubscriptionObserver>[] = []; - const queryManager = new QueryManager({ - cache: new InMemoryCache(), - link: new ApolloLink((operation, forward) => { - return new Observable((observer) => { - observers.push(observer); - }); - }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache(), + link: new ApolloLink((operation, forward) => { + return new Observable((observer) => { + observers.push(observer); + }); + }), + }) + ); const observableQuery = queryManager.watchQuery({ query, variables: { id: 1 }, @@ -1125,14 +1134,16 @@ describe("ObservableQuery", () => { it("calling refetch multiple times with different variables will return only results for the most recent variables", async () => { const observers: SubscriptionObserver>[] = []; - const queryManager = new QueryManager({ - cache: new InMemoryCache(), - link: new ApolloLink((operation, forward) => { - return new Observable((observer) => { - observers.push(observer); - }); - }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache(), + link: new ApolloLink((operation, forward) => { + return new Observable((observer) => { + observers.push(observer); + }); + }), + }) + ); const observableQuery = queryManager.watchQuery({ query, variables: { id: 1 }, @@ -1697,10 +1708,12 @@ describe("ObservableQuery", () => { // manually to be able to turn off warnings for this test. const mocks = [makeMock("a", "b", "c"), makeMock("d", "e")]; const firstRequest = mocks[0].request; - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link: new MockLink(mocks, true, { showWarnings: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link: new MockLink(mocks, true, { showWarnings: false }), + }) + ); const observableWithVarsVar = queryManager.watchQuery({ query: firstRequest.query, @@ -2136,9 +2149,9 @@ describe("ObservableQuery", () => { .result() .then(() => reject("Observable did not error when it should have")) .catch((currentError) => { - expect(currentError).toEqual(error); + expect(currentError).toEqual(wrappedError); const lastError = observable.getLastError(); - expect(lastError).toEqual(error); + expect(lastError).toEqual(wrappedError); resolve(); }) .catch(reject); @@ -2177,9 +2190,9 @@ describe("ObservableQuery", () => { ) ) .catch((currentError) => { - expect(currentError).toEqual(error); + expect(currentError).toEqual(wrappedError); const lastError = observable.getLastError(); - expect(lastError).toEqual(error); + expect(lastError).toEqual(wrappedError); resolve(); }) .catch(reject) @@ -2738,7 +2751,9 @@ describe("ObservableQuery", () => { const cache = new InMemoryCache({}); cache.writeQuery({ query, data: cacheValues.initial }); - const queryManager = new QueryManager({ link, cache }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ link, cache }) + ); const observableQuery = queryManager.watchQuery({ query, fetchPolicy, diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts index 49068a77548..e0bda61a9a5 100644 --- a/src/core/__tests__/QueryManager/index.ts +++ b/src/core/__tests__/QueryManager/index.ts @@ -18,7 +18,9 @@ import { } from "../../../cache/inmemory/types"; // mocks -import mockQueryManager from "../../../testing/core/mocking/mockQueryManager"; +import mockQueryManager, { + getDefaultOptionsForQueryManagerTests, +} from "../../../testing/core/mocking/mockQueryManager"; import mockWatchQuery from "../../../testing/core/mocking/mockWatchQuery"; import { MockApolloLink, @@ -89,14 +91,16 @@ describe("QueryManager", () => { clientAwareness?: { [key: string]: string }; queryDeduplication?: boolean; }) => { - return new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false, ...config }), - clientAwareness, - queryDeduplication, - // Enable client.queryManager.mutationStore tracking. - onBroadcast() {}, - }); + return new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false, ...config }), + clientAwareness, + queryDeduplication, + // Enable client.queryManager.mutationStore tracking. + onBroadcast() {}, + }) + ); }; // Helper method that sets up a mockQueryManager and then passes on the @@ -539,10 +543,12 @@ describe("QueryManager", () => { }); }); - const mockedQueryManger = new QueryManager({ - link: mockedSingleLink, - cache: new InMemoryCache({ addTypename: false }), - }); + const mockedQueryManger = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link: mockedSingleLink, + cache: new InMemoryCache({ addTypename: false }), + }) + ); const observableQuery = mockedQueryManger.watchQuery({ query: request.query, @@ -620,22 +626,24 @@ describe("QueryManager", () => { }); }); - const mockedQueryManger = new QueryManager({ - link: mockedSingleLink, - cache: new InMemoryCache({ addTypename: false }), - defaultOptions: { - watchQuery: { - fetchPolicy: "cache-and-network", - returnPartialData: false, - partialRefetch: true, - notifyOnNetworkStatusChange: true, - }, - query: { - fetchPolicy: "network-only", + const mockedQueryManger = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link: mockedSingleLink, + cache: new InMemoryCache({ addTypename: false }), + defaultOptions: { + watchQuery: { + fetchPolicy: "cache-and-network", + returnPartialData: false, + partialRefetch: true, + notifyOnNetworkStatusChange: true, + }, + query: { + fetchPolicy: "network-only", + }, }, - }, - queryDeduplication: false, - }); + queryDeduplication: false, + }) + ); const observableQuery = mockedQueryManger.watchQuery< (typeof expResult)["data"], @@ -2893,14 +2901,16 @@ describe("QueryManager", () => { age: "32", }, }; - const queryManager = new QueryManager({ - link: mockSingleLink( - { request: { query: queryA }, result: { data: dataA } }, - { request: { query: queryB }, result: { data: dataB }, delay: 20 } - ).setOnError(reject), - cache: new InMemoryCache({}), - ssrMode: true, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link: mockSingleLink( + { request: { query: queryA }, result: { data: dataA } }, + { request: { query: queryB }, result: { data: dataB }, delay: 20 } + ).setOnError(reject), + cache: new InMemoryCache({}), + ssrMode: true, + }) + ); const observableA = queryManager.watchQuery({ query: queryA, @@ -3070,24 +3080,26 @@ describe("QueryManager", () => { }, }; - const queryManager = new QueryManager({ - link: mockSingleLink( - { - request: { query, variables }, - result: { data: data1 }, - }, - { - request: { query, variables }, - result: { data: data2 }, - }, - { - request: { query, variables }, - result: { data: data2 }, - } - ).setOnError(reject), - cache: new InMemoryCache({ addTypename: false }), - ssrMode: true, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link: mockSingleLink( + { + request: { query, variables }, + result: { data: data1 }, + }, + { + request: { query, variables }, + result: { data: data2 }, + }, + { + request: { query, variables }, + result: { data: data2 }, + } + ).setOnError(reject), + cache: new InMemoryCache({ addTypename: false }), + ssrMode: true, + }) + ); const observable = queryManager.watchQuery({ query, @@ -5984,10 +5996,12 @@ describe("QueryManager", () => { ).setOnError(reject); const cache = new InMemoryCache(); - const queryManager = new QueryManager({ - link, - cache, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache, + }) + ); return queryManager .query({ query: query1 }) diff --git a/src/core/__tests__/QueryManager/links.ts b/src/core/__tests__/QueryManager/links.ts index 7719a8f201f..53d3b22f6bb 100644 --- a/src/core/__tests__/QueryManager/links.ts +++ b/src/core/__tests__/QueryManager/links.ts @@ -15,6 +15,7 @@ import { itAsync, MockSubscriptionLink } from "../../../testing/core"; // core import { QueryManager } from "../../QueryManager"; import { NextLink, Operation, Reference } from "../../../core"; +import { getDefaultOptionsForQueryManagerTests } from "../../../testing/core/mocking/mockQueryManager"; describe("Link interactions", () => { itAsync( @@ -56,10 +57,12 @@ describe("Link interactions", () => { const mockLink = new MockSubscriptionLink(); const link = ApolloLink.from([evictionLink, mockLink]); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -102,10 +105,12 @@ describe("Link interactions", () => { }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -176,10 +181,12 @@ describe("Link interactions", () => { }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -256,10 +263,12 @@ describe("Link interactions", () => { const mockLink = new MockSubscriptionLink(); const link = ApolloLink.from([evictionLink, mockLink]); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); queryManager.mutate({ mutation }); @@ -298,10 +307,12 @@ describe("Link interactions", () => { const mockLink = new MockSubscriptionLink(); const link = ApolloLink.from([evictionLink, mockLink]); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); queryManager.mutate({ mutation, context: { planet: "Tatooine" } }); @@ -341,34 +352,36 @@ describe("Link interactions", () => { return Observable.of({ data: bookData }); }); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ - typePolicies: { - Query: { - fields: { - book(_, { args, toReference, readField }) { - if (!args) { - throw new Error("arg must never be null"); - } - - const ref = toReference({ __typename: "Book", id: args.id }); - if (!ref) { - throw new Error("ref must never be null"); - } - - expect(ref).toEqual({ __ref: `Book:${args.id}` }); - const found = readField("books")!.find( - (book) => book.__ref === ref.__ref - ); - expect(found).toBeTruthy(); - return found; + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ + typePolicies: { + Query: { + fields: { + book(_, { args, toReference, readField }) { + if (!args) { + throw new Error("arg must never be null"); + } + + const ref = toReference({ __typename: "Book", id: args.id }); + if (!ref) { + throw new Error("ref must never be null"); + } + + expect(ref).toEqual({ __ref: `Book:${args.id}` }); + const found = readField("books")!.find( + (book) => book.__ref === ref.__ref + ); + expect(found).toBeTruthy(); + return found; + }, }, }, }, - }, - }), - }); + }), + }) + ); await queryManager.query({ query }); @@ -418,10 +431,12 @@ describe("Link interactions", () => { }); }); - const queryManager = new QueryManager({ - link, - cache: new InMemoryCache({ addTypename: false }), - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); await queryManager.query({ query }); diff --git a/src/core/__tests__/QueryManager/multiple-results.ts b/src/core/__tests__/QueryManager/multiple-results.ts index b72f382dca5..1d49bbb770b 100644 --- a/src/core/__tests__/QueryManager/multiple-results.ts +++ b/src/core/__tests__/QueryManager/multiple-results.ts @@ -8,6 +8,7 @@ import { itAsync, MockSubscriptionLink } from "../../../testing/core"; // core import { QueryManager } from "../../QueryManager"; import { GraphQLError } from "graphql"; +import { getDefaultOptionsForQueryManagerTests } from "../../../testing/core/mocking/mockQueryManager"; describe("mutiple results", () => { itAsync("allows multiple query results from link", (resolve, reject) => { @@ -37,10 +38,12 @@ describe("mutiple results", () => { }, }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -96,10 +99,12 @@ describe("mutiple results", () => { }, }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -166,10 +171,12 @@ describe("mutiple results", () => { }, }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -242,10 +249,12 @@ describe("mutiple results", () => { }, }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, @@ -313,10 +322,12 @@ describe("mutiple results", () => { }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); const observable = queryManager.watchQuery({ query, diff --git a/src/core/__tests__/QueryManager/recycler.ts b/src/core/__tests__/QueryManager/recycler.ts index e650d0a126e..fccddc901de 100644 --- a/src/core/__tests__/QueryManager/recycler.ts +++ b/src/core/__tests__/QueryManager/recycler.ts @@ -16,6 +16,7 @@ import { InMemoryCache } from "../../../cache"; // mocks import { MockSubscriptionLink } from "../../../testing/core"; +import { getDefaultOptionsForQueryManagerTests } from "../../../testing/core/mocking/mockQueryManager"; describe("Subscription lifecycles", () => { itAsync( @@ -40,10 +41,12 @@ describe("Subscription lifecycles", () => { }; const link = new MockSubscriptionLink(); - const queryManager = new QueryManager({ - cache: new InMemoryCache({ addTypename: false }), - link, - }); + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + cache: new InMemoryCache({ addTypename: false }), + link, + }) + ); // step 1, get some data const observable = queryManager.watchQuery({ diff --git a/src/core/types.ts b/src/core/types.ts index 8085d013839..fefe245b04c 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,4 +1,4 @@ -import type { DocumentNode, GraphQLError } from "graphql"; +import type { DocumentNode, GraphQLFormattedError } from "graphql"; import type { ApolloCache } from "../cache/index.js"; import type { FetchResult } from "../link/core/index.js"; @@ -145,7 +145,7 @@ export interface ApolloQueryResult { * A list of any errors that occurred during server-side execution of a GraphQL operation. * See https://www.apollographql.com/docs/react/data/error-handling/ for more information. */ - errors?: ReadonlyArray; + errors?: ReadonlyArray; /** * The single Error object that is passed to onError and useQuery hooks, and is often thrown during manual `client.query` calls. * This will contain both a NetworkError field and any GraphQLErrors. diff --git a/src/core/watchQueryOptions.ts b/src/core/watchQueryOptions.ts index 5810c6464c4..0627f04ebc5 100644 --- a/src/core/watchQueryOptions.ts +++ b/src/core/watchQueryOptions.ts @@ -206,6 +206,9 @@ export interface SubscriptionOptions< /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#context:member} */ context?: DefaultContext; + + /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#extensions:member} */ + extensions?: Record; } export interface MutationBaseOptions< @@ -217,7 +220,10 @@ export interface MutationBaseOptions< /** {@inheritDoc @apollo/client!MutationOptionsDocumentation#optimisticResponse:member} */ optimisticResponse?: | TData - | ((vars: TVariables, { IGNORE }: { IGNORE: IgnoreModifier }) => TData); + | (( + vars: TVariables, + { IGNORE }: { IGNORE: IgnoreModifier } + ) => TData | IgnoreModifier); /** {@inheritDoc @apollo/client!MutationOptionsDocumentation#updateQueries:member} */ updateQueries?: MutationQueryReducersMap; diff --git a/src/errors/__tests__/ApolloError.ts b/src/errors/__tests__/ApolloError.ts index 1881bade231..e672536f667 100644 --- a/src/errors/__tests__/ApolloError.ts +++ b/src/errors/__tests__/ApolloError.ts @@ -1,11 +1,10 @@ import { ApolloError } from ".."; -import { GraphQLError } from "graphql"; describe("ApolloError", () => { it("should construct itself correctly", () => { const graphQLErrors = [ - new GraphQLError("Something went wrong with GraphQL"), - new GraphQLError("Something else went wrong with GraphQL"), + { message: "Something went wrong with GraphQL" }, + { message: "Something else went wrong with GraphQL" }, ]; const protocolErrors = [ { @@ -41,7 +40,7 @@ describe("ApolloError", () => { }); it("should add a graphql error to the message", () => { - const graphQLErrors = [new GraphQLError("this is an error message")]; + const graphQLErrors = [{ message: "this is an error message" }]; const apolloError = new ApolloError({ graphQLErrors, }); @@ -51,8 +50,8 @@ describe("ApolloError", () => { it("should add multiple graphql errors to the message", () => { const graphQLErrors = [ - new GraphQLError("this is new"), - new GraphQLError("this is old"), + { message: "this is new" }, + { message: "this is old" }, ]; const apolloError = new ApolloError({ graphQLErrors, @@ -64,7 +63,7 @@ describe("ApolloError", () => { }); it("should add both network and graphql errors to the message", () => { - const graphQLErrors = [new GraphQLError("graphql error message")]; + const graphQLErrors = [{ message: "graphql error message" }]; const networkError = new Error("network error message"); const apolloError = new ApolloError({ graphQLErrors, @@ -77,7 +76,7 @@ describe("ApolloError", () => { }); it("should add both protocol and graphql errors to the message", () => { - const graphQLErrors = [new GraphQLError("graphql error message")]; + const graphQLErrors = [{ message: "graphql error message" }]; const protocolErrors = [ { message: "cannot read message from websocket", @@ -99,7 +98,7 @@ describe("ApolloError", () => { }); it("should contain a stack trace", () => { - const graphQLErrors = [new GraphQLError("graphql error message")]; + const graphQLErrors = [{ message: "graphql error message" }]; const networkError = new Error("network error message"); const apolloError = new ApolloError({ graphQLErrors, diff --git a/src/errors/index.ts b/src/errors/index.ts index 69277055500..11a14007734 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1,6 +1,10 @@ import "../utilities/globals/index.js"; -import type { GraphQLError, GraphQLErrorExtensions } from "graphql"; +import type { + GraphQLError, + GraphQLErrorExtensions, + GraphQLFormattedError, +} from "graphql"; import { isNonNullObject } from "../utilities/index.js"; import type { ServerParseError } from "../link/http/index.js"; @@ -17,7 +21,7 @@ type FetchResultWithSymbolExtensions = FetchResult & { }; export interface ApolloErrorOptions { - graphQLErrors?: ReadonlyArray; + graphQLErrors?: ReadonlyArray; protocolErrors?: ReadonlyArray<{ message: string; extensions?: GraphQLErrorExtensions[]; @@ -67,6 +71,13 @@ const generateErrorMessage = (err: ApolloError) => { ); }; +/** + * @deprecated This type is deprecated and will be removed in the next major version of Apollo Client. + * It mistakenly referenced `GraqhQLError` instead of `GraphQLFormattedError`. + * + * Use `ReadonlyArray` instead. + */ +// eslint-disable-next-line @typescript-eslint/ban-types export type GraphQLErrors = ReadonlyArray; export type NetworkError = Error | ServerParseError | ServerError | null; @@ -74,21 +85,35 @@ export type NetworkError = Error | ServerParseError | ServerError | null; export class ApolloError extends Error { public name: string; public message: string; - public graphQLErrors: GraphQLErrors; + public graphQLErrors: ReadonlyArray; public protocolErrors: ReadonlyArray<{ message: string; extensions?: GraphQLErrorExtensions[]; }>; public clientErrors: ReadonlyArray; public networkError: Error | ServerParseError | ServerError | null; + /** + * Indicates the specific original cause of the error. + * + * This field contains the first available `networkError`, `graphQLError`, `protocolError`, `clientError`, or `null` if none are available. + */ + public cause: + | ({ + readonly message: string; + extensions?: + | GraphQLErrorExtensions[] + | GraphQLFormattedError["extensions"]; + } & Omit & Partial, "extensions">) + | null; // An object that can be used to provide some additional information // about an error, e.g. specifying the type of error this is. Used // internally within Apollo Client. public extraInfo: any; - // Constructs an instance of ApolloError given a GraphQLError - // or a network error. Note that one of these has to be a valid + // Constructs an instance of ApolloError given serialized GraphQL errors, + // client errors, protocol errors or network errors. + // Note that one of these has to be a valid // value or the constructed error will be meaningless. constructor({ graphQLErrors, @@ -106,6 +131,13 @@ export class ApolloError extends Error { this.networkError = networkError || null; this.message = errorMessage || generateErrorMessage(this); this.extraInfo = extraInfo; + this.cause = + [ + networkError, + ...(graphQLErrors || []), + ...(protocolErrors || []), + ...(clientErrors || []), + ].find((e) => !!e) || null; // We're not using `Object.setPrototypeOf` here as it isn't fully // supported on Android (see issue #3236). diff --git a/src/link/core/types.ts b/src/link/core/types.ts index a898f1a598e..c596ecac0c2 100644 --- a/src/link/core/types.ts +++ b/src/link/core/types.ts @@ -1,4 +1,4 @@ -import type { ExecutionResult, GraphQLError } from "graphql"; +import type { GraphQLFormattedError } from "graphql"; import type { DocumentNode } from "graphql"; import type { DefaultContext } from "../../core/index.js"; export type { DocumentNode }; @@ -18,7 +18,7 @@ export interface ExecutionPatchInitialResult< // if data is present, incremental is not data: TData | null | undefined; incremental?: never; - errors?: ReadonlyArray; + errors?: ReadonlyArray; extensions?: TExtensions; } @@ -28,7 +28,7 @@ export interface IncrementalPayload { data: TData | null; label?: string; path: Path; - errors?: ReadonlyArray; + errors?: ReadonlyArray; extensions?: TExtensions; } @@ -91,10 +91,12 @@ export interface SingleExecutionResult< TData = Record, TContext = DefaultContext, TExtensions = Record, -> extends ExecutionResult { +> { // data might be undefined if errorPolicy was set to 'ignore' data?: TData | null; context?: TContext; + errors?: ReadonlyArray; + extensions?: TExtensions; } export type FetchResult< diff --git a/src/link/error/index.ts b/src/link/error/index.ts index 00b0701ab6f..bf9494c5dfa 100644 --- a/src/link/error/index.ts +++ b/src/link/error/index.ts @@ -1,14 +1,14 @@ -import type { ExecutionResult } from "graphql"; +import type { FormattedExecutionResult, GraphQLFormattedError } from "graphql"; -import type { NetworkError, GraphQLErrors } from "../../errors/index.js"; +import type { NetworkError } from "../../errors/index.js"; import { Observable } from "../../utilities/index.js"; import type { Operation, FetchResult, NextLink } from "../core/index.js"; import { ApolloLink } from "../core/index.js"; export interface ErrorResponse { - graphQLErrors?: GraphQLErrors; + graphQLErrors?: ReadonlyArray; networkError?: NetworkError; - response?: ExecutionResult; + response?: FormattedExecutionResult; operation: Operation; forward: NextLink; } diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index 920633bd7f3..e631098652d 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -1,7 +1,11 @@ import { invariant } from "../../utilities/globals/index.js"; import { print } from "../../utilities/index.js"; -import type { DocumentNode, ExecutionResult, GraphQLError } from "graphql"; +import type { + DocumentNode, + FormattedExecutionResult, + GraphQLFormattedError, +} from "graphql"; import type { Operation } from "../core/index.js"; import { ApolloLink } from "../core/index.js"; @@ -21,9 +25,9 @@ import { export const VERSION = 1; export interface ErrorResponse { - graphQLErrors?: readonly GraphQLError[]; + graphQLErrors?: ReadonlyArray; networkError?: NetworkError; - response?: ExecutionResult; + response?: FormattedExecutionResult; operation: Operation; meta: ErrorMeta; } @@ -59,7 +63,10 @@ export namespace PersistedQueryLink { } function processErrors( - graphQLErrors: GraphQLError[] | readonly GraphQLError[] | undefined + graphQLErrors: + | GraphQLFormattedError[] + | ReadonlyArray + | undefined ): ErrorMeta { const byMessage = Object.create(null), byCode = Object.create(null); @@ -165,7 +172,7 @@ export const createPersistedQueryLink = ( const { query } = operation; - return new Observable((observer: Observer) => { + return new Observable((observer: Observer) => { let subscription: ObservableSubscription; let retried = false; let originalFetchOptions: any; @@ -174,13 +181,16 @@ export const createPersistedQueryLink = ( { response, networkError, - }: { response?: ExecutionResult; networkError?: ServerError }, + }: { + response?: FormattedExecutionResult; + networkError?: ServerError; + }, cb: () => void ) => { if (!retried && ((response && response.errors) || networkError)) { retried = true; - const graphQLErrors: GraphQLError[] = []; + const graphQLErrors: GraphQLFormattedError[] = []; const responseErrors = response && response.errors; if (isNonEmptyArray(responseErrors)) { @@ -193,7 +203,7 @@ export const createPersistedQueryLink = ( networkErrors = networkError && networkError.result && - (networkError.result.errors as GraphQLError[]); + (networkError.result.errors as GraphQLFormattedError[]); } if (isNonEmptyArray(networkErrors)) { graphQLErrors.push(...networkErrors); @@ -243,7 +253,7 @@ export const createPersistedQueryLink = ( cb(); }; const handler = { - next: (response: ExecutionResult) => { + next: (response: FormattedExecutionResult) => { maybeRetry({ response }, () => observer.next!(response)); }, error: (networkError: ServerError) => { diff --git a/src/link/subscriptions/index.ts b/src/link/subscriptions/index.ts index db56154718c..4823f7a22ee 100644 --- a/src/link/subscriptions/index.ts +++ b/src/link/subscriptions/index.ts @@ -29,12 +29,13 @@ // THE SOFTWARE. import { print } from "../../utilities/index.js"; -import type { Client } from "graphql-ws"; +import type { Client, Sink } from "graphql-ws"; import type { Operation, FetchResult } from "../core/index.js"; import { ApolloLink } from "../core/index.js"; import { isNonNullObject, Observable } from "../../utilities/index.js"; import { ApolloError } from "../../errors/index.js"; +import type { FormattedExecutionResult } from "graphql"; // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event function isLikeCloseEvent(val: unknown): val is CloseEvent { @@ -80,7 +81,8 @@ export class GraphQLWsLink extends ApolloLink { }) ); }, - } + // casting around a wrong type in graphql-ws, which incorrectly expects `Sink` + } satisfies Sink as any ); }); } diff --git a/src/react/components/__tests__/client/Mutation.test.tsx b/src/react/components/__tests__/client/Mutation.test.tsx index b71ba82f7cc..7a88e64b5ae 100644 --- a/src/react/components/__tests__/client/Mutation.test.tsx +++ b/src/react/components/__tests__/client/Mutation.test.tsx @@ -1,6 +1,10 @@ import React, { useState, PropsWithChildren } from "react"; import gql from "graphql-tag"; -import { ExecutionResult, GraphQLError } from "graphql"; +import { + ExecutionResult, + FormattedExecutionResult, + GraphQLError, +} from "graphql"; import userEvent from "@testing-library/user-event"; import { render, screen, waitFor, act } from "@testing-library/react"; @@ -301,7 +305,9 @@ describe("General Mutation testing", () => { {(createTodo: any) => { if (!called) { createTodo().catch((error: any) => { - expect(error).toEqual(new Error("Error 1")); + expect(error).toEqual( + new ApolloError({ networkError: new Error("Error 1") }) + ); done = true; }); } @@ -378,9 +384,13 @@ describe("General Mutation testing", () => { const onError = (error: Error) => { if (count === 1) { - expect(error).toEqual(new Error("Error 1")); + expect(error).toEqual( + new ApolloError({ networkError: new Error("Error 1") }) + ); } else if (count === 3) { - expect(error).toEqual(new Error("Error 2")); + expect(error).toEqual( + new ApolloError({ networkError: new Error("Error 2") }) + ); } }; const Component = () => ( @@ -398,7 +408,9 @@ describe("General Mutation testing", () => { expect(result.loading).toEqual(false); expect(result.data).toEqual(undefined); expect(result.called).toEqual(true); - expect(result.error).toEqual(new Error("Error 2")); + expect(result.error).toEqual( + new ApolloError({ networkError: new Error("Error 2") }) + ); } count++; return
; @@ -495,12 +507,16 @@ describe("General Mutation testing", () => { {(createTodo: any, result: any) => { if (count === 0) { createTodo().catch((err: any) => { - expect(err).toEqual(new Error("error occurred")); + expect(err).toEqual( + new ApolloError({ networkError: new Error("error occurred") }) + ); }); } else if (count === 1) { expect(result.loading).toBeTruthy(); } else if (count === 2) { - expect(result.error).toEqual(new Error("error occurred")); + expect(result.error).toEqual( + new ApolloError({ networkError: new Error("error occurred") }) + ); } count++; return
; @@ -1195,7 +1211,7 @@ describe("General Mutation testing", () => { })); it("has an update prop for updating the store after the mutation", async () => { - const update = (_proxy: DataProxy, response: ExecutionResult) => { + const update = (_proxy: DataProxy, response: FormattedExecutionResult) => { expect(response.data).toEqual(data); }; diff --git a/src/react/components/__tests__/client/Query.test.tsx b/src/react/components/__tests__/client/Query.test.tsx index 9182e61e19e..d8027c0c509 100644 --- a/src/react/components/__tests__/client/Query.test.tsx +++ b/src/react/components/__tests__/client/Query.test.tsx @@ -213,7 +213,9 @@ describe("Query component", () => { return null; } try { - expect(result.error).toEqual(new Error("error occurred")); + expect(result.error).toEqual( + new ApolloError({ networkError: new Error("error occurred") }) + ); finished = true; } catch (error) { reject(error); diff --git a/src/react/components/__tests__/client/Subscription.test.tsx b/src/react/components/__tests__/client/Subscription.test.tsx index 4584913a30d..1604623810f 100644 --- a/src/react/components/__tests__/client/Subscription.test.tsx +++ b/src/react/components/__tests__/client/Subscription.test.tsx @@ -2,13 +2,13 @@ import React from "react"; import gql from "graphql-tag"; import { render, waitFor } from "@testing-library/react"; -import { ApolloClient } from "../../../../core"; +import { ApolloClient, ApolloError } from "../../../../core"; import { InMemoryCache as Cache } from "../../../../cache"; import { ApolloProvider } from "../../../context"; -import { ApolloLink, Operation } from "../../../../link/core"; +import { ApolloLink, DocumentNode, Operation } from "../../../../link/core"; import { itAsync, MockSubscriptionLink } from "../../../../testing"; import { Subscription } from "../../Subscription"; -import { spyOnConsole } from "../../../../testing/internal"; +import { profile, spyOnConsole } from "../../../../testing/internal"; const results = [ "Luke Skywalker", @@ -390,7 +390,9 @@ itAsync("renders an error", (resolve, reject) => { expect(error).toBeUndefined(); } else if (count === 1) { expect(loading).toBe(false); - expect(error).toEqual(new Error("error occurred")); + expect(error).toEqual( + new ApolloError({ protocolErrors: [new Error("error occurred")] }) + ); expect(data).toBeUndefined(); } } catch (error) { @@ -422,77 +424,74 @@ describe("should update", () => { cache: new Cache({ addTypename: false }), }); - let count = 0; - let testFailures: any[] = []; + function Container() { + return ( + + {(r: any) => { + ProfiledContainer.replaceSnapshot(r); + return null; + }} + + ); + } + const ProfiledContainer = profile({ + Component: Container, + }); - class Component extends React.Component { - state = { - client: client, - }; + const { rerender } = render( + + + + ); - render() { - return ( - - - {(result: any) => { - const { loading, data } = result; - try { - switch (count++) { - case 0: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 1: - setTimeout(() => { - this.setState( - { - client: client2, - }, - () => { - link2.simulateResult(results[1]); - } - ); - }); - // fallthrough - case 2: - expect(loading).toBeFalsy(); - expect(data).toEqual(results[0].result.data); - break; - case 3: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 4: - expect(loading).toBeFalsy(); - expect(data).toEqual(results[1].result.data); - break; - default: - throw new Error("too many rerenders"); - } - } catch (error) { - testFailures.push(error); - } - return null; - }} - - - ); - } + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); } - render(); - link.simulateResult(results[0]); - await waitFor(() => { - if (testFailures.length > 0) { - throw testFailures[0]; - } - expect(count).toBe(5); - }); + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(results[0].result.data); + } + + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); + + rerender( + + + + ); + + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); + } + + link2.simulateResult(results[1]); + + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(results[1].result.data); + } + + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); }); - itAsync("if the query changes", (resolve, reject) => { + it("if the query changes", async () => { const subscriptionHero = gql` subscription HeroInfo { hero { @@ -524,72 +523,71 @@ describe("should update", () => { cache: new Cache({ addTypename: false }), }); - let count = 0; - - class Component extends React.Component { - state = { - subscription, - }; - - render() { - return ( - - {(result: any) => { - const { loading, data } = result; - try { - switch (count) { - case 0: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 1: - setTimeout(() => { - this.setState( - { - subscription: subscriptionHero, - }, - () => { - heroLink.simulateResult(heroResult); - } - ); - }); - // fallthrough - case 2: - expect(loading).toBeFalsy(); - expect(data).toEqual(results[0].result.data); - break; - case 3: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 4: - expect(loading).toBeFalsy(); - expect(data).toEqual(heroResult.result.data); - break; - } - } catch (error) { - reject(error); - } - count++; - return null; - }} - - ); - } + function Container({ subscription }: { subscription: DocumentNode }) { + return ( + + {(r: any) => { + ProfiledContainer.replaceSnapshot(r); + return null; + }} + + ); } + const ProfiledContainer = profile({ + Component: Container, + }); - render( - - - + const { rerender } = render( + , + { + wrapper: ({ children }) => ( + {children} + ), + } ); + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); + } userLink.simulateResult(results[0]); - waitFor(() => expect(count).toBe(5)).then(resolve, reject); + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(results[0].result.data); + } + + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); + + rerender(); + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); + } + + heroLink.simulateResult(heroResult); + + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(heroResult.result.data); + } + + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); }); - itAsync("if the variables change", (resolve, reject) => { + it("if the variables change", async () => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -620,75 +618,72 @@ describe("should update", () => { cache, }); - let count = 0; + function Container({ variables }: { variables: any }) { + return ( + + {(r: any) => { + ProfiledContainer.replaceSnapshot(r); + return null; + }} + + ); + } + const ProfiledContainer = profile({ + Component: Container, + }); - class Component extends React.Component { - state = { - variables: variablesLuke, - }; + const { rerender } = render( + , + { + wrapper: ({ children }) => ( + {children} + ), + } + ); - render() { - return ( - - {(result: any) => { - const { loading, data } = result; - try { - switch (count) { - case 0: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 1: - setTimeout(() => { - this.setState( - { - variables: variablesHan, - }, - () => { - mockLink.simulateResult({ - result: { data: dataHan }, - }); - } - ); - }); - // fallthrough - case 2: - expect(loading).toBeFalsy(); - expect(data).toEqual(dataLuke); - break; - case 3: - expect(loading).toBeTruthy(); - expect(data).toBeUndefined(); - break; - case 4: - expect(loading).toBeFalsy(); - expect(data).toEqual(dataHan); - break; - } - } catch (error) { - reject(error); - } + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); + } + mockLink.simulateResult({ result: { data: dataLuke } }); - count++; - return null; - }} - - ); - } + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(dataLuke); } - render( - - - - ); + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); - mockLink.simulateResult({ result: { data: dataLuke } }); + rerender(); + + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeTruthy(); + expect(data).toBeUndefined(); + } + mockLink.simulateResult({ + result: { data: dataHan }, + }); + { + const { + snapshot: { loading, data }, + } = await ProfiledContainer.takeRender(); + expect(loading).toBeFalsy(); + expect(data).toEqual(dataHan); + } - waitFor(() => expect(count).toBe(5)).then(resolve, reject); + await expect(ProfiledContainer).not.toRerender({ timeout: 50 }); }); }); diff --git a/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx b/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx index b81567ca0d5..ecb517fd14e 100644 --- a/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx +++ b/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx @@ -11,8 +11,6 @@ import { itAsync, MockSubscriptionLink } from "../../../../testing"; import { graphql } from "../../graphql"; import { ChildProps } from "../../types"; -const IS_REACT_18 = React.version.startsWith("18"); - describe("subscriptions", () => { let error: typeof console.error; @@ -301,29 +299,17 @@ describe("subscriptions", () => { if (count === 0) expect(user).toEqual(results[0].result.data.user); if (count === 1) { - if (IS_REACT_18) { - expect(user).toEqual(results[1].result.data.user); - } else { - expect(user).toEqual(results[0].result.data.user); - } + expect(user).toEqual(results[0].result.data.user); } if (count === 2) expect(user).toEqual(results[2].result.data.user); if (count === 3) expect(user).toEqual(results[2].result.data.user); if (count === 4) { - if (IS_REACT_18) { - expect(user).toEqual(results[2].result.data.user); - } else { - expect(user).toEqual(results3[2].result.data.user); - } + expect(user).toEqual(results3[2].result.data.user); } if (count === 5) { - if (IS_REACT_18) { - expect(user).toEqual(results3[3].result.data.user); - } else { - expect(user).toEqual(results3[2].result.data.user); - } + expect(user).toEqual(results3[2].result.data.user); resolve(); } } catch (e) { diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index 91d142a4df5..ac0ef98b87a 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -16,6 +16,7 @@ import { TypedDocumentNode, ApolloLink, Observable, + split, } from "../../../core"; import { MockedResponse, @@ -29,6 +30,7 @@ import { concatPagination, offsetLimitPagination, DeepPartial, + getMainDefinition, } from "../../../utilities"; import { useBackgroundQuery } from "../useBackgroundQuery"; import { UseReadQueryResult, useReadQuery } from "../useReadQuery"; @@ -37,7 +39,10 @@ import { QueryRef, QueryReference } from "../../internal"; import { InMemoryCache } from "../../../cache"; import { SuspenseQueryHookFetchPolicy } from "../../types/types"; import equal from "@wry/equality"; -import { RefetchWritePolicy } from "../../../core/watchQueryOptions"; +import { + RefetchWritePolicy, + SubscribeToMoreOptions, +} from "../../../core/watchQueryOptions"; import { skipToken } from "../constants"; import { PaginatedCaseData, @@ -54,6 +59,7 @@ import { spyOnConsole, useTrackRenders, } from "../../../testing/internal"; +import { SubscribeToMoreFunction } from "../useSuspenseQuery"; afterEach(() => { jest.useRealTimers(); @@ -6052,6 +6058,135 @@ describe("fetchMore", () => { await expect(Profiler).not.toRerender(); }); + + it("can subscribe to subscriptions and react to cache updates via `subscribeToMore`", async () => { + interface SubscriptionData { + greetingUpdated: string; + } + + type UpdateQueryFn = NonNullable< + SubscribeToMoreOptions< + SimpleCaseData, + Record, + SubscriptionData + >["updateQuery"] + >; + + const subscription: TypedDocumentNode< + SubscriptionData, + Record + > = gql` + subscription { + greetingUpdated + } + `; + + const { mocks, query } = setupSimpleCase(); + + const wsLink = new MockSubscriptionLink(); + const mockLink = new MockLink(mocks); + + const link = split( + ({ query }) => { + const definition = getMainDefinition(query); + + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, + wsLink, + mockLink + ); + + const client = new ApolloClient({ link, cache: new InMemoryCache() }); + + const Profiler = createProfiler({ + initialSnapshot: { + subscribeToMore: null as SubscribeToMoreFunction< + SimpleCaseData, + Record + > | null, + result: null as UseReadQueryResult | null, + }, + }); + + const { SuspenseFallback, ReadQueryHook } = + createDefaultTrackedComponents(Profiler); + + function App() { + useTrackRenders(); + const [queryRef, { subscribeToMore }] = useBackgroundQuery(query); + + Profiler.mergeSnapshot({ subscribeToMore }); + + return ( + }> + + + ); + } + + renderWithClient(, { client, wrapper: Profiler }); + + { + const { renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, SuspenseFallback]); + } + + { + const { renderedComponents, snapshot } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + const updateQuery = jest.fn< + ReturnType, + Parameters + >((_, { subscriptionData: { data } }) => { + return { greeting: data.greetingUpdated }; + }); + + const { snapshot } = Profiler.getCurrentRender(); + + snapshot.subscribeToMore!({ document: subscription, updateQuery }); + + wsLink.simulateResult({ + result: { + data: { + greetingUpdated: "Subscription hello", + }, + }, + }); + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Subscription hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + expect(updateQuery).toHaveBeenCalledTimes(1); + expect(updateQuery).toHaveBeenCalledWith( + { greeting: "Hello" }, + { + subscriptionData: { + data: { greetingUpdated: "Subscription hello" }, + }, + variables: {}, + } + ); + }); }); describe.skip("type tests", () => { diff --git a/src/react/hooks/__tests__/useLazyQuery.test.tsx b/src/react/hooks/__tests__/useLazyQuery.test.tsx index 08f94df5c60..df38ea6adc0 100644 --- a/src/react/hooks/__tests__/useLazyQuery.test.tsx +++ b/src/react/hooks/__tests__/useLazyQuery.test.tsx @@ -1069,14 +1069,14 @@ describe("useLazyQuery Hook", () => { { request: { query: helloQuery }, result: { - errors: [new GraphQLError("error 1")], + errors: [{ message: "error 1" }], }, delay: 20, }, { request: { query: helloQuery }, result: { - errors: [new GraphQLError("error 2")], + errors: [{ message: "error 2" }], }, delay: 20, }, @@ -1111,7 +1111,9 @@ describe("useLazyQuery Hook", () => { const [, result] = await ProfiledHook.takeSnapshot(); expect(result.loading).toBe(false); expect(result.data).toBeUndefined(); - expect(result.error).toEqual(new Error("error 1")); + expect(result.error).toEqual( + new ApolloError({ graphQLErrors: [{ message: "error 1" }] }) + ); } await executePromise.then((result) => { @@ -1126,14 +1128,18 @@ describe("useLazyQuery Hook", () => { const [, result] = await ProfiledHook.takeSnapshot(); expect(result.loading).toBe(true); expect(result.data).toBeUndefined(); - expect(result.error).toEqual(new Error("error 1")); + expect(result.error).toEqual( + new ApolloError({ graphQLErrors: [{ message: "error 1" }] }) + ); } { const [, result] = await ProfiledHook.takeSnapshot(); expect(result.loading).toBe(false); expect(result.data).toBeUndefined(); - expect(result.error).toEqual(new Error("error 2")); + expect(result.error).toEqual( + new ApolloError({ graphQLErrors: [{ message: "error 2" }] }) + ); } }); diff --git a/src/react/hooks/__tests__/useLoadableQuery.test.tsx b/src/react/hooks/__tests__/useLoadableQuery.test.tsx index a5e97ca52e8..9c83a0bd6c5 100644 --- a/src/react/hooks/__tests__/useLoadableQuery.test.tsx +++ b/src/react/hooks/__tests__/useLoadableQuery.test.tsx @@ -22,6 +22,8 @@ import { Observable, OperationVariables, RefetchWritePolicy, + SubscribeToMoreOptions, + split, } from "../../../core"; import { MockedProvider, @@ -35,6 +37,7 @@ import { concatPagination, offsetLimitPagination, DeepPartial, + getMainDefinition, } from "../../../utilities"; import { useLoadableQuery } from "../useLoadableQuery"; import type { UseReadQueryResult } from "../useReadQuery"; @@ -43,7 +46,11 @@ import { ApolloProvider } from "../../context"; import { InMemoryCache } from "../../../cache"; import { LoadableQueryHookFetchPolicy } from "../../types/types"; import { QueryRef } from "../../../react"; -import { FetchMoreFunction, RefetchFunction } from "../useSuspenseQuery"; +import { + FetchMoreFunction, + RefetchFunction, + SubscribeToMoreFunction, +} from "../useSuspenseQuery"; import invariant, { InvariantError } from "ts-invariant"; import { Profiler, @@ -4667,6 +4674,218 @@ it("allows loadQuery to be called in useEffect on first render", async () => { expect(() => renderWithMocks(, { mocks })).not.toThrow(); }); +it("can subscribe to subscriptions and react to cache updates via `subscribeToMore`", async () => { + interface SubscriptionData { + greetingUpdated: string; + } + + type UpdateQueryFn = NonNullable< + SubscribeToMoreOptions< + SimpleCaseData, + Record, + SubscriptionData + >["updateQuery"] + >; + + const subscription: TypedDocumentNode< + SubscriptionData, + Record + > = gql` + subscription { + greetingUpdated + } + `; + + const { mocks, query } = setupSimpleCase(); + + const wsLink = new MockSubscriptionLink(); + const mockLink = new MockLink(mocks); + + const link = split( + ({ query }) => { + const definition = getMainDefinition(query); + + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, + wsLink, + mockLink + ); + + const client = new ApolloClient({ link, cache: new InMemoryCache() }); + + const Profiler = createProfiler({ + initialSnapshot: { + subscribeToMore: null as SubscribeToMoreFunction< + SimpleCaseData, + Record + > | null, + result: null as UseReadQueryResult | null, + }, + }); + + const { SuspenseFallback, ReadQueryHook } = + createDefaultProfiledComponents(Profiler); + + function App() { + useTrackRenders(); + const [loadQuery, queryRef, { subscribeToMore }] = useLoadableQuery(query); + + Profiler.mergeSnapshot({ subscribeToMore }); + + return ( +
+ + }> + {queryRef && } + +
+ ); + } + + const { user } = renderWithClient(, { client, wrapper: Profiler }); + // initial render + await Profiler.takeRender(); + + await act(() => user.click(screen.getByText("Load query"))); + + { + const { renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, SuspenseFallback]); + } + + { + const { renderedComponents, snapshot } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + const updateQuery = jest.fn< + ReturnType, + Parameters + >((_, { subscriptionData: { data } }) => { + return { greeting: data.greetingUpdated }; + }); + + const { snapshot } = Profiler.getCurrentRender(); + + snapshot.subscribeToMore!({ document: subscription, updateQuery }); + + wsLink.simulateResult({ + result: { + data: { + greetingUpdated: "Subscription hello", + }, + }, + }); + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Subscription hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + expect(updateQuery).toHaveBeenCalledTimes(1); + expect(updateQuery).toHaveBeenCalledWith( + { greeting: "Hello" }, + { + subscriptionData: { + data: { greetingUpdated: "Subscription hello" }, + }, + variables: {}, + } + ); +}); + +it("throws when calling `subscribeToMore` before loading the query", async () => { + interface SubscriptionData { + greetingUpdated: string; + } + + const subscription: TypedDocumentNode< + SubscriptionData, + Record + > = gql` + subscription { + greetingUpdated + } + `; + + const { mocks, query } = setupSimpleCase(); + + const wsLink = new MockSubscriptionLink(); + const mockLink = new MockLink(mocks); + + const link = split( + ({ query }) => { + const definition = getMainDefinition(query); + + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, + wsLink, + mockLink + ); + + const client = new ApolloClient({ link, cache: new InMemoryCache() }); + + const Profiler = createProfiler({ + initialSnapshot: { + subscribeToMore: null as SubscribeToMoreFunction< + SimpleCaseData, + Record + > | null, + result: null as UseReadQueryResult | null, + }, + }); + + const { SuspenseFallback, ReadQueryHook } = + createDefaultProfiledComponents(Profiler); + + function App() { + useTrackRenders(); + const [loadQuery, queryRef, { subscribeToMore }] = useLoadableQuery(query); + + Profiler.mergeSnapshot({ subscribeToMore }); + + return ( +
+ + }> + {queryRef && } + +
+ ); + } + + renderWithClient(, { client, wrapper: Profiler }); + // initial render + await Profiler.takeRender(); + + const { snapshot } = Profiler.getCurrentRender(); + + expect(() => { + snapshot.subscribeToMore!({ document: subscription }); + }).toThrow( + new InvariantError("The query has not been loaded. Please load the query.") + ); +}); + describe.skip("type tests", () => { it("returns unknown when TData cannot be inferred", () => { const query = gql``; diff --git a/src/react/hooks/__tests__/useMutation.test.tsx b/src/react/hooks/__tests__/useMutation.test.tsx index 452b1ad77de..da26fd2c87d 100644 --- a/src/react/hooks/__tests__/useMutation.test.tsx +++ b/src/react/hooks/__tests__/useMutation.test.tsx @@ -8,6 +8,7 @@ import fetchMock from "fetch-mock"; import { ApolloClient, + ApolloError, ApolloLink, ApolloQueryResult, Cache, @@ -346,7 +347,7 @@ describe("useMutation Hook", () => { variables, }, result: { - errors: [new GraphQLError(CREATE_TODO_ERROR)], + errors: [{ message: CREATE_TODO_ERROR }], }, }, ]; @@ -371,7 +372,9 @@ describe("useMutation Hook", () => { throw new Error("function did not error"); }); - expect(fetchError).toEqual(new GraphQLError(CREATE_TODO_ERROR)); + expect(fetchError).toEqual( + new ApolloError({ graphQLErrors: [{ message: CREATE_TODO_ERROR }] }) + ); }); it(`should reject when errorPolicy is 'none'`, async () => { @@ -961,14 +964,13 @@ describe("useMutation Hook", () => { expect(fetchResult).toEqual({ data: undefined, - // Not sure why we unwrap errors here. - errors: errors[0], + errors: new ApolloError({ graphQLErrors: errors }), }); expect(onCompleted).toHaveBeenCalledTimes(0); expect(onError).toHaveBeenCalledTimes(1); expect(onError).toHaveBeenCalledWith( - errors[0], + new ApolloError({ graphQLErrors: errors }), expect.objectContaining({ variables }) ); }); @@ -1012,7 +1014,7 @@ describe("useMutation Hook", () => { }); it("should allow updating onError while mutation is executing", async () => { - const errors = [new GraphQLError(CREATE_TODO_ERROR)]; + const errors = [{ message: CREATE_TODO_ERROR }]; const variables = { priority: "Low", description: "Get milk.", @@ -1060,15 +1062,14 @@ describe("useMutation Hook", () => { expect(fetchResult).toEqual({ data: undefined, - // Not sure why we unwrap errors here. - errors: errors[0], + errors: new ApolloError({ graphQLErrors: errors }), }); expect(onCompleted).toHaveBeenCalledTimes(0); expect(onError).toHaveBeenCalledTimes(0); expect(onError1).toHaveBeenCalledTimes(1); expect(onError1).toHaveBeenCalledWith( - errors[0], + new ApolloError({ graphQLErrors: errors }), expect.objectContaining({ variables }) ); }); diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index bfcd534c7e3..19a1ba57687 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -11,6 +11,7 @@ import { OperationVariables, TypedDocumentNode, WatchQueryFetchPolicy, + WatchQueryOptions, } from "../../../core"; import { InMemoryCache } from "../../../cache"; import { ApolloProvider } from "../../context"; @@ -36,9 +37,9 @@ import { } from "../../../testing/internal"; import { useApolloClient } from "../useApolloClient"; import { useLazyQuery } from "../useLazyQuery"; +import { mockFetchQuery } from "../../../core/__tests__/ObservableQuery"; const IS_REACT_17 = React.version.startsWith("17"); -const IS_REACT_19 = React.version.startsWith("19"); describe("useQuery Hook", () => { describe("General use", () => { @@ -1536,33 +1537,7 @@ describe("useQuery Hook", () => { function checkObservableQueries(expectedLinkCount: number) { const obsQueries = client.getObservableQueries("all"); - /* -This is due to a timing change in React 19 - -In React 18, you observe this pattern: - - 1. render - 2. useState initializer - 3. component continues to render with first state - 4. strictMode: render again - 5. strictMode: call useState initializer again - 6. component continues to render with second state - -now, in React 19 it looks like this: - - 1. render - 2. useState initializer - 3. strictMode: call useState initializer again - 4. component continues to render with one of these two states - 5. strictMode: render again - 6. component continues to render with the same state as during the first render - -Since useQuery breaks the rules of React and mutably creates an ObservableQuery on the state during render if none is present, React 18 did create two, while React 19 only creates one. - -This is pure coincidence though, and the useQuery rewrite that doesn't break the rules of hooks as much and creates the ObservableQuery as part of the state initializer will end up with behaviour closer to the old React 18 behaviour again. - -*/ - expect(obsQueries.size).toBe(IS_REACT_19 ? 1 : 2); + expect(obsQueries.size).toBe(2); const activeSet = new Set(); const inactiveSet = new Set(); @@ -7098,6 +7073,131 @@ This is pure coincidence though, and the useQuery rewrite that doesn't break the expect(reasons).toEqual(["variables-changed", "after-fetch"]); }); + + it("should prioritize a `nextFetchPolicy` function over a `fetchPolicy` option when changing variables", async () => { + const query = gql` + { + hello + } + `; + const link = new MockLink([ + { + request: { query, variables: { id: 1 } }, + result: { data: { hello: "from link" } }, + delay: 10, + }, + { + request: { query, variables: { id: 2 } }, + result: { data: { hello: "from link2" } }, + delay: 10, + }, + ]); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link, + }); + + const mocks = mockFetchQuery(client["queryManager"]); + + const expectQueryTriggered = ( + nth: number, + fetchPolicy: WatchQueryFetchPolicy + ) => { + expect(mocks.fetchQueryByPolicy).toHaveBeenCalledTimes(nth); + expect(mocks.fetchQueryByPolicy).toHaveBeenNthCalledWith( + nth, + expect.anything(), + expect.objectContaining({ fetchPolicy }), + expect.any(Number) + ); + }; + let nextFetchPolicy: WatchQueryOptions< + OperationVariables, + any + >["nextFetchPolicy"] = (_, context) => { + if (context.reason === "variables-changed") { + return "cache-and-network"; + } else if (context.reason === "after-fetch") { + return "cache-only"; + } + throw new Error("should never happen"); + }; + nextFetchPolicy = jest.fn(nextFetchPolicy); + + const { result, rerender } = renderHook< + QueryResult, + { + variables: { id: number }; + } + >( + ({ variables }) => + useQuery(query, { + fetchPolicy: "network-only", + variables, + notifyOnNetworkStatusChange: true, + nextFetchPolicy, + }), + { + initialProps: { + variables: { id: 1 }, + }, + wrapper: ({ children }) => ( + {children} + ), + } + ); + // first network request triggers with initial fetchPolicy + expectQueryTriggered(1, "network-only"); + + await waitFor(() => { + expect(result.current.networkStatus).toBe(NetworkStatus.ready); + }); + + expect(nextFetchPolicy).toHaveBeenCalledTimes(1); + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 1, + "network-only", + expect.objectContaining({ + reason: "after-fetch", + }) + ); + // `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to + // cache-only + expect(result.current.observable.options.fetchPolicy).toBe("cache-only"); + + rerender({ + variables: { id: 2 }, + }); + + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 2, + // has been reset to the initial `fetchPolicy` of "network-only" because + // we changed variables, then `nextFetchPolicy` is called + "network-only", + expect.objectContaining({ + reason: "variables-changed", + }) + ); + // the return value of `nextFetchPolicy(..., {reason: "variables-changed"})` + expectQueryTriggered(2, "cache-and-network"); + + await waitFor(() => { + expect(result.current.networkStatus).toBe(NetworkStatus.ready); + }); + + expect(nextFetchPolicy).toHaveBeenCalledTimes(3); + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 3, + "cache-and-network", + expect.objectContaining({ + reason: "after-fetch", + }) + ); + // `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to + // cache-only + expect(result.current.observable.options.fetchPolicy).toBe("cache-only"); + }); }); describe("Missing Fields", () => { diff --git a/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx b/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx index 012f7fb1872..536d8ca2edb 100644 --- a/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx +++ b/src/react/hooks/__tests__/useQueryRefHandlers.test.tsx @@ -4,10 +4,16 @@ import { ApolloClient, InMemoryCache, NetworkStatus, + SubscribeToMoreOptions, TypedDocumentNode, gql, + split, } from "../../../core"; -import { MockLink, MockedResponse } from "../../../testing"; +import { + MockLink, + MockSubscriptionLink, + MockedResponse, +} from "../../../testing"; import { PaginatedCaseData, SimpleCaseData, @@ -19,13 +25,14 @@ import { } from "../../../testing/internal"; import { useQueryRefHandlers } from "../useQueryRefHandlers"; import { UseReadQueryResult, useReadQuery } from "../useReadQuery"; +import type { SubscribeToMoreFunction } from "../useSuspenseQuery"; import { Suspense } from "react"; import { createQueryPreloader } from "../../query-preloader/createQueryPreloader"; import userEvent from "@testing-library/user-event"; import { QueryRef } from "../../internal"; import { useBackgroundQuery } from "../useBackgroundQuery"; import { useLoadableQuery } from "../useLoadableQuery"; -import { concatPagination } from "../../../utilities"; +import { concatPagination, getMainDefinition } from "../../../utilities"; test("does not interfere with updates from useReadQuery", async () => { const { query, mocks } = setupSimpleCase(); @@ -1927,3 +1934,147 @@ test("`fetchMore` works with startTransition from useBackgroundQuery and useQuer await expect(Profiler).not.toRerender(); }); + +test("can subscribe to subscriptions and react to cache updates via `subscribeToMore`", async () => { + interface SubscriptionData { + greetingUpdated: string; + } + + type UpdateQueryFn = NonNullable< + SubscribeToMoreOptions< + SimpleCaseData, + Record, + SubscriptionData + >["updateQuery"] + >; + + const subscription: TypedDocumentNode< + SubscriptionData, + Record + > = gql` + subscription { + greetingUpdated + } + `; + + const { mocks, query } = setupSimpleCase(); + + const wsLink = new MockSubscriptionLink(); + const mockLink = new MockLink(mocks); + + const link = split( + ({ query }) => { + const definition = getMainDefinition(query); + + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, + wsLink, + mockLink + ); + + const client = new ApolloClient({ link, cache: new InMemoryCache() }); + + const preloadQuery = createQueryPreloader(client); + const queryRef = preloadQuery(query); + + const Profiler = createProfiler({ + initialSnapshot: { + subscribeToMore: null as SubscribeToMoreFunction< + SimpleCaseData, + Record + > | null, + result: null as UseReadQueryResult | null, + }, + }); + + function SuspenseFallback() { + useTrackRenders(); + return

Loading

; + } + + function ReadQueryHook() { + useTrackRenders(); + Profiler.mergeSnapshot({ result: useReadQuery(queryRef) }); + + return null; + } + + function App() { + useTrackRenders(); + // We can ignore the return result here since we are testing the mechanics + // of this hook to ensure it doesn't interfere with the updates from + // useReadQuery + const { subscribeToMore } = useQueryRefHandlers(queryRef); + + Profiler.mergeSnapshot({ subscribeToMore }); + + return ( + }> + + + ); + } + + renderWithClient(, { client, wrapper: Profiler }); + + { + const { renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, SuspenseFallback]); + } + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + const updateQuery = jest.fn< + ReturnType, + Parameters + >((_, { subscriptionData: { data } }) => { + return { greeting: data.greetingUpdated }; + }); + + const { snapshot } = Profiler.getCurrentRender(); + + snapshot.subscribeToMore!({ document: subscription, updateQuery }); + + wsLink.simulateResult({ + result: { + data: { + greetingUpdated: "Subscription hello", + }, + }, + }); + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([ReadQueryHook]); + expect(snapshot.result).toEqual({ + data: { greeting: "Subscription hello" }, + error: undefined, + networkStatus: NetworkStatus.ready, + }); + } + + expect(updateQuery).toHaveBeenCalledTimes(1); + expect(updateQuery).toHaveBeenCalledWith( + { greeting: "Hello" }, + { + subscriptionData: { + data: { greetingUpdated: "Subscription hello" }, + }, + variables: {}, + } + ); +}); diff --git a/src/react/hooks/__tests__/useSubscription.test.tsx b/src/react/hooks/__tests__/useSubscription.test.tsx index decdd17b973..0c9002638d1 100644 --- a/src/react/hooks/__tests__/useSubscription.test.tsx +++ b/src/react/hooks/__tests__/useSubscription.test.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { renderHook, waitFor } from "@testing-library/react"; +import { render, renderHook, waitFor } from "@testing-library/react"; import gql from "graphql-tag"; import { @@ -14,7 +14,12 @@ import { InMemoryCache as Cache } from "../../../cache"; import { ApolloProvider } from "../../context"; import { MockSubscriptionLink } from "../../../testing"; import { useSubscription } from "../useSubscription"; -import { spyOnConsole } from "../../../testing/internal"; +import { profileHook, spyOnConsole } from "../../../testing/internal"; +import { SubscriptionHookOptions } from "../../types/types"; +import { ErrorBoundary } from "react-error-boundary"; +import { MockedSubscriptionResult } from "../../../testing/core/mocking/mockSubscriptionLink"; +import { GraphQLError } from "graphql"; +import { InvariantError } from "ts-invariant"; describe("useSubscription Hook", () => { it("should handle a simple subscription properly", async () => { @@ -454,6 +459,74 @@ describe("useSubscription Hook", () => { expect(context!).toBe("Audi"); }); + it("should share extensions set in options", async () => { + const subscription = gql` + subscription { + car { + make + } + } + `; + + const results = ["Audi", "BMW"].map((make) => ({ + result: { data: { car: { make } } }, + })); + + let extensions: string; + const link = new MockSubscriptionLink(); + const extensionsLink = new ApolloLink((operation, forward) => { + extensions = operation.extensions.make; + return forward(operation); + }); + const client = new ApolloClient({ + link: concat(extensionsLink, link), + cache: new Cache({ addTypename: false }), + }); + + const { result } = renderHook( + () => + useSubscription(subscription, { + extensions: { make: "Audi" }, + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + + expect(result.current.loading).toBe(true); + expect(result.current.error).toBe(undefined); + expect(result.current.data).toBe(undefined); + setTimeout(() => { + link.simulateResult(results[0]); + }, 100); + + await waitFor( + () => { + expect(result.current.data).toEqual(results[0].result.data); + }, + { interval: 1 } + ); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + + setTimeout(() => { + link.simulateResult(results[1]); + }); + + await waitFor( + () => { + expect(result.current.data).toEqual(results[1].result.data); + }, + { interval: 1 } + ); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + + expect(extensions!).toBe("Audi"); + }); + it("should handle multiple subscriptions properly", async () => { const subscription = gql` subscription { @@ -1120,6 +1193,871 @@ followed by new in-flight setup", async () => { unmount(); }); + + describe("errorPolicy", () => { + function setup() { + const subscription: TypedDocumentNode<{ totalLikes: number }, {}> = gql` + subscription ($id: ID!) { + totalLikes + } + `; + const errorBoundaryOnError = jest.fn(); + const link = new MockSubscriptionLink(); + const client = new ApolloClient({ + link, + cache: new Cache(), + }); + const ProfiledHook = profileHook( + (options: SubscriptionHookOptions<{ totalLikes: number }, {}>) => + useSubscription(subscription, options) + ); + const wrapper = ({ children }: { children: any }) => ( + + error}> + {children} + + + ); + const graphQlErrorResult: MockedSubscriptionResult = { + result: { + data: { totalLikes: 42 }, + errors: [{ message: "test" } as any], + }, + }; + const protocolErrorResult: MockedSubscriptionResult = { + error: new Error("Socket closed with event -1: I'm a test!"), + }; + return { + client, + link, + errorBoundaryOnError, + ProfiledHook, + wrapper, + graphQlErrorResult, + protocolErrorResult, + }; + } + describe("GraphQL error", () => { + it.each([undefined, "none"] as const)( + "`errorPolicy: '%s'`: returns `{ error }`, calls `onError`", + async (errorPolicy) => { + const { + ProfiledHook, + wrapper, + link, + graphQlErrorResult, + errorBoundaryOnError, + } = setup(); + const onData = jest.fn(); + const onError = jest.fn(); + render( + , + { + wrapper, + } + ); + + await ProfiledHook.takeSnapshot(); + link.simulateResult(graphQlErrorResult); + { + const snapshot = await ProfiledHook.takeSnapshot(); + console.dir({ graphQlErrorResult, snapshot }, { depth: 5 }); + expect(snapshot).toStrictEqual({ + loading: false, + error: new ApolloError({ + graphQLErrors: graphQlErrorResult.result!.errors as any, + }), + data: undefined, + restart: expect.any(Function), + variables: undefined, + }); + expect(snapshot.error).toBeInstanceOf(ApolloError); + } + expect(onError).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledWith( + new ApolloError({ + graphQLErrors: graphQlErrorResult.result!.errors as any, + }) + ); + expect(onError).toHaveBeenCalledWith(expect.any(ApolloError)); + expect(onData).toHaveBeenCalledTimes(0); + expect(errorBoundaryOnError).toHaveBeenCalledTimes(0); + } + ); + it("`errorPolicy: 'all'`: returns `{ error, data }`, calls `onError`", async () => { + const { + ProfiledHook, + wrapper, + link, + graphQlErrorResult, + errorBoundaryOnError, + } = setup(); + const onData = jest.fn(); + const onError = jest.fn(); + render( + , + { + wrapper, + } + ); + + await ProfiledHook.takeSnapshot(); + link.simulateResult(graphQlErrorResult); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: new ApolloError({ + errorMessage: "test", + graphQLErrors: graphQlErrorResult.result!.errors as any, + }), + data: { totalLikes: 42 }, + restart: expect.any(Function), + variables: undefined, + }); + expect(snapshot.error).toBeInstanceOf(ApolloError); + } + + expect(onError).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledWith( + new ApolloError({ + errorMessage: "test", + graphQLErrors: graphQlErrorResult.result!.errors as any, + }) + ); + expect(onError).toHaveBeenCalledWith(expect.any(ApolloError)); + expect(onData).toHaveBeenCalledTimes(0); + expect(errorBoundaryOnError).toHaveBeenCalledTimes(0); + }); + it("`errorPolicy: 'ignore'`: returns `{ data }`, calls `onData`", async () => { + const { + ProfiledHook, + wrapper, + link, + graphQlErrorResult, + errorBoundaryOnError, + } = setup(); + const onData = jest.fn(); + const onError = jest.fn(); + render( + , + { + wrapper, + } + ); + + await ProfiledHook.takeSnapshot(); + link.simulateResult(graphQlErrorResult); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: { totalLikes: 42 }, + restart: expect.any(Function), + variables: undefined, + }); + } + + expect(onError).toHaveBeenCalledTimes(0); + expect(onData).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenCalledWith({ + client: expect.anything(), + data: { + data: { totalLikes: 42 }, + loading: false, + // should this be undefined? + error: undefined, + variables: undefined, + }, + }); + expect(errorBoundaryOnError).toHaveBeenCalledTimes(0); + }); + }); + describe("protocol error", () => { + it.each([undefined, "none", "all", "ignore"] as const)( + "`errorPolicy: '%s'`: returns `{ error }`, calls `onError`", + async (errorPolicy) => { + const { + ProfiledHook, + wrapper, + link, + protocolErrorResult, + errorBoundaryOnError, + } = setup(); + const onData = jest.fn(); + const onError = jest.fn(); + render( + , + { + wrapper, + } + ); + + await ProfiledHook.takeSnapshot(); + link.simulateResult(protocolErrorResult); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: new ApolloError({ + protocolErrors: [protocolErrorResult.error!], + }), + data: undefined, + restart: expect.any(Function), + variables: undefined, + }); + expect(snapshot.error).toBeInstanceOf(ApolloError); + } + + expect(onError).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledWith(expect.any(ApolloError)); + expect(onError).toHaveBeenCalledWith( + new ApolloError({ + protocolErrors: [protocolErrorResult.error!], + }) + ); + expect(onData).toHaveBeenCalledTimes(0); + expect(errorBoundaryOnError).toHaveBeenCalledTimes(0); + } + ); + }); + }); +}); + +describe("`restart` callback", () => { + function setup() { + const subscription: TypedDocumentNode< + { totalLikes: number }, + { id: string } + > = gql` + subscription ($id: ID!) { + totalLikes(postId: $id) + } + `; + const onSubscribe = jest.fn(); + const onUnsubscribe = jest.fn(); + const link = new MockSubscriptionLink(); + link.onSetup(onSubscribe); + link.onUnsubscribe(onUnsubscribe); + const client = new ApolloClient({ + link, + cache: new Cache(), + }); + const ProfiledHook = profileHook( + ( + options: SubscriptionHookOptions<{ totalLikes: number }, { id: string }> + ) => useSubscription(subscription, options) + ); + return { client, link, ProfiledHook, onSubscribe, onUnsubscribe }; + } + it("can restart a running subscription", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(1)); + expect(onSubscribe).toHaveBeenCalledTimes(2); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("will use the most recently passed in options", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + const { rerender } = render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + // deliberately keeping a reference to a very old `restart` function + // to show that the most recent options are used even with that + const restart = ProfiledHook.getCurrentSnapshot().restart; + link.simulateResult({ result: { data: { totalLikes: 1 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + rerender(); + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(1)); + expect(onSubscribe).toHaveBeenCalledTimes(2); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1000 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1000 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(2); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + restart(); + + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(2)); + expect(onSubscribe).toHaveBeenCalledTimes(3); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1005 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1005 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + }); + it("can restart a subscription that has completed", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1 } } }, true); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await waitFor(() => expect(onSubscribe).toHaveBeenCalledTimes(2)); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("can restart a subscription that has errored", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + const error = new GraphQLError("error"); + link.simulateResult({ + result: { errors: [error] }, + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: undefined, + error: new ApolloError({ graphQLErrors: [error] }), + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await waitFor(() => expect(onSubscribe).toHaveBeenCalledTimes(2)); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("will not restart a subscription that has been `skip`ped", async () => { + const { client, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(0); + + expect(() => ProfiledHook.getCurrentSnapshot().restart()).toThrow( + new InvariantError("A subscription that is skipped cannot be restarted.") + ); + + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(0); + }); +}); + +describe("ignoreResults", () => { + const subscription = gql` + subscription { + car { + make + } + } + `; + + const results = ["Audi", "BMW"].map((make) => ({ + result: { data: { car: { make } } }, + })); + + it("should not rerender when ignoreResults is true, but will call `onData` and `onComplete`", async () => { + const link = new MockSubscriptionLink(); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + + const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); + const onError = jest.fn((() => {}) as SubscriptionHookOptions["onError"]); + const onComplete = jest.fn( + (() => {}) as SubscriptionHookOptions["onComplete"] + ); + const ProfiledHook = profileHook(() => + useSubscription(subscription, { + ignoreResults: true, + onData, + onError, + onComplete, + }) + ); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: undefined, + variables: undefined, + restart: expect.any(Function), + }); + link.simulateResult(results[0]); + + await waitFor(() => { + expect(onData).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenLastCalledWith( + expect.objectContaining({ + data: { + data: results[0].result.data, + error: undefined, + loading: false, + variables: undefined, + }, + }) + ); + expect(onError).toHaveBeenCalledTimes(0); + expect(onComplete).toHaveBeenCalledTimes(0); + }); + + link.simulateResult(results[1], true); + await waitFor(() => { + expect(onData).toHaveBeenCalledTimes(2); + expect(onData).toHaveBeenLastCalledWith( + expect.objectContaining({ + data: { + data: results[1].result.data, + error: undefined, + loading: false, + variables: undefined, + }, + }) + ); + expect(onError).toHaveBeenCalledTimes(0); + expect(onComplete).toHaveBeenCalledTimes(1); + }); + + await expect(ProfiledHook).not.toRerender(); + }); + + it("should not rerender when ignoreResults is true and an error occurs", async () => { + const link = new MockSubscriptionLink(); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + + const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); + const onError = jest.fn((() => {}) as SubscriptionHookOptions["onError"]); + const onComplete = jest.fn( + (() => {}) as SubscriptionHookOptions["onComplete"] + ); + const ProfiledHook = profileHook(() => + useSubscription(subscription, { + ignoreResults: true, + onData, + onError, + onComplete, + }) + ); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: undefined, + variables: undefined, + restart: expect.any(Function), + }); + link.simulateResult(results[0]); + + await waitFor(() => { + expect(onData).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenLastCalledWith( + expect.objectContaining({ + data: { + data: results[0].result.data, + error: undefined, + loading: false, + variables: undefined, + }, + }) + ); + expect(onError).toHaveBeenCalledTimes(0); + expect(onComplete).toHaveBeenCalledTimes(0); + }); + + const error = new Error("test"); + link.simulateResult({ error }); + await waitFor(() => { + expect(onData).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenLastCalledWith( + new ApolloError({ protocolErrors: [error] }) + ); + expect(onComplete).toHaveBeenCalledTimes(0); + }); + + await expect(ProfiledHook).not.toRerender(); + }); + + it("can switch from `ignoreResults: true` to `ignoreResults: false` and will start rerendering, without creating a new subscription", async () => { + const subscriptionCreated = jest.fn(); + const link = new MockSubscriptionLink(); + link.onSetup(subscriptionCreated); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + + const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); + const ProfiledHook = profileHook( + ({ ignoreResults }: { ignoreResults: boolean }) => + useSubscription(subscription, { + ignoreResults, + onData, + }) + ); + const { rerender } = render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + expect(subscriptionCreated).toHaveBeenCalledTimes(1); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: undefined, + variables: undefined, + restart: expect.any(Function), + }); + expect(onData).toHaveBeenCalledTimes(0); + } + link.simulateResult(results[0]); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onData).toHaveBeenCalledTimes(1); + + rerender(); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + // `data` appears immediately after changing to `ignoreResults: false` + data: results[0].result.data, + variables: undefined, + restart: expect.any(Function), + }); + // `onData` should not be called again for the same result + expect(onData).toHaveBeenCalledTimes(1); + } + + link.simulateResult(results[1]); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: results[1].result.data, + variables: undefined, + restart: expect.any(Function), + }); + expect(onData).toHaveBeenCalledTimes(2); + } + // a second subscription should not have been started + expect(subscriptionCreated).toHaveBeenCalledTimes(1); + }); + it("can switch from `ignoreResults: false` to `ignoreResults: true` and will stop rerendering, without creating a new subscription", async () => { + const subscriptionCreated = jest.fn(); + const link = new MockSubscriptionLink(); + link.onSetup(subscriptionCreated); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + + const onData = jest.fn((() => {}) as SubscriptionHookOptions["onData"]); + const ProfiledHook = profileHook( + ({ ignoreResults }: { ignoreResults: boolean }) => + useSubscription(subscription, { + ignoreResults, + onData, + }) + ); + const { rerender } = render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + expect(subscriptionCreated).toHaveBeenCalledTimes(1); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + error: undefined, + data: undefined, + variables: undefined, + restart: expect.any(Function), + }); + expect(onData).toHaveBeenCalledTimes(0); + } + link.simulateResult(results[0]); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + data: results[0].result.data, + variables: undefined, + restart: expect.any(Function), + }); + expect(onData).toHaveBeenCalledTimes(1); + } + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + + rerender(); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + error: undefined, + // switching back to the default `ignoreResults: true` return value + data: undefined, + variables: undefined, + restart: expect.any(Function), + }); + // `onData` should not be called again + expect(onData).toHaveBeenCalledTimes(1); + } + + link.simulateResult(results[1]); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); + expect(onData).toHaveBeenCalledTimes(2); + + // a second subscription should not have been started + expect(subscriptionCreated).toHaveBeenCalledTimes(1); + }); }); describe.skip("Type Tests", () => { diff --git a/src/react/hooks/internal/wrapHook.ts b/src/react/hooks/internal/wrapHook.ts index c22ec726e9d..59b112c3216 100644 --- a/src/react/hooks/internal/wrapHook.ts +++ b/src/react/hooks/internal/wrapHook.ts @@ -9,10 +9,12 @@ import type { import type { QueryManager } from "../../../core/QueryManager.js"; import type { ApolloClient } from "../../../core/ApolloClient.js"; import type { ObservableQuery } from "../../../core/ObservableQuery.js"; +import type { createQueryPreloader } from "../../query-preloader/createQueryPreloader.js"; const wrapperSymbol = Symbol.for("apollo.hook.wrappers"); interface WrappableHooks { + createQueryPreloader: typeof createQueryPreloader; useQuery: typeof useQuery; useSuspenseQuery: typeof useSuspenseQuery; useBackgroundQuery: typeof useBackgroundQuery; diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index 4b5a5668389..ba8dc1e71fd 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -17,7 +17,11 @@ import type { CacheKey, QueryRef } from "../internal/index.js"; import type { BackgroundQueryHookOptions, NoInfer } from "../types/types.js"; import { wrapHook } from "./internal/index.js"; import { useWatchQueryOptions } from "./useSuspenseQuery.js"; -import type { FetchMoreFunction, RefetchFunction } from "./useSuspenseQuery.js"; +import type { + FetchMoreFunction, + RefetchFunction, + SubscribeToMoreFunction, +} from "./useSuspenseQuery.js"; import { canonicalStringify } from "../../cache/index.js"; import type { DeepPartial } from "../../utilities/index.js"; import type { SkipToken } from "./constants.js"; @@ -26,7 +30,11 @@ export type UseBackgroundQueryResult< TData = unknown, TVariables extends OperationVariables = OperationVariables, > = { + /** {@inheritDoc @apollo/client!ObservableQuery#subscribeToMore:member(1)} */ + subscribeToMore: SubscribeToMoreFunction; + /** {@inheritDoc @apollo/client!ObservableQuery#fetchMore:member(1)} */ fetchMore: FetchMoreFunction; + /** {@inheritDoc @apollo/client!ObservableQuery#refetch:member(1)} */ refetch: RefetchFunction; }; @@ -281,6 +289,10 @@ function _useBackgroundQuery< return [ didFetchResult.current ? wrappedQueryRef : void 0, - { fetchMore, refetch }, + { + fetchMore, + refetch, + subscribeToMore: queryRef.observable.subscribeToMore, + }, ]; } diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts index a8d6eb00a67..911d2b9e69c 100644 --- a/src/react/hooks/useLazyQuery.ts +++ b/src/react/hooks/useLazyQuery.ts @@ -2,16 +2,30 @@ import type { DocumentNode } from "graphql"; import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; import * as React from "rehackt"; -import type { OperationVariables } from "../../core/index.js"; +import type { + ApolloClient, + ApolloQueryResult, + OperationVariables, + WatchQueryOptions, +} from "../../core/index.js"; import { mergeOptions } from "../../utilities/index.js"; import type { LazyQueryHookExecOptions, LazyQueryHookOptions, LazyQueryResultTuple, NoInfer, + QueryHookOptions, + QueryResult, } from "../types/types.js"; -import { useInternalState } from "./useQuery.js"; -import { useApolloClient } from "./useApolloClient.js"; +import type { InternalResult, ObsQueryWithMeta } from "./useQuery.js"; +import { + createMakeWatchQueryOptions, + getDefaultFetchPolicy, + getObsQueryOptions, + toQueryResult, + useQueryInternals, +} from "./useQuery.js"; +import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js"; // The following methods, when called will execute the query, regardless of // whether the useLazyQuery execute function was called before. @@ -21,6 +35,7 @@ const EAGER_METHODS = [ "fetchMore", "updateQuery", "startPolling", + "stopPolling", "subscribeToMore", ] as const; @@ -80,21 +95,27 @@ export function useLazyQuery< optionsRef.current = options; queryRef.current = document; - const internalState = useInternalState( - useApolloClient(options && options.client), - document - ); - - const useQueryResult = internalState.useQuery({ + const queryHookOptions = { ...merged, skip: !execOptionsRef.current, - }); + }; + const { + obsQueryFields, + result: useQueryResult, + client, + resultData, + observable, + onQueryExecuted, + } = useQueryInternals(document, queryHookOptions); const initialFetchPolicy = - useQueryResult.observable.options.initialFetchPolicy || - internalState.getDefaultFetchPolicy(); + observable.options.initialFetchPolicy || + getDefaultFetchPolicy( + queryHookOptions.defaultOptions, + client.defaultOptions + ); - const { forceUpdateState, obsQueryFields } = internalState; + const forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; // We use useMemo here to make sure the eager methods have a stable identity. const eagerMethods = React.useMemo(() => { const eagerMethods: Record = {}; @@ -111,7 +132,7 @@ export function useLazyQuery< }; } - return eagerMethods; + return eagerMethods as typeof obsQueryFields; }, [forceUpdateState, obsQueryFields]); const called = !!execOptionsRef.current; @@ -141,9 +162,14 @@ export function useLazyQuery< ...execOptionsRef.current, }); - const promise = internalState - .executeQuery({ ...options, skip: false }) - .then((queryResult) => Object.assign(queryResult, eagerMethods)); + const promise = executeQuery( + resultData, + observable, + client, + document, + { ...options, skip: false }, + onQueryExecuted + ).then((queryResult) => Object.assign(queryResult, eagerMethods)); // Because the return value of `useLazyQuery` is usually floated, we need // to catch the promise to prevent unhandled rejections. @@ -151,8 +177,80 @@ export function useLazyQuery< return promise; }, - [eagerMethods, initialFetchPolicy, internalState] + [ + client, + document, + eagerMethods, + initialFetchPolicy, + observable, + resultData, + onQueryExecuted, + ] + ); + + const executeRef = React.useRef(execute); + useIsomorphicLayoutEffect(() => { + executeRef.current = execute; + }); + + const stableExecute = React.useCallback( + (...args) => executeRef.current(...args), + [] ); + return [stableExecute, result]; +} - return [execute, result]; +function executeQuery( + resultData: InternalResult, + observable: ObsQueryWithMeta, + client: ApolloClient, + currentQuery: DocumentNode, + options: QueryHookOptions & { + query?: DocumentNode; + }, + onQueryExecuted: (options: WatchQueryOptions) => void +) { + const query = options.query || currentQuery; + const watchQueryOptions = createMakeWatchQueryOptions( + client, + query, + options, + false + )(observable); + + const concast = observable.reobserveAsConcast( + getObsQueryOptions(observable, client, options, watchQueryOptions) + ); + onQueryExecuted(watchQueryOptions); + + return new Promise< + Omit, (typeof EAGER_METHODS)[number]> + >((resolve) => { + let result: ApolloQueryResult; + + // Subscribe to the concast independently of the ObservableQuery in case + // the component gets unmounted before the promise resolves. This prevents + // the concast from terminating early and resolving with `undefined` when + // there are no more subscribers for the concast. + concast.subscribe({ + next: (value) => { + result = value; + }, + error: () => { + resolve( + toQueryResult( + observable.getCurrentResult(), + resultData.previousData, + observable, + client + ) + ); + }, + complete: () => { + resolve( + toQueryResult(result, resultData.previousData, observable, client) + ); + }, + }); + }); } diff --git a/src/react/hooks/useLoadableQuery.ts b/src/react/hooks/useLoadableQuery.ts index 15d1e2a7e56..b9aa70d11e2 100644 --- a/src/react/hooks/useLoadableQuery.ts +++ b/src/react/hooks/useLoadableQuery.ts @@ -18,7 +18,11 @@ import type { CacheKey, QueryRef } from "../internal/index.js"; import type { LoadableQueryHookOptions } from "../types/types.js"; import { __use, useRenderGuard } from "./internal/index.js"; import { useWatchQueryOptions } from "./useSuspenseQuery.js"; -import type { FetchMoreFunction, RefetchFunction } from "./useSuspenseQuery.js"; +import type { + FetchMoreFunction, + RefetchFunction, + SubscribeToMoreFunction, +} from "./useSuspenseQuery.js"; import { canonicalStringify } from "../../cache/index.js"; import type { DeepPartial, @@ -49,6 +53,8 @@ export type UseLoadableQueryResult< fetchMore: FetchMoreFunction; /** {@inheritDoc @apollo/client!QueryResultDocumentation#refetch:member} */ refetch: RefetchFunction; + /** {@inheritDoc @apollo/client!ObservableQuery#subscribeToMore:member(1)} */ + subscribeToMore: SubscribeToMoreFunction; /** * A function that resets the `queryRef` back to `null`. */ @@ -255,9 +261,22 @@ export function useLoadableQuery< ] ); + const subscribeToMore: SubscribeToMoreFunction = + React.useCallback( + (options) => { + invariant( + internalQueryRef, + "The query has not been loaded. Please load the query." + ); + + return internalQueryRef.observable.subscribeToMore(options); + }, + [internalQueryRef] + ); + const reset: ResetFunction = React.useCallback(() => { setQueryRef(null); }, []); - return [loadQuery, queryRef, { fetchMore, refetch, reset }]; + return [loadQuery, queryRef, { fetchMore, refetch, reset, subscribeToMore }]; } diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 61ca66527b6..5a483f9b4e8 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -1,3 +1,22 @@ +/** + * Function parameters in this file try to follow a common order for the sake of + * readability and consistency. The order is as follows: + * + * resultData + * observable + * client + * query + * options + * watchQueryOptions + * makeWatchQueryOptions + * isSSRAllowed + * disableNetworkFetches + * partialRefetch + * renderPromises + * isSyncSSR + * callbacks + */ +/** */ import { invariant } from "../../utilities/globals/index.js"; import * as React from "rehackt"; @@ -5,15 +24,15 @@ import { useSyncExternalStore } from "./useSyncExternalStore.js"; import { equal } from "@wry/equality"; import type { + ApolloClient, + DefaultOptions, OperationVariables, WatchQueryFetchPolicy, } from "../../core/index.js"; import { mergeOptions } from "../../utilities/index.js"; -import type { ApolloContextValue } from "../context/index.js"; import { getApolloContext } from "../context/index.js"; import { ApolloError } from "../../errors/index.js"; import type { - ApolloClient, ApolloQueryResult, ObservableQuery, DocumentNode, @@ -31,17 +50,57 @@ import type { import { DocumentType, verifyDocumentType } from "../parser/index.js"; import { useApolloClient } from "./useApolloClient.js"; import { - canUseWeakMap, compact, isNonEmptyArray, maybeDeepFreeze, } from "../../utilities/index.js"; import { wrapHook } from "./internal/index.js"; +import type { RenderPromises } from "../ssr/RenderPromises.js"; const { prototype: { hasOwnProperty }, } = Object; +type InternalQueryResult = Omit< + QueryResult, + Exclude, "variables"> +>; + +function noop() {} +export const lastWatchOptions = Symbol(); + +export interface ObsQueryWithMeta + extends ObservableQuery { + [lastWatchOptions]?: WatchQueryOptions; +} + +export interface InternalResult { + // These members are populated by getCurrentResult and setResult, and it's + // okay/normal for them to be initially undefined. + current?: undefined | InternalQueryResult; + previousData?: undefined | TData; +} + +interface InternalState { + client: ReturnType; + query: DocumentNode | TypedDocumentNode; + observable: ObsQueryWithMeta; + resultData: InternalResult; +} + +export type UpdateInternalState< + TData, + TVariables extends OperationVariables, +> = (state: InternalState) => void; + +interface Callbacks { + // Defining these methods as no-ops on the prototype allows us to call + // state.onCompleted and/or state.onError without worrying about whether a + // callback was provided. + onCompleted(data: TData): void; + onError(error: ApolloError): void; +} + /** * A hook for executing queries in an Apollo application. * @@ -100,295 +159,203 @@ function _useQuery< query: DocumentNode | TypedDocumentNode, options: QueryHookOptions, NoInfer> ) { - return useInternalState(useApolloClient(options.client), query).useQuery( - options + const { result, obsQueryFields } = useQueryInternals(query, options); + return React.useMemo( + () => ({ ...result, ...obsQueryFields }), + [result, obsQueryFields] ); } -export function useInternalState( - client: ApolloClient, - query: DocumentNode | TypedDocumentNode -): InternalState { - // By default, InternalState.prototype.forceUpdate is an empty function, but - // we replace it here (before anyone has had a chance to see this state yet) - // with a function that unconditionally forces an update, using the latest - // setTick function. Updating this state by calling state.forceUpdate or the - // uSES notification callback are the only way we trigger React component updates. - const forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; - +function useInternalState< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + client: ApolloClient, + query: DocumentNode | TypedDocumentNode, + options: QueryHookOptions, NoInfer>, + renderPromises: RenderPromises | undefined, + makeWatchQueryOptions: () => WatchQueryOptions +) { function createInternalState(previous?: InternalState) { - return Object.assign(new InternalState(client, query, previous), { - forceUpdateState, - }); + verifyDocumentType(query, DocumentType.Query); + + const internalState: InternalState = { + client, + query, + observable: + // See if there is an existing observable that was used to fetch the same + // data and if so, use it instead since it will contain the proper queryId + // to fetch the result set. This is used during SSR. + (renderPromises && + renderPromises.getSSRObservable(makeWatchQueryOptions())) || + client.watchQuery( + getObsQueryOptions(void 0, client, options, makeWatchQueryOptions()) + ), + resultData: { + // Reuse previousData from previous InternalState (if any) to provide + // continuity of previousData even if/when the query or client changes. + previousData: previous?.resultData.current?.data, + }, + }; + + return internalState as InternalState; } - let [state, updateState] = React.useState(createInternalState); + let [internalState, updateInternalState] = + React.useState(createInternalState); + + /** + * Used by `useLazyQuery` when a new query is executed. + * We keep this logic here since it needs to update things in unsafe + * ways and here we at least can keep track of that in a single place. + */ + function onQueryExecuted( + watchQueryOptions: WatchQueryOptions + ) { + // this needs to be set to prevent an immediate `resubscribe` in the + // next rerender of the `useQuery` internals + Object.assign(internalState.observable, { + [lastWatchOptions]: watchQueryOptions, + }); + const resultData = internalState.resultData; + updateInternalState({ + ...internalState, + // might be a different query + query: watchQueryOptions.query, + resultData: Object.assign(resultData, { + // We need to modify the previous `resultData` object as we rely on the + // object reference in other places + previousData: resultData.current?.data || resultData.previousData, + current: undefined, + }), + }); + } - if (client !== state.client || query !== state.query) { + if (client !== internalState.client || query !== internalState.query) { // If the client or query have changed, we need to create a new InternalState. // This will trigger a re-render with the new state, but it will also continue // to run the current render function to completion. // Since we sometimes trigger some side-effects in the render function, we // re-assign `state` to the new state to ensure that those side-effects are // triggered with the new state. - updateState((state = createInternalState(state))); + const newInternalState = createInternalState(internalState); + updateInternalState(newInternalState); + return [newInternalState, onQueryExecuted] as const; } - return state; + return [internalState, onQueryExecuted] as const; } -class InternalState { - constructor( - public readonly client: ReturnType, - public readonly query: DocumentNode | TypedDocumentNode, - previous?: InternalState - ) { - verifyDocumentType(query, DocumentType.Query); +export function useQueryInternals< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + query: DocumentNode | TypedDocumentNode, + options: QueryHookOptions, NoInfer> +) { + const client = useApolloClient(options.client); + + const renderPromises = React.useContext(getApolloContext()).renderPromises; + const isSyncSSR = !!renderPromises; + const disableNetworkFetches = client.disableNetworkFetches; + const ssrAllowed = options.ssr !== false && !options.skip; + const partialRefetch = options.partialRefetch; + + const makeWatchQueryOptions = createMakeWatchQueryOptions( + client, + query, + options, + isSyncSSR + ); - // Reuse previousData from previous InternalState (if any) to provide - // continuity of previousData even if/when the query or client changes. - const previousResult = previous && previous.result; - const previousData = previousResult && previousResult.data; - if (previousData) { - this.previousData = previousData; - } - } + const [{ observable, resultData }, onQueryExecuted] = useInternalState( + client, + query, + options, + renderPromises, + makeWatchQueryOptions + ); - /** - * Forces an update using local component state. - * As this is not batched with `useSyncExternalStore` updates, - * this is only used as a fallback if the `useSyncExternalStore` "force update" - * method is not registered at the moment. - * See https://github.com/facebook/react/issues/25191 - * */ - forceUpdateState() { - // Replaced (in useInternalState) with a method that triggers an update. - invariant.warn( - "Calling default no-op implementation of InternalState#forceUpdate" - ); - } + const watchQueryOptions: Readonly> = + makeWatchQueryOptions(observable); - /** - * Will be overwritten by the `useSyncExternalStore` "force update" method - * whenever it is available and reset to `forceUpdateState` when it isn't. - */ - forceUpdate = () => this.forceUpdateState(); + useResubscribeIfNecessary( + resultData, // might get mutated during render + observable, // might get mutated during render + client, + options, + watchQueryOptions + ); - executeQuery( - options: QueryHookOptions & { - query?: DocumentNode; - } - ) { - if (options.query) { - Object.assign(this, { query: options.query }); + const obsQueryFields = React.useMemo< + Omit, "variables"> + >(() => bindObservableMethods(observable), [observable]); + + useRegisterSSRObservable(observable, renderPromises, ssrAllowed); + + const result = useObservableSubscriptionResult( + resultData, + observable, + client, + options, + watchQueryOptions, + disableNetworkFetches, + partialRefetch, + isSyncSSR, + { + onCompleted: options.onCompleted || noop, + onError: options.onError || noop, } + ); - this.watchQueryOptions = this.createWatchQueryOptions( - (this.queryHookOptions = options) - ); - - const concast = this.observable.reobserveAsConcast( - this.getObsQueryOptions() - ); - - // Make sure getCurrentResult returns a fresh ApolloQueryResult, - // but save the current data as this.previousData, just like setResult - // usually does. - this.previousData = this.result?.data || this.previousData; - this.result = void 0; - this.forceUpdate(); - - return new Promise>((resolve) => { - let result: ApolloQueryResult; - - // Subscribe to the concast independently of the ObservableQuery in case - // the component gets unmounted before the promise resolves. This prevents - // the concast from terminating early and resolving with `undefined` when - // there are no more subscribers for the concast. - concast.subscribe({ - next: (value) => { - result = value; - }, - error: () => { - resolve(this.toQueryResult(this.observable.getCurrentResult())); - }, - complete: () => { - resolve(this.toQueryResult(result)); - }, - }); - }); - } - - // Methods beginning with use- should be called according to the standard - // rules of React hooks: only at the top level of the calling function, and - // without any dynamic conditional logic. - useQuery(options: QueryHookOptions) { - // The renderPromises field gets initialized here in the useQuery method, at - // the beginning of everything (for a given component rendering, at least), - // so we can safely use this.renderPromises in other/later InternalState - // methods without worrying it might be uninitialized. Even after - // initialization, this.renderPromises is usually undefined (unless SSR is - // happening), but that's fine as long as it has been initialized that way, - // rather than left uninitialized. - // eslint-disable-next-line react-hooks/rules-of-hooks - this.renderPromises = React.useContext(getApolloContext()).renderPromises; - - this.useOptions(options); - - const obsQuery = this.useObservableQuery(); - - // eslint-disable-next-line react-hooks/rules-of-hooks - const result = useSyncExternalStore( - // eslint-disable-next-line react-hooks/rules-of-hooks - React.useCallback( - (handleStoreChange) => { - if (this.renderPromises) { - return () => {}; - } - - this.forceUpdate = handleStoreChange; - - const onNext = () => { - const previousResult = this.result; - // We use `getCurrentResult()` instead of the onNext argument because - // the values differ slightly. Specifically, loading results will have - // an empty object for data instead of `undefined` for some reason. - const result = obsQuery.getCurrentResult(); - // Make sure we're not attempting to re-render similar results - if ( - previousResult && - previousResult.loading === result.loading && - previousResult.networkStatus === result.networkStatus && - equal(previousResult.data, result.data) - ) { - return; - } - - this.setResult(result); - }; - - const onError = (error: Error) => { - subscription.unsubscribe(); - subscription = obsQuery.resubscribeAfterError(onNext, onError); - - if (!hasOwnProperty.call(error, "graphQLErrors")) { - // The error is not a GraphQL error - throw error; - } - - const previousResult = this.result; - if ( - !previousResult || - (previousResult && previousResult.loading) || - !equal(error, previousResult.error) - ) { - this.setResult({ - data: (previousResult && previousResult.data) as TData, - error: error as ApolloError, - loading: false, - networkStatus: NetworkStatus.error, - }); - } - }; - - let subscription = obsQuery.subscribe(onNext, onError); - - // Do the "unsubscribe" with a short delay. - // This way, an existing subscription can be reused without an additional - // request if "unsubscribe" and "resubscribe" to the same ObservableQuery - // happen in very fast succession. - return () => { - setTimeout(() => subscription.unsubscribe()); - this.forceUpdate = () => this.forceUpdateState(); - }; - }, - [ - // We memoize the subscribe function using useCallback and the following - // dependency keys, because the subscribe function reference is all that - // useSyncExternalStore uses internally as a dependency key for the - // useEffect ultimately responsible for the subscription, so we are - // effectively passing this dependency array to that useEffect buried - // inside useSyncExternalStore, as desired. - obsQuery, - // eslint-disable-next-line react-hooks/exhaustive-deps - this.renderPromises, - // eslint-disable-next-line react-hooks/exhaustive-deps - this.client.disableNetworkFetches, - ] - ), - - () => this.getCurrentResult(), - () => this.getCurrentResult() - ); - - // TODO Remove this method when we remove support for options.partialRefetch. - this.unsafeHandlePartialRefetch(result); + return { + result, + obsQueryFields, + observable, + resultData, + client, + onQueryExecuted, + }; +} - return this.toQueryResult(result); +function useObservableSubscriptionResult< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + resultData: InternalResult, + observable: ObservableQuery, + client: ApolloClient, + options: QueryHookOptions, NoInfer>, + watchQueryOptions: Readonly>, + disableNetworkFetches: boolean, + partialRefetch: boolean | undefined, + isSyncSSR: boolean, + callbacks: { + onCompleted: (data: TData) => void; + onError: (error: ApolloError) => void; } - - // These members (except for renderPromises) are all populated by the - // useOptions method, which is called unconditionally at the beginning of the - // useQuery method, so we can safely use these members in other/later methods - // without worrying they might be uninitialized. - private renderPromises: ApolloContextValue["renderPromises"]; - private queryHookOptions!: QueryHookOptions; - private watchQueryOptions!: WatchQueryOptions; - - private useOptions(options: QueryHookOptions) { - const watchQueryOptions = this.createWatchQueryOptions( - (this.queryHookOptions = options) - ); - - // Update this.watchQueryOptions, but only when they have changed, which - // allows us to depend on the referential stability of - // this.watchQueryOptions elsewhere. - const currentWatchQueryOptions = this.watchQueryOptions; - - if (!equal(watchQueryOptions, currentWatchQueryOptions)) { - this.watchQueryOptions = watchQueryOptions; - - if (currentWatchQueryOptions && this.observable) { - // Though it might be tempting to postpone this reobserve call to the - // useEffect block, we need getCurrentResult to return an appropriate - // loading:true result synchronously (later within the same call to - // useQuery). Since we already have this.observable here (not true for - // the very first call to useQuery), we are not initiating any new - // subscriptions, though it does feel less than ideal that reobserve - // (potentially) kicks off a network request (for example, when the - // variables have changed), which is technically a side-effect. - this.observable.reobserve(this.getObsQueryOptions()); - - // Make sure getCurrentResult returns a fresh ApolloQueryResult, - // but save the current data as this.previousData, just like setResult - // usually does. - this.previousData = this.result?.data || this.previousData; - this.result = void 0; - } - } - +) { + const callbackRef = React.useRef>(callbacks); + React.useEffect(() => { // Make sure state.onCompleted and state.onError always reflect the latest // options.onCompleted and options.onError callbacks provided to useQuery, // since those functions are often recreated every time useQuery is called. // Like the forceUpdate method, the versions of these methods inherited from // InternalState.prototype are empty no-ops, but we can override them on the // base state object (without modifying the prototype). - this.onCompleted = - options.onCompleted || InternalState.prototype.onCompleted; - this.onError = options.onError || InternalState.prototype.onError; + callbackRef.current = callbacks; + }); - if ( - (this.renderPromises || this.client.disableNetworkFetches) && - this.queryHookOptions.ssr === false && - !this.queryHookOptions.skip - ) { + const resultOverride = + ( + (isSyncSSR || disableNetworkFetches) && + options.ssr === false && + !options.skip + ) ? // If SSR has been explicitly disabled, and this function has been called // on the server side, return the default loading state. - this.result = this.ssrDisabledResult; - } else if ( - this.queryHookOptions.skip || - this.watchQueryOptions.fetchPolicy === "standby" - ) { + ssrDisabledResult + : options.skip || watchQueryOptions.fetchPolicy === "standby" ? // When skipping a query (ie. we're not querying for data but still want to // render children), make sure the `data` is cleared out and `loading` is // set to `false` (since we aren't loading anything). @@ -399,61 +366,203 @@ class InternalState { // previously received data is all of a sudden removed. Unfortunately, // changing this is breaking, so we'll have to wait until Apollo Client 4.0 // to address this. - this.result = this.skipStandbyResult; - } else if ( - this.result === this.ssrDisabledResult || - this.result === this.skipStandbyResult - ) { - this.result = void 0; - } - } - - private getObsQueryOptions(): WatchQueryOptions { - const toMerge: Array>> = []; + skipStandbyResult + : void 0; + + const previousData = resultData.previousData; + const currentResultOverride = React.useMemo( + () => + resultOverride && + toQueryResult(resultOverride, previousData, observable, client), + [client, observable, resultOverride, previousData] + ); - const globalDefaults = this.client.defaultOptions.watchQuery; - if (globalDefaults) toMerge.push(globalDefaults); + return useSyncExternalStore( + React.useCallback( + (handleStoreChange) => { + // reference `disableNetworkFetches` here to ensure that the rules of hooks + // keep it as a dependency of this effect, even though it's not used + disableNetworkFetches; + + if (isSyncSSR) { + return () => {}; + } + + const onNext = () => { + const previousResult = resultData.current; + // We use `getCurrentResult()` instead of the onNext argument because + // the values differ slightly. Specifically, loading results will have + // an empty object for data instead of `undefined` for some reason. + const result = observable.getCurrentResult(); + // Make sure we're not attempting to re-render similar results + if ( + previousResult && + previousResult.loading === result.loading && + previousResult.networkStatus === result.networkStatus && + equal(previousResult.data, result.data) + ) { + return; + } - if (this.queryHookOptions.defaultOptions) { - toMerge.push(this.queryHookOptions.defaultOptions); - } + setResult( + result, + resultData, + observable, + client, + partialRefetch, + handleStoreChange, + callbackRef.current + ); + }; + + const onError = (error: Error) => { + subscription.current.unsubscribe(); + subscription.current = observable.resubscribeAfterError( + onNext, + onError + ); + + if (!hasOwnProperty.call(error, "graphQLErrors")) { + // The error is not a GraphQL error + throw error; + } - // We use compact rather than mergeOptions for this part of the merge, - // because we want watchQueryOptions.variables (if defined) to replace - // this.observable.options.variables whole. This replacement allows - // removing variables by removing them from the variables input to - // useQuery. If the variables were always merged together (rather than - // replaced), there would be no way to remove existing variables. - // However, the variables from options.defaultOptions and globalDefaults - // (if provided) should be merged, to ensure individual defaulted - // variables always have values, if not otherwise defined in - // observable.options or watchQueryOptions. - toMerge.push( - compact( - this.observable && this.observable.options, - this.watchQueryOptions + const previousResult = resultData.current; + if ( + !previousResult || + (previousResult && previousResult.loading) || + !equal(error, previousResult.error) + ) { + setResult( + { + data: (previousResult && previousResult.data) as TData, + error: error as ApolloError, + loading: false, + networkStatus: NetworkStatus.error, + }, + resultData, + observable, + client, + partialRefetch, + handleStoreChange, + callbackRef.current + ); + } + }; + + // TODO evaluate if we keep this in + // React Compiler cannot handle scoped `let` access, but a mutable object + // like this is fine. + // was: + // let subscription = observable.subscribe(onNext, onError); + const subscription = { current: observable.subscribe(onNext, onError) }; + + // Do the "unsubscribe" with a short delay. + // This way, an existing subscription can be reused without an additional + // request if "unsubscribe" and "resubscribe" to the same ObservableQuery + // happen in very fast succession. + return () => { + setTimeout(() => subscription.current.unsubscribe()); + }; + }, + + [ + disableNetworkFetches, + isSyncSSR, + observable, + resultData, + partialRefetch, + client, + ] + ), + () => + currentResultOverride || + getCurrentResult( + resultData, + observable, + callbackRef.current, + partialRefetch, + client + ), + () => + currentResultOverride || + getCurrentResult( + resultData, + observable, + callbackRef.current, + partialRefetch, + client ) - ); + ); +} + +function useRegisterSSRObservable( + observable: ObsQueryWithMeta, + renderPromises: RenderPromises | undefined, + ssrAllowed: boolean +) { + if (renderPromises && ssrAllowed) { + renderPromises.registerSSRObservable(observable); - return toMerge.reduce(mergeOptions) as WatchQueryOptions; + if (observable.getCurrentResult().loading) { + // TODO: This is a legacy API which could probably be cleaned up + renderPromises.addObservableQueryPromise(observable); + } } +} - private ssrDisabledResult = maybeDeepFreeze({ - loading: true, - data: void 0 as unknown as TData, - error: void 0, - networkStatus: NetworkStatus.loading, - }); +// this hook is not compatible with any rules of React, and there's no good way to rewrite it. +// it should stay a separate hook that will not be optimized by the compiler +function useResubscribeIfNecessary< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + /** this hook will mutate properties on `resultData` */ + resultData: InternalResult, + /** this hook will mutate properties on `observable` */ + observable: ObsQueryWithMeta, + client: ApolloClient, + options: QueryHookOptions, NoInfer>, + watchQueryOptions: Readonly> +) { + if ( + observable[lastWatchOptions] && + !equal(observable[lastWatchOptions], watchQueryOptions) + ) { + // Though it might be tempting to postpone this reobserve call to the + // useEffect block, we need getCurrentResult to return an appropriate + // loading:true result synchronously (later within the same call to + // useQuery). Since we already have this.observable here (not true for + // the very first call to useQuery), we are not initiating any new + // subscriptions, though it does feel less than ideal that reobserve + // (potentially) kicks off a network request (for example, when the + // variables have changed), which is technically a side-effect. + observable.reobserve( + getObsQueryOptions(observable, client, options, watchQueryOptions) + ); - private skipStandbyResult = maybeDeepFreeze({ - loading: false, - data: void 0 as unknown as TData, - error: void 0, - networkStatus: NetworkStatus.ready, - }); + // Make sure getCurrentResult returns a fresh ApolloQueryResult, + // but save the current data as this.previousData, just like setResult + // usually does. + resultData.previousData = + resultData.current?.data || resultData.previousData; + resultData.current = void 0; + } + observable[lastWatchOptions] = watchQueryOptions; +} - // A function to massage options before passing them to ObservableQuery. - private createWatchQueryOptions({ +/* + * A function to massage options before passing them to ObservableQuery. + * This is two-step curried because we want to reuse the `make` function, + * but the `observable` might differ between calls to `make`. + */ +export function createMakeWatchQueryOptions< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + client: ApolloClient, + query: DocumentNode | TypedDocumentNode, + { skip, ssr, onCompleted, @@ -463,17 +572,19 @@ class InternalState { // makes otherOptions almost a WatchQueryOptions object, except for the // query property that we add below. ...otherOptions - }: QueryHookOptions = {}): WatchQueryOptions< - TVariables, - TData - > { + }: QueryHookOptions = {}, + isSyncSSR: boolean +) { + return ( + observable?: ObservableQuery + ): WatchQueryOptions => { // This Object.assign is safe because otherOptions is a fresh ...rest object // that did not exist until just now, so modifications are still allowed. const watchQueryOptions: WatchQueryOptions = - Object.assign(otherOptions, { query: this.query }); + Object.assign(otherOptions, { query }); if ( - this.renderPromises && + isSyncSSR && (watchQueryOptions.fetchPolicy === "network-only" || watchQueryOptions.fetchPolicy === "cache-and-network") ) { @@ -487,208 +598,237 @@ class InternalState { } if (skip) { - const { - fetchPolicy = this.getDefaultFetchPolicy(), - initialFetchPolicy = fetchPolicy, - } = watchQueryOptions; - // When skipping, we set watchQueryOptions.fetchPolicy initially to // "standby", but we also need/want to preserve the initial non-standby // fetchPolicy that would have been used if not skipping. - Object.assign(watchQueryOptions, { - initialFetchPolicy, - fetchPolicy: "standby", - }); + watchQueryOptions.initialFetchPolicy = + watchQueryOptions.initialFetchPolicy || + watchQueryOptions.fetchPolicy || + getDefaultFetchPolicy(defaultOptions, client.defaultOptions); + watchQueryOptions.fetchPolicy = "standby"; } else if (!watchQueryOptions.fetchPolicy) { watchQueryOptions.fetchPolicy = - this.observable?.options.initialFetchPolicy || - this.getDefaultFetchPolicy(); + observable?.options.initialFetchPolicy || + getDefaultFetchPolicy(defaultOptions, client.defaultOptions); } return watchQueryOptions; - } + }; +} - getDefaultFetchPolicy(): WatchQueryFetchPolicy { - return ( - this.queryHookOptions.defaultOptions?.fetchPolicy || - this.client.defaultOptions.watchQuery?.fetchPolicy || - "cache-first" - ); +export function getObsQueryOptions< + TData, + TVariables extends OperationVariables, +>( + observable: ObservableQuery | undefined, + client: ApolloClient, + queryHookOptions: QueryHookOptions, + watchQueryOptions: Partial> +): WatchQueryOptions { + const toMerge: Array>> = []; + + const globalDefaults = client.defaultOptions.watchQuery; + if (globalDefaults) toMerge.push(globalDefaults); + + if (queryHookOptions.defaultOptions) { + toMerge.push(queryHookOptions.defaultOptions); } - // Defining these methods as no-ops on the prototype allows us to call - // state.onCompleted and/or state.onError without worrying about whether a - // callback was provided. - private onCompleted(data: TData) {} - private onError(error: ApolloError) {} - - private observable!: ObservableQuery; - public obsQueryFields!: Omit< - ObservableQueryFields, - "variables" - >; - - private useObservableQuery() { - // See if there is an existing observable that was used to fetch the same - // data and if so, use it instead since it will contain the proper queryId - // to fetch the result set. This is used during SSR. - const obsQuery = (this.observable = - (this.renderPromises && - this.renderPromises.getSSRObservable(this.watchQueryOptions)) || - this.observable || // Reuse this.observable if possible (and not SSR) - this.client.watchQuery(this.getObsQueryOptions())); - - // eslint-disable-next-line react-hooks/rules-of-hooks - this.obsQueryFields = React.useMemo( - () => ({ - refetch: obsQuery.refetch.bind(obsQuery), - reobserve: obsQuery.reobserve.bind(obsQuery), - fetchMore: obsQuery.fetchMore.bind(obsQuery), - updateQuery: obsQuery.updateQuery.bind(obsQuery), - startPolling: obsQuery.startPolling.bind(obsQuery), - stopPolling: obsQuery.stopPolling.bind(obsQuery), - subscribeToMore: obsQuery.subscribeToMore.bind(obsQuery), - }), - [obsQuery] - ); - - const ssrAllowed = !( - this.queryHookOptions.ssr === false || this.queryHookOptions.skip - ); - - if (this.renderPromises && ssrAllowed) { - this.renderPromises.registerSSRObservable(obsQuery); - - if (obsQuery.getCurrentResult().loading) { - // TODO: This is a legacy API which could probably be cleaned up - this.renderPromises.addObservableQueryPromise(obsQuery); - } - } + // We use compact rather than mergeOptions for this part of the merge, + // because we want watchQueryOptions.variables (if defined) to replace + // this.observable.options.variables whole. This replacement allows + // removing variables by removing them from the variables input to + // useQuery. If the variables were always merged together (rather than + // replaced), there would be no way to remove existing variables. + // However, the variables from options.defaultOptions and globalDefaults + // (if provided) should be merged, to ensure individual defaulted + // variables always have values, if not otherwise defined in + // observable.options or watchQueryOptions. + toMerge.push(compact(observable && observable.options, watchQueryOptions)); + + return toMerge.reduce(mergeOptions) as WatchQueryOptions; +} - return obsQuery; +function setResult( + nextResult: ApolloQueryResult, + resultData: InternalResult, + observable: ObservableQuery, + client: ApolloClient, + partialRefetch: boolean | undefined, + forceUpdate: () => void, + callbacks: Callbacks +) { + const previousResult = resultData.current; + if (previousResult && previousResult.data) { + resultData.previousData = previousResult.data; } - // These members are populated by getCurrentResult and setResult, and it's - // okay/normal for them to be initially undefined. - private result: undefined | ApolloQueryResult; - private previousData: undefined | TData; - - private setResult(nextResult: ApolloQueryResult) { - const previousResult = this.result; - if (previousResult && previousResult.data) { - this.previousData = previousResult.data; - } - this.result = nextResult; - // Calling state.setResult always triggers an update, though some call sites - // perform additional equality checks before committing to an update. - this.forceUpdate(); - this.handleErrorOrCompleted(nextResult, previousResult); + if (!nextResult.error && isNonEmptyArray(nextResult.errors)) { + // Until a set naming convention for networkError and graphQLErrors is + // decided upon, we map errors (graphQLErrors) to the error options. + // TODO: Is it possible for both result.error and result.errors to be + // defined here? + nextResult.error = new ApolloError({ graphQLErrors: nextResult.errors }); } - private handleErrorOrCompleted( - result: ApolloQueryResult, - previousResult?: ApolloQueryResult - ) { - if (!result.loading) { - const error = this.toApolloError(result); - - // wait a tick in case we are in the middle of rendering a component - Promise.resolve() - .then(() => { - if (error) { - this.onError(error); - } else if ( - result.data && - previousResult?.networkStatus !== result.networkStatus && - result.networkStatus === NetworkStatus.ready - ) { - this.onCompleted(result.data); - } - }) - .catch((error) => { - invariant.warn(error); - }); - } - } + resultData.current = toQueryResult( + unsafeHandlePartialRefetch(nextResult, observable, partialRefetch), + resultData.previousData, + observable, + client + ); + // Calling state.setResult always triggers an update, though some call sites + // perform additional equality checks before committing to an update. + forceUpdate(); + handleErrorOrCompleted(nextResult, previousResult?.networkStatus, callbacks); +} - private toApolloError( - result: ApolloQueryResult - ): ApolloError | undefined { - return isNonEmptyArray(result.errors) ? - new ApolloError({ graphQLErrors: result.errors }) - : result.error; +function handleErrorOrCompleted( + result: ApolloQueryResult, + previousNetworkStatus: NetworkStatus | undefined, + callbacks: Callbacks +) { + if (!result.loading) { + const error = toApolloError(result); + + // wait a tick in case we are in the middle of rendering a component + Promise.resolve() + .then(() => { + if (error) { + callbacks.onError(error); + } else if ( + result.data && + previousNetworkStatus !== result.networkStatus && + result.networkStatus === NetworkStatus.ready + ) { + callbacks.onCompleted(result.data); + } + }) + .catch((error) => { + invariant.warn(error); + }); } +} - private getCurrentResult(): ApolloQueryResult { - // Using this.result as a cache ensures getCurrentResult continues returning - // the same (===) result object, unless state.setResult has been called, or - // we're doing server rendering and therefore override the result below. - if (!this.result) { - this.handleErrorOrCompleted( - (this.result = this.observable.getCurrentResult()) - ); - } - return this.result; +function getCurrentResult( + resultData: InternalResult, + observable: ObservableQuery, + callbacks: Callbacks, + partialRefetch: boolean | undefined, + client: ApolloClient +): InternalQueryResult { + // Using this.result as a cache ensures getCurrentResult continues returning + // the same (===) result object, unless state.setResult has been called, or + // we're doing server rendering and therefore override the result below. + if (!resultData.current) { + // WARNING: SIDE-EFFECTS IN THE RENDER FUNCTION + // this could call unsafeHandlePartialRefetch + setResult( + observable.getCurrentResult(), + resultData, + observable, + client, + partialRefetch, + () => {}, + callbacks + ); } + return resultData.current!; +} - // This cache allows the referential stability of this.result (as returned by - // getCurrentResult) to translate into referential stability of the resulting - // QueryResult object returned by toQueryResult. - private toQueryResultCache = new (canUseWeakMap ? WeakMap : Map)< - ApolloQueryResult, - QueryResult - >(); - - toQueryResult( - result: ApolloQueryResult - ): QueryResult { - let queryResult = this.toQueryResultCache.get(result); - if (queryResult) return queryResult; - - const { data, partial, ...resultWithoutPartial } = result; - this.toQueryResultCache.set( - result, - (queryResult = { - data, // Ensure always defined, even if result.data is missing. - ...resultWithoutPartial, - ...this.obsQueryFields, - client: this.client, - observable: this.observable, - variables: this.observable.variables, - called: !this.queryHookOptions.skip, - previousData: this.previousData, - }) - ); +export function getDefaultFetchPolicy< + TData, + TVariables extends OperationVariables, +>( + queryHookDefaultOptions?: Partial>, + clientDefaultOptions?: DefaultOptions +): WatchQueryFetchPolicy { + return ( + queryHookDefaultOptions?.fetchPolicy || + clientDefaultOptions?.watchQuery?.fetchPolicy || + "cache-first" + ); +} - if (!queryResult.error && isNonEmptyArray(result.errors)) { - // Until a set naming convention for networkError and graphQLErrors is - // decided upon, we map errors (graphQLErrors) to the error options. - // TODO: Is it possible for both result.error and result.errors to be - // defined here? - queryResult.error = new ApolloError({ graphQLErrors: result.errors }); - } +export function toApolloError( + result: Pick, "errors" | "error"> +): ApolloError | undefined { + return isNonEmptyArray(result.errors) ? + new ApolloError({ graphQLErrors: result.errors }) + : result.error; +} - return queryResult; - } +export function toQueryResult( + result: ApolloQueryResult, + previousData: TData | undefined, + observable: ObservableQuery, + client: ApolloClient +): InternalQueryResult { + const { data, partial, ...resultWithoutPartial } = result; + const queryResult: InternalQueryResult = { + data, // Ensure always defined, even if result.data is missing. + ...resultWithoutPartial, + client: client, + observable: observable, + variables: observable.variables, + called: result !== ssrDisabledResult && result !== skipStandbyResult, + previousData, + }; + return queryResult; +} - private unsafeHandlePartialRefetch(result: ApolloQueryResult) { - // WARNING: SIDE-EFFECTS IN THE RENDER FUNCTION - // - // TODO: This code should be removed when the partialRefetch option is - // removed. I was unable to get this hook to behave reasonably in certain - // edge cases when this block was put in an effect. - if ( - result.partial && - this.queryHookOptions.partialRefetch && - !result.loading && - (!result.data || Object.keys(result.data).length === 0) && - this.observable.options.fetchPolicy !== "cache-only" - ) { - Object.assign(result, { - loading: true, - networkStatus: NetworkStatus.refetch, - }); - this.observable.refetch(); - } +function unsafeHandlePartialRefetch< + TData, + TVariables extends OperationVariables, +>( + result: ApolloQueryResult, + observable: ObservableQuery, + partialRefetch: boolean | undefined +): ApolloQueryResult { + // TODO: This code should be removed when the partialRefetch option is + // removed. I was unable to get this hook to behave reasonably in certain + // edge cases when this block was put in an effect. + if ( + result.partial && + partialRefetch && + !result.loading && + (!result.data || Object.keys(result.data).length === 0) && + observable.options.fetchPolicy !== "cache-only" + ) { + observable.refetch(); + return { + ...result, + loading: true, + networkStatus: NetworkStatus.refetch, + }; } + return result; +} + +const ssrDisabledResult = maybeDeepFreeze({ + loading: true, + data: void 0 as any, + error: void 0, + networkStatus: NetworkStatus.loading, +}); + +const skipStandbyResult = maybeDeepFreeze({ + loading: false, + data: void 0 as any, + error: void 0, + networkStatus: NetworkStatus.ready, +}); + +function bindObservableMethods( + observable: ObservableQuery +) { + return { + refetch: observable.refetch.bind(observable), + reobserve: observable.reobserve.bind(observable), + fetchMore: observable.fetchMore.bind(observable), + updateQuery: observable.updateQuery.bind(observable), + startPolling: observable.startPolling.bind(observable), + stopPolling: observable.stopPolling.bind(observable), + subscribeToMore: observable.subscribeToMore.bind(observable), + }; } diff --git a/src/react/hooks/useQueryRefHandlers.ts b/src/react/hooks/useQueryRefHandlers.ts index 95036eafcf3..a621d579691 100644 --- a/src/react/hooks/useQueryRefHandlers.ts +++ b/src/react/hooks/useQueryRefHandlers.ts @@ -8,7 +8,11 @@ import { } from "../internal/index.js"; import type { QueryRef } from "../internal/index.js"; import type { OperationVariables } from "../../core/types.js"; -import type { RefetchFunction, FetchMoreFunction } from "./useSuspenseQuery.js"; +import type { + RefetchFunction, + FetchMoreFunction, + SubscribeToMoreFunction, +} from "./useSuspenseQuery.js"; import type { FetchMoreQueryOptions } from "../../core/watchQueryOptions.js"; import { useApolloClient } from "./useApolloClient.js"; import { wrapHook } from "./internal/index.js"; @@ -21,6 +25,8 @@ export interface UseQueryRefHandlersResult< refetch: RefetchFunction; /** {@inheritDoc @apollo/client!ObservableQuery#fetchMore:member(1)} */ fetchMore: FetchMoreFunction; + /** {@inheritDoc @apollo/client!ObservableQuery#subscribeToMore:member(1)} */ + subscribeToMore: SubscribeToMoreFunction; } /** @@ -112,5 +118,9 @@ function _useQueryRefHandlers< [internalQueryRef] ); - return { refetch, fetchMore }; + return { + refetch, + fetchMore, + subscribeToMore: internalQueryRef.observable.subscribeToMore, + }; } diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index 0ba57c64346..fc2280c7bfb 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -10,8 +10,21 @@ import type { SubscriptionHookOptions, SubscriptionResult, } from "../types/types.js"; -import type { OperationVariables } from "../../core/index.js"; +import type { + ApolloClient, + DefaultContext, + ErrorPolicy, + FetchPolicy, + FetchResult, + OperationVariables, +} from "../../core/index.js"; +import { ApolloError, Observable } from "../../core/index.js"; import { useApolloClient } from "./useApolloClient.js"; +import { useDeepMemo } from "./internal/useDeepMemo.js"; +import { useSyncExternalStore } from "./useSyncExternalStore.js"; +import { toApolloError } from "./useQuery.js"; +import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js"; + /** * > Refer to the [Subscriptions](https://www.apollographql.com/docs/react/data/subscriptions/) section for a more in-depth overview of `useSubscription`. * @@ -35,11 +48,11 @@ import { useApolloClient } from "./useApolloClient.js"; * } * ``` * @remarks - * #### Subscriptions and React 18 Automatic Batching - * - * With React 18's [automatic batching](https://react.dev/blog/2022/03/29/react-v18#new-feature-automatic-batching), multiple state updates may be grouped into a single re-render for better performance. + * #### Consider using `onData` instead of `useEffect` * - * If your subscription API sends multiple messages at the same time or in very fast succession (within fractions of a millisecond), it is likely that only the last message received in that narrow time frame will result in a re-render. + * If you want to react to incoming data, please use the `onData` option instead of `useEffect`. + * State updates you make inside a `useEffect` hook might cause additional rerenders, and `useEffect` is mostly meant for side effects of rendering, not as an event handler. + * State updates made in an event handler like `onData` might - depending on the React version - be batched and cause only a single rerender. * * Consider the following component: * @@ -61,10 +74,6 @@ import { useApolloClient } from "./useApolloClient.js"; * } * ``` * - * If your subscription back-end emits two messages with the same timestamp, only the last message received by Apollo Client will be rendered. This is because React 18 will batch these two state updates into a single re-render. - * - * Since the component above is using `useEffect` to push `data` into a piece of local state on each `Subscriptions` re-render, the first message will never be added to the `accumulatedData` array since its render was skipped. - * * Instead of using `useEffect` here, we can re-write this component to use the `onData` callback function accepted in `useSubscription`'s `options` object: * * ```jsx @@ -102,24 +111,19 @@ export function useSubscription< TVariables extends OperationVariables = OperationVariables, >( subscription: DocumentNode | TypedDocumentNode, - options?: SubscriptionHookOptions, NoInfer> + options: SubscriptionHookOptions< + NoInfer, + NoInfer + > = Object.create(null) ) { const hasIssuedDeprecationWarningRef = React.useRef(false); - const client = useApolloClient(options?.client); + const client = useApolloClient(options.client); verifyDocumentType(subscription, DocumentType.Subscription); - const [result, setResult] = React.useState< - SubscriptionResult - >({ - loading: !options?.skip, - error: void 0, - data: void 0, - variables: options?.variables, - }); if (!hasIssuedDeprecationWarningRef.current) { hasIssuedDeprecationWarningRef.current = true; - if (options?.onSubscriptionData) { + if (options.onSubscriptionData) { invariant.warn( options.onData ? "'useSubscription' supports only the 'onSubscriptionData' or 'onData' option, but not both. Only the 'onData' option will be used." @@ -127,7 +131,7 @@ export function useSubscription< ); } - if (options?.onSubscriptionComplete) { + if (options.onSubscriptionComplete) { invariant.warn( options.onComplete ? "'useSubscription' supports only the 'onSubscriptionComplete' or 'onComplete' option, but not both. Only the 'onComplete' option will be used." @@ -136,146 +140,234 @@ export function useSubscription< } } - const [observable, setObservable] = React.useState(() => { - if (options?.skip) { - return null; - } + const { + skip, + fetchPolicy, + errorPolicy, + shouldResubscribe, + context, + extensions, + ignoreResults, + } = options; + const variables = useDeepMemo(() => options.variables, [options.variables]); - return client.subscribe({ - query: subscription, - variables: options?.variables, - fetchPolicy: options?.fetchPolicy, - context: options?.context, - }); - }); + const recreate = () => + createSubscription( + client, + subscription, + variables, + fetchPolicy, + errorPolicy, + context, + extensions + ); - const canResetObservableRef = React.useRef(false); - React.useEffect(() => { - return () => { - canResetObservableRef.current = true; - }; - }, []); + let [observable, setObservable] = React.useState( + options.skip ? null : recreate + ); - const ref = React.useRef({ client, subscription, options }); - React.useEffect(() => { - let shouldResubscribe = options?.shouldResubscribe; - if (typeof shouldResubscribe === "function") { - shouldResubscribe = !!shouldResubscribe(options!); - } + const recreateRef = React.useRef(recreate); + useIsomorphicLayoutEffect(() => { + recreateRef.current = recreate; + }); - if (options?.skip) { - if ( - !options?.skip !== !ref.current.options?.skip || - canResetObservableRef.current - ) { - setResult({ - loading: false, - data: void 0, - error: void 0, - variables: options?.variables, - }); - setObservable(null); - canResetObservableRef.current = false; - } - } else if ( - (shouldResubscribe !== false && - (client !== ref.current.client || - subscription !== ref.current.subscription || - options?.fetchPolicy !== ref.current.options?.fetchPolicy || - !options?.skip !== !ref.current.options?.skip || - !equal(options?.variables, ref.current.options?.variables))) || - canResetObservableRef.current - ) { - setResult({ - loading: true, - data: void 0, - error: void 0, - variables: options?.variables, - }); - setObservable( - client.subscribe({ - query: subscription, - variables: options?.variables, - fetchPolicy: options?.fetchPolicy, - context: options?.context, - }) - ); - canResetObservableRef.current = false; + if (skip) { + if (observable) { + setObservable((observable = null)); } + } else if ( + !observable || + ((client !== observable.__.client || + subscription !== observable.__.query || + fetchPolicy !== observable.__.fetchPolicy || + errorPolicy !== observable.__.errorPolicy || + !equal(variables, observable.__.variables)) && + (typeof shouldResubscribe === "function" ? + !!shouldResubscribe(options!) + : shouldResubscribe) !== false) + ) { + setObservable((observable = recreate())); + } - Object.assign(ref.current, { client, subscription, options }); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [client, subscription, options, canResetObservableRef.current]); - + const optionsRef = React.useRef(options); React.useEffect(() => { - if (!observable) { - return; - } + optionsRef.current = options; + }); - let subscriptionStopped = false; - const subscription = observable.subscribe({ - next(fetchResult) { - if (subscriptionStopped) { - return; - } + const fallbackLoading = !skip && !ignoreResults; + const fallbackResult = React.useMemo>( + () => ({ + loading: fallbackLoading, + error: void 0, + data: void 0, + variables, + }), + [fallbackLoading, variables] + ); - const result = { - loading: false, - // TODO: fetchResult.data can be null but SubscriptionResult.data - // expects TData | undefined only - data: fetchResult.data!, - error: void 0, - variables: options?.variables, - }; - setResult(result); + const ignoreResultsRef = React.useRef(ignoreResults); + useIsomorphicLayoutEffect(() => { + // We cannot reference `ignoreResults` directly in the effect below + // it would add a dependency to the `useEffect` deps array, which means the + // subscription would be recreated if `ignoreResults` changes + // As a result, on resubscription, the last result would be re-delivered, + // rendering the component one additional time, and re-triggering `onData`. + // The same applies to `fetchPolicy`, which results in a new `observable` + // being created. We cannot really avoid it in that case, but we can at least + // avoid it for `ignoreResults`. + ignoreResultsRef.current = ignoreResults; + }); - if (ref.current.options?.onData) { - ref.current.options.onData({ - client, - data: result, - }); - } else if (ref.current.options?.onSubscriptionData) { - ref.current.options.onSubscriptionData({ - client, - subscriptionData: result, - }); + const ret = useSyncExternalStore>( + React.useCallback( + (update) => { + if (!observable) { + return () => {}; } - }, - error(error) { - if (!subscriptionStopped) { - setResult({ - loading: false, - data: void 0, - error, - variables: options?.variables, + + let subscriptionStopped = false; + const variables = observable.__.variables; + const client = observable.__.client; + const subscription = observable.subscribe({ + next(fetchResult) { + if (subscriptionStopped) { + return; + } + + const result = { + loading: false, + // TODO: fetchResult.data can be null but SubscriptionResult.data + // expects TData | undefined only + data: fetchResult.data!, + error: toApolloError(fetchResult), + variables, + }; + observable.__.setResult(result); + if (!ignoreResultsRef.current) update(); + + if (result.error) { + optionsRef.current.onError?.(result.error); + } else if (optionsRef.current.onData) { + optionsRef.current.onData({ + client, + data: result, + }); + } else if (optionsRef.current.onSubscriptionData) { + optionsRef.current.onSubscriptionData({ + client, + subscriptionData: result, + }); + } + }, + error(error) { + error = + error instanceof ApolloError ? error : ( + new ApolloError({ protocolErrors: [error] }) + ); + if (!subscriptionStopped) { + observable.__.setResult({ + loading: false, + data: void 0, + error, + variables, + }); + if (!ignoreResultsRef.current) update(); + optionsRef.current.onError?.(error); + } + }, + complete() { + if (!subscriptionStopped) { + if (optionsRef.current.onComplete) { + optionsRef.current.onComplete(); + } else if (optionsRef.current.onSubscriptionComplete) { + optionsRef.current.onSubscriptionComplete(); + } + } + }, + }); + + return () => { + // immediately stop receiving subscription values, but do not unsubscribe + // until after a short delay in case another useSubscription hook is + // reusing the same underlying observable and is about to subscribe + subscriptionStopped = true; + setTimeout(() => { + subscription.unsubscribe(); }); - ref.current.options?.onError?.(error); - } + }; }, - complete() { - if (!subscriptionStopped) { - if (ref.current.options?.onComplete) { - ref.current.options.onComplete(); - } else if (ref.current.options?.onSubscriptionComplete) { - ref.current.options.onSubscriptionComplete(); - } - } + [observable] + ), + () => + observable && !skip && !ignoreResults ? + observable.__.result + : fallbackResult + ); + return React.useMemo( + () => ({ + ...ret, + restart() { + invariant( + !optionsRef.current.skip, + "A subscription that is skipped cannot be restarted." + ); + setObservable(recreateRef.current()); }, - }); + }), + [ret] + ); +} - return () => { - // immediately stop receiving subscription values, but do not unsubscribe - // until after a short delay in case another useSubscription hook is - // reusing the same underlying observable and is about to subscribe - subscriptionStopped = true; - setTimeout(() => { - subscription.unsubscribe(); - }); - }; - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [observable]); +function createSubscription< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + client: ApolloClient, + query: TypedDocumentNode, + variables: TVariables | undefined, + fetchPolicy: FetchPolicy | undefined, + errorPolicy: ErrorPolicy | undefined, + context: DefaultContext | undefined, + extensions: Record | undefined +) { + const options = { + query, + variables, + fetchPolicy, + errorPolicy, + context, + extensions, + }; + const __ = { + ...options, + client, + result: { + loading: true, + data: void 0, + error: void 0, + variables, + } as SubscriptionResult, + setResult(result: SubscriptionResult) { + __.result = result; + }, + }; - return result; + let observable: Observable> | null = null; + return Object.assign( + new Observable>((observer) => { + // lazily start the subscription when the first observer subscribes + // to get around strict mode + if (!observable) { + observable = client.subscribe(options); + } + const sub = observable.subscribe(observer); + return () => sub.unsubscribe(); + }), + { + /** + * A tracking object to store details about the observable and the latest result of the subscription. + */ + __, + } + ); } diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index fe438ab6240..e3395390a6b 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -274,13 +274,7 @@ function _useSuspenseQuery< [queryRef] ); - const subscribeToMore: SubscribeToMoreFunction< - TData | undefined, - TVariables - > = React.useCallback( - (options) => queryRef.observable.subscribeToMore(options), - [queryRef] - ); + const subscribeToMore = queryRef.observable.subscribeToMore; return React.useMemo< UseSuspenseQueryResult diff --git a/src/react/query-preloader/createQueryPreloader.ts b/src/react/query-preloader/createQueryPreloader.ts index 226723dab9a..6389992519c 100644 --- a/src/react/query-preloader/createQueryPreloader.ts +++ b/src/react/query-preloader/createQueryPreloader.ts @@ -16,6 +16,7 @@ import type { import { InternalQueryReference, wrapQueryRef } from "../internal/index.js"; import type { PreloadedQueryRef } from "../internal/index.js"; import type { NoInfer } from "../index.js"; +import { wrapHook } from "../hooks/internal/index.js"; type VariablesOption = [TVariables] extends [never] ? @@ -168,6 +169,14 @@ export interface PreloadQueryFunction { export function createQueryPreloader( client: ApolloClient ): PreloadQueryFunction { + return wrapHook( + "createQueryPreloader", + _createQueryPreloader, + client + )(client); +} + +const _createQueryPreloader: typeof createQueryPreloader = (client) => { return function preloadQuery< TData = unknown, TVariables extends OperationVariables = OperationVariables, @@ -189,4 +198,4 @@ export function createQueryPreloader( return wrapQueryRef(queryRef) as PreloadedQueryRef; }; -} +}; diff --git a/src/react/types/types.documentation.ts b/src/react/types/types.documentation.ts index 186d651dfd8..515834e7cb7 100644 --- a/src/react/types/types.documentation.ts +++ b/src/react/types/types.documentation.ts @@ -531,6 +531,12 @@ export interface SubscriptionOptionsDocumentation { */ shouldResubscribe: unknown; + /** + * If `true`, the hook will not cause the component to rerender. This is useful when you want to control the rendering of your component yourself with logic in the `onData` and `onError` callbacks. + * + * Changing this to `true` when the hook already has `data` will reset the `data` to `undefined`. + */ + ignoreResults: unknown; /** * An `ApolloClient` instance. By default `useSubscription` / `Subscription` uses the client passed down via context, but a different client can be passed in. */ @@ -546,6 +552,11 @@ export interface SubscriptionOptionsDocumentation { */ context: unknown; + /** + * Shared context between your component and your network interface (Apollo Link). + */ + extensions: unknown; + /** * Allows the registration of a callback function that will be triggered each time the `useSubscription` Hook / `Subscription` component completes the subscription. * diff --git a/src/react/types/types.ts b/src/react/types/types.ts index 41cff9e8835..ab4444596f4 100644 --- a/src/react/types/types.ts +++ b/src/react/types/types.ts @@ -1,5 +1,5 @@ import type * as ReactTypes from "react"; -import type { DocumentNode } from "graphql"; +import type { DocumentNode, GraphQLFormattedError } from "graphql"; import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; import type { @@ -148,6 +148,11 @@ export interface QueryResult< previousData?: TData; /** {@inheritDoc @apollo/client!QueryResultDocumentation#error:member} */ error?: ApolloError; + /** + * @deprecated This property will be removed in a future version of Apollo Client. + * Please use `error.graphQLErrors` instead. + */ + errors?: ReadonlyArray; /** {@inheritDoc @apollo/client!QueryResultDocumentation#loading:member} */ loading: boolean; /** {@inheritDoc @apollo/client!QueryResultDocumentation#networkStatus:member} */ @@ -437,6 +442,8 @@ export interface BaseSubscriptionOptions< variables?: TVariables; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#fetchPolicy:member} */ fetchPolicy?: FetchPolicy; + /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#errorPolicy:member} */ + errorPolicy?: ErrorPolicy; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#shouldResubscribe:member} */ shouldResubscribe?: | boolean @@ -447,6 +454,8 @@ export interface BaseSubscriptionOptions< skip?: boolean; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#context:member} */ context?: DefaultContext; + /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#extensions:member} */ + extensions?: Record; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#onComplete:member} */ onComplete?: () => void; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#onData:member} */ @@ -457,6 +466,11 @@ export interface BaseSubscriptionOptions< onError?: (error: ApolloError) => void; /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#onSubscriptionComplete:member} */ onSubscriptionComplete?: () => void; + /** + * {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#ignoreResults:member} + * @defaultValue `false` + */ + ignoreResults?: boolean; } export interface SubscriptionResult { @@ -479,6 +493,9 @@ export interface SubscriptionHookOptions< TVariables extends OperationVariables = OperationVariables, > extends BaseSubscriptionOptions {} +/** + * @deprecated This type is not used anymore. It will be removed in the next major version of Apollo Client + */ export interface SubscriptionDataOptions< TData = any, TVariables extends OperationVariables = OperationVariables, diff --git a/src/testing/core/mocking/__tests__/mockLink.ts b/src/testing/core/mocking/__tests__/mockLink.ts index 4207a42503a..fe618ae4f48 100644 --- a/src/testing/core/mocking/__tests__/mockLink.ts +++ b/src/testing/core/mocking/__tests__/mockLink.ts @@ -1,7 +1,7 @@ import gql from "graphql-tag"; import { MockLink, MockedResponse } from "../mockLink"; import { execute } from "../../../../link/core/execute"; -import { ObservableStream } from "../../../internal"; +import { ObservableStream, enableFakeTimers } from "../../../internal"; describe("MockedResponse.newData", () => { const setup = () => { @@ -73,9 +73,6 @@ We've chosen this value as the MAXIMUM_DELAY since values that don't fit into a const MAXIMUM_DELAY = 0x7f_ff_ff_ff; describe("mockLink", () => { - beforeAll(() => jest.useFakeTimers()); - afterAll(() => jest.useRealTimers()); - const query = gql` query A { a @@ -83,6 +80,8 @@ describe("mockLink", () => { `; it("should not require a result or error when delay equals Infinity", async () => { + using _fakeTimers = enableFakeTimers(); + const mockLink = new MockLink([ { request: { @@ -104,6 +103,8 @@ describe("mockLink", () => { }); it("should require result or error when delay is just large", (done) => { + using _fakeTimers = enableFakeTimers(); + const mockLink = new MockLink([ { request: { @@ -126,6 +127,81 @@ describe("mockLink", () => { jest.advanceTimersByTime(MAXIMUM_DELAY); }); + + it("should fill in default variables if they are missing in mocked requests", async () => { + const query = gql` + query GetTodo($done: Boolean = true, $user: String!) { + todo(user: $user, done: $done) { + id + } + } + `; + const mocks = [ + { + // default should get filled in here + request: { query, variables: { user: "Tim" } }, + result: { + data: { todo: { id: 1 } }, + }, + }, + { + // we provide our own `done`, so it should not get filled in + request: { query, variables: { user: "Tim", done: false } }, + result: { + data: { todo: { id: 2 } }, + }, + }, + { + // one more that has a different user variable and should never match + request: { query, variables: { user: "Tom" } }, + result: { + data: { todo: { id: 2 } }, + }, + }, + ]; + + // Apollo Client will always fill in default values for missing variables + // in the operation before calling the Link, so we have to do the same here + // when we call `execute` + const defaults = { done: true }; + const link = new MockLink(mocks, false, { showWarnings: false }); + { + // Non-optional variable is missing, should not match. + const stream = new ObservableStream( + execute(link, { query, variables: { ...defaults } }) + ); + await stream.takeError(); + } + { + // Execute called incorrectly without a default variable filled in. + // This will never happen in Apollo Client since AC always fills these + // before calling `execute`, so it's okay if it results in a "no match" + // scenario here. + const stream = new ObservableStream( + execute(link, { query, variables: { user: "Tim" } }) + ); + await stream.takeError(); + } + { + // Expect default value to be filled in the mock request. + const stream = new ObservableStream( + execute(link, { query, variables: { ...defaults, user: "Tim" } }) + ); + const result = await stream.takeNext(); + expect(result).toEqual({ data: { todo: { id: 1 } } }); + } + { + // Test that defaults don't overwrite explicitly different values in a mock request. + const stream = new ObservableStream( + execute(link, { + query, + variables: { ...defaults, user: "Tim", done: false }, + }) + ); + const result = await stream.takeNext(); + expect(result).toEqual({ data: { todo: { id: 2 } } }); + } + }); }); test("removes @nonreactive directives from fields", async () => { diff --git a/src/testing/core/mocking/mockLink.ts b/src/testing/core/mocking/mockLink.ts index f38474e1c20..646567b2da9 100644 --- a/src/testing/core/mocking/mockLink.ts +++ b/src/testing/core/mocking/mockLink.ts @@ -16,6 +16,8 @@ import { cloneDeep, stringifyForDisplay, print, + getOperationDefinition, + getDefaultValues, removeDirectivesFromDocument, checkDocument, } from "../../../utilities/index.js"; @@ -233,17 +235,21 @@ ${unmatchedVars.map((d) => ` ${stringifyForDisplay(d)}`).join("\n")} } private normalizeVariableMatching(mockedResponse: MockedResponse) { - const variables = mockedResponse.request.variables; - if (mockedResponse.variableMatcher && variables) { + const request = mockedResponse.request; + if (mockedResponse.variableMatcher && request.variables) { throw new Error( "Mocked response should contain either variableMatcher or request.variables" ); } if (!mockedResponse.variableMatcher) { + request.variables = { + ...getDefaultValues(getOperationDefinition(request.query)), + ...request.variables, + }; mockedResponse.variableMatcher = (vars) => { const requestVariables = vars || {}; - const mockedResponseVariables = variables || {}; + const mockedResponseVariables = request.variables || {}; return equal(requestVariables, mockedResponseVariables); }; } diff --git a/src/testing/core/mocking/mockQueryManager.ts b/src/testing/core/mocking/mockQueryManager.ts index a4ded3dfbc5..6be4ab764ed 100644 --- a/src/testing/core/mocking/mockQueryManager.ts +++ b/src/testing/core/mocking/mockQueryManager.ts @@ -1,13 +1,33 @@ +import type { QueryManagerOptions } from "../../../core/QueryManager.js"; import { QueryManager } from "../../../core/QueryManager.js"; import type { MockedResponse } from "./mockLink.js"; import { mockSingleLink } from "./mockLink.js"; import { InMemoryCache } from "../../../cache/index.js"; +import { LocalState } from "../../../core/LocalState.js"; + +export const getDefaultOptionsForQueryManagerTests = ( + options: Pick, "cache" | "link"> & + Partial> +) => ({ + defaultOptions: Object.create(null), + documentTransform: undefined, + queryDeduplication: false, + onBroadcast: undefined, + ssrMode: false, + clientAwareness: {}, + localState: new LocalState({ cache: options.cache }), + assumeImmutableResults: !!options.cache.assumeImmutableResults, + defaultContext: undefined, + ...options, +}); // Helper method for the tests that construct a query manager out of a // a list of mocked responses for a mocked network interface. export default (...mockedResponses: MockedResponse[]) => { - return new QueryManager({ - link: mockSingleLink(...mockedResponses), - cache: new InMemoryCache({ addTypename: false }), - }); + return new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link: mockSingleLink(...mockedResponses), + cache: new InMemoryCache({ addTypename: false }), + }) + ); }; diff --git a/src/testing/experimental/__tests__/createTestSchema.test.tsx b/src/testing/experimental/__tests__/createTestSchema.test.tsx index 99b579ddde2..60ad1e08bd5 100644 --- a/src/testing/experimental/__tests__/createTestSchema.test.tsx +++ b/src/testing/experimental/__tests__/createTestSchema.test.tsx @@ -13,7 +13,7 @@ import { spyOnConsole, } from "../../internal/index.js"; import { createTestSchema } from "../createTestSchema.js"; -import { GraphQLError, buildSchema } from "graphql"; +import { buildSchema } from "graphql"; import type { UseSuspenseQueryResult } from "../../../react/index.js"; import { useMutation, useSuspenseQuery } from "../../../react/index.js"; import userEvent from "@testing-library/user-event"; @@ -740,7 +740,9 @@ describe("schema proxy", () => { expect(snapshot.error).toEqual( new ApolloError({ - graphQLErrors: [new GraphQLError("Could not resolve type")], + graphQLErrors: [ + { message: "Could not resolve type", path: ["viewer", "book"] }, + ], }) ); } @@ -816,7 +818,7 @@ describe("schema proxy", () => { expect(snapshot.error).toEqual( new ApolloError({ graphQLErrors: [ - new GraphQLError('Expected { foo: "bar" } to be a GraphQL schema.'), + { message: 'Expected { foo: "bar" } to be a GraphQL schema.' }, ], }) ); diff --git a/src/testing/experimental/createSchemaFetch.ts b/src/testing/experimental/createSchemaFetch.ts index 5c03ea0da0d..c8eb9374759 100644 --- a/src/testing/experimental/createSchemaFetch.ts +++ b/src/testing/experimental/createSchemaFetch.ts @@ -1,6 +1,6 @@ -import { execute, validate } from "graphql"; -import type { GraphQLError, GraphQLSchema } from "graphql"; -import { ApolloError, gql } from "../../core/index.js"; +import { execute, GraphQLError, validate } from "graphql"; +import type { GraphQLFormattedError, GraphQLSchema } from "graphql"; +import { gql } from "../../core/index.js"; import { withCleanup } from "../internal/index.js"; import { wait } from "../core/wait.js"; @@ -30,6 +30,8 @@ import { wait } from "../core/wait.js"; * ``` * @since 3.10.0 * @alpha + * @deprecated `createSchemaFetch` is deprecated and will be removed in 3.12.0. + * Please migrate to [`@apollo/graphql-testing-library`](https://github.com/apollographql/graphql-testing-library). */ const createSchemaFetch = ( schema: GraphQLSchema, @@ -61,13 +63,15 @@ const createSchemaFetch = ( const document = gql(body.query); if (mockFetchOpts.validate) { - let validationErrors: readonly Error[] = []; + let validationErrors: readonly GraphQLFormattedError[] = []; try { validationErrors = validate(schema, document); } catch (e) { validationErrors = [ - new ApolloError({ graphQLErrors: [e as GraphQLError] }), + e instanceof Error ? + GraphQLError.prototype.toJSON.apply(e) + : (e as any), ]; } diff --git a/src/testing/experimental/createTestSchema.ts b/src/testing/experimental/createTestSchema.ts index 5a4002c2902..e07dd923e4e 100644 --- a/src/testing/experimental/createTestSchema.ts +++ b/src/testing/experimental/createTestSchema.ts @@ -50,6 +50,8 @@ interface TestSchemaOptions { * ``` * @since 3.9.0 * @alpha + * @deprecated `createTestSchema` is deprecated and will be removed in 3.12.0. + * Please migrate to [`@apollo/graphql-testing-library`](https://github.com/apollographql/graphql-testing-library). */ const createTestSchema = ( schemaWithTypeDefs: GraphQLSchema, diff --git a/src/testing/internal/disposables/enableFakeTimers.ts b/src/testing/internal/disposables/enableFakeTimers.ts new file mode 100644 index 00000000000..6be85d24730 --- /dev/null +++ b/src/testing/internal/disposables/enableFakeTimers.ts @@ -0,0 +1,26 @@ +import { withCleanup } from "./withCleanup.js"; + +declare global { + interface DateConstructor { + /* Jest uses @sinonjs/fake-timers, that add this flag */ + isFake: boolean; + } +} + +export function enableFakeTimers( + config?: FakeTimersConfig | LegacyFakeTimersConfig +) { + if (global.Date.isFake === true) { + // Nothing to do here, fake timers have already been set up. + // That also means we don't want to clean that up later. + return withCleanup({}, () => {}); + } + + jest.useFakeTimers(config); + return withCleanup({}, () => { + if (global.Date.isFake === true) { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + } + }); +} diff --git a/src/testing/internal/disposables/index.ts b/src/testing/internal/disposables/index.ts index 9895d129589..9d61c88fd90 100644 --- a/src/testing/internal/disposables/index.ts +++ b/src/testing/internal/disposables/index.ts @@ -1,3 +1,4 @@ export { disableActWarnings } from "./disableActWarnings.js"; export { spyOnConsole } from "./spyOnConsole.js"; export { withCleanup } from "./withCleanup.js"; +export { enableFakeTimers } from "./enableFakeTimers.js"; diff --git a/src/testing/react/__tests__/MockedProvider.test.tsx b/src/testing/react/__tests__/MockedProvider.test.tsx index 4ebd0878a43..f4a5caed150 100644 --- a/src/testing/react/__tests__/MockedProvider.test.tsx +++ b/src/testing/react/__tests__/MockedProvider.test.tsx @@ -10,6 +10,7 @@ import { InMemoryCache } from "../../../cache"; import { QueryResult } from "../../../react/types/types"; import { ApolloLink, FetchResult } from "../../../link/core"; import { Observable } from "zen-observable-ts"; +import { ApolloError } from "../../../errors"; const variables = { username: "mock_username", @@ -422,7 +423,9 @@ describe("General use", () => { variables, }); if (!loading) { - expect(error).toEqual(new Error("something went wrong")); + expect(error).toEqual( + new ApolloError({ networkError: new Error("something went wrong") }) + ); finished = true; } return null; diff --git a/src/testing/react/__tests__/__snapshots__/MockedProvider.test.tsx.snap b/src/testing/react/__tests__/__snapshots__/MockedProvider.test.tsx.snap index 6706568ffd2..e6dfab1ab04 100644 --- a/src/testing/react/__tests__/__snapshots__/MockedProvider.test.tsx.snap +++ b/src/testing/react/__tests__/__snapshots__/MockedProvider.test.tsx.snap @@ -8,18 +8,42 @@ Object { `; exports[`General use should error if the query in the mock and component do not match 1`] = ` -[ApolloError: No more mocked responses for the query: query GetUser($username: String!) { +ApolloError { + "cause": [Error: No more mocked responses for the query: query GetUser($username: String!) { user(username: $username) { id __typename } } Expected variables: {"username":"mock_username"} -] +], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {\\"username\\":\\"mock_username\\"} +", + "name": "ApolloError", + "networkError": [Error: No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {"username":"mock_username"} +], + "protocolErrors": Array [], +} `; exports[`General use should error if the variableMatcher returns false 1`] = ` -[ApolloError: No more mocked responses for the query: query GetUser($username: String!) { +ApolloError { + "cause": [Error: No more mocked responses for the query: query GetUser($username: String!) { user(username: $username) { id __typename @@ -29,11 +53,40 @@ Expected variables: {"username":"mock_username"} Failed to match 1 mock for this query. The mocked response had the following variables: {} -] +], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {\\"username\\":\\"mock_username\\"} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {} +", + "name": "ApolloError", + "networkError": [Error: No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {"username":"mock_username"} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {} +], + "protocolErrors": Array [], +} `; exports[`General use should error if the variables do not deep equal 1`] = ` -[ApolloError: No more mocked responses for the query: query GetUser($username: String!) { +ApolloError { + "cause": [Error: No more mocked responses for the query: query GetUser($username: String!) { user(username: $username) { id __typename @@ -43,11 +96,40 @@ Expected variables: {"username":"some_user","age":42} Failed to match 1 mock for this query. The mocked response had the following variables: {"age":13,"username":"some_user"} -] +], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {\\"username\\":\\"some_user\\",\\"age\\":42} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {\\"age\\":13,\\"username\\":\\"some_user\\"} +", + "name": "ApolloError", + "networkError": [Error: No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {"username":"some_user","age":42} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {"age":13,"username":"some_user"} +], + "protocolErrors": Array [], +} `; exports[`General use should error if the variables in the mock and component do not match 1`] = ` -[ApolloError: No more mocked responses for the query: query GetUser($username: String!) { +ApolloError { + "cause": [Error: No more mocked responses for the query: query GetUser($username: String!) { user(username: $username) { id __typename @@ -57,7 +139,35 @@ Expected variables: {"username":"other_user","age":} Failed to match 1 mock for this query. The mocked response had the following variables: {"username":"mock_username"} -] +], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {\\"username\\":\\"other_user\\",\\"age\\":} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {\\"username\\":\\"mock_username\\"} +", + "name": "ApolloError", + "networkError": [Error: No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {"username":"other_user","age":} + +Failed to match 1 mock for this query. The mocked response had the following variables: + {"username":"mock_username"} +], + "protocolErrors": Array [], +} `; exports[`General use should mock the data 1`] = ` @@ -76,19 +186,64 @@ Object { } `; -exports[`General use should pipe exceptions thrown in custom onError functions through the link chain 1`] = `[ApolloError: oh no!]`; +exports[`General use should pipe exceptions thrown in custom onError functions through the link chain 1`] = ` +ApolloError { + "cause": [Error: oh no!], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "oh no!", + "name": "ApolloError", + "networkError": [Error: oh no!], + "protocolErrors": Array [], +} +`; -exports[`General use should return "Mocked response should contain" errors in response 1`] = `[ApolloError: Mocked response should contain either \`result\`, \`error\` or a \`delay\` of \`Infinity\`: {"query":"query GetUser($username: String!) {\\n user(username: $username) {\\n id\\n __typename\\n }\\n}"}]`; +exports[`General use should return "Mocked response should contain" errors in response 1`] = ` +ApolloError { + "cause": [Error: Mocked response should contain either \`result\`, \`error\` or a \`delay\` of \`Infinity\`: {"query":"query GetUser($username: String!) {\\n user(username: $username) {\\n id\\n __typename\\n }\\n}"}], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "Mocked response should contain either \`result\`, \`error\` or a \`delay\` of \`Infinity\`: {\\"query\\":\\"query GetUser($username: String!) {\\\\n user(username: $username) {\\\\n id\\\\n __typename\\\\n }\\\\n}\\"}", + "name": "ApolloError", + "networkError": [Error: Mocked response should contain either \`result\`, \`error\` or a \`delay\` of \`Infinity\`: {"query":"query GetUser($username: String!) {\\n user(username: $username) {\\n id\\n __typename\\n }\\n}"}], + "protocolErrors": Array [], +} +`; exports[`General use should return "No more mocked responses" errors in response 1`] = ` -[ApolloError: No more mocked responses for the query: query GetUser($username: String!) { +ApolloError { + "cause": [Error: No more mocked responses for the query: query GetUser($username: String!) { user(username: $username) { id __typename } } Expected variables: {} -] +], + "clientErrors": Array [], + "extraInfo": undefined, + "graphQLErrors": Array [], + "message": "No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {} +", + "name": "ApolloError", + "networkError": [Error: No more mocked responses for the query: query GetUser($username: String!) { + user(username: $username) { + id + __typename + } +} +Expected variables: {} +], + "protocolErrors": Array [], +} `; exports[`General use should support custom error handling using setOnError 1`] = ` diff --git a/src/testing/react/__tests__/mockSubscriptionLink.test.tsx b/src/testing/react/__tests__/mockSubscriptionLink.test.tsx index 8b26aea0dd3..31c3abe70f7 100644 --- a/src/testing/react/__tests__/mockSubscriptionLink.test.tsx +++ b/src/testing/react/__tests__/mockSubscriptionLink.test.tsx @@ -8,9 +8,6 @@ import { InMemoryCache as Cache } from "../../../cache"; import { ApolloProvider } from "../../../react/context"; import { useSubscription } from "../../../react/hooks"; -const IS_REACT_18 = React.version.startsWith("18"); -const IS_REACT_19 = React.version.startsWith("19"); - describe("mockSubscriptionLink", () => { it("should work with multiple subscribers to the same mock websocket", async () => { const subscription = gql` @@ -65,7 +62,7 @@ describe("mockSubscriptionLink", () => { ); - const numRenders = IS_REACT_18 || IS_REACT_19 ? 2 : results.length + 1; + const numRenders = results.length + 1; // automatic batching in React 18 means we only see 2 renders vs. 5 in v17 await waitFor( diff --git a/src/tsconfig.json b/src/tsconfig.json index efeb2f2da38..d7e90510ecc 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -5,6 +5,8 @@ { "compilerOptions": { "noEmit": true, + "declaration": false, + "declarationMap": false, "lib": ["es2015", "esnext.asynciterable", "ES2021.WeakRef"], "types": ["jest", "node", "./testing/matchers/index.d.ts"] },