Skip to content

Commit 8be1b76

Browse files
authored
Merge branch 'release-4.0' into pr/codemod-legacyEntryPoints
2 parents 8254e01 + 777349b commit 8be1b76

9 files changed

Lines changed: 1043 additions & 425 deletions

File tree

.api-reports/api-report-masking.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ type ExtractByMatchingTypeNames<Union extends {
6262

6363
// Warning: (ae-forgotten-export) The symbol "PreserveTypes" needs to be exported by the entry point index.d.ts
6464
//
65-
// @public (undocumented)
66-
export type FragmentType<TData> = ApplyHKTImplementationWithDefault<TypeOverrides, "FragmentType", PreserveTypes.TypeOverrides, TData>;
65+
// @public
66+
export type FragmentType<TFragmentData> = ApplyHKTImplementationWithDefault<TypeOverrides, "FragmentType", PreserveTypes.TypeOverrides, TFragmentData>;
6767

6868
// @public (undocumented)
6969
export namespace GraphQLCodegenDataMasking {

.api-reports/api-report.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,8 +1146,8 @@ interface FragmentRegistryAPI {
11461146

11471147
// Warning: (ae-forgotten-export) The symbol "PreserveTypes" needs to be exported by the entry point index.d.ts
11481148
//
1149-
// @public (undocumented)
1150-
export type FragmentType<TData> = ApplyHKTImplementationWithDefault<TypeOverrides, "FragmentType", PreserveTypes.TypeOverrides, TData>;
1149+
// @public
1150+
export type FragmentType<TFragmentData> = ApplyHKTImplementationWithDefault<TypeOverrides, "FragmentType", PreserveTypes.TypeOverrides, TFragmentData>;
11511151

11521152
// @public @deprecated (undocumented)
11531153
export const from: typeof ApolloLink.from;

docs/source/api/link/introduction.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ const link = new OperationCountLink();
517517

518518
As an operation moves along the link chain, it maintains a `context` that each link can read and modify. This enables both links and request-based APIs (such as [`useQuery`](../react/useQuery)) to pass metadata along the chain that other links use in their execution logic. Context is not included in the terminating link's request to the GraphQL server or other destination.
519519

520+
<Note>
521+
522+
If your application is built using TypeScript, we recommend reading the [Defining context types](../../development-testing/static-typing#defining-context-types) guide to learn how to provide types for the context object.
523+
524+
</Note>
525+
520526
### Reading context
521527

522528
You get the current context object by calling `operation.getContext()` in a [request handler](#the-request-handler).

docs/source/data/fragments.mdx

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,31 @@ To migrate from CodeGen's fragment masking feature to Apollo Client's data maski
12861286
12871287
3. [Enable data masking](#enabling-data-masking) in Apollo Client.
12881288
1289+
#### Setting data masking types
1290+
1291+
By default, Apollo Client makes no modification to the operation types provided to its APIs, regardless of whether the type definitions are masked or unmasked. This provides a simpler upgrade path when you're ready to [incrementally adopt](#incremental-adoption-in-an-existing-application) data masking.
1292+
1293+
To use GraphQL Codegen's masking format with your operation types, you need to tell Apollo Client to use the associated GraphQL Codegen masking types. You do this by defining a [type override](../development-testing/static-typing#overriding-type-implementations-of-built-in-types) that uses the GraphQL Codegen masking format.
1294+
1295+
Create a TypeScript file that will be used to modify the `TypeOverrides` interface. Extend `TypeOverrides` with the `GraphQLCodegenDataMasking.TypeOverrides` interface.
1296+
1297+
```ts title="apollo-client.d.ts"
1298+
// This import is necessary to ensure all Apollo Client imports
1299+
// are still available to the rest of the application.
1300+
import "@apollo/client";
1301+
import type { GraphQLCodegenDataMasking } from "@apollo/client/masking";
1302+
1303+
declare module "@apollo/client" {
1304+
interface TypeOverrides extends GraphQLCodegenDataMasking.TypeOverrides {}
1305+
}
1306+
```
1307+
1308+
<Note>
1309+
1310+
This example uses `apollo-client.d.ts` as the file name to make it easily identifiable. You can give this file any name.
1311+
1312+
</Note>
1313+
12891314
#### Using with fragments
12901315
12911316
When using [colocated fragments](#colocating-fragments) with your components, it's best to ensure the object passed to your component is done in a type-safe way. This means:
@@ -1519,3 +1544,137 @@ query GetPosts {
15191544
Repeat this process until all `@unmask` directives have been removed from your codebase.
15201545
15211546
Congratulations 🎉! Your application is now using data masking everywhere 😎.
1547+
1548+
### Defining your own masking types using higher-kinded types
1549+
1550+
<Note>
1551+
1552+
If you are using the [data masking types generated from GraphQL Codegen](#setting-data-masking-types), you can safely skip this section.
1553+
1554+
</Note>
1555+
1556+
Apollo Client provides an [integration](#setting-data-masking-types) with GraphQL Codegen's [fragment masking](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#fragment-masking) output and orients its masking utility types around this format. If you aren't using GraphQL Codegen to generate your types, or your types are generated in a different format, the type implementations built into Apollo Client might be incompatible which results in inaccurate types.
1557+
1558+
You can use a technique called _higher-kinded types_ (HKT) to provide your own type implementations for Apollo Client's masking types. You can think of higher-kinded types as a way to define types and interfaces with generics that can be filled in by Apollo Client internals at a later time. Passing around un-evaluated types is otherwise not possible in TypeScript.
1559+
1560+
#### Example
1561+
1562+
Let's add our own type overrides for the `MaybeMasked` and `Unmasked` utility types.
1563+
1564+
The `MaybeMasked` type is used throughout Apollo Client to return the masked or unmasked type definition for a given type, assuming they have data masking types enabled and are maskable. Otherwise, it returns the unmasked type that is passed in.
1565+
1566+
The `Unmasked` type unwraps a masked type to its full result type and is used throughout Apollo Client where the full data type is needed for a given API (e.g. `client.writeQuery`).
1567+
1568+
For this example, we'll assume the data masking types are enabled and the operation types are generated using two variations:
1569+
1570+
1. Data masked types are generated with a `__masked` virtual property. Its value is the operation type with any masked fields removed from the type.
1571+
1572+
```ts
1573+
type MaskedQuery = {
1574+
// The masked variant of the operation type is provided under the
1575+
// `__masked` virtual property
1576+
__masked?: { user: { __typename: "User"; id: number } };
1577+
1578+
// The full result type includes all other fields in the type
1579+
user: { __typename: "User"; id: number; name: string };
1580+
};
1581+
```
1582+
1583+
2. Unmasked types are generated as their full result type with no `__masked` virtual property applied to them.
1584+
1585+
```ts
1586+
type UnmaskedQuery = {
1587+
user: { __typename: "User"; id: number; name: string };
1588+
};
1589+
```
1590+
1591+
<Note>
1592+
1593+
This is a hypothetical format that doesn't exist in Apollo Client or any known code generation tool. This format is used specifically for this example to illustrate how to provide type overrides to Apollo Client.
1594+
1595+
</Note>
1596+
1597+
First, let's define our custom implementation of the `MaybeMasked` type. The implementation works by checking if the `__masking` virtual property exists on the type. If so, it returns the value on the `__masked` property as the type, otherwise it returns the input type unmodified.
1598+
1599+
```ts title="masked-types.ts"
1600+
type MaybeMasked<TData> =
1601+
TData extends { __masked?: infer TMaskedData } ? TMaskedData : TData;
1602+
```
1603+
1604+
Now let's provide an implementation for the `Unmasked` type that works in contrast to `MaybeMasked` by returning the unwrapped full result type. The implementation works by removing the `__masked` virtual property on the input type. This can be accomplished using the built-in [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) type.
1605+
1606+
```ts {4} title="masked-types.ts"
1607+
type MaybeMasked<TData> =
1608+
TData extends { __masked?: infer TMaskedData } ? TMaskedData : TData;
1609+
1610+
type Unmasked<TData> = Omit<TData, "__masked">;
1611+
```
1612+
1613+
Now that our custom type implementations are in place, we need to define higher-kinded types for each of these custom types. This provides the bridge needed by Apollo Client to use our custom type implementations. This is done by extending the `HKT` interface exported by `@apollo/client/utilities`.
1614+
1615+
Let's provide HKTs for our `MaybeMasked` and `Unmasked` types. We'll put these in the same file as our type implementations.
1616+
1617+
```ts {1,8-11,13-16} title="masked-types.ts"
1618+
import { HKT } from "@apollo/client/utilities";
1619+
1620+
type MaybeMasked<TData> =
1621+
TData extends { __masked?: infer TMaskedData } ? TMaskedData : TData;
1622+
1623+
type Unmasked<TData> = Omit<TData, "__masked">;
1624+
1625+
export interface MaybeMaskedHKT extends HKT {
1626+
arg1: unknown; // TData
1627+
return: MaybeMasked<this["arg1"]>;
1628+
}
1629+
1630+
export interface UnmaskedHKT extends HKT {
1631+
arg1: unknown; // TData
1632+
return: Unmasked<this["arg1"]>;
1633+
}
1634+
```
1635+
1636+
With our HKT types in place, we now need to tell Apollo Client about them.
1637+
1638+
Apollo Client uses [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) to provide a hook point for overridable types. The `TypeOverrides` interface exported by the `@apollo/client` package is used for this purpose. Each property in the `TypeOverrides` interface corresponds to an overridable type in Apollo Client.
1639+
1640+
Let's add type overrides for our custom type implementations. Create a TypeScript file and define a `TypeOverrides` interface for the `@apollo/client` module.
1641+
1642+
```ts title=apollo-client.d.ts
1643+
// This import is necessary to ensure all Apollo Client imports
1644+
// are still available to the rest of the application.
1645+
import "@apollo/client";
1646+
1647+
declare module "@apollo/client" {
1648+
export interface TypeOverrides {
1649+
// Type overrides will go here
1650+
}
1651+
}
1652+
```
1653+
1654+
Now we'll import our HKT types and add them as keys in the `TypeOverrides` interface.
1655+
1656+
```ts {4,8-9} title=apollo-client.d.ts
1657+
// This import is necessary to ensure all Apollo Client imports
1658+
// are still available to the rest of the application.
1659+
import "@apollo/client";
1660+
import { MaybeMaskedHKT, UnmaskedHKT } from "./masked-types.ts";
1661+
1662+
declare module "@apollo/client" {
1663+
export interface TypeOverrides {
1664+
MaybeMasked: MaybeMaskedHKT;
1665+
Unmasked: UnmaskedHKT;
1666+
}
1667+
}
1668+
```
1669+
1670+
And that's it! Now when Apollo Client uses the `MaybeMasked` or `Unmasked` types in its APIs, our custom implementation will be used instead 🎉.
1671+
1672+
#### Available type overrides
1673+
1674+
The following masking utility types are available to override:
1675+
1676+
- `FragmentType<TFragmentData>` - Type used with [fragments](#using-with-fragments) to ensure parent objects contain the fragment spread from the type.
1677+
- `MaybeMasked<TData>` - Conditionally returns `TData` as either its masked type or unmasked type
1678+
- `Unmasked<TData>` - Unwraps `TData` into the full result type
1679+
1680+
For more information about other overridable types in Apollo Client, see the [TypeScript guide](../development-testing/static-typing).

docs/source/data/mutations.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ This article demonstrates how to send updates to your GraphQL server with the `u
1313
1414
## Prerequisites
1515

16+
<Note>
17+
18+
If your application is built using TypeScript, we recommend reading the [TypeScript guide](../development-testing/static-typing) to learn how to use TypeScript with Apollo Client.
19+
20+
</Note>
21+
1622
This article assumes you're familiar with building basic GraphQL mutations. If you need a refresher, we recommend that you
1723
[read this guide](http://graphql.org/learn/queries/#mutations).
1824

docs/source/data/queries.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ This article shows how to fetch GraphQL data in React with the `useQuery` hook a
1616

1717
## Prerequisites
1818

19+
<Note>
20+
21+
If your application is built using TypeScript, we recommend reading the [TypeScript guide](../development-testing/static-typing) to learn how to use TypeScript with Apollo Client.
22+
23+
</Note>
24+
1925
This article assumes you're familiar with building basic GraphQL queries. If you need a refresher, we recommend [this guide](http://graphql.org/learn/queries/). You can also build example queries against Apollo's [full-stack tutorial server](https://apollo-fullstack-tutorial.herokuapp.com/graphql).
2026

2127
This article also assumes that you've already set up Apollo Client and have wrapped your React app in an `ApolloProvider` component. For more information, see the [getting started guide](../get-started/).
@@ -70,6 +76,12 @@ As our query executes and the values of `loading`, `error`, and `data` change, t
7076

7177
When the user selects a dog breed from the populated dropdown, the selection is sent to the parent component via the provided `onDogSelected` function.
7278

79+
<Note>
80+
81+
If you are using TypeScript, `data` is typed as `unknown` when the query type is unknown. Accessing fields on `data` results in a TypeScript error. See the [TypeScript with Apollo Client](../development-testing/static-typing) guide to learn how to add types for your queries.
82+
83+
</Note>
84+
7385
In the next step, we'll associate the dropdown with a more sophisticated query that uses GraphQL variables.
7486

7587
## Caching query results

0 commit comments

Comments
 (0)