@apollo/client
Version:
A fully-featured caching GraphQL client.
1 lines • 257 kB
Source Map (JSON)
{"version":3,"file":"ObservableQuery.cjs","sources":["../../../src/core/ObservableQuery.ts"],"sourcesContent":["import { equal } from \"@wry/equality\";\nimport type { DocumentNode } from \"graphql\";\nimport type {\n InteropObservable,\n MonoTypeOperatorFunction,\n Observer,\n OperatorFunction,\n Subscribable,\n Subscription,\n} from \"rxjs\";\nimport { BehaviorSubject, Observable, share, Subject, tap } from \"rxjs\";\n\nimport type { Cache, MissingFieldError } from \"@apollo/client/cache\";\nimport type { MissingTree } from \"@apollo/client/cache\";\nimport type { MaybeMasked, Unmasked } from \"@apollo/client/masking\";\nimport type { DeepPartial } from \"@apollo/client/utilities\";\nimport { isNetworkRequestInFlight } from \"@apollo/client/utilities\";\nimport { __DEV__ } from \"@apollo/client/utilities/environment\";\nimport {\n compact,\n equalByQuery,\n filterMap,\n getOperationDefinition,\n getOperationName,\n getQueryDefinition,\n preventUnhandledRejection,\n toQueryResult,\n variablesUnknownSymbol,\n} from \"@apollo/client/utilities/internal\";\nimport { invariant } from \"@apollo/client/utilities/invariant\";\n\nimport type { ApolloClient } from \"./ApolloClient.js\";\nimport { NetworkStatus } from \"./networkStatus.js\";\nimport type { QueryManager } from \"./QueryManager.js\";\nimport type {\n DataState,\n DefaultContext,\n ErrorLike,\n GetDataState,\n OperationVariables,\n QueryNotification,\n TypedDocumentNode,\n} from \"./types.js\";\nimport type {\n ErrorPolicy,\n NextFetchPolicyContext,\n RefetchWritePolicy,\n SubscribeToMoreUpdateQueryFn,\n UpdateQueryMapFn,\n UpdateQueryOptions,\n WatchQueryFetchPolicy,\n} from \"./watchQueryOptions.js\";\n\nconst { assign, hasOwnProperty } = Object;\n\ninterface TrackedOperation {\n /**\n * This NetworkStatus will be used to override the current networkStatus\n */\n override?: NetworkStatus;\n /**\n * Will abort tracking the operation from this ObservableQuery and remove it from `activeOperations`\n */\n abort: () => void;\n /**\n * `query` that was used by the `ObservableQuery` as the \"main query\" at the time the operation was started\n * This is not necessarily the same query as the query the operation itself is doing.\n */\n query: DocumentNode;\n variables: OperationVariables;\n}\n\nconst uninitialized: ObservableQuery.Result<any> = {\n loading: true,\n networkStatus: NetworkStatus.loading,\n data: undefined,\n dataState: \"empty\",\n partial: true,\n};\n\nconst empty: ObservableQuery.Result<any> = {\n loading: false,\n networkStatus: NetworkStatus.ready,\n data: undefined,\n dataState: \"empty\",\n partial: true,\n};\n\nconst enum EmitBehavior {\n /**\n * Emit will be calculated by the normal rules. (`undefined` will be treated the same as this)\n */\n default = 0,\n /**\n * This result should always be emitted, even if the result is equal to the\n * previous result. (e.g. the first value after a `refetch`)\n */\n force = 1,\n /**\n * Never emit this result, it is only used to update `currentResult`.\n */\n never = 2,\n /**\n * This is a result carrying only a \"network status change\"/loading state update,\n * emit according to the `notifyOnNetworkStatusChange` option.\n */\n networkStatusChange = 3,\n}\ninterface Meta {\n shouldEmit?: EmitBehavior;\n /** can be used to override `ObservableQuery.options.fetchPolicy` for this notification */\n fetchPolicy?: WatchQueryFetchPolicy;\n}\n\nexport declare namespace ObservableQuery {\n export type Options<\n TData = unknown,\n TVariables extends OperationVariables = OperationVariables,\n > = {\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#fetchPolicy:member} */\n fetchPolicy: WatchQueryFetchPolicy;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#nextFetchPolicy:member} */\n nextFetchPolicy?:\n | WatchQueryFetchPolicy\n | ((\n this: ApolloClient.WatchQueryOptions<TData, TVariables>,\n currentFetchPolicy: WatchQueryFetchPolicy,\n context: NextFetchPolicyContext<TData, TVariables>\n ) => WatchQueryFetchPolicy);\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#initialFetchPolicy:member} */\n initialFetchPolicy: WatchQueryFetchPolicy;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#refetchWritePolicy:member} */\n refetchWritePolicy?: RefetchWritePolicy;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#errorPolicy:member} */\n errorPolicy?: ErrorPolicy;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#context:member} */\n context?: DefaultContext;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#pollInterval:member} */\n pollInterval?: number;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#notifyOnNetworkStatusChange:member} */\n notifyOnNetworkStatusChange?: boolean;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#returnPartialData:member} */\n returnPartialData?: boolean;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#skipPollAttempt:member} */\n skipPollAttempt?: () => boolean;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#query:member} */\n query: DocumentNode | TypedDocumentNode<TData, TVariables>;\n\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#variables:member} */\n variables: TVariables;\n };\n\n export type FetchMoreOptions<\n TData,\n TVariables extends OperationVariables,\n TFetchData = TData,\n TFetchVars extends OperationVariables = TVariables,\n > = {\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#query:member} */\n query?: DocumentNode | TypedDocumentNode<TFetchData, TFetchVars>;\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#variables:member} */\n variables?: Partial<NoInfer<TFetchVars>>;\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#errorPolicy:member} */\n errorPolicy?: ErrorPolicy;\n /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#context:member} */\n context?: DefaultContext;\n updateQuery?: (\n previousQueryResult: Unmasked<TData>,\n options: {\n fetchMoreResult: Unmasked<TFetchData>;\n variables: TFetchVars;\n }\n ) => Unmasked<TData>;\n };\n\n export interface SubscribeToMoreOptions<\n // eslint-disable-next-line local-rules/tdata-tvariables-order\n TData = unknown,\n TSubscriptionVariables extends OperationVariables = OperationVariables,\n TSubscriptionData = TData,\n TVariables extends OperationVariables = TSubscriptionVariables,\n > {\n document:\n | DocumentNode\n | TypedDocumentNode<TSubscriptionData, TSubscriptionVariables>;\n variables?: TSubscriptionVariables;\n updateQuery?: SubscribeToMoreUpdateQueryFn<\n TData,\n TVariables,\n TSubscriptionData\n >;\n onError?: (error: ErrorLike) => void;\n context?: DefaultContext;\n }\n\n /**\n * @internal\n * This describes the `WatchOptions` used by `ObservableQuery` to\n * subscribe to the cache.\n */\n interface CacheWatchOptions<\n TData = unknown,\n TVariables extends OperationVariables = OperationVariables,\n > extends Cache.WatchOptions<TData, TVariables> {\n /**\n * @internal\n * We cannot suppress the broadcast completely, since that would\n * result in external updates to be lost if we go from\n * (external A) -> (own B) -> (external C) when A and C have the same\n * value.\n * Without the `own B` being broadcast, the `cache.watch` would swallow\n * C.\n * So instead we track the last \"own diff\" and suppress further processing\n * in the callback.\n */\n lastOwnDiff?: Cache.DiffResult<TData>;\n }\n\n export type Result<\n TData,\n TStates extends\n DataState<TData>[\"dataState\"] = DataState<TData>[\"dataState\"],\n > = {\n /** {@inheritDoc @apollo/client!QueryResultDocumentation#error:member} */\n error?: ErrorLike;\n /** {@inheritDoc @apollo/client!QueryResultDocumentation#loading:member} */\n loading: boolean;\n /** {@inheritDoc @apollo/client!QueryResultDocumentation#networkStatus:member} */\n networkStatus: NetworkStatus;\n /** {@inheritDoc @apollo/client!QueryResultDocumentation#partial:member} */\n partial: boolean;\n } & GetDataState<TData, TStates>;\n\n /**\n * Promise returned by `reobserve` and `refetch` methods.\n *\n * By default, if the `ObservableQuery` is not interested in the result\n * of this operation anymore, the network operation will be cancelled.\n *\n * This has an additional `retain` method that can be used to keep the\n * network operation running until it is finished nonetheless.\n */\n interface ResultPromise<T> extends Promise<T> {\n /**\n * Keep the network operation running until it is finished, even if\n * `ObservableQuery` unsubscribed from the operation.\n */\n retain(): this;\n }\n\n export namespace DocumentationTypes {\n type OperatorFunctionChain<From, To> = [];\n interface ObservableMethods<TData, OperatorResult> {\n /** {@inheritDoc @apollo/client!ObservableQuery#pipe:member} */\n pipe(\n ...operators: OperatorFunctionChain<\n ObservableQuery.Result<TData>,\n OperatorResult\n >\n ): Observable<OperatorResult>;\n\n /** {@inheritDoc @apollo/client!ObservableQuery#subscribe:member} */\n subscribe(\n observerOrNext:\n | Partial<Observer<ObservableQuery.Result<MaybeMasked<TData>>>>\n | ((value: ObservableQuery.Result<MaybeMasked<TData>>) => void)\n ): Subscription;\n }\n }\n}\n\ninterface SubjectValue<TData, TVariables extends OperationVariables> {\n query: DocumentNode | TypedDocumentNode<TData, TVariables>;\n variables: TVariables;\n result: ObservableQuery.Result<TData>;\n meta: Meta;\n}\n\nexport class ObservableQuery<\n TData = unknown,\n TVariables extends OperationVariables = OperationVariables,\n >\n implements\n Subscribable<ObservableQuery.Result<MaybeMasked<TData>>>,\n InteropObservable<ObservableQuery.Result<MaybeMasked<TData>>>\n{\n public readonly options: ObservableQuery.Options<TData, TVariables>;\n public readonly queryName?: string;\n private variablesUnknown: boolean = false;\n\n /** @internal will be read and written from `QueryInfo` */\n public _lastWrite?: unknown;\n\n // The `query` computed property will always reflect the document transformed\n // by the last run query. `this.options.query` will always reflect the raw\n // untransformed query to ensure document transforms with runtime conditionals\n // are run on the original document.\n public get query(): TypedDocumentNode<TData, TVariables> {\n return this.lastQuery;\n }\n\n /**\n * An object containing the variables that were provided for the query.\n */\n public get variables(): TVariables {\n return this.options.variables;\n }\n\n private unsubscribeFromCache?: {\n (): void;\n query: TypedDocumentNode<TData, TVariables>;\n variables: TVariables;\n };\n private input!: Subject<\n QueryNotification.Value<TData> & {\n query: DocumentNode | TypedDocumentNode<TData, TVariables>;\n variables: TVariables;\n meta: Meta;\n }\n >;\n private subject!: BehaviorSubject<\n SubjectValue<MaybeMasked<TData>, TVariables>\n >;\n\n private isTornDown: boolean;\n private queryManager: QueryManager;\n private subscriptions = new Set<Subscription>();\n\n /**\n * If an `ObservableQuery` is created with a `network-only` fetch policy,\n * it should actually start receiving cache updates, but not before it has\n * received the first result from the network.\n */\n private waitForNetworkResult: boolean;\n private lastQuery: DocumentNode;\n\n private linkSubscription?: Subscription;\n\n private pollingInfo?: {\n interval: number;\n timeout: ReturnType<typeof setTimeout>;\n };\n\n private get networkStatus(): NetworkStatus {\n return this.subject.getValue().result.networkStatus;\n }\n\n constructor({\n queryManager,\n options,\n transformedQuery = queryManager.transform(options.query),\n }: {\n queryManager: QueryManager;\n options: ApolloClient.WatchQueryOptions<TData, TVariables>;\n transformedQuery?: DocumentNode | TypedDocumentNode<TData, TVariables>;\n queryId?: string;\n }) {\n this.queryManager = queryManager;\n\n // active state\n this.waitForNetworkResult = options.fetchPolicy === \"network-only\";\n this.isTornDown = false;\n\n this.subscribeToMore = this.subscribeToMore.bind(this);\n this.maskResult = this.maskResult.bind(this);\n\n const {\n watchQuery: { fetchPolicy: defaultFetchPolicy = \"cache-first\" } = {},\n } = queryManager.defaultOptions;\n\n const {\n fetchPolicy = defaultFetchPolicy,\n // Make sure we don't store \"standby\" as the initialFetchPolicy.\n initialFetchPolicy = fetchPolicy === \"standby\" ? defaultFetchPolicy : (\n fetchPolicy\n ),\n } = options;\n\n if (options[variablesUnknownSymbol]) {\n invariant(\n fetchPolicy === \"standby\",\n \"The `variablesUnknown` option can only be used together with a `standby` fetch policy.\"\n );\n this.variablesUnknown = true;\n }\n\n this.lastQuery = transformedQuery;\n\n this.options = {\n ...options,\n\n // Remember the initial options.fetchPolicy so we can revert back to this\n // policy when variables change. This information can also be specified\n // (or overridden) by providing options.initialFetchPolicy explicitly.\n initialFetchPolicy,\n\n // This ensures this.options.fetchPolicy always has a string value, in\n // case options.fetchPolicy was not provided.\n fetchPolicy,\n variables: this.getVariablesWithDefaults(options.variables),\n };\n\n this.initializeObservablesQueue();\n\n this[\"@@observable\"] = () => this;\n if (Symbol.observable) {\n this[Symbol.observable] = () => this;\n }\n\n const opDef = getOperationDefinition(this.query);\n this.queryName = opDef && opDef.name && opDef.name.value;\n }\n\n private initializeObservablesQueue() {\n this.subject = new BehaviorSubject<\n SubjectValue<MaybeMasked<TData>, TVariables>\n >({\n query: this.query,\n variables: this.variables,\n result: uninitialized,\n meta: {},\n });\n const observable = this.subject.pipe(\n tap({\n subscribe: () => {\n if (!this.subject.observed) {\n this.reobserve();\n\n // TODO: See if we can rework updatePolling to better handle this.\n // reobserve calls updatePolling but this `subscribe` callback is\n // called before the subject is subscribed to so `updatePolling`\n // can't accurately detect if there is an active subscription.\n // Calling it again here ensures that it can detect if it can poll\n setTimeout(() => this.updatePolling());\n }\n },\n unsubscribe: () => {\n if (!this.subject.observed) {\n this.tearDownQuery();\n }\n },\n }),\n filterMap(\n (\n { query, variables, result: current, meta },\n context: {\n previous?: ObservableQuery.Result<TData>;\n previousVariables?: TVariables;\n }\n ) => {\n const { shouldEmit } = meta;\n\n if (current === uninitialized) {\n // reset internal state after `ObservableQuery.reset()`\n context.previous = undefined;\n context.previousVariables = undefined;\n }\n if (\n this.options.fetchPolicy === \"standby\" ||\n shouldEmit === EmitBehavior.never\n )\n return;\n if (shouldEmit === EmitBehavior.force) return emit();\n\n const { previous, previousVariables } = context;\n\n if (previous) {\n const documentInfo = this.queryManager.getDocumentInfo(query);\n const dataMasking = this.queryManager.dataMasking;\n const maskedQuery =\n dataMasking ? documentInfo.nonReactiveQuery : query;\n\n const resultIsEqual =\n dataMasking || documentInfo.hasNonreactiveDirective ?\n equalByQuery(maskedQuery, previous, current, variables)\n : equal(previous, current);\n\n if (resultIsEqual && equal(previousVariables, variables)) {\n return;\n }\n }\n\n if (\n shouldEmit === EmitBehavior.networkStatusChange &&\n (!this.options.notifyOnNetworkStatusChange ||\n equal(previous, current))\n ) {\n return;\n }\n return emit();\n\n function emit() {\n context.previous = current;\n context.previousVariables = variables;\n return current;\n }\n },\n () => ({})\n )\n );\n\n this.pipe = observable.pipe.bind(observable);\n this.subscribe = observable.subscribe.bind(observable);\n\n this.input = new Subject();\n // we want to feed many streams into `this.subject`, but none of them should\n // be able to close `this.input`\n this.input.complete = () => {};\n this.input.pipe(this.operator).subscribe(this.subject);\n }\n\n // We can't use Observable['subscribe'] here as the type as it conflicts with\n // the ability to infer T from Subscribable<T>. This limits the surface area\n // to the non-deprecated signature which works properly with type inference.\n /**\n * Subscribes to the `ObservableQuery`.\n * @param observerOrNext - Either an RxJS `Observer` with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @returns A subscription reference to the registered handlers.\n */\n public subscribe!: (\n observerOrNext:\n | Partial<Observer<ObservableQuery.Result<MaybeMasked<TData>>>>\n | ((value: ObservableQuery.Result<MaybeMasked<TData>>) => void)\n ) => Subscription;\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * @example\n *\n * ```ts\n * import { filter, map } from 'rxjs';\n *\n * observableQuery\n * .pipe(\n * filter(...),\n * map(...),\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @returns The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n public pipe!: Observable<ObservableQuery.Result<MaybeMasked<TData>>>[\"pipe\"];\n\n public [Symbol.observable]!: () => Subscribable<\n ObservableQuery.Result<MaybeMasked<TData>>\n >;\n public [\"@@observable\"]: () => Subscribable<\n ObservableQuery.Result<MaybeMasked<TData>>\n >;\n\n /**\n * @internal\n */\n public getCacheDiff({ optimistic = true } = {}) {\n return this.queryManager.cache.diff<TData>({\n query: this.query,\n variables: this.variables,\n returnPartialData: true,\n optimistic,\n });\n }\n\n private getInitialResult(\n initialFetchPolicy?: WatchQueryFetchPolicy\n ): ObservableQuery.Result<MaybeMasked<TData>> {\n const fetchPolicy =\n this.queryManager.prioritizeCacheValues ?\n \"cache-first\"\n : initialFetchPolicy || this.options.fetchPolicy;\n\n const cacheResult = (): ObservableQuery.Result<TData> => {\n const diff = this.getCacheDiff();\n // TODO: queryInfo.getDiff should handle this since cache.diff returns a\n // null when returnPartialData is false\n const data =\n this.options.returnPartialData || diff.complete ?\n (diff.result as TData) ?? undefined\n : undefined;\n\n return this.maskResult({\n data,\n dataState:\n diff.complete ? \"complete\"\n : data === undefined ? \"empty\"\n : \"partial\",\n loading: !diff.complete,\n networkStatus:\n diff.complete ? NetworkStatus.ready : NetworkStatus.loading,\n partial: !diff.complete,\n } as ObservableQuery.Result<TData>);\n };\n\n switch (fetchPolicy) {\n case \"cache-only\": {\n return {\n ...cacheResult(),\n loading: false,\n networkStatus: NetworkStatus.ready,\n };\n }\n case \"cache-first\":\n return cacheResult();\n case \"cache-and-network\":\n return {\n ...cacheResult(),\n loading: true,\n networkStatus: NetworkStatus.loading,\n };\n case \"standby\":\n return empty;\n\n default:\n return uninitialized;\n }\n }\n\n private resubscribeCache() {\n const { variables, fetchPolicy } = this.options;\n const query = this.query;\n\n const shouldUnsubscribe =\n fetchPolicy === \"standby\" ||\n fetchPolicy === \"no-cache\" ||\n this.waitForNetworkResult;\n\n const shouldResubscribe =\n !isEqualQuery({ query, variables }, this.unsubscribeFromCache) &&\n !this.waitForNetworkResult;\n\n if (shouldUnsubscribe || shouldResubscribe) {\n this.unsubscribeFromCache?.();\n }\n\n if (shouldUnsubscribe || !shouldResubscribe) {\n return;\n }\n\n const watch: ObservableQuery.CacheWatchOptions<TData, TVariables> = {\n query,\n variables,\n optimistic: true,\n watcher: this,\n callback: (diff) => {\n const info = this.queryManager.getDocumentInfo(query);\n if (info.hasClientExports || info.hasForcedResolvers) {\n // If this is not set to something different than `diff`, we will\n // not be notified about future cache changes with an equal `diff`.\n // That would be the case if we are working with client-only fields\n // that are forced or with `exports` fields that might change, causing\n // local resolvers to return a new result.\n // This is based on an implementation detail of `InMemoryCache`, which\n // is not optimal - but the only alternative to this would be to\n // resubscribe to the cache asynchonouly, which would bear the risk of\n // missing further synchronous updates.\n watch.lastDiff = undefined;\n }\n if (watch.lastOwnDiff === diff) {\n // skip cache updates that were caused by our own writes\n return;\n }\n\n const { result: previousResult } = this.subject.getValue();\n\n if (\n !diff.complete &&\n // If we are trying to deliver an incomplete cache result, we avoid\n // reporting it if the query has errored, otherwise we let the broadcast try\n // and repair the partial result by refetching the query. This check avoids\n // a situation where a query that errors and another succeeds with\n // overlapping data does not report the partial data result to the errored\n // query.\n //\n // See https://github.com/apollographql/apollo-client/issues/11400 for more\n // information on this issue.\n (previousResult.error ||\n // Prevent to schedule a notify directly after the `ObservableQuery`\n // has been `reset` (which will set the `previousResult` to `uninitialized` or `empty`)\n // as in those cases, `resetCache` will manually call `refetch` with more intentional timing.\n previousResult === uninitialized ||\n previousResult === empty)\n ) {\n return;\n }\n\n if (!equal(previousResult.data, diff.result)) {\n this.scheduleNotify();\n }\n },\n };\n const cancelWatch = this.queryManager.cache.watch(watch);\n\n this.unsubscribeFromCache = Object.assign(\n () => {\n this.unsubscribeFromCache = undefined;\n cancelWatch();\n },\n { query, variables }\n );\n }\n\n private stableLastResult?: ObservableQuery.Result<MaybeMasked<TData>>;\n public getCurrentResult(): ObservableQuery.Result<MaybeMasked<TData>> {\n const { result: current } = this.subject.getValue();\n let value =\n (\n // if the `current` result is in an error state, we will always return that\n // error state, even if we have no observers\n current.networkStatus === NetworkStatus.error ||\n // if we have observers, we are watching the cache and\n // this.subject.getValue() will always be up to date\n this.hasObservers() ||\n // if we are using a `no-cache` fetch policy in which case this\n // `ObservableQuery` cannot have been updated from the outside - in\n // that case, we prefer to keep the current value\n this.options.fetchPolicy === \"no-cache\"\n ) ?\n current\n // otherwise, the `current` value might be outdated due to missed\n // external updates - calculate it again\n : this.getInitialResult();\n\n if (value === uninitialized) {\n value = this.getInitialResult();\n }\n if (!equal(this.stableLastResult, value)) {\n this.stableLastResult = value;\n }\n return this.stableLastResult!;\n }\n\n /**\n * Update the variables of this observable query, and fetch the new results.\n * This method should be preferred over `setVariables` in most use cases.\n *\n * Returns a `ResultPromise` with an additional `.retain()` method. Calling\n * `.retain()` keeps the network operation running even if the `ObservableQuery`\n * no longer requires the result.\n *\n * Note: `refetch()` guarantees that a value will be emitted from the\n * observable, even if the result is deep equal to the previous value.\n *\n * @param variables - The new set of variables. If there are missing variables,\n * the previous values of those variables will be used.\n */\n public refetch(\n variables?: Partial<TVariables>\n ): ObservableQuery.ResultPromise<ApolloClient.QueryResult<TData>> {\n const { fetchPolicy } = this.options;\n\n const reobserveOptions: Partial<\n ObservableQuery.Options<TData, TVariables>\n > = {\n // Always disable polling for refetches.\n pollInterval: 0,\n };\n\n // Unless the provided fetchPolicy always consults the network\n // (no-cache, network-only, or cache-and-network), override it with\n // network-only to force the refetch for this fetchQuery call.\n if (fetchPolicy === \"no-cache\") {\n reobserveOptions.fetchPolicy = \"no-cache\";\n } else {\n reobserveOptions.fetchPolicy = \"network-only\";\n }\n\n if (__DEV__ && variables && hasOwnProperty.call(variables, \"variables\")) {\n const queryDef = getQueryDefinition(this.query);\n const vars = queryDef.variableDefinitions;\n if (!vars || !vars.some((v) => v.variable.name.value === \"variables\")) {\n invariant.warn(\n `Called refetch(%o) for query %o, which does not declare a $variables variable.\nDid you mean to call refetch(variables) instead of refetch({ variables })?`,\n variables,\n queryDef.name?.value || queryDef\n );\n }\n }\n\n if (variables && !equal(this.variables, variables)) {\n // Update the existing options with new variables\n reobserveOptions.variables = this.options.variables =\n this.getVariablesWithDefaults({ ...this.variables, ...variables });\n }\n\n this._lastWrite = undefined;\n return this._reobserve(reobserveOptions, {\n newNetworkStatus: NetworkStatus.refetch,\n });\n }\n\n /**\n * A function that helps you fetch the next set of results for a [paginated list field](https://www.apollographql.com/docs/react/pagination/core-api/).\n */\n public fetchMore<\n TFetchData = TData,\n TFetchVars extends OperationVariables = TVariables,\n >(\n options: ObservableQuery.FetchMoreOptions<\n TData,\n TVariables,\n TFetchData,\n TFetchVars\n >\n ): Promise<ApolloClient.QueryResult<TFetchData>>;\n public fetchMore<\n TFetchData = TData,\n TFetchVars extends OperationVariables = TVariables,\n >({\n query,\n variables,\n context,\n errorPolicy,\n updateQuery,\n }: ObservableQuery.FetchMoreOptions<\n TData,\n TVariables,\n TFetchData,\n TFetchVars\n >): Promise<ApolloClient.QueryResult<TFetchData>> {\n invariant(\n this.options.fetchPolicy !== \"cache-only\",\n \"Cannot execute `fetchMore` for 'cache-only' query '%s'. Please use a different fetch policy.\",\n getOperationName(this.query, \"(anonymous)\")\n );\n const combinedOptions = {\n ...compact(\n this.options,\n { errorPolicy: \"none\" },\n {\n query,\n context,\n errorPolicy,\n }\n ),\n variables: (query ? variables : (\n {\n ...this.variables,\n ...variables,\n }\n )) as TFetchVars,\n // The fetchMore request goes immediately to the network and does\n // not automatically write its result to the cache (hence no-cache\n // instead of network-only), because we allow the caller of\n // fetchMore to provide an updateQuery callback that determines how\n // the data gets written to the cache.\n fetchPolicy: \"no-cache\",\n notifyOnNetworkStatusChange: this.options.notifyOnNetworkStatusChange,\n } as ApolloClient.QueryOptions<TFetchData, TFetchVars>;\n\n combinedOptions.query = this.transformDocument(combinedOptions.query);\n\n // If a temporary query is passed to `fetchMore`, we don't want to store\n // it as the last query result since it may be an optimized query for\n // pagination. We will however run the transforms on the original document\n // as well as the document passed in `fetchMoreOptions` to ensure the cache\n // uses the most up-to-date document which may rely on runtime conditionals.\n this.lastQuery =\n query ?\n this.transformDocument(this.options.query)\n : combinedOptions.query;\n\n let wasUpdated = false;\n\n const isCached = this.options.fetchPolicy !== \"no-cache\";\n\n if (!isCached) {\n invariant(\n updateQuery,\n \"You must provide an `updateQuery` function when using `fetchMore` with a `no-cache` fetch policy.\"\n );\n }\n\n const { finalize, pushNotification } = this.pushOperation(\n NetworkStatus.fetchMore\n );\n pushNotification(\n {\n source: \"newNetworkStatus\",\n kind: \"N\",\n value: {},\n },\n { shouldEmit: EmitBehavior.networkStatusChange }\n );\n return this.queryManager\n .fetchQuery(combinedOptions, NetworkStatus.fetchMore)\n .then((fetchMoreResult) => {\n // disable the `fetchMore` override that is currently active\n // the next updates caused by this should not be `fetchMore` anymore,\n // but `ready` or whatever other calculated loading state is currently\n // appropriate\n finalize();\n\n if (isCached) {\n // Separately getting a diff here before the batch - `onWatchUpdated` might be\n // called with an `undefined` `lastDiff` on the watcher if the cache was just subscribed to.\n const lastDiff = this.getCacheDiff();\n // Performing this cache update inside a cache.batch transaction ensures\n // any affected cache.watch watchers are notified at most once about any\n // updates. Most watchers will be using the QueryInfo class, which\n // responds to notifications by calling reobserveCacheFirst to deliver\n // fetchMore cache results back to this ObservableQuery.\n this.queryManager.cache.batch({\n update: (cache) => {\n if (updateQuery) {\n cache.updateQuery(\n {\n query: this.query,\n variables: this.variables,\n returnPartialData: true,\n optimistic: false,\n },\n (previous) =>\n updateQuery(previous! as any, {\n fetchMoreResult: fetchMoreResult.data as any,\n variables: combinedOptions.variables as TFetchVars,\n })\n );\n } else {\n // If we're using a field policy instead of updateQuery, the only\n // thing we need to do is write the new data to the cache using\n // combinedOptions.variables (instead of this.variables, which is\n // what this.updateQuery uses, because it works by abusing the\n // original field value, keyed by the original variables).\n cache.writeQuery({\n query: combinedOptions.query,\n variables: combinedOptions.variables,\n data: fetchMoreResult.data as Unmasked<any>,\n });\n }\n },\n onWatchUpdated: (watch, diff) => {\n if (\n watch.watcher === this &&\n !equal(diff.result, lastDiff.result)\n ) {\n wasUpdated = true;\n }\n },\n });\n } else {\n // There is a possibility `lastResult` may not be set when\n // `fetchMore` is called which would cause this to crash. This should\n // only happen if we haven't previously reported a result. We don't\n // quite know what the right behavior should be here since this block\n // of code runs after the fetch result has executed on the network.\n // We plan to let it crash in the meantime.\n //\n // If we get bug reports due to the `data` property access on\n // undefined, this should give us a real-world scenario that we can\n // use to test against and determine the right behavior. If we do end\n // up changing this behavior, this may require, for example, an\n // adjustment to the types on `updateQuery` since that function\n // expects that the first argument always contains previous result\n // data, but not `undefined`.\n const lastResult = this.getCurrentResult();\n const data = updateQuery!(lastResult.data as Unmasked<TData>, {\n fetchMoreResult: fetchMoreResult.data as Unmasked<TFetchData>,\n variables: combinedOptions.variables as TFetchVars,\n });\n // was reportResult\n pushNotification({\n kind: \"N\",\n value: {\n ...lastResult,\n networkStatus: NetworkStatus.ready,\n // will be overwritten anyways, just here for types sake\n loading: false,\n data: data as any,\n dataState:\n lastResult.dataState === \"streaming\" ? \"streaming\" : \"complete\",\n },\n source: \"network\",\n });\n }\n\n return this.maskResult(fetchMoreResult);\n })\n .finally(() => {\n // call `finalize` a second time in case the `.then` case above was not reached\n finalize();\n\n // In case the cache writes above did not generate a broadcast\n // notification (which would have been intercepted by onWatchUpdated),\n // likely because the written data were the same as what was already in\n // the cache, we still want fetchMore to deliver its final loading:false\n // result with the unchanged data.\n if (isCached && !wasUpdated) {\n pushNotification(\n {\n kind: \"N\",\n source: \"newNetworkStatus\",\n value: {},\n },\n { shouldEmit: EmitBehavior.force }\n );\n }\n });\n }\n\n // XXX the subscription variables are separate from the query variables.\n // if you want to update subscription variables, right now you have to do that separately,\n // and you can only do it by stopping the subscription and then subscribing again with new variables.\n /**\n * A function that enables you to execute a [subscription](https://www.apollographql.com/docs/react/data/subscriptions/), usually to subscribe to specific fields that were included in the query.\n *\n * This function returns _another_ function that you can call to terminate the subscription.\n */\n public subscribeToMore<\n TSubscriptionData = TData,\n TSubscriptionVariables extends OperationVariables = TVariables,\n >(\n options: ObservableQuery.SubscribeToMoreOptions<\n TData,\n TSubscriptionVariables,\n TSubscriptionData,\n TVariables\n >\n ): () => void {\n const subscription = this.queryManager\n .startGraphQLSubscription({\n query: options.document,\n variables: options.variables,\n context: options.context,\n })\n .subscribe({\n next: (subscriptionData) => {\n const { updateQuery, onError } = options;\n const { error } = subscriptionData;\n\n if (error) {\n if (onError) {\n onError(error);\n } else {\n invariant.error(\"Unhandled GraphQL subscription error\", error);\n }\n\n return;\n }\n\n if (updateQuery) {\n this.updateQuery((previous, updateOptions) =>\n updateQuery(previous, {\n subscriptionData: subscriptionData as {\n data: Unmasked<TSubscriptionData>;\n },\n ...updateOptions,\n })\n );\n }\n },\n });\n\n this.subscriptions.add(subscription);\n\n return () => {\n if (this.subscriptions.delete(subscription)) {\n subscription.unsubscribe();\n }\n };\n }\n\n /** @internal */\n public applyOptions(\n newOptions: Partial<ObservableQuery.Options<TData, TVariables>>\n ): void {\n const mergedOptions = compact(this.options, newOptions || {});\n assign(this.options, mergedOptions);\n this.updatePolling();\n }\n\n /**\n * Update the variables of this observable query, and fetch the new results\n * if they've changed. Most users should prefer `refetch` instead of\n * `setVariables` in order to to be properly notified of results even when\n * they come from the cache.\n *\n * Note: `setVariables()` guarantees that a value will be emitted from the\n * observable, even if the result is deeply equal to the previous value.\n *\n * Note: the promise will resolve with the last emitted result\n * when either the variables match the current variables or there\n * are no subscribers to the query.\n *\n * @param variables - The new set of variables. If there are missing variables,\n * the previous values of those variables will be used.\n */\n public async setVariables(\n variables: TVariables\n ): Promise<ApolloClient.QueryResult<TData>> {\n variables = this.getVariablesWithDefaults(variables);\n\n if (equal(this.variables, variables)) {\n // If we have no observers, then we don't actually want to make a network\n // request. As soon as someone observes the query, the request will kick\n // off. For now, we just store any changes. (See #1077)\n return toQueryResult(this.getCurrentResult());\n }\n\n this.options.variables = variables;\n\n // See comment above\n if (!this.hasObservers()) {\n return toQueryResult(this.getCurrentResult());\n }\n\n return this._reobserve(\n {\n // Reset options.fetchPolicy to its original value.\n fetchPolicy: this.options.initialFetchPolicy,\n variables,\n },\n { newNetworkStatus: NetworkStatus.setVariables }\n );\n }\n\n /**\n * A function that enables you to update the query's cached result without executing a followup GraphQL operation.\n *\n * See [using updateQuery and updateFragment](https://www.apollographql.com/docs/react/caching/cache-interaction/#using-updatequery-and-updatefragment) for additional information.\n */\n public updateQuery(mapFn: UpdateQueryMapFn<TData, TVariables>): void {\n const { queryManager } = this;\n const { result, complete } = this.getCacheDiff({ optimistic: false });\n\n const newResult = mapFn(\n result! as DeepPartial<Unmasked<TData>>,\n {\n variables: this.variables,\n complete: !!complete,\n previousData: result,\n } as UpdateQueryOptions<TData, TVariables>\n );\n\n if (newResult) {\n queryManager.cache.writeQuery({\n query: this.options.query,\n data: newResult,\n variables: this.variables,\n });\n\n queryManager.broadcastQueries();\n }\n }\n\n /**\n * A function that instructs the query to begin re-executing at a specified interval (in milliseconds).\n */\n public startPolling(pollInterval: number) {\n this.options.pollInterval = pollInterval;\n this.updatePolling();\n }\n\n /**\n * A function that instructs the query to stop polling after a previous call to `startPolling`.\n */\n public stopPolling() {\n this.options.pollInterval = 0;\n this.updatePolling();\n }\n\n // Update options.fetchPolicy according to options.nextFetchPolicy.\n private applyNextFetchPolicy(\n reason: NextFetchPolicyContext<TData, TVariables>[\"reason\"],\n // It's possible to use this method to apply options.nextFetchPolicy to\n // options.fetchPolicy even if options !== this.options, though that happens\n // most often when the options are temporary, used for only one request and\n // then thrown away, so nextFetchPolicy may not end up mattering.\n options: ApolloClient.WatchQueryOptions<TData, TVariables>\n ) {\n if (options.nextFetchPolicy) {\n const { fetchPolicy = \"cache-first\", initialFetchPolicy = fetchPolicy } =\n options;\n\n if (fetchPolicy === \"standby\") {\n // Do nothing, leaving options.fetchPolicy unchanged.\n } else if (typeof options.nextFetchPolicy === \"function\") {\n // When someone chooses \"cache-and-network\" or \"network-only\" as their\n // initial FetchPolicy, they often do not want future cache updates to\n // trigger unconditional network requests, which is what repeatedly\n // applying the \"cache-and-network\" or \"network-only\" policies would\n // seem to imply. Instead, when the cache reports an update after the\n // initial network request, it may be desirable for subsequent network\n // requests to be triggered only if the cache result is incomplete. To\n // that end, the options.nextFetchPolicy option provides an easy way to\n // update options.fetchPolicy after the initial network request, without\n // having to call observableQuery.reobserve.\n options.fetchPolicy = options.nextFetchPolicy.call(\n options as any,\n fetchPolicy,\n { reason, options, observable: this, initialFetchPolicy }\n );\n } else if (reason === \"variables-changed\") {\n options.fetchPolicy = initialFetchPolicy;\n } else {\n options.fetchPolicy = options.nextFetchPolicy;\n }\n }\n\n return options.fetchPolicy;\n }\n\n private fetch(\n options: ObservableQuery.Options<TData, TVariables>,\n networkStatus: NetworkStatus,\n fetchQuery: DocumentNode,\n operator: MonoTypeOperatorFunction<QueryNotification.Value<TData>>\n ) {\n // TODO Make sure we update the networkStatus (and infer fetchVariables)\n // before actually committing to the fetch.\n const initialFetchPolicy = this.options.fetchPolicy;\n options.context ??= {};\n\n let synchronouslyEmitted = false;\n const onCacheHit = () => {\n synchronouslyEmitted = true;\n };\n const fetchQueryOperator = // we cannot use `tap` here, since it allows only for a \"before subscription\"\n // hook with `subscribe` and we care for \"directly before and after subscription\"\n <T>(source: Observable<T>) =>\n new Observable<T>((subscriber) => {\n try {\n return source.subscribe({\n next(value) {\n synchronouslyEmitted = true;\n subscriber.next(value);\n },\n error: (error) => subscriber.error(error),\n complete: () => subscriber.complete(),\n });\n } finally {\n if (!synchronouslyEmitted) {\n operation.override = networkStatus;\n this.input.next({\n kind: \"N\",\n source: \"newNetworkStatus\",\n value: {\n resetError: true,\n },\n query,\n variables,\n meta: {\n shouldEmit: EmitBehavior.networkStatusChange,\n /*\n * The moment this notification is emitted, `nextFetchPolicy`\n * might already have switched from a `network-only` to a\n * `cache-something` policy, so we want to ensure that the\n * loading state emit doesn't accidentally read from the cache\n * in those cases.\n */\n fetchPolicy: initialFetchPolicy,\n },\n });\n }\n }\n });\n\n let { observable, fromLink } = this.queryManager.fetchObservableWithInfo(\n options,\n {\n networkStatus,\n query: fetchQuery,\n onCacheHit,\n fetchQueryOperator,\n observableQuery: this,\n }\n );\n\n // track query and variables from the start of the operation\n const { query, variables } = this;\n const operation: TrackedOperation = {\n abort: () => {\n subscription.unsubscribe();\n },\n query,\n variables,\n };\n this.activeOperations.add(operation);\n\n let forceFirstValueEmit =\n networkStatus == NetworkStatus.refetch ||\n networkStatus == NetworkStatus.setVariables;\n observable = observable.pipe(operator, share());\n const subscription = observable\n .pipe(\n tap({\n next: (notification) => {\n if (\n notification.source === \"newNetworkStatus\" ||\n (notification.kind === \"N\" && notification.value.loading)\n ) {\n operation.override = networkStatus;\n } else {\n delete operation.override;\n }\n },\n finalize: () => this.activeOperations.delete(operation),\n })\n )\n .subscribe({\n next: (value) => {\n const meta: Meta = {};\n\n if (\n forceFirstValueEmit &&\n value.kind === \"N\" &&\n \"loading\" in value.value &&\n !value.value.loading\n ) {\n forceFirstValueEmit = false;\n meta.shouldEmit = EmitBehavior.force;\n }\n\n this.input.next({ ...value, query, variables, meta });\n },\n });\n\n return { fromLink, subscription, observable };\n }\n\n // Turns polling on or off based on this.options.pollInterval.\n private didWarnCacheOnlyPolling = false;\n private updatePolling() {\n // Avoid polling in SSR mode\n if (this.queryManager.ssrMode) {\n return;\n }\n\n const {\n pollingInfo,\n options: { fetchPolicy, pollInterval },\n } = this;\n\n if (!pollInterval || !this.hasObservers() || fetchPolicy === \"cache-only\") {\n if (__DEV__) {\n if (\n !this.didWarnCacheOnlyPolling &&\n pollInterval &&\n fetchPolicy === \"cache-only\"\n ) {\n invariant.warn(\n \"Cannot poll on 'cache-only' query '%s' and as such, polling is disabled. Please use a different fetch policy.\",\n getOperationName(this.query, \"(anonymous)\")\n );\n this.didWarnCacheOnlyPolling = true;\n }\n }\n\n this.cancelPolling();\n return;\n }\n\n if (pollingInfo?.interval === pollInterval) {\n return;\n }\n\n const info = pollingInfo || (this.pollingInfo = {} as any);\n info.interval = pollInterval;\n\n const maybeFetch = () => {\n if (this.pollingInfo) {\n if (\n !isNetworkRequestInFlight(this.networkStatus) &&\n !this.options.skipPollAttempt?.()\n ) {\n this._reobserve(\n {\n // Most fetchPolicy options don't make sense to use in a polling context, as\n // users wouldn't want to be polling the cache directly. However, network-only and\n // no-cache are both useful for when the user wants to control whether or not the\n // polled results are written to the cache.\n fetchPolicy:\n this.options.initialFetchPolicy === \"no-cache\" ?\n \"no-cache\"\n : \"network-only\",\n },\n {\n newNetworkStatus: NetworkStatus.poll,\n }\n ).then(poll, poll);\n } else {\n poll();\n }\n }\n };\n\n const poll = () => {\n const info = this.pollingInfo;\n if (info) {\n clearTimeout(info.timeout);\n info.timeout = setTimeout(maybeFetch, info.interval);\n }\n };\n\n poll();\n }\n\n // This differs from stopPolling in that it does not set pollInterval to 0\n private cancel