Skip to content

Releases: apollographql/apollo-client

@apollo/client@4.3.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 19:03
d4f8770

Minor Changes

  • #13447 24133fe Thanks @jerelmiller! - Field policies and inputObjects can 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 scalar parse/serialize functions.

    This required some breaking changes from previous prerelease versions:

    • The field policy scalar option and inputObjects type string now use GraphQL list syntax to mark a field as a list of scalars
    • The abstract cache.getScalarForField is now cache.getScalarTypeForField and is expected to return the string representing the scalar type rather than the Scalar instance
    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 } } }
  • #13324 0abd8de Thanks @jerelmiller! - Fix the accuracy of dataState in complex incremental streaming scenarios, especially when combined with returnPartialData: true.

    Prior to this change, all intermediate chunks used for both @defer and @stream directives returned a dataState of streaming, regardless of whether the actual data shape fit the definition of the streaming data state. The streaming data state represents an incomplete incremental response where the only holes in the data occur at @defer boundaries.

    Let's use the following example of where the previous dataState fell down when combined with returnPartialData.

    query GreetingQuery {
      greeting {
        message
        ... @defer {
          recipient {
            name
            email
          }
        }
      }
    }
    1. Scenario 1: partial data inside a @defer boundary 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 complete because recipient.email is missing. This data is also not streaming because the data requirements in the @defer boundary are partially fulfilled due to the existence of recipient. This could lead to runtime crashes on recipient.email if you use the existence of recipient to detect whether data in the @defer boundary has streamed in or not. This change now accurately reports this as partial to ensure the field is marked as a partial field in recipient.

    1. Scenario 2: partial data written to the cache that fulfills the data requirements of the @defer boundary

    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 as dataState: "complete" since it is safe to access data on all fields.

    This change also means @stream queries by definition fulfill the data requirements of the query after the first chunk arrives since @stream operates on lists and contains no data holes. @stream queries now accurately report dataState as complete or partial, 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 dataState reported as "streaming" are now reported as partial or complete.

    If you use dataState to determine whether an incremental request is still in-flight, please use networkStatus instead to check for NetworkStatus.streaming. dataState is type narrowing feature and not intended to report the network status.

  • #13274 7b10078 Thanks @jerelmiller! - Adds Scalar.fromGraphQLScalarType helper to create a Scalar instance from an existing graphql.js GraphQLScalarType.

    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 d6197a4 Thanks @jerelmiller! - The minimum supported TypeScript version is now 5.9.x.

  • #13270 d080f11 Thanks @jerelmiller! - Adds the plumbing and types implementation for declaring custom scalars and configuring custom scalars in InMemoryCache.

    You can declare custom scalar types with declaration merging on the ApolloCache.Scalars interface:

    // apollo.d.ts
    import "@apollo/client";
    
    declare module "@apollo/client" {
      namespace ApolloCache {
        interface Scalars {
          Date: { serialized: string; parsed: Date };
        }
      }
    }

    This enables the scalars option in InMemoryCache:

    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 bad7035 Thanks @jerelmiller! - Add the ability to define the cache type for the client. client.cache currently returns ApolloCache as the cache type regardless of what cache you've provided to ApolloClient.

    Declare the cache type using the cache property in the TypeOverrides interface 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 cache is accessible, the type is the declared cache type:

    client.cache;
    //     ^? InMemoryCache
    
    client.mutate({
      update: (cache) => {
        //     ^? I...
Read more

@apollo/client-graphql-codegen@2.2.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 19:03
d4f8770

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 8ab63fc Thanks @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 01f255b Thanks @jerelmiller! - The @apollo/client-graphql-codegen/custom-scalars GraphQL 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 scalarTypePolicies object 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 a9beaff Thanks @jerelmiller! - Version bump only to rc.

  • #13447 24133fe Thanks @jerelmiller! - The @apollo/client-graphql-codegen/custom-scalars plugin now emits GraphQL list syntax in inputObjects and scalarTypePolicies (for example "[DateTime]").

@apollo/client@4.3.0-rc.2

Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Sep 17:49
f342046

Patch Changes

  • #13448 77e1e35 Thanks @jerelmiller! - Mark skip as deprecated in useQuery and useSubscription now that both of these hooks support skipToken.

@apollo/client@4.3.0-rc.1

Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Sep 03:00
c573e42

Minor Changes

  • #13447 24133fe Thanks @jerelmiller! - Field policies and inputObjects can 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 scalar parse/serialize functions.

    This required some breaking changes from previous prerelease versions:

    • The field policy scalar option and inputObjects type string now use GraphQL list syntax to mark a field as a list of scalars
    • The abstract cache.getScalarForField is now cache.getScalarTypeForField and is expected to return the string representing the scalar type rather than the Scalar instance
    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 } } }

Patch Changes

  • #13442 ed033d4 Thanks @jerelmiller! - Remove the optional modifier from the variables property provided to the update function in client.mutate and useMutation. variables is always a defined object, even when variables are not provided to the mutation.

@apollo/client-graphql-codegen@2.2.0-rc.1

Choose a tag to compare

@github-actions github-actions released this 03 Sep 03:00
c573e42

Minor Changes

  • #13447 24133fe Thanks @jerelmiller! - The @apollo/client-graphql-codegen/custom-scalars plugin now emits GraphQL list syntax in inputObjects and scalarTypePolicies (for example "[DateTime]").

@apollo/client@4.3.0-rc.0

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Aug 17:41
bca27aa

Minor Changes

@apollo/client@4.3.0-alpha.11

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Aug 16:28
f9c564c

Minor Changes

  • #13386 0be8fd8 Thanks @atharv-sys32! - Support skipToken with useSubscription to 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 d2bca2e Thanks @jerelmiller! - Remove the custom NoInfer type utility in favor of the native NoInfer introduced in TypeScript 5.4.

@apollo/client-graphql-codegen@2.2.0-rc.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 17:41
bca27aa

Minor Changes

@apollo/client@4.3.0-alpha.10

Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Aug 03:40
834e7bf

Minor Changes

  • #13421 d6197a4 Thanks @jerelmiller! - The minimum supported TypeScript version is now 5.9.x.

  • #13337 2df711f Thanks @jcostello-atlassian! - Allow overriding the from input of useFragment, useSuspenseFragment, readFragment, writeFragment and related fragment APIs via a new FromOptionValue key on the TypeOverrides interface.

    By default, from continues to accept StoreObject | Reference | FragmentType<TData> | string. Apps can now supply a stricter policy (for example, requiring __typename and disallowing nullish identifier values) without affecting StoreObject, cache.identify, cache.modify or 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

Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Aug 18:57
2498c6d

Minor Changes

  • #13416 f2d5d5a Thanks @jerelmiller! - Add GraphQLCodegenIncremental type overrides that assemble GraphQL Codegen @defer operation types when dataState is "complete".