Releases: apollographql/apollo-client
@apollo/[email protected]
Major Changes
-
#12673
cee90ab
Thanks @phryneas! - TheincludeExtensions
option ofHttpLink
andBatchHttpLink
now defaults
totrue
.If
includeExtensions
istrue
, butextensions
is not set or empty, extensions
will not be included in outgoing requests. -
#12673
cee90ab
Thanks @phryneas! - TheApolloClient
constructor optionsname
andversion
that are used to
configure the client awareness feature have moved onto aclientAwareness
key.const client = new ApolloClient({ // .. - name: "my-app", - version: "1.0.0", + clientAwareness: { + name: "my-app", + version: "1.0.0", + }, });
-
#12690
5812759
Thanks @phryneas! - Aliasing any other field to__typename
is now forbidden. -
#12690
5812759
Thanks @phryneas! - Aliasing a field to an alias beginning with__ac_
is now forbidden - this namespace is now reserved for internal use. -
#12673
cee90ab
Thanks @phryneas! - Adds enhanced client awareness to the client.HttpLink
andBatchHttpLink
will now per default send information about the
client library you are using inextensions
.This could look like this:
{ "query": "query GetUser($id: ID!) { user(id: $id) { __typename id name } }", "variables": { "id": 5 }, "extensions": { "clientLibrary": { "name": "@apollo/client", "version": "4.0.0" } } }
This feature can be disabled by passing
enhancedClientAwareness: { transport: false }
to your
ApolloClient
,HttpLink
orBatchHttpLink
constructor options.
Minor Changes
-
#12698
be77d1a
Thanks @phryneas! - Adjusted the accept header for multipart requests according to the new GraphQL over HTTP spec with these changes:-multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json +multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/graphql-response+json,application/json;q=0.9
-multipart/mixed;deferSpec=20220824,application/json +multipart/mixed;deferSpec=20220824,application/graphql-response+json,application/json;q=0.9
-
#12673
cee90ab
Thanks @phryneas! - Add the newClientAwarenessLink
.This link is already included in
HttpLink
andBatchHttpLink
to enable the
"client awareness" and "enhanced client awareness" features, but you can also use
ClientAwarenessLink
directly in your link chain to combine it with other
terminating links.If you want to save the bundle size that
ClientAwarenessLink
adds toHttpLink
andBatchHttpLink
, you can useBaseHttpLink
orBaseBatchHttpLink
instead.
These links come without theClientAwarenessLink
included.For example:
import { ApolloClient, - HttpLink, } from "@apollo/client"; +import { BaseHttpLink } from "@apollo/client/link/http"; const client = new ApolloClient({ - link: new HttpLink({ + link: new BaseHttpLink({ uri, }), cache: new InMemoryCache(), });
-
#12698
be77d1a
Thanks @phryneas! - Adds anaccept
option toHttpOptions
that allows to add additionalAccept
headers to be merged in without overriding user-specified or default accept headers.
Patch Changes
@apollo/[email protected]
Major Changes
-
#12686
dc4b1d0
Thanks @jerelmiller! - A@defer
query that has not yet finished streaming is now considered loading and thus theloading
flag will betrue
until the response has completed. A newNetworkStatus.streaming
value has been introduced and will be set as thenetworkStatus
while the response is streaming. -
#12685
3b74800
Thanks @jerelmiller! - Remove the check and warning forcache.fragmentMatches
when applying data masking.cache.fragmentMatches
is a required API and data masking may crash whencache.fragmentMatches
does not exist. -
#12684
e697431
Thanks @jerelmiller! - Removecontext
fromuseLazyQuery
hook options. If used,context
must now be provided to theexecute
function.context
will reset to{}
if not provided as an option toexecute
.
@apollo/[email protected]
Major Changes
-
#12675
8f1d974
Thanks @phryneas! -ObservableQuery
no longer has aqueryId
property.
ApolloClient.getObservableQueries
no longer returns aMap<string, ObservableQuery>
, but aSet<ObservableQuery>
. -
#12647
a70fac6
Thanks @phryneas! -ObservableQuery
s will now only be registered with theApolloClient
while they
have subscribers.That means that
ApolloClient.getObservableQueries
andApolloClient.refetchQueries
will only be able to return/refetch queries that have at least one subscriber.This changes the previous meaning of
active
andinactive
queries:inactive
queries are queries with a subscriber that are skipped from a
React hook or have afetchPolicy
ofstandby
active
queries are queries with at least one subscriber that are not skipped or instandby
.
ObservableQuery
s without subscribers but with an active ongoing network request
(e.g. caused by callingreobserve
) will be handled as if they had a subscriber
for the duration of the query. -
#12678
91a876b
Thanks @jerelmiller! -queryRef
s created bypreloadQuery
no longer have a.toPromise()
function. InsteadpreloadQuery
now has atoPromise
function that accepts a queryRef and will resolve when the underlying promise has been resolved.const queryRef = preloadQuery(query, options); - await queryRef.toPromise(); + await preloadQuery.toPromise(queryRef);
-
#12647
a70fac6
Thanks @phryneas! -ApolloClient.stop()
now cleans up more agressively to prevent memory leaks:- It will now unsubscribe all active
ObservableQuery
instances by emitting acompleted
event. - It will now reject all currently running queries with
"QueryManager stopped while query was in flight"
. - It will remove all queryRefs from the suspense cache.
- It will now unsubscribe all active
Minor Changes
@apollo/[email protected]
Major Changes
- #12663
01512f2
Thanks @jerelmiller! - Unsubscribing from anObservableQuery
before a value has been emitted will remove the query from the tracked list of queries and will no longer be eligible for query deduplication.
Minor Changes
-
#12663
01512f2
Thanks @jerelmiller! - Subscriptions created byclient.subscribe()
can now be restarted. Restarting a subscription will terminate the connection with the link chain and recreate the request. Restarts also work across deduplicated subscriptions so callingrestart
on anobservable
who's request is deduplicated will restart the connection for each observable.const observable = client.subscribe({ query: subscription }); // Restart the connection to the link observable.restart();
-
#12663
01512f2
Thanks @jerelmiller! - Deduplicating subscription operations is now supported. Previously it was possible to deduplicate a subscription only if the new subscription was created before a previously subscribed subscription emitted any values. As soon as a value was emitted from a subscription, new subscriptions would create new connections. Deduplication is now active for as long as a subscription connection is open (i.e. the source observable hasn't emitted acomplete
orerror
notification yet.)To disable deduplication and force a new connection, use the
queryDeduplication
option incontext
like you would a query operation.As a result of this change, calling the
restart
function returned fromuseSubscription
will now restart the connection on deduplicated subscriptions.
@apollo/[email protected]
Minor Changes
-
#12670
0a880ea
Thanks @phryneas! - Provide a mechanism to override the DataMasking types.Up until now, our types
Masked
,MaskedDocumentNode
,FragmentType
,MaybeMasked
andUnmasked
would assume that you are stictly using the type output format of GraphQL Codegen.With this change, you can now modify the behaviour of those types if you use a different form of codegen that produces different types for your queries.
A simple implementation that would override the
Masked
type to remove all fields starting with_
from a type would look like this:// your actual implementation of `Masked` type CustomMaskedImplementation<TData> = { [K in keyof TData as K extends `_${string}` ? never : K]: TData[K]; }; import { HKT } from "@apollo/client/utilities"; // transform this type into a higher kinded type that can be evaulated at a later time interface CustomMaskedType extends HKT { arg1: unknown; // TData return: CustomMaskedImplementation<this["arg1"]>; } // create an "implementation interface" for the types you want to override export interface CustomDataMaskingImplementation { Masked: CustomMaskedType; // other possible keys: `MaskedDocumentNode`, `FragmentType`, `MaybeMasked` and `Unmasked` }
then you would use that
CustomDataMaskingImplementation
interface in your project to extend theDataMasking
interface exported by@apollo/client
with it's functionality:declare module "@apollo/client" { export interface DataMasking extends CustomDataMaskingImplementation {} }
After that, all internal usage of
Masked
in Apollo Client as well as all usage in your code base will use the newCustomMaskedType
implementation.If you don't specify overrides, Apollo Client will still default to the GraphQL Codegen data masking implementation.
The types for that are also explicitly exported as theGraphQLCodegenDataMasking
namespace in@apollo/client/masking
.
@apollo/[email protected]
Major Changes
-
#12649
0be92ad
Thanks @jerelmiller! - TheTData
generic provided to types that return adataState
property is now modified by the givenDataState
generic instead of passing a modifiedTData
type. For example, aQueryRef
that could return partial data was defined asQueryRef<DeepPartial<TData>, TVariables>
. NowTData
should be provided unmodified and a set of allowed states should be given instead:QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'>
.To migrate, use the following guide to replace your type with the right set of states (all types listed below are changed the same way):
- QueryRef<TData, TVariables> // `QueryRef`'s default is 'complete' | 'streaming' so this can also be left alone if you prefer // All other types affected by this change default to all states + QueryRef<TData, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming'> - QueryRef<TData | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'empty'> - QueryRef<DeepPartial<TData>, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'> - QueryRef<DeepPartial<TData> | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial' | 'empty'>
The following types are affected. Provide the allowed
dataState
values to theTDataState
generic:ApolloQueryResult
QueryRef
PreloadedQueryRef
useLazyQuery.Result
useQuery.Result
useReadQuery.Result
useSuspenseQuery.Result
All
*QueryRef
types default tocomplete | streaming
states while the rest of the types default to'complete' | 'streaming' | 'partial' | 'empty'
states. You shouldn't need to provide the states unless you need to either allow for partial data/empty values (*QueryRef
) or a restricted set of states. -
#12649
0be92ad
Thanks @jerelmiller! - Remove the deprecatedQueryReference
type. Please useQueryRef
instead. -
#12633
9bfb51f
Thanks @phryneas! - If theexecute
function ofuseLazyQuery
is executed, previously started queries
from the sameuseLazyQuery
usage will be rejected with anAbortError
unless
.retain()
is called on the promise returned by previousexecute
calls.Please keep in mind that
useLazyQuery
is primarily meant as a means to synchronize
your component to the status of a query and that it's purpose it not to make a
series of network calls.
If you plan on making a series of network calls without the need to synchronize
the result with your component, consider usingApolloClient.query
instead.
Minor Changes
-
#12633
9bfb51f
Thanks @phryneas! -ObservableQuery.refetch
andObservableQuery.reobserve
and theexecute
function ofuseLazyQuery
now return a
ResultPromise
with an additional.retain
method.
If this method is called, the underlying network operation will be kept running even if theObservableQuery
itself does
not require the result anymore, and the Promise will resolve with the final result instead of resolving with an intermediate
result in the case of early cancellation. -
#12649
0be92ad
Thanks @jerelmiller! - Add a newdataState
property that determines the completeness of thedata
property.dataState
helps narrow the type ofdata
.dataState
is now emitted fromObservableQuery
and returned from all React hooks that return adata
property.The
dataState
values are:empty
: No data could be fulfilled from the cache or the result is incomplete.data
isundefined
.partial
: Some data could be fulfilled from the cache butdata
is incomplete. This is only possible whenreturnPartialData
istrue
.streaming
:data
is incomplete as a result of a deferred query and the result is still streaming in.complete
:data
is a fully satisfied query result fulfilled either from the cache or network.
Example:
const { data, dataState } = useQuery<TData>(query); if (dataState === "empty") { expectTypeOf(data).toEqualTypeOf<undefined>(); } if (dataState === "partial") { expectTypeOf(data).toEqualTypeOf<DeepPartial<TData>>(); } if (dataState === "streaming") { expectTypeOf(data).toEqualTypeOf<TData>(); } if (dataState === "complete") { expectTypeOf(data).toEqualTypeOf<TData>(); }
@apollo/[email protected]
Major Changes
-
#12644
fe2f005
Thanks @jerelmiller! - Replace theresult
property onServerError
withbodyText
.bodyText
is set to the raw string body.HttpLink
andBatchHttpLink
no longer try and parse the response body as JSON when aServerError
is thrown. -
#12644
fe2f005
Thanks @jerelmiller! - More strictly adhere to the GraphQL over HTTP spec. This change adds support for theapplication/graphql-response+json
media type and modifies the behavior of theapplication/json
media type.- The client will parse the response as a well-formed GraphQL response when the server encodes
content-type
usingapplication/graphql-response+json
with a non-200 status code. - The client will now throw a
ServerError
when the server encodescontent-type
usingapplication/json
and returns a non-200 status code. - The client will now throw a
ServerError
when the server encodes using any othercontent-type
and returns a non-200 status code.
NOTE: If you use a testing utility to mock requests in your test, you may experience different behavior than production if your testing utility responds as
application/json
but your production server responds asapplication/graphql-response+json
. If acontent-type
header is not set, the client interprets the response asapplication/json
. - The client will parse the response as a well-formed GraphQL response when the server encodes
-
#12644
fe2f005
Thanks @jerelmiller! - Change the defaultAccept
header toapplication/graphql-response+json,application/json;q=0.9
. -
#12644
fe2f005
Thanks @jerelmiller! -HttpLink
andBatchHttpLink
no longer emit anext
notification with the JSON-parsed response body when a well-formed GraphQL response is returned and aServerError
is thrown.
@apollo/[email protected]
Major Changes
-
#12617
ea633a1
Thanks @jerelmiller! - Introduce a new GraphQL Codegen plugin aimed at creating resolver types forLocalState
. This plugin is similar to@graphql-codegen/typescript-resolvers
but tailored to provide types that work withLocalState
.To use the plugin, install
@apollo/client-graphql-codegen
and add the following to your codegen config:// codegen.ts const config: CodegenConfig = { // ... generates: { "./path/to/local/resolvers.ts": { schema: ["./path/to/localSchema.graphql"], plugins: ["typescript", "@apollo/client-graphql-codegen/local-state"], // ... }, }, };
This will generate a
Resolvers
type in the generated file that can be used to provide type information toLocalState
.import type { Resolvers } from "./path/to/resolvers-types.ts"; const localState = new LocalState<Resolvers>({ // ... });
It is also recommended to add the following config:
// codegen.ts import type { LocalStatePluginConfig } from "@apollo/client-graphql-codegen/local-state"; const config: CodegenConfig = { // ... generates: { "./path/to/local/resolvers.ts": { config: { // Ensures you return a `__typename` for any `@client` fields that // return object or array types nonOptionalTypename: true, // Required if your localSchema extends existing schema types. baseTypesPath: "./relative/path/to/base/schema/types", // If you provide a `context` function to customize the context value, // provide the path or type here. contextType: "./path/to/contextValue#ContextValue", } satisfies LocalStatePluginConfig, }, }, };
NOTE: It is recommended that the schema file passed to the
schema
option is your local schema, not your entire app schema in order to only generate resolver types for your local fields, otherwise the plugin will generate resolver types for your entire remote schema as well.
v4.0.0-alpha.15
Major Changes
-
#12639
1bdf489
Thanks @jerelmiller! - Move internal testing utilities in@apollo/client/testing
to@apollo/client/testing/internal
and remove deprecated testing utilities. Some of the testing utilities exported from the@apollo/client/testing
endpoint were not considered stable. As a result of this change, testing utilities or types exported from@apollo/client/testing
are now considered stable and will not undergo breaking changes.The following APIs were removed. To migrate, update usages of the following APIs as such:
createMockClient
- const client = createMockClient(data, query, variables); + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]), + });
mockObservableLink
- const link = mockObservableLink(); + const link = new MockSubscriptionLink();
mockSingleLink
- const link = mockSingleLink({ - request: { query, variables }, - result: { data }, - }); + const link = new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]);
-
#12637
d2a60d4
Thanks @phryneas! -useQuery
: only advancepreviousData
ifdata
actually changed -
#12631
b147cac
Thanks @phryneas! -ObservableQuery
will now return aloading: false
state forfetchPolicy
standby
, even before subscription -
#12639
1bdf489
Thanks @jerelmiller! - Remove the@apollo/client/testing/core
entrypoint in favor of@apollo/client/testing
.
Minor Changes
-
#12639
1bdf489
Thanks @jerelmiller! - MoveMockLink
types toMockLink
namespace. This affects theMockedResponse
,MockLinkOptions
, andResultFunction
types. These types are still exported but are deprecated in favor of the namespace. To migrate, use the types on theMockLink
namespace instead.import { - MockedResponse, - MockLinkOptions, - ResultFunction, + MockLink } from "@apollo/client/testing"; - const mocks: MockedResponse = []; + const mocks: MockLink.MockedResponse = []; - const result: ResultFunction = () => {/* ... */ } + const result: MockLink.ResultFunction = () => {/* ... */ } - const options: MockLinkOptions = {} + const options: MockLink.Options = {}
Patch Changes
v4.0.0-alpha.14
Major Changes
-
#12614
d2851e2
Thanks @jerelmiller! - ThegetCacheKey
function is no longer available fromoperation.getContext()
in the link chain. Useoperation.client.cache.identify(obj)
in the link chain instead. -
#12556
c3fceda
Thanks @phryneas! -ObservableQuery
will now keep previousdata
around when emitting aloading
state, unlessquery
orvariables
changed.
Note that@exports
variables are not taken into account for this, sodata
will stay around even if they change. -
#12556
c3fceda
Thanks @phryneas! - RemovedgetLastResult
,getLastError
andresetLastResults
fromObservableQuery
-
#12614
d2851e2
Thanks @jerelmiller! - Removes theresolvers
option fromApolloClient
. Local resolvers have instead been moved to the newLocalState
instance which is assigned to thelocalState
option inApolloClient
. To migrate, move theresolvers
values into aLocalState
instance and assign that instance tolocalState
.new ApolloClient({ - resolvers: { /* ... */ } + localState: new LocalState({ + resolvers: { /* ... */ } + }), });
-
#12614
d2851e2
Thanks @jerelmiller! - Remove local resolvers APIs fromApolloClient
in favor oflocalState
. Methods removed are:addResolvers
getResolvers
setResolvers
setLocalStateFragmentMatcher
-
#12614
d2851e2
Thanks @jerelmiller! - Third-party caches must now implement thefragmentMatches
API. AdditionallyfragmentMatches
must be able to handle bothInlineFragmentNode
andFragmentDefinitionNode
nodes.class MyCache extends ApolloCache { // This is now required public fragmentMatches( fragment: InlineFragmentNode | FragmentDefinitionNode, typename: string ): boolean { return; // ... logic to determine if typename matches fragment } }
-
#12556
c3fceda
Thanks @phryneas! - Reworked the logic for then a loading state is triggered. If the link chain responds synchronously, a loading state will be omitted, otherwise it will be triggered.
If local resolvers are used, the time window for "sync vs async" starts as soon as@exports
variables are resolved. -
#12556
c3fceda
Thanks @phryneas! - Dropped thesaveAsLastResult
argument fromObservableQuery.getCurrentResult
-
#12614
d2851e2
Thanks @jerelmiller! - The resolver function'scontext
argument (the 3rd argument) has changed to provide additional information without the possibility of name clashes. Previously thecontext
argument would spread request context and override theclient
andcache
properties to give access to both inside of a resolver. Thecontext
argument takes now takes the following shape:{ // the request context. By default `TContextValue` is of type `DefaultContext`, // but can be changed if a `context` function is provided. requestContext: TContextValue, // The client instance making the request client: ApolloClient, // Whether the resolver is run as a result of gathering exported variables // or resolving the value as part of the result phase: "exports" | "resolve" }
To migrate, pull any request context from
requestContext
and thecache
from theclient
property:new LocalState({ resolvers: { Query: { - myResolver: (parent, args, { someValue, cache }) => { + myResolver: (parent, args, { requestContext, client }) => { + const someValue = requestContext.someValue; + const cache = client.cache; } } } });
-
#12614
d2851e2
Thanks @jerelmiller! - Apollo Client no longer ships with support for@client
fields out-of-the-box and now must be opt-in. To opt in to use@client
fields, pass an instantiatedLocalState
instance to thelocalState
option. If a query contains@client
and local state hasn't been configured, an error will be thrown.import { LocalState } from "@apollo/client/local-state"; new ApolloClient({ localState: new LocalState(), });
-
#12614
d2851e2
Thanks @jerelmiller! - Remove thefragmentMatcher
option fromApolloClient
. Custom fragment matchers used with local state are no longer supported. Fragment matching is now performed by the configuredcache
via thecache.fragmentMatches
API. -
#12556
c3fceda
Thanks @phryneas! - A call toObservableQuery.setVariables
with different variables or aObservableQuery.refetch
call will always now guarantee that a value will be emitted from the observable, even if it is deep equal to the previous value.
Minor Changes
-
#12614
d2851e2
Thanks @jerelmiller! - Revamp local resolvers and fix several issues from the existingresolvers
option.- Throwing errors in a resolver will set the field value as
null
and add an error to the response'serrors
array. - Remote results are dealiased before they are passed as the parent object to a resolver so that you can access fields by their field name.
- You can now specify a
context
function that you can use to customize therequestContext
given to resolvers. - The
LocalState
class accepts aResolvers
generic that provides autocompletion and type checking against your resolver types to ensure your resolvers are type-safe. data: null
is now handled correctly and does not call your local resolvers when the server does not provide a result.- Additional warnings have been added to provide hints when resolvers behave unexpectedly.
import { LocalState } from "@apollo/client/local-state"; import { Resolvers } from "./path/to/local-resolvers-types.ts"; // LocalState now accepts a `Resolvers` generic. const localState = new LocalState<Resolvers>({ // The return value of this funciton context: (options) => ({ // ... }), resolvers: { // ... }, }); // You may also pass a `ContextValue` generic used to ensure the `context` // function returns the correct type. This type is inferred from your resolvers // if not provided. new LocalState<Resolvers, ContextValue>({ // ... });
- Throwing errors in a resolver will set the field value as