Releases: apollographql/apollo-client
Release list
@apollo/client@4.3.0
Minor Changes
-
#13447
24133feThanks @jerelmiller! - Field policies andinputObjectscan now tell the cache whether a field is a list of scalars or a scalar whose value is an array. Previously all arrays were iterated and only the inner type was provided to the scalarparse/serializefunctions.This required some breaking changes from previous prerelease versions:
- The field policy
scalaroption andinputObjectstype string now use GraphQL list syntax to mark a field as a list of scalars - The abstract
cache.getScalarForFieldis nowcache.getScalarTypeForFieldand is expected to return the string representing the scalar type rather than theScalarinstance
new InMemoryCache({ scalars: { DateTime: new Scalar(/*...*/), }, inputObjects: { EventFilter: { fields: { // Previously only the scalar type was provided datesBefore: "DateTime", // List syntax now required datesAfter: "[DateTime]", dates2d: "[[DateTime]]", }, }, }, typePolicies: { Event: { fields: { // Previously only the scalar type was provided datesBefore: { scalar: "DateTime", }, // List syntax now required datesAfter: { scalar: "[DateTime]", }, dates2d: { scalar: "[[DateTime]]", }, }, }, }, });
Now it's possible to handle scalars that are represented by arrays:
const dateTimeRangeScalar = new Scalar< [string, string], { start: Date; end: Date } >({ parse: ([start, end]) => ({ start: new Date(start), end: new Date(end), }), serialize: (range) => [range.start.toISOString(), range.end.toISOString()], is: (value) => !Array.isArray(value), }); const cache = new InMemoryCache({ scalars: { DateTimeRange: dateTimeRangeScalar, }, typePolicies: { Event: { fields: { range: { scalar: "DateTimeRange", }, }, }, }, }); const query = gql` query { event { range } } `; cache.writeQuery({ query, data: { event: { __typename: "Event", // Server returns DateTimeRange as a JSON array range: ["2024-01-01T00:00:00Z", "2024-06-01T00:00:00Z"], }, }, }); const { data } = useQuery(query); // => { event: { __typename: "Event", range: { start: Date, end: Date } } }
- The field policy
-
#13324
0abd8deThanks @jerelmiller! - Fix the accuracy ofdataStatein complex incremental streaming scenarios, especially when combined withreturnPartialData: true.Prior to this change, all intermediate chunks used for both
@deferand@streamdirectives returned adataStateofstreaming, regardless of whether the actual data shape fit the definition of thestreamingdata state. Thestreamingdata state represents an incomplete incremental response where the only holes in the data occur at@deferboundaries.Let's use the following example of where the previous
dataStatefell down when combined withreturnPartialData.query GreetingQuery { greeting { message ... @defer { recipient { name email } } } }
- Scenario 1: partial data inside a
@deferboundary written to the cache
Let's say the cache contained the following partial data:
{ greeting: { __typename: "Greeting", recipient: { __typename: "Person", name: "John Doe", }, }, };
After the first chunk arrives from the server, the data looks like the following:
{ greeting: { __typename: "Greeting", message: "Hello, John", recipient: { __typename: "Person", name: "John Doe", }, }, };
This data is not
completebecauserecipient.emailis missing. This data is also notstreamingbecause the data requirements in the@deferboundary are partially fulfilled due to the existence ofrecipient. This could lead to runtime crashes onrecipient.emailif you use the existence ofrecipientto detect whether data in the@deferboundary has streamed in or not. This change now accurately reports this aspartialto ensure the field is marked as a partial field inrecipient.- Scenario 2: partial data written to the cache that fulfills the data requirements of the
@deferboundary
Let's say the cache contained the following partial data:
{ greeting: { __typename: "Greeting", recipient: { __typename: "Person", name: "John Doe", email: "john@example.com", }, }, };
After the first chunk arrives from the server, the data looks like the following:
{ greeting: { __typename: "Greeting", message: "Hello, John", recipient: { __typename: "Person", name: "John Doe", email: "john@example.com", }, }, };
In this case, the combination of the first chunk and the partial data in the cache now fulfills the data requirements of the query. Even though the server is still streaming data (
NetworkStatus.streaming), we can report this asdataState: "complete"since it is safe to access data on all fields.This change also means
@streamqueries by definition fulfill the data requirements of the query after the first chunk arrives since@streamoperates on lists and contains no data holes.@streamqueries now accurately reportdataStateascompleteorpartial, depending on whether the list mixes partial data with streamed list items.As a result of this change, some cases where you'd previously see
dataStatereported as"streaming"are now reported aspartialorcomplete.If you use
dataStateto determine whether an incremental request is still in-flight, please usenetworkStatusinstead to check forNetworkStatus.streaming.dataStateis type narrowing feature and not intended to report the network status. - Scenario 1: partial data inside a
-
#13274
7b10078Thanks @jerelmiller! - AddsScalar.fromGraphQLScalarTypehelper to create aScalarinstance from an existing graphql.jsGraphQLScalarType.import { GraphQLScalarType } from "graphql"; import { Scalar } from "@apollo/client"; const dateTimeScalarType = new GraphQLScalarType<Date, string>({ // ... }); const dateTimeScalar = Scalar.fromGraphQLScalarType(dateTimeScalarType, { is: (value) => value instanceof Date, });
-
#13421
d6197a4Thanks @jerelmiller! - The minimum supported TypeScript version is now 5.9.x. -
#13270
d080f11Thanks @jerelmiller! - Adds the plumbing and types implementation for declaring custom scalars and configuring custom scalars inInMemoryCache.You can declare custom scalar types with declaration merging on the
ApolloCache.Scalarsinterface:// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloCache { interface Scalars { Date: { serialized: string; parsed: Date }; } } }
This enables the
scalarsoption inInMemoryCache:import { Scalar } from "@apollo/client"; const cache = new InMemoryCache({ scalars: { Date: new Scalar({ parse: (dateString) => new Date(dateString), serialize: (date) => date.toISOString(), is: (value) => value instanceof Date, }), }, });
-
#13250
bad7035Thanks @jerelmiller! - Add the ability to define the cache type for the client.client.cachecurrently returnsApolloCacheas the cache type regardless of what cache you've provided toApolloClient.Declare the cache type using the
cacheproperty in theTypeOverridesinterface to set the cache implementation used for the client.// apollo.d.ts import type { InMemoryCache } from "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { cache: InMemoryCache; } }
Now anywhere
cacheis accessible, the type is the declared cache type:client.cache; // ^? InMemoryCache client.mutate({ update: (cache) => { // ^? I...
@apollo/client-graphql-codegen@2.2.0
Add support for generating configuration objects for custom scalars in Apollo Client.
// codegen.ts
import type { CustomScalarsPluginConfig } from "@apollo/client-graphql-codegen/custom-scalars";
const config: CodegenConfig = {
// ...
generates: {
"./path/to/custom-scalars.ts": {
plugins: ["@apollo/client-graphql-codegen/custom-scalars"],
config: {
// ...
} satisfies CustomScalarsPluginConfig,
},
},
};This will generate both an inputObjects object and a scalarTypePolicies object in the generated file that can be used to configure custom scalars in InMemoryCache.
import { inputObjects, scalarTypePolicies } from "./path/to/custom-scalars";
const cache = new InMemoryCache({
inputObjects,
});
cache.policies.addTypePolicies(scalarTypePolicies);For more information on using custom scalars, read the Custom Scalars guide.
Minor Changes
- #13310
8ab63fcThanks @jerelmiller! - Introduce a new GraphQL Codegen plugin to generate the input object configuration needed to configure custom scalars for each field.
// codegen.ts
import type { CustomScalarsPluginConfig } from "@apollo/client-graphql-codegen/custom-scalars";
const config: CodegenConfig = {
// ...
generates: {
"./path/to/custom-scalars.ts": {
plugins: ["@apollo/client-graphql-codegen/custom-scalars"],
config: {
// ...
} satisfies CustomScalarsPluginConfig,
},
},
};This will generate an inputObjects object in the generated file that can be used to configure the inputObjects option for InMemoryCache.
import { inputObjects } from "./path/to/custom-scalars";
const cache = new InMemoryCache({
inputObjects,
});-
#13318
01f255bThanks @jerelmiller! - The@apollo/client-graphql-codegen/custom-scalarsGraphQL Codegen plugin now generates the type policy configuration needed to configure custom scalars for each field.// codegen.ts import type { CustomScalarsPluginConfig } from "@apollo/client-graphql-codegen/custom-scalars"; const config: CodegenConfig = { // ... generates: { "./path/to/custom-scalars.ts": { plugins: ["@apollo/client-graphql-codegen/custom-scalars"], config: { // ... } satisfies CustomScalarsPluginConfig, }, }, };
This will generate a
scalarTypePoliciesobject in the generated file that can be used to configure type policies.import { scalarTypePolicies } from "./path/to/custom-scalars"; const cache = new InMemoryCache(); cache.policies.addTypePolicies(scalarTypePolicies);
-
#13426
a9beaffThanks @jerelmiller! - Version bump only torc. -
#13447
24133feThanks @jerelmiller! - The@apollo/client-graphql-codegen/custom-scalarsplugin now emits GraphQL list syntax ininputObjectsandscalarTypePolicies(for example"[DateTime]").
@apollo/client@4.3.0-rc.2
Patch Changes
- #13448
77e1e35Thanks @jerelmiller! - Markskipas deprecated inuseQueryanduseSubscriptionnow that both of these hooks supportskipToken.
@apollo/client@4.3.0-rc.1
Minor Changes
-
#13447
24133feThanks @jerelmiller! - Field policies andinputObjectscan now tell the cache whether a field is a list of scalars or a scalar whose value is an array. Previously all arrays were iterated and only the inner type was provided to the scalarparse/serializefunctions.This required some breaking changes from previous prerelease versions:
- The field policy
scalaroption andinputObjectstype string now use GraphQL list syntax to mark a field as a list of scalars - The abstract
cache.getScalarForFieldis nowcache.getScalarTypeForFieldand is expected to return the string representing the scalar type rather than theScalarinstance
new InMemoryCache({ scalars: { DateTime: new Scalar(/*...*/), }, inputObjects: { EventFilter: { fields: { // Previously only the scalar type was provided datesBefore: "DateTime", // List syntax now required datesAfter: "[DateTime]", dates2d: "[[DateTime]]", }, }, }, typePolicies: { Event: { fields: { // Previously only the scalar type was provided datesBefore: { scalar: "DateTime", }, // List syntax now required datesAfter: { scalar: "[DateTime]", }, dates2d: { scalar: "[[DateTime]]", }, }, }, }, });
Now it's possible to handle scalars that are represented by arrays:
const dateTimeRangeScalar = new Scalar< [string, string], { start: Date; end: Date } >({ parse: ([start, end]) => ({ start: new Date(start), end: new Date(end), }), serialize: (range) => [range.start.toISOString(), range.end.toISOString()], is: (value) => !Array.isArray(value), }); const cache = new InMemoryCache({ scalars: { DateTimeRange: dateTimeRangeScalar, }, typePolicies: { Event: { fields: { range: { scalar: "DateTimeRange", }, }, }, }, }); const query = gql` query { event { range } } `; cache.writeQuery({ query, data: { event: { __typename: "Event", // Server returns DateTimeRange as a JSON array range: ["2024-01-01T00:00:00Z", "2024-06-01T00:00:00Z"], }, }, }); const { data } = useQuery(query); // => { event: { __typename: "Event", range: { start: Date, end: Date } } }
- The field policy
Patch Changes
- #13442
ed033d4Thanks @jerelmiller! - Remove the optional modifier from thevariablesproperty provided to theupdatefunction inclient.mutateanduseMutation.variablesis always a defined object, even when variables are not provided to the mutation.
@apollo/client-graphql-codegen@2.2.0-rc.1
Minor Changes
- #13447
24133feThanks @jerelmiller! - The@apollo/client-graphql-codegen/custom-scalarsplugin now emits GraphQL list syntax ininputObjectsandscalarTypePolicies(for example"[DateTime]").
@apollo/client@4.3.0-rc.0
@apollo/client@4.3.0-alpha.11
Minor Changes
-
#13386
0be8fd8Thanks @atharv-sys32! - SupportskipTokenwithuseSubscriptionto provide a more type-safe way to skip subscription execution with required variables.import { skipToken, useSubscription } from "@apollo/client/react"; // Use `skipToken` in place of `skip: true` for better type safety // for required variables const { data } = useSubscription( SUBSCRIPTION, id ? { variables: { id } } : skipToken );
-
#13424
d2bca2eThanks @jerelmiller! - Remove the customNoInfertype utility in favor of the nativeNoInferintroduced in TypeScript 5.4.
@apollo/client-graphql-codegen@2.2.0-rc.0
@apollo/client@4.3.0-alpha.10
Minor Changes
-
#13421
d6197a4Thanks @jerelmiller! - The minimum supported TypeScript version is now 5.9.x. -
#13337
2df711fThanks @jcostello-atlassian! - Allow overriding thefrominput ofuseFragment,useSuspenseFragment,readFragment,writeFragmentand related fragment APIs via a newFromOptionValuekey on theTypeOverridesinterface.By default,
fromcontinues to acceptStoreObject | Reference | FragmentType<TData> | string. Apps can now supply a stricter policy (for example, requiring__typenameand disallowing nullish identifier values) without affectingStoreObject,cache.identify,cache.modifyor optimistic writes.// apollo.d.ts import "@apollo/client"; import type { HKT, StoreValue } from "@apollo/client/utilities"; type StrictFrom<TData extends { __typename: string }> = | { // the `__typename` has to match the one of the fragment type __typename: TData["__typename"]; // `& {}` forces values to be "defined" so an explicit `undefined` // (as well as `null`) is rejected. [key: string]: Exclude<StoreValue, null | undefined> & {}; } | { __ref: string } | string | null; interface StrictFromHKT extends HKT { arg1: { __typename: string }; // TData return: StrictFrom<this["arg1"]>; } declare module "@apollo/client" { export interface TypeOverrides { FromOptionValue: StrictFromHKT; } }
@apollo/client@4.3.0-alpha.9
Minor Changes
- #13416
f2d5d5aThanks @jerelmiller! - AddGraphQLCodegenIncrementaltype overrides that assemble GraphQL Codegen@deferoperation types whendataStateis"complete".