From 78d31322180b19a77b256170562c74c8d735e6f3 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Mon, 27 Jan 2025 15:25:17 -0800 Subject: [PATCH] Added documentation for @typePolicy (apollographql/apollo-ios-dev#586) --- docs/source/_sidebar.yaml | 170 ++++++++++++++ docs/source/caching/cache-key-resolution.mdx | 179 ++++----------- .../caching/programmatic-cache-keys.mdx | 208 ++++++++++++++++++ docs/source/client-directives.mdx | 12 +- docs/source/config.json | 141 ------------ 5 files changed, 429 insertions(+), 281 deletions(-) create mode 100644 docs/source/_sidebar.yaml create mode 100644 docs/source/caching/programmatic-cache-keys.mdx delete mode 100644 docs/source/config.json diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml new file mode 100644 index 000000000..1d776eae1 --- /dev/null +++ b/docs/source/_sidebar.yaml @@ -0,0 +1,170 @@ +switcher: + heading: "Apollo iOS" + versions: + - label: v1 + latest: true + href: ./ + - label: v0.x (legacy) + href: ./v0-legacy +items: + - label: Introduction + href: "." + - label: Get Started + href: ./get-started + - label: Project Configuration + children: + - label: Introduction + href: ./project-configuration/intro + - label: 1. Project Modularization + href: ./project-configuration/modularization + - label: 2. Schema Types + href: ./project-configuration/schema-types + - label: 3. Operation Models + href: ./project-configuration/operation-models + - label: SDK components + href: ./project-configuration/sdk-components + - label: Migration Guides + children: + - label: v1.0 + href: ./migrations/1.0 + - label: v1.2 + href: ./migrations/1.2 + - label: v1.3 + href: ./migrations/1.3 + - label: v1.5 + href: ./migrations/1.5 + - label: v1.6 + href: ./migrations/1.6 + - label: v1.7 + href: ./migrations/1.7 + - label: Tutorial + children: + - label: Code Generation + href: ./tutorial/codegen-getting-started + - label: Build a project with Apollo + children: + - label: Introduction + href: ./tutorial/tutorial-introduction + - label: 1. Configure your project + href: ./tutorial/tutorial-configure-project + - label: 2. Add the GraphQL schema + href: ./tutorial/tutorial-add-graphql-schema + - label: 3. Write your first query + href: ./tutorial/tutorial-write-your-first-query + - label: 4. Running code generation + href: ./tutorial/tutorial-running-code-generation + - label: 5. Execute your first query + href: ./tutorial/tutorial-execute-first-query + - label: 6. Connect your queries to your UI + href: ./tutorial/tutorial-connect-queries-to-ui + - label: 7. Add more info to the list + href: ./tutorial/tutorial-add-more-info-to-list + - label: 8. Paginate results + href: ./tutorial/tutorial-paginate-results + - label: 9. Complete the details view + href: ./tutorial/tutorial-complete-details-view + - label: 10. Write your first mutation + href: ./tutorial/tutorial-first-mutation + - label: 11. Authenticate your operations + href: ./tutorial/tutorial-authenticate-operations + - label: 12. Define additional mutations + href: ./tutorial/tutorial-define-additional-mutations + - label: 13. Write your first subscription + href: ./tutorial/tutorial-subscriptions + - label: API Reference + children: + - label: Overview + href: https://www.apollographql.com/docs/ios/docc/documentation/index + - label: Apollo Client + href: https://www.apollographql.com/docs/ios/docc/documentation/Apollo + - label: ApolloAPI + href: https://www.apollographql.com/docs/ios/docc/documentation/ApolloAPI + - label: ApolloWebSocket + href: https://www.apollographql.com/docs/ios/docc/documentation/ApolloWebSocket + - label: ApolloSQLite + href: https://www.apollographql.com/docs/ios/docc/documentation/ApolloSQLite + - label: ApolloCodegenLib + href: https://www.apollographql.com/docs/ios/docc/documentation/ApolloCodegenLib + - label: ApolloPagination + href: https://www.apollographql.com/docs/ios/docc/documentation/ApolloPagination + - label: Client Directives + href: ./client-directives + - label: Code Generation + children: + - label: Introduction + href: ./code-generation/introduction + - label: The Codegen CLI + href: ./code-generation/codegen-cli + - label: Configuration + href: ./code-generation/codegen-configuration + - label: Downloading a Schema + href: ./code-generation/downloading-schema + - label: Running Code Generation in Swift Code + href: ./code-generation/run-codegen-in-swift-code + - label: Code Generation Troubleshooting + href: ./troubleshooting/codegen-troubleshooting + - label: Fetching + children: + - label: Fetching Data + href: ./fetching/fetching-data + - label: Operations + children: + - label: Queries + href: ./fetching/queries + - label: Mutations + href: ./fetching/mutations + - label: Subscriptions + href: ./fetching/subscriptions + - label: Fragments + href: ./fetching/fragments + - label: Operation Arguments + href: ./fetching/operation-arguments + - label: Error Handling + href: ./fetching/error-handling + - label: Type Conditions + href: ./fetching/type-conditions + - label: Custom Scalars + href: ./custom-scalars + - label: Persisted Queries + href: ./fetching/persisted-queries + - label: "@defer support (experimental)" + href: ./fetching/defer + - label: Caching + children: + - label: Introduction + href: ./caching/introduction + - label: Setup + href: ./caching/cache-setup + - label: Direct Cache Access + href: ./caching/cache-transactions + - label: Custom Cache Keys + href: ./caching/cache-key-resolution + - label: Programmatic Cache Keys + href: ./caching/programmatic-cache-keys + - label: Networking + children: + - label: Creating a Client + href: ./networking/client-creation + - label: Pagination + children: + - label: Introduction + href: ./pagination/introduction + - label: Using Custom Response Models + href: ./pagination/custom-types + - label: Directional Pagination + href: ./pagination/directional-pagers + - label: Multi-query Pagination + href: ./pagination/multi-query + - label: Development & Testing + children: + - label: Test Mocks + href: ./testing/test-mocks + - label: Including Apollo as an XCFramework + href: ./development/using-xcframework + - label: Advanced + children: + - label: File Uploads + href: ./file-uploads + - label: Request Chain Customization + href: ./networking/request-pipeline + diff --git a/docs/source/caching/cache-key-resolution.mdx b/docs/source/caching/cache-key-resolution.mdx index da6294ceb..b3524c1a5 100644 --- a/docs/source/caching/cache-key-resolution.mdx +++ b/docs/source/caching/cache-key-resolution.mdx @@ -8,181 +8,82 @@ The normalized cache computes a **cache key** for each object that is stored in > To learn more, read about how the cache [normalizes objects by cache key](./introduction#normalizing-objects-by-cache-key). -## `CacheKeyInfo` +For most use cases, you can configure cache keys declaratively using the methods described on this page. For advanced use cases not supported by declarative cache keys, you can configure cache keys [programmatically](./programmatic-cache-keys). -The information needed to construct a **cache key** is represented by a [`CacheKeyInfo`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/cachekeyinfo) value. This `struct` consists of two properties you can provide to affect how a cache key is computed: +## Schema extensions -1. `let uniqueKeyGroup: String?` +Declarative cache keys work by adding directives to your backend schema types to indicate how cache keys should be resolved for those types. To do this, you can extend your backend schema by creating a schema extension file with the file extension `.graphqls`. You can add any number of schema extension files to your project. - An optional **group identifier** for a set of objects that should be grouped together in the normalized cache. This is used as the first component of the **cache key**. + - > #### Important: Cache key group uniqueness - > - > All objects with the same `uniqueKeyGroup` must have unique `id`s across all types. +Make sure you include your schema extension files in the `schemaSearchPaths` of your code generation configuration's [`FileInput`](./../code-generation/codegen-configuration#file-input). - To prevent cache key collisions, cache keys will always have a **group identifier** component. When the `uniqueKeyGroup` is `nil` (the default value), to [`__typename`](https://spec.graphql.org/draft/#sec-Type-Name-Introspection) of the response object is used as the **group identifier** by default. + - If multiple distinct types can be grouped together in the cache, the `CacheKeyInfo` for each `Object` should have the same `uniqueKeyGroup`. +## The `@typePolicy` directive - > **Tip:** By grouping objects together, their **keys** in the normalized cache will have the same prefix. This allows you to search for cached objects in the same group by their cache `id`. To learn more, read about [direct cache access](./cache-transactions) +The `@typePolicy` directive lets you specify an object's cache ID using [key fields](#key-fields) of the object returned by your GraphQL server. You can apply `@typePolicy` to both concrete object types and interface types in your schema. -2. `let id: String` +To declare a type policy, extend the type with the `@typePolicy` directive in a `.graphqls` schema extension file. - The unique **cache ID** representing the object. This is used as the second component of the **cache key**. +### Key fields - > #### Important: Cache ID uniqueness - > - > The ID must be deterministic and unique for all objects with the same **group identifier** (`__typename` or `uniqueKeyGroup`). - > - >That is, the key will be the same every time for a response object representing the same entity in the cache and the same key will never be used for reponse objects representing different objects that also have the same **group identifier**. +The `@typePolicy` directive has a single `keyFields` argument, which takes a string indicating the fields used to determine a type's cache key. -The normalized cache constructs cache keys with the format: - -`"${GroupIdentifier}:${CacheID}"` - -Given a `CacheKeyInfo`: -```swift -CacheKeyInfo(id: "123", uniqueKeyGroup: "Animal") -``` -Apollo iOS would construct a cache key of `"Animal:123"`. - -## The `SchemaConfiguration` file - -The `SchemaConfiguration` file is your entry point to configuring **cache keys** for the types in your schema. - -When Apollo iOS [generates code for your project](../code-generation/introduction), it will generate a set of metadata types representing the GraphQL schema for your application. One of these files is named `SchemaConfiguration.swift`. - - The code generation engine creates this file if it doesn't exist yet, but never overwrites an existing `SchemaConfiguration.swift` file. This means you can edit your schema configuration without those changes being overwritten on subsequent code generation runs. - -> *Tip:* You can configure the location of the generated schema types with the [`output.schemaTypes` option in your code generation configuration](./../code-generation/codegen-configuration). - -## Specifying cache IDs - -The `SchemaConfiguration` contains a [`cacheKeyInfo(for type:object:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/schemaconfiguration/cachekeyinfo(for:object:)) function. This function provides you a JSON response `object` and the concrete `type` of the object represented by the response. - -The `object` parameter provides a JSON response from either a network request or a cache hit. - -The `type` parameter provides a strongly typed [`Object`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/object) type. This is a generated metadata type representing a concrete `type` in your GraphQL schema. - -To configure how cache keys are computed from a response object, you can create and return [`CacheKeyInfo`](#cachekeyinfo) values from your implementation of [`cacheKeyInfo(for:object:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/schemaconfiguration/cachekeyinfo(for:object:)). - -> **Note:** When specifying **cache IDs**, make sure that you are always fetching the fields used to construct those IDs in your operations. Any response objects that don't contain the **cache ID** fields will not be able to be [merged via cache normalization](./introduction#normalizing-responses). - -### Using a default cache ID field - -If your schema provides a common unique identifier across many of your objects types, you may want to use that field as the **cache ID** by default. - -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - guard let id = object["id"] as? String else { - return nil - } - - return CacheKeyInfo(id: id) - } -} +```graphql title="typePolicies.graphqls" showLineNumbers=false +extend type Book @typePolicy(keyFields: "id") ``` -If the JSON response `object` has no `id` field, the function returns `nil` and the cache will normalize the object using the [default response path normalization](./introduction#normalizing-responses). - -#### JSON value convenience initializer +If you add the above schema extension file , Apollo iOS resolves the cache key for all objects with a `__typename` of `"Book"` by using the value of their `id` field. The concrete type of the object is always prepended to the cache key. This means that a `Book` object with an `id` of `456` will resolve to have a cache key of `Book:456`. -Alternatively, you can use the [`init(jsonValue:uniqueKeyGroup:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/cachekeyinfo/init(jsonvalue:uniquekeygroup:)) convenience initializer. This initializer attempts to use the value of a key in the JSON response, throwing an error if the key does not exist. + -If you want to return `nil` when the value does not exist, you can use `try?`. +All of a type's `@typePolicy` key fields must return a scalar type. To use non-scalar fields in cache keys, use [programmatic cache key configuration](./programmatic-cache-keys). -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - return try? CacheKeyInfo(jsonValue: object["id"]) - } -} -``` + -### Specifying cache IDs by `Object` type +### Multiple key fields -If you would like to specify **cache IDs** differently for different types of objects, you can use the `type` parameter. +You can specify _multiple_ key fields for an object if they are all required to uniquely identify a particular cache entry. Separate multiple key fields with a single space when declaring them: -For an object of the type `Dog` with a unique key represented by an `id` field, you may implement cache key resolution as: - -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - switch type { - case Objects.Dog: - return try? CacheKeyInfo(jsonValue: object["id"]) - - default: - return nil - } - } -} +```graphql title="typePolicies.graphqls" +extend type Author @typePolicy(keyFields: "firstName lastName") ``` -### Specifying cache IDs by abstract types - -If object types sharing the same `interface` or `union` in your schema have the same cache key resolution strategy, you can resolve the key based on those abstract types. +With this extension, the resolved cache key includes all declared key fields and separates them by the `+` character. In this case, an `Author` object with a `firstName` of `"Ray"` and a last name of `"Bradbury"` would resolve to have a cache key of `Author:Ray+Bradbury`. -The generated schema metadata includes `Interfaces` and `Unions` types that contain a list of all the abstract types used in your GraphQL schema. +### Type policies on interface types -For example, for a schema with `Dog` and `Cat` types that implement `interface Pet`, you may implement cache key resolution as: +You can also use `@typePolicy` on interface types. Doing so specifies a default set of key fields for all types that implement that interface. -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - if type.implements(Interfaces.Pet) { - return try? CacheKeyInfo(jsonValue: object["id"]) - } - - return nil - } -} +```graphql title="extra.graphqls" +extend interface Node @typePolicy(keyFields: "id") ``` -To instead configure cache key resolution based on a `union` type, use the union's [`possibleTypes`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/union/possibletypes) property. + -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - if Unions.ClassroomPets.possibleTypes.contains(type) { - return try? CacheKeyInfo(jsonValue: object["id"]) - } +Overriding type policies is not currently supported. If an object type has a `@typePolicy`, it must match any type policies of any interfaces the object type implements. - return nil - } -} -``` + -### Grouping cached objects with `uniqueKeyGroup` +## Caveats and limitations -If your **cache IDs** values are guaranteed to be unique across a number of different types, you may want to group them together in the cache with a common [`uniqueKeyGroup`](#cachekeyinfo). +Declarative cache key resolution has a few limitations you should be aware of while implementing your `@typePolicy` directives: -> See [`uniqueKeyGroup`](#cachekeyinfo) for more information on grouping cached objects. +### Conflicting Type Policies -For example, if all objects that implement `interface Animal` will have unique **cache IDs**, whether they are `Dog`, `Cat`, or any other type that implements `Animal`, they can share a `uniqueKeyGroup`. +If any type implements two interfaces with conflicting type policies, Apollo iOS throws a validation error when executing code generation. -```swift title="SchemaConfiguration.swift" -public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { - static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { - if type.implements(Interfaces.Pet) { - return try? CacheKeyInfo( - jsonValue: object["id"], - uniqueKeyGroupId: Interfaces.Pet.name - ) - } +### Cache Inconsistencies - return nil - } -} -``` +When an object is returned for a field of an interface type where the interface has a `@typePolicy`, Apollo iOS will first attempt to find a `@typePolicy` for the concrete type by using the `__typename` field of the returned object. If the type does not have an `@typePolicy` of its own, the interface's `@typePolicy` will be applied. -## Caveats & limitations +If the same object is returned for multiple fields of different interface types with conflicting type policies, it is possible that the same object is resolved with two different type policies, leading to cache inconsistencies. -Cache key resolution has a few notable quirks and limitations you should be aware of while implementing your cache key resolution function: +In most circumstances, validation that is run during code generation prevents this from occurring. However, this may still occur if changes are made to the schema after code generation runs or after your application is published. -1. While the cache key for an object can use a field from another nested object, if the fields on the referenced object are changed in another operation, the cache key for the dependent object will not be updated. For nested objects that are not normalized with their own cache key, this will never occur, but if the nested object is an entity with its own cache key, it can be mutated independently. In that case, any other objects whose cache keys are dependent on the mutated entity will not be updated automatically. You must take care to update those entities manually with a cache mutation. +This is possible if: -2. The `object` passed to this function represents data for an object in an specific operation model, not a type in your schema. This means that [aliased fields](https://spec.graphql.org/draft/#sec-Field-Alias) will be keyed on their alias name, not the name of the field on the schema type. + A) A type is added to your schema after code generation is executed and that type implements two different interfaces with different type policies -3. The `object` parameter of this function is an ``ObjectData`` struct that wraps the underlying object data. Because cache key resolution is performed both on raw JSON (from a network response) and `SelectionSet` model data (when writing to the cache directly), the underlying data will have different formats. The ``ObjectData`` wrapper is used to normalize this data to a consistent format in this context. \ No newline at end of file + B) Implementation of an interface is added to an existing type which already implements an interface with a different type policy diff --git a/docs/source/caching/programmatic-cache-keys.mdx b/docs/source/caching/programmatic-cache-keys.mdx new file mode 100644 index 000000000..365984ba2 --- /dev/null +++ b/docs/source/caching/programmatic-cache-keys.mdx @@ -0,0 +1,208 @@ +--- +title: Programmatic Cache Keys +--- + +When [declarative cache IDs](./cache-key-resolution) don't fit your use case, you can programmatically generate cache IDs for object types in your normalized cache. + +To configure cache keys programmatically, you can alter the [`SchemaConfiguration.cacheKeyInfo(for type:object:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/schemaconfiguration/cachekeyinfo(for:object:)) method to return a `CacheKeyInfo` object. + + + +Programmatic cache key usage will take precedence over declarative cache keys. If you return a `CacheKeyInfo` for an object, it will be used to compute that object's cache key and any relevant `@typePolicy` will be ignored. Returning `nil` from this function will still fall back to using declarative cache keys. + + + +## `CacheKeyInfo` + +The information needed to construct a **cache key** is represented by a [`CacheKeyInfo`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/cachekeyinfo) value. This `struct` consists of two properties you can provide to determine how a cache key is computed: + +1. `let uniqueKeyGroup: String?` + + An optional **group identifier** for a set of objects that should be grouped together in the normalized cache. This is used as the first component of the **cache key**. + + + + To ensure cache key group uniqueness, all objects with the same `uniqueKeyGroup` must have unique `id`s across all types. + + + + To prevent cache key collisions, cache keys will always have a **group identifier** component. When the `uniqueKeyGroup` is `nil` (the default value), the [`__typename`](https://spec.graphql.org/draft/#sec-Type-Name-Introspection) of the response object is used as the **group identifier**. + + If multiple distinct types can be grouped together in the cache, the `CacheKeyInfo` for each `Object` should have the same `uniqueKeyGroup`. + + + + By grouping objects together, their **keys** in the normalized cache will have the same prefix. This allows you to search for cached objects in the same group by their cache `id`. To learn more, read about [direct cache access](./cache-transactions) + + + +2. `let id: String` + + The unique **cache ID** representing the object. This is used as the second component of the **cache key**. + + + + To ensure cache ID uniqueness, the `id` must be deterministic and unique for all objects with the same **group identifier** (`__typename` or `uniqueKeyGroup`). + + That is, a response object for the same entity will always have the same key in the cache. Different objects, even with the same group identifier, will always have distinct keys. + + + +The normalized cache constructs cache keys with the format: + +`"${GroupIdentifier}:${CacheID}"` + +Given the following a `CacheKeyInfo`: +```swift showLineNumbers=false +CacheKeyInfo(id: "123", uniqueKeyGroup: "Animal") +``` +Apollo iOS would construct a cache key of `"Animal:123"`. + +## The `SchemaConfiguration` file + +The `SchemaConfiguration` file is your entry point to configuring **cache keys** for the types in your schema. + +When Apollo iOS [generates code for your project](../code-generation/introduction), it will generate a set of metadata types representing the GraphQL schema for your application. One of these files is named `SchemaConfiguration.swift`. + + The code generation engine creates this file if it doesn't exist yet, but never overwrites an existing `SchemaConfiguration.swift` file. This means you can edit your schema configuration without those changes being overwritten on subsequent code generation runs. + + + +You can configure the location of the generated schema types with the [`output.schemaTypes` option in your code generation configuration](./../code-generation/codegen-configuration). + + + +## Specifying cache IDs + +The `SchemaConfiguration` contains a [`cacheKeyInfo(for type:object:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/schemaconfiguration/cachekeyinfo(for:object:)) function. This function provides you a JSON response `object` and the concrete `type` of the object represented by the response. + +The `object` parameter provides a JSON response from either a network request or a cache hit. + +The `type` parameter provides a strongly typed [`Object`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/object) type. This is a generated metadata type representing a concrete `type` in your GraphQL schema. + +To configure how cache keys are computed from a response object, you can create and return [`CacheKeyInfo`](#cachekeyinfo) values from your implementation of [`cacheKeyInfo(for:object:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/schemaconfiguration/cachekeyinfo(for:object:)). + + + +When specifying **cache IDs**, make sure that you are always fetching the fields used to construct those IDs in your operations. Any response objects that don't contain the **cache ID** fields will not be able to be [merged via cache normalization](./introduction#normalizing-responses). + + + +### Using a default cache ID field + +If your schema provides a common unique identifier across many of your objects types, you may want to use that field as the **cache ID** by default. + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + guard let id = object["id"] as? String else { + return nil + } + + return CacheKeyInfo(id: id) + } +} +``` + +If the JSON response `object` has no `id` field, the function returns `nil` and the cache will normalize the object using the [default response path normalization](./introduction#normalizing-responses). + +#### JSON value convenience initializer + +Alternatively, you can use the [`init(jsonValue:uniqueKeyGroup:)`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/cachekeyinfo/init(jsonvalue:uniquekeygroup:)) convenience initializer. This initializer attempts to use the value of a key in the JSON response, throwing an error if the key does not exist. + +If you want to return `nil` when the value does not exist, you can use `try?`. + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + return try? CacheKeyInfo(jsonValue: object["id"]) + } +} +``` + +### Specifying cache IDs by `Object` type + +If you would like to specify **cache IDs** differently for different types of objects, you can use the `type` parameter. + +For an object of the type `Dog` with a unique key represented by an `id` field, you may implement cache key resolution as: + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + switch type { + case Objects.Dog: + return try? CacheKeyInfo(jsonValue: object["id"]) + + default: + return nil + } + } +} +``` + +### Specifying cache IDs by abstract types + +If object types sharing the same `interface` or `union` in your schema have the same cache key resolution strategy, you can resolve the key based on those abstract types. + +The generated schema metadata includes `Interfaces` and `Unions` types that contain a list of all the abstract types used in your GraphQL schema. + +For example, for a schema with `Dog` and `Cat` types that implement `interface Pet`, you may implement cache key resolution as: + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + if type.implements(Interfaces.Pet) { + return try? CacheKeyInfo(jsonValue: object["id"]) + } + + return nil + } +} +``` + +To instead configure cache key resolution based on a `union` type, use the union's [`possibleTypes`](https://www.apollographql.com/docs/ios/docc/documentation/apolloapi/union/possibletypes) property. + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + if Unions.ClassroomPets.possibleTypes.contains(type) { + return try? CacheKeyInfo(jsonValue: object["id"]) + } + + return nil + } +} +``` + +### Grouping cached objects with `uniqueKeyGroup` + +If your **cache IDs** values are guaranteed to be unique across a number of different types, you may want to group them together in the cache with a common [`uniqueKeyGroup`](#cachekeyinfo). + +> See [`uniqueKeyGroup`](#cachekeyinfo) for more information on grouping cached objects. + +For example, if all objects that implement `interface Animal` will have unique **cache IDs**, whether they are `Dog`, `Cat`, or any other type that implements `Animal`, they can share a `uniqueKeyGroup`. + +```swift title="SchemaConfiguration.swift" +public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? { + if type.implements(Interfaces.Pet) { + return try? CacheKeyInfo( + jsonValue: object["id"], + uniqueKeyGroupId: Interfaces.Pet.name + ) + } + + return nil + } +} +``` + +## Caveats and limitations + +Programmatic cache key resolution has a few notable quirks and limitations you should be aware of while implementing your cache key resolution function: + +1. While the cache key for an object can use a field from another nested object, if the fields on the referenced object are changed in another operation, the cache key for the dependent object will not be updated. For nested objects that are not normalized with their own cache key, this will never occur, but if the nested object is an entity with its own cache key, it can be mutated independently. In that case, any other objects whose cache keys are dependent on the mutated entity will not be updated automatically. You must take care to update those entities manually with a cache mutation. + +2. The `object` passed to this function represents data for an object in a specific operation model, not a type in your schema. This means that [aliased fields](https://spec.graphql.org/draft/#sec-Field-Alias) will be keyed on their alias name, not the name of the field on the schema type. + +3. The `object` parameter of this function is an ``ObjectData`` struct that wraps the underlying object data. Because cache key resolution is performed both on raw JSON (from a network response) and `SelectionSet` model data (when writing to the cache directly), the underlying data will have different formats. The ``ObjectData`` wrapper is used to normalize this data to a consistent format in this context. \ No newline at end of file diff --git a/docs/source/client-directives.mdx b/docs/source/client-directives.mdx index a94a7cfb3..4b4e2f121 100644 --- a/docs/source/client-directives.mdx +++ b/docs/source/client-directives.mdx @@ -25,4 +25,14 @@ This directive can be used to enable sharing of fragment models across generated Defines a definition as a local cache mutation. This changes makes the generated operation model mutable. -See the documentation on [defining local cache mutations](./caching/cache-transactions#defining-local-cache-mutations) for more information. \ No newline at end of file +See the documentation on [defining local cache mutations](./caching/cache-transactions#defining-local-cache-mutations) for more information. + +--- + +### `@typePolicy(keyFields: String!)` + +`directive @typePolicy(module: String!) repeatable on OBJECT | INTERFACE` + +Adds a type policy to a type that can be used to resolve cache keys for objects of that type. + +This directive can be used to configure how the normalized cache operates. See the documentation on [custom cache keys](./caching/cache-key-resolution) for more information. \ No newline at end of file diff --git a/docs/source/config.json b/docs/source/config.json deleted file mode 100644 index b40d742f8..000000000 --- a/docs/source/config.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "title": "Client (iOS)", - "version": "v1", - "algoliaFilters": [ - "docset:apollo-ios" - ], - "sidebar": { - "Introduction": "/", - "Get Started": "/get-started", - "Project Configuration": [ - { - "Introduction": "/project-configuration/intro", - "1. Project Modularization": "/project-configuration/modularization", - "2. Schema Types": "/project-configuration/schema-types", - "3. Operation Models": "/project-configuration/operation-models", - "SDK components": "/project-configuration/sdk-components" - }, - true - ], - "Migration Guides": [ - { - "v1.0": "/migrations/1.0", - "v1.2": "/migrations/1.2", - "v1.3": "/migrations/1.3", - "v1.5": "/migrations/1.5", - "v1.6": "/migrations/1.6", - "v1.7": "/migrations/1.7" - }, - true - ], - "Tutorial": [ - { - "Code Generation": "/tutorial/codegen-getting-started", - "Build a project with Apollo": [ - { - "Introduction": "/tutorial/tutorial-introduction", - "1. Configure your project": "/tutorial/tutorial-configure-project", - "2. Add the GraphQL schema": "/tutorial/tutorial-add-graphql-schema", - "3. Write your first query": "/tutorial/tutorial-write-your-first-query", - "4. Running code generation": "/tutorial/tutorial-running-code-generation", - "5. Execute your first query": "/tutorial/tutorial-execute-first-query", - "6. Connect your queries to your UI": "/tutorial/tutorial-connect-queries-to-ui", - "7. Add more info to the list": "/tutorial/tutorial-add-more-info-to-list", - "8. Paginate results": "/tutorial/tutorial-paginate-results", - "9. Complete the details view": "/tutorial/tutorial-complete-details-view", - "10. Write your first mutation": "/tutorial/tutorial-first-mutation", - "11. Authenticate your operations": "/tutorial/tutorial-authenticate-operations", - "12. Define additional mutations": "/tutorial/tutorial-define-additional-mutations", - "13. Write your first subscription": "/tutorial/tutorial-subscriptions" - }, - true - ] - }, - true - ], - "API Reference": [ - { - "Overview": "https://www.apollographql.com/docs/ios/docc/documentation/index", - "Apollo Client": "https://www.apollographql.com/docs/ios/docc/documentation/Apollo", - "ApolloAPI": "https://www.apollographql.com/docs/ios/docc/documentation/ApolloAPI", - "ApolloWebSocket": "https://www.apollographql.com/docs/ios/docc/documentation/ApolloWebSocket", - "ApolloSQLite": "https://www.apollographql.com/docs/ios/docc/documentation/ApolloSQLite", - "ApolloCodegenLib": "https://www.apollographql.com/docs/ios/docc/documentation/ApolloCodegenLib", - "ApolloPagination": "https://www.apollographql.com/docs/ios/docc/documentation/ApolloPagination", - "Client Directives": "/client-directives" - }, - true - ], - "Code Generation": [ - { - "Introduction": "/code-generation/introduction", - "The Codegen CLI": "/code-generation/codegen-cli", - "Configuration": "/code-generation/codegen-configuration", - "Downloading a Schema": "/code-generation/downloading-schema", - "Running Code Generation in Swift Code": "/code-generation/run-codegen-in-swift-code", - "Code Generation Troubleshooting": "/troubleshooting/codegen-troubleshooting" - }, - true - ], - "Fetching": [ - { - "Fetching Data": "/fetching/fetching-data", - "Operations": [ - { - "Queries": "/fetching/queries", - "Mutations": "/fetching/mutations", - "Subscriptions": "/fetching/subscriptions", - "Fragments": "/fetching/fragments" - }, - true - ], - "Operation Arguments": "/fetching/operation-arguments", - "Error Handling": "/fetching/error-handling", - "Type Conditions": "/fetching/type-conditions", - "Custom Scalars": "/custom-scalars", - "Persisted Queries": "/fetching/persisted-queries", - "@defer support (experimental)": "/fetching/defer" - }, - true - ], - "Caching": [ - { - "Introduction": "/caching/introduction", - "Setup": "/caching/cache-setup", - "Direct Cache Access": "/caching/cache-transactions", - "Custom Cache Keys": "/caching/cache-key-resolution" - }, - true - ], - "Networking": [ - { - "Creating a Client": "/networking/client-creation" - }, - true - ], - "Pagination": [ - { - "Introduction": "/pagination/introduction", - "Using Custom Response Models": "/pagination/custom-types", - "Directional Pagination": "/pagination/directional-pagers", - "Multi-query Pagination": "/pagination/multi-query" - }, - true - ], - "Development & Testing": [ - { - "Selection Set Initializers": "/development/selection-set-initializers", - "Test Mocks": "/testing/test-mocks", - "Including Apollo as an XCFramework": "/development/using-xcframework" - }, - true - ], - "Advanced": [ - { - "File Uploads": "/file-uploads", - "Request Chain Customization": "/networking/request-pipeline" - }, - true - ] - } -}