UNPKG

@urql/exchange-graphcache

Version:

A normalized and configurable cache exchange for urql

1,000 lines (995 loc) 40.9 kB
import { FormattedNode, ErrorLike, DocumentInput, TypedDocumentNode, AnyVariables, RequestExtensions } from '@urql/core'; import { FragmentDefinitionNode, DocumentNode } from '@0no-co/graphql.web'; /*@ts-ignore*/ import * as GraphQL from 'graphql'; type OrNever<T> = void extends T ? never : T; type IntrospectionQuery = { readonly __schema: { queryType: { name: string; kind?: any; }; mutationType?: { name: string; kind?: any; } | null; subscriptionType?: { name: string; kind?: any; } | null; types?: readonly IntrospectionType[]; }; } | OrNever<GraphQL.IntrospectionQuery>; type IntrospectionTypeRef = { readonly kind: 'SCALAR' | 'OBJECT' | 'INTERFACE' | 'ENUM' | 'UNION' | 'INPUT_OBJECT'; readonly name?: string; readonly ofType?: IntrospectionTypeRef; } | OrNever<GraphQL.IntrospectionTypeRef>; type IntrospectionInputTypeRef = { readonly kind: 'SCALAR' | 'ENUM' | 'INPUT_OBJECT'; readonly name?: string; readonly ofType?: IntrospectionInputTypeRef; } | OrNever<GraphQL.IntrospectionInputTypeRef>; type IntrospectionInputValue = { readonly name: string; readonly description?: string | null; readonly defaultValue?: string | null; readonly type: IntrospectionInputTypeRef; } | OrNever<GraphQL.IntrospectionInputValue>; type IntrospectionType = { readonly kind: string; readonly name: string; readonly fields?: readonly any[]; readonly interfaces?: readonly any[]; readonly possibleTypes?: readonly any[]; } | OrNever<GraphQL.IntrospectionType>; interface SchemaField { name: string; type: IntrospectionTypeRef; args(): Record<string, IntrospectionInputValue | void>; } interface SchemaObject { name: string; kind: 'INTERFACE' | 'OBJECT'; interfaces(): Record<string, unknown>; fields(): Record<string, SchemaField | void>; } interface SchemaUnion { name: string; kind: 'UNION'; types(): Record<string, unknown>; } interface SchemaIntrospector { query: string | null; mutation: string | null; subscription: string | null; types?: Map<string, SchemaObject | SchemaUnion>; isSubType(abstract: string, possible: string): boolean; } interface PartialIntrospectionSchema { queryType: { name: string; kind?: any; }; mutationType?: { name: string; kind?: any; } | null; subscriptionType?: { name: string; kind?: any; } | null; types?: readonly any[]; } type IntrospectionData = IntrospectionQuery | { __schema: PartialIntrospectionSchema; }; /** Nullable GraphQL list types of `T`. * * @remarks * Any GraphQL list of a given type `T` that is nullable is * expected to contain nullable values. Nested lists are * also taken into account in Graphcache. */ type NullArray<T> = Array<null | T | NullArray<T>>; /** Dictionary of GraphQL Fragment definitions by their names. * * @remarks * A map of {@link FragmentDefinitionNode | FragmentDefinitionNodes} by their * fragment names from the original GraphQL document that Graphcache is * executing. */ interface Fragments { [fragmentName: string]: void | FormattedNode<FragmentDefinitionNode>; } /** Non-object JSON values as serialized by a GraphQL API * @see {@link https://spec.graphql.org/October2021/#sel-DAPJDHAAEJHAKmzP} for the * GraphQL spec’s serialization format. */ type Primitive = null | number | boolean | string; /** Any GraphQL scalar object * * @remarks * A GraphQL schema may define custom scalars that are resolved * and serialized as objects. These objects could also be turned * on the client-side into a non-JSON object, e.g. a `Date`. * * @see {@link https://spec.graphql.org/October2021/#sec-Scalars} for the * GraphQL spec’s information on custom scalars. */ interface ScalarObject { constructor?: Function; [key: string]: any; } /** GraphQL scalar value * @see {@link https://spec.graphql.org/October2021/#sec-Scalars} for the GraphQL * spec’s definition of scalars */ type Scalar = Primitive | ScalarObject; /** Fields that Graphcache expects on GraphQL object (“entity”) results. * * @remarks * Any object that comes back from a GraphQL API will have * a `__typename` field from GraphQL Object types. * * The `__typename` field must be present as Graphcache updates * GraphQL queries with type name introspection. * Furthermore, Graphcache always checks for its default key * fields, `id` and `_id` to be present. */ interface SystemFields { /** GraphQL Object type name as returned by Type Name Introspection. * @see {@link https://spec.graphql.org/October2021/#sec-Type-Name-Introspection} for * more information on GraphQL’s Type Name introspection. */ __typename: string; _id?: string | number | null; id?: string | number | null; } /** Scalar values are stored separately from relations between entities. * @internal */ type EntityField = undefined | Scalar | NullArray<Scalar>; /** Values on GraphQL object (“entity”) results. * * @remarks * Any field that comes back from a GraphQL API will have * values that are scalars, other objects, or arrays * of scalars or objects. */ type DataField = Scalar | Data | NullArray<Scalar> | NullArray<Data>; /** Definition of GraphQL object (“entity”) fields. * * @remarks * Any object that comes back from a GraphQL API will have * values that are scalars, other objects, or arrays * of scalars or objects, i.e. the {@link DataField} type. */ interface DataFields { [fieldName: string]: DataField; } /** Definition of GraphQL variables objects. * @remarks * Variables, as passed to GraphQL queries, can only contain scalar values. * * @see {@link https://spec.graphql.org/October2021/#sec-Coercing-Variable-Values} for the * GraphQL spec’s coercion of GraphQL variables. */ interface Variables { [name: string]: Scalar | Scalar[] | Variables | NullArray<Variables>; } /** Definition of GraphQL objects (“entities”). * * @remarks * An entity is expected to consist of a `__typename` * fields, optionally the default `id` or `_id` key * fields, and scalar values or other entities * otherwise. */ type Data = SystemFields & DataFields; /** An entity, a key of an entity, or `null` * * @remarks * When Graphcache accepts a reference to an entity, you may pass it a key of an entity, * as retrieved for instance by {@link Cache.keyOfEntity} or a partial GraphQL object * (i.e. an object with a `__typename` and key field). */ type Entity = undefined | null | Data | string; /** A key of an entity, or `null`; or a list of keys. * * @remarks * When Graphcache accepts a reference to one or more entities, you may pass it a * key, an entity, or a list of entities or keys. This is often passed to {@link Cache.link} * to update a field pointing to other GraphQL objects. */ type Link<Key = string> = null | Key | NullArray<Key>; /** Arguments passed to a Graphcache field resolver. * * @remarks * Arguments a field receives are similar to variables and can * only contain scalars or other arguments objects. This * is equivalent to the {@link Variables} type. * * @see {@link https://spec.graphql.org/October2021/#sec-Coercing-Field-Arguments} for the * GraphQL spec’s coercion of field arguments. */ type FieldArgs = Variables | null | undefined; /** Metadata about an entity’s cached field. * * @remarks * As returned by {@link Cache.inspectFields}, `FieldInfo` specifies an entity’s cached field, * split into the field’s key itself and the field’s original name and arguments. */ interface FieldInfo { /** The field’s key which combines `fieldName` and `arguments`. */ fieldKey: string; /** The field’s name, as defined on a GraphQL Object type. */ fieldName: string; /** The arguments passed to the field as found on the cache. */ arguments: Variables | null; } /** A key to an entity field split back into the entity’s key and the field’s key part. * @internal */ interface KeyInfo { entityKey: string; fieldKey: string; } /** Abstract type for GraphQL requests. * * @remarks * Similarly to `@urql/core`’s `GraphQLRequest` type, `OperationRequest` * requires the minimum fields that Grapcache requires to execute a * GraphQL operation: its query document and variables. */ interface OperationRequest { query: FormattedNode<DocumentNode> | DocumentNode; variables?: any; } /** Metadata object passed to all resolver functions. * * @remarks * `ResolveInfo`, similar to GraphQL.js’ `GraphQLResolveInfo` object, * gives your resolvers a global state of the current GraphQL * document traversal. * * `parent`, `parenTypeName`, `parentKey`, and `parentFieldKey` * are particularly useful to make reusable resolver functions that * must know on which field and type they’re being called on. */ interface ResolveInfo { /** The parent GraphQL object. * * @remarks * The GraphQL object that the resolver has been called on. Because this is * a reference to raw GraphQL data, this may be incomplete or contain * aliased fields! */ parent: Data; /** The parent object’s typename that the resolver has been called on. */ parentTypeName: string; /** The parent object’s entity key that the resolver has been called on. */ parentKey: string; /** Current field’s key that the resolver has been called on. */ parentFieldKey: string; /** Current field that the resolver has been called on. */ fieldName: string; /** Map of fragment definitions from the query document. */ fragments: Fragments; /** Full original {@link Variables} object on the {@link OperationRequest}. */ variables: Variables; /** Error that occurred for the current field, if any. * * @remarks * If a {@link GraphQLError.path} points at the current field, the error * will be set and provided here. This can be useful to recover from an * error on a specific field. */ error: ErrorLike | undefined; /** Flag used to indicate whether the current GraphQL query is only partially cached. * * @remarks * When Graphcache has {@link CacheExchangeOpts.schema} introspection information, * it can automatically generate partial results and trigger a full API request * in the background. * Hence, this field indicates whether any data so far has only been partially * resolved from the cache, and is only in use on {@link Resolver | Resolvers}. * * However, you can also flip this flag to `true` manually to indicate to * the {@link cacheExchange} that it should still make a network request. */ partial?: boolean; /** Flag used to indicate whether the current GraphQL mutation is optimistically executed. * * @remarks * An {@link UpdateResolver} is called for both API mutation responses and * optimistic mutation reuslts, as generated by {@link OptimisticMutationResolver}. * * Since an update sometimes needs to perform different actions if it’s run * optimistically, this flag is set to `true` during optimisti cupdates. */ optimistic?: boolean; /** Internal state used by Graphcache. * @internal */ __internal?: unknown; } /** GraphQL document and variables that should be queried against the cache. * * @remarks * `QueryInput` is a generic GraphQL request that should be executed against * cached data, as accepted by {@link cache.readQuery}. */ interface QueryInput<T = Data, V = Variables> { query: DocumentInput<T, V>; variables?: V; } /** Interface to interact with cached data, which resolvers receive. */ interface Cache { /** Returns the cache key for a given entity or `null` if it’s unkeyable. * * @param entity - the {@link Entity} to generate a key for. * @returns the entity’s key or `null`. * * @remarks * `cache.keyOfEntity` may be called with a partial GraphQL object (“entity”) * and generates a key for it. It uses your {@link KeyingConfig} and otherwise * defaults to `id` and `_id` fields. * * If it’s passed a `string` or `null`, it will simply return what it’s been passed. * Objects that lack a `__typename` field will return `null`. */ keyOfEntity(entity: Entity | undefined): string | null; /** Returns the cache key for a field. * * @param fieldName - the field’s name. * @param args - the field’s arguments, if any. * @returns the field key * * @remarks * `cache.keyOfField` is used to create a field’s cache key from a given * field name and its arguments. This is used internally by {@link cache.resolve} * to combine an entity key and a field key into a path that normalized data is * accessed on in Graphcache’s internal data structure. */ keyOfField(fieldName: string, args?: FieldArgs): string | null; /** Returns a cached value on a given entity’s field. * * @param entity - a GraphQL object (“entity”) or an entity key. * @param fieldName - the field’s name. * @param args - the field’s arguments, if any. * @returns the field’s value or the entity key(s) this field is pointing at. * * @remarks * `cache.resolve` is used to retrieve either the cached value of a field, or * to get the relation of the field (“link”). When a cached field points at * another normalized entity, this method will return the related entity key * (or a list, if it’s pointing at a list of entities). * * As such, if you’re accessing a nested field, you may have to call * `cache.resolve` again and chain its calls. * * Hint: If you have a field key from {@link FieldInfo} or {@link cache.keyOfField}, * you may pass it as a second argument. * * @example * ```ts * const authorName = cache.resolve( * cache.resolve({ __typename: 'Book', id }, 'author'), * 'name' * ); * ``` */ resolve(entity: Entity | undefined, fieldName: string, args?: FieldArgs): DataField | undefined; /** Returns a list of cached fields for a given GraphQL object (“entity”). * * @param entity - a GraphQL object (“entity”) or an entity key. * @returns a list of {@link FieldInfo} objects. * * @remarks * `cache.inspectFields` can be used to list out all known fields * of a given entity. This can be useful in an {@link UpdateResolver} * if you have a `Query` field that accepts many different arguments, * for instance a paginated field. * * The returned list of fields are all fields that the cache knows about, * and you may have to filter them by name or arguments to find only which * ones you need. * * Hint: This method is theoretically a slower operation than simple * cache lookups, as it has to decode field keys. It’s only recommended * to be used in updaters. */ inspectFields(entity: Entity): FieldInfo[]; /** Deletes a cached entity or an entity’s field. * * @param entity - a GraphQL object (“entity”) or an entity key. * @param fieldName - optionally, a field name. * @param args - optionally, the field’s arguments, if any. * * @remarks * `cache.invalidate` can be used in updaters to delete data from * the cache. This will cause the {@link cacheExchange} to reexecute * queries that contain the deleted data. * * If you only pass its first argument, the entire entity is deleted. * However, if a field name (and optionally, its arguments) are passed, * only a single field is erased. */ invalidate(entity: Entity | undefined, fieldName?: string, args?: FieldArgs): void; /** Updates a GraphQL query‘s cached data. * * @param input - a {@link QueryInput}, which is a GraphQL query request. * @param updater - a function called with the query’s result or `null` in case of a cache miss, which * may return updated data, which is written to the cache using the query. * * @remarks * `cache.updateQuery` can be used to update data for an entire GraphQL query document. * When it's passed a GraphQL query request, it calls the passed `updater` function * with the cached result for this query. You may then modify and update the data and * return it, after which it’s written back to the cache. * * Hint: While this allows for large updates at once, {@link cache.link}, * {@link cache.resolve}, and {@link cache.writeFragment} are often better * choices for more granular and compact updater code. * * @example * ```ts * cache.updateQuery({ query: TodoList }, data => { * data.todos.push(newTodo); * return data; * }); * ``` */ updateQuery<T = Data, V = Variables>(input: QueryInput<T, V>, updater: (data: T | null) => T | null): void; /** Returns a GraphQL query‘s cached result. * * @param input - a {@link QueryInput}, which is a GraphQL query request. * @returns the cached data result of the query or `null`, in case of a cache miss. * * @remarks * `cache.readQuery` can be used to read an entire query’s data all at once * from the cache. * * This can be useful when typing out many {@link cache.resolve} * calls is too tedious. * * @example * ```ts * const data = cache.readQuery({ * query: TodosQuery, * variables: { from: 0, limit: 10 } * }); * ``` */ readQuery<T = Data, V = Variables>(input: QueryInput<T, V>): T | null; /** Returns a GraphQL fragment‘s cached result. * * @param fragment - a {@link DocumentNode} containing a fragment definition. * @param entity - a GraphQL object (“entity”) or an entity key to read the fragment on. * @param variables - optionally, GraphQL variables, if the fragments use any. * @returns the cached data result of the fragment or `null`, in case of a cache miss. * * @remarks * `cache.readFragment` can be used to read an entire query’s data all at once * from the cache. * * It attempts to read the fragment starting from the `entity` that’s passed to it. * If the entity can’t be resolved or has mismatching types, `null` is returned. * * This can be useful when typing out many {@link cache.resolve} * calls is too tedious. * * @example * ```ts * const data = cache.readFragment( * gql`fragment _ on Todo { id, text }`, * { id: '123' } * ); * ``` */ readFragment<T = Data, V = Variables>(fragment: TypedDocumentNode<any, any> | TypedDocumentNode<T, V>, entity: string | Data | T, variables?: V): T | null; /** Writes a GraphQL fragment to the cache. * * @param fragment - a {@link DocumentNode} containing a fragment definition. * @param data - a GraphQL object to be written with the given fragment. * @param variables - optionally, GraphQL variables, if the fragments use any. * * @remarks * `cache.writeFragment` can be used to write an entity to the cache. * The method will generate a key for the `data` it’s passed, and start writing * it using the fragment. * * This method is used when writing scalar values to the cache. * Since it's rare for an updater to write values to the cache, {@link cache.link} * only allows relations (“links”) to be updated, and `cache.writeFragment` is * instead used when writing multiple scalars. * * @example * ```ts * const data = cache.writeFragment( * gql`fragment _ on Todo { id, text }`, * { id: '123', text: 'New Text' } * ); * ``` */ writeFragment<T = Data, V = Variables>(fragment: TypedDocumentNode<any, any> | TypedDocumentNode<T, V>, data: T, variables?: V): void; /** Updates the relation (“link”) from an entity’s field to another entity. * * @param entity - a GraphQL object (“entity”) or an entity key. * @param fieldName - the field’s name. * @param args - optionally, the field’s arguments, if any. * @param link - the GraphQL object(s) that should be set on this field. * * @remarks * The normalized cache stores relations between GraphQL objects separately. * As such, a field can be updated using `cache.link` to point to a new entity, * or a list of entities. * * In other words, `cache.link` is used to set a field to point to another * entity or a list of entities. * * @example * ```ts * const todos = cache.resolve('Query', 'todos'); * cache.link('Query', 'todos', [...todos, newTodo]); * ``` */ link(entity: Entity, field: string, args: FieldArgs, link: Link<Entity>): void; link(entity: Entity, field: string, value: Link<Entity>): void; } /** Values a {@link Resolver} may return. * * @remarks * A resolver may return any value that a GraphQL object may contain. * * Additionally however, a resolver may return `undefined` to indicate that data * isn’t available from the cache, i.e. to trigger a cache miss. */ type ResolverResult = DataField | (DataFields & { __typename?: string; }) | null | undefined; type Logger = (severity: 'debug' | 'error' | 'warn', message: string) => void; /** Input parameters for the {@link cacheExchange}. */ type CacheExchangeOpts = { /** Configure a custom-logger for graphcache, this function wll be called with a severity and a message. * * @remarks * By default we will invoke `console.warn` for warnings during development, however you might want to opt * out of this because you are re-using urql for a different library. This setting allows you to stub the logger * function or filter to only logs you want. */ logger?: Logger; /** Configures update functions which are called when the mapped fields are written to the cache. * * @remarks * `updates` are commonly used to define additional changes to the cache for * mutation or subscription fields. It may commonly be used to invalidate * cached data or to modify lists after mutations. * This is a map of types to fields to {@link UpdateResolver} functions. * * @see {@link https://urql.dev/goto/docs/graphcache/cache-updates} for the full updates docs. */ updates?: UpdatesConfig; /** Configures resolvers which replace cached reuslts with custom values. * * @remarks * `resolvers` is a map of types to fields to {@link Resolver} functions. * These functions allow us to replace cached field values with a custom * result, either to replace values on GraphQL results, or to resolve * entities from the cache for queries that haven't been sent to the API * yet. * * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers} for the full resolvers docs. */ resolvers?: ResolverConfig; /** Configures directives which can perform custom logic on fields. * * @remarks * A {@link DirectivesConfig} may be passed to allow local directives to be used. For example, when `@_custom` is placed on a field and the configuration contains `custom` then this directive is executed by Graphcache. * * @see {@link https://urql.dev/goto/docs/graphcache/local-directives} for the full directives docs. */ directives?: DirectivesConfig; /** Configures optimistic updates to react to mutations instantly before an API response. * * @remarks * `optimistic` is a map of mutation fields to {@link OptimisticMutationResolver} functions. * These functions allow us to return result data for mutations to optimistically apply them. * Optimistic updates are temporary updates to the cache’s data which allow an app to * instantly reflect changes that a mutation will make. * * @see {@link https://urql.dev/goto/docs/graphcache/cache-updates/#optimistic-updates} for the * full optimistic updates docs. */ optimistic?: OptimisticMutationConfig; /** Configures keying functions for GraphQL types. * * @remarks * `keys` is a map of GraphQL object type names to {@link KeyGenerator} functions. * If a type in your API has no key field or a key field that isn't the default * `id` or `_id` fields, you may define a custom key generator for the type. * * Hint: Graphcache will log warnings when it finds objects that have no keyable * fields, which will remind you to define these functions gradually for every * type that needs them. * * @see {@link https://urql.dev/goto/docs/graphcache/normalized-caching/#custom-keys-and-non-keyable-entities} for * the full keys docs. */ keys?: KeyingConfig; /** Enables global IDs for keying GraphQL types. * * @remarks * When `globalIDs` are enabled, GraphQL object type names will not contribute * to the keys of entities and instead only their ID fields (or `keys` return * values will be used. * * This is useful to overlap types of differing typenames. While this isn’t recommended * it can be necessary to represent more complex interface relationships. * * If this should only be applied to a limited set of type names, a list of * type names may be passed instead. */ globalIDs?: string[] | boolean; /** Configures abstract to concrete types mapping for GraphQL types. * * @remarks * This will disable heuristic fragment matching, allowing Graphcache to match * fragment deterministically. * * When both `possibleTypes` and `schema` is set, `possibleTypes` value will be * ignored. */ possibleTypes?: PossibleTypesConfig; /** Configures Graphcache with Schema Introspection data. * * @remarks * Passing a `schema` to Graphcache enables it to do non-heuristic fragment * matching, and be certain when a fragment matches against a union or interface * on your schema. * * It also enables a mode called “Schema Awareness”, which allows Graphcache to * return partial GraphQL results, `null`-ing out fields that aren’t in the cache * that are nullable on your schema, while requesting the full API response in * the background. * * @see {@link https://urql.dev/goto/urql/docs/graphcache/schema-awareness} for * the full keys docs on Schema Awareness. */ schema?: IntrospectionData; /** Configures an offline storage adapter for Graphcache. * * @remarks * A {@link StorageAdapter} allows Graphcache to write data to an external, * asynchronous storage, and hydrate data from it when it first loads. * This allows you to preserve normalized data between restarts/reloads. * * Hint: If you’re trying to use Graphcache’s Offline Support, you may * want to swap out the `cacheExchange` with the {@link offlineExchange}. * * @see {@link https://urql.dev/goto/docs/graphcache/offline} for the full Offline Support docs. */ storage?: StorageAdapter; }; /** Cache Resolver, which may resolve or replace data during cache reads. * * @param parent - The GraphQL object that is currently being constructed from cache data. * @param args - This field’s arguments. * @param cache - {@link Cache} interface. * @param info - {@link ResolveInfo} interface. * @returns a {@link ResolverResult}, which is an updated value, partial entity, or entity key * * @remarks * A `Resolver`, as defined on the {@link ResolverConfig}, is called for * a field’s type during cache reads, and can be used to deserialize or replace * scalar values, or to resolve an entity from cached data, even if the * current field hasn’t been cached from an API response yet. * * For instance, if you have a `Query.picture(id: ID!)` field, you may define * a resolver that returns `{ __typename: 'Picture', id: args.id }`, since you * know the key fields of the GraphQL object. * * @example * ```ts * cacheExchange({ * resolvers: { * Query: { * // resolvers can be used to resolve cached entities without API requests * todo: (_parent, args) => ({ __typename: 'Todo', id: args.id }), * }, * Todo: { * // resolvers can also be used to replace/deserialize scalars * updatedAt: parent => new Date(parent.updatedAt), * }, * }, * }); * ``` * * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers} for the full resolvers docs. */ type Resolver<ParentData = DataFields, Args = Variables, Result = ResolverResult> = { bivarianceHack(parent: ParentData, args: Args, cache: Cache, info: ResolveInfo): Result; }['bivarianceHack']; /** Configures resolvers which replace cached reuslts with custom values. * * @remarks * A `ResolverConfig` is a map of types to fields to {@link Resolver} functions. * These functions allow us to replace cached field values with a custom * result, either to replace values on GraphQL results, or to resolve * entities from the cache for queries that haven't been sent to the API * yet. * * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers} for the full resolvers docs. */ type ResolverConfig = { [typeName: string]: { [fieldName: string]: Resolver | void; } | void; }; type Directive = (directiveArguments: Record<string, unknown> | null) => Resolver; type DirectivesConfig = { [directiveName: string]: Directive; }; /** Cache Updater, which defines additional cache updates after cache writes. * * @param parent - The GraphQL object that is currently being written to the cache. * @param args - This field’s arguments. * @param cache - {@link Cache} interface. * @param info - {@link ResolveInfo} interface. * * @remarks * An `UpdateResolver` (“updater”), as defined on the {@link UpdatesConfig}, is * called for a field’s type during cache writes, and can be used to instruct * the {@link Cache} to perform other cache updates at the same time. * * This is often used, for instance, to update lists or invalidate entities * after a mutation response has come back from the API. * * @example * ```ts * cacheExchange({ * updates: { * Mutation: { * // updaters can invalidate data from the cache * deleteAuthor: (_parent, args, cache) => { * cache.invalidate({ __typename: 'Author', id: args.id }); * }, * }, * }, * }); * ``` * * @see {@link https://urql.dev/goto/docs/graphcache/cache-updates} for the * full cache updates docs. */ type UpdateResolver<ParentData = DataFields, Args = Variables> = { bivarianceHack(parent: ParentData, args: Args, cache: Cache, info: ResolveInfo): void; }['bivarianceHack']; /** A key functon, which is called to create a cache key for a GraphQL object (“entity”). * * @param data - The GraphQL object that a key is generated for. * @returns a key `string` or `null` or unkeyable objects. * * @remarks * By default, Graphcache will use an object’s `__typename`, and `id` or `_id` fields * to generate a key for an object. However, not all GraphQL objects will have a unique * field, and some objects don’t have a key at all. * * When one of your GraphQL object types has a different key field, you may define a * function on the {@link KeyingConfig} to return its key field. * You may also have objects that don’t have keys, like “Edge” objects, or scalar-like * objects. For these, you can define a function that returns `null`, which tells * Graphcache that it’s an embedded object, which only occurs on its parent and is * globally unique. * * @see {@link https://urql.dev/goto/docs/graphcache/normalized-caching/#custom-keys-and-non-keyable-entities} for * the full keys docs. * * @example * ```ts * cacheExchange({ * keys: { * Image: data => data.url, * LatLng: () => null, * }, * }); * ``` */ type KeyGenerator = { bivarianceHack(data: Data): string | null; }['bivarianceHack']; /** Configures update functions which are called when the mapped fields are written to the cache. * * @remarks * `UpdatesConfig` is a map of types to fields to {@link UpdateResolver} functions. * These update functions are defined to instruct the cache to make additional changes * when a field is written to the cache. * * As changes are often made after a mutation or subscription, the `typeName` is * often set to `'Mutation'` or `'Subscription'`. * * @see {@link https://urql.dev/goto/docs/graphcache/cache-updates} for the full updates docs. * * @example * ```ts * const updates = { * Mutation: { * deleteAuthor(_parent, args, cache) { * // Delete the Author from the cache when Mutation.deleteAuthor is sent * cache.invalidate({ __typename: 'Author', id: args.id }); * }, * }, * }; */ type UpdatesConfig = { [typeName: string | 'Query' | 'Mutation' | 'Subscription']: { [fieldName: string]: UpdateResolver | void; } | void; }; /** Remaps result type to allow for nested optimistic mutation resolvers. * * @remarks * An {@link OptimisticMutationResolver} can not only return partial, nested * mutation result data, but may also contain more optimistic mutation resolvers * for nested fields, which allows fields with arguments to optimistically be * resolved to dynamic values. * * @see {@link OptimisticMutationConfig} for more information. */ type MakeFunctional<T> = T extends { __typename: string; } ? WithTypename<{ [P in keyof T]?: MakeFunctional<T[P]>; }> : OptimisticMutationResolver<Variables, T> | T; /** Optimistic mutation resolver, which may return data that a mutation response will return. * * @param args - This field’s arguments. * @param cache - {@link Cache} interface. * @param info - {@link ResolveInfo} interface. * @returns the field’s optimistic data * * @remarks * Graphcache can update its cache optimistically via the {@link OptimisticMutationConfig}. * An `OptimisticMutationResolver` should return partial data that a mutation will return * once it completes and we receive its result. * * For instance, it could return the data that a deletion mutation may return * optimistically, which might allow an updater to run early and your UI to update * instantly. * * The result that this function returns may miss some fields that your mutation may return, * especially if it contains GraphQL object that are already cached. It may also contain * other, nested resolvers, which allows you to handle fields that accept arguments. */ type OptimisticMutationResolver<Args = Variables, Result = Link<Data> | Scalar> = { bivarianceHack(args: Args, cache: Cache, info: ResolveInfo): MakeFunctional<Result>; }['bivarianceHack']; /** Configures optimistic result functions which are called to get a mutation’s optimistic result. * * @remarks * `OptimisticMutationConfig` is a map of mutation fields to {@link OptimisticMutationResolver} * functions, which return result data for mutations to optimistically apply them. * Optimistic updates are temporary updates to the cache’s data which allow an app to * instantly reflect changes that a mutation will make. * * Hint: Results returned from optimistic functions may be partial, and may contain functions. * If the returned optimistic object contains functions on fields, these are executed as nested * optimistic resolver functions. * * @see {@link https://urql.dev/goto/docs/graphcache/cache-updates/#optimistic-updates} for the * full optimistic updates docs. * * @example * ```ts * const optimistic = { * updateProfile: (args) => ({ * __typename: 'UserProfile', * id: args.id, * name: args.newName, * }), * }; */ type OptimisticMutationConfig = { [mutationFieldName: string]: OptimisticMutationResolver; }; /** Configures keying functions for GraphQL types. * * @remarks * `KeyingConfig` is a map of GraphQL object type names to {@link KeyGenerator} functions. * If a type in your API has no key field or a key field that isn't the default * `id` or `_id` fields, you may define a custom key generator for the type. * * Keys are important to a normalized cache, because they’re the identity of the object * that is shared across the cache, and helps the cache recognize shared/normalized data. * * Hint: Graphcache will log warnings when it finds objects that have no keyable * fields, which will remind you to define these functions gradually for every * type that needs them. * * @see {@link https://urql.dev/goto/docs/graphcache/normalized-caching/#custom-keys-and-non-keyable-entities} for * the full keys docs. * * @example * ```ts * const keys = { * Image: data => data.url, * LatLng: () => null, * }; * ``` */ type KeyingConfig = { [typename: string]: KeyGenerator; }; type PossibleTypesConfig = { [abstractType: string]: string[]; }; /** Serialized normalized caching data. */ interface SerializedEntries { [key: string]: string | undefined; } /** A serialized GraphQL request for offline storage. */ interface SerializedRequest { query: string; variables: AnyVariables | undefined; extensions?: RequestExtensions | undefined; } /** Interface for a storage adapter, used by the {@link offlineExchange} for Offline Support. * @see {@link https://urql.dev/goto/docs/graphcache/offline} for the full Offline Support docs. * @see `@urql/exchange-graphcache/default-storage` for an example implementation using IndexedDB. */ interface StorageAdapter { /** Called to rehydrate data when the {@link cacheExchange} first loads. * @remarks * `readData` is called when Graphcache first starts up, and loads cache entries * using which it'll repopulate its normalized cache data. */ readData(): Promise<SerializedEntries>; /** Called by the {@link cacheExchange} to write new data to the offline storage. * @remarks * `writeData` is called when Graphcache updated its cached data and wishes to * persist this data to the offline storage. The data is a partial object and * Graphcache does not write all its data at once. */ writeData(delta: SerializedEntries): Promise<any>; /** Called to rehydrate metadata when the {@link offlineExchange} first loads. * @remarks * `readMetadata` is called when Graphcache first starts up, and loads * metadata informing it of pending mutations that failed while the device * was offline. */ readMetadata?(): Promise<null | SerializedRequest[]>; /** Called by the {@link offlineExchange} to persist failed mutations. * @remarks * `writeMetadata` is called when a mutation failed to persist a queue * of failed mutations to the offline storage that must be retried when * the application is reloaded. */ writeMetadata?(json: SerializedRequest[]): void; /** Called to register a callback called when the device is back online. * @remarks * `onOnline` is called by the {@link offlineExchange} with a callback. * This callback must be called when the device comes back online and * will cause all failed mutations in the queue to be retried. */ onOnline?(cb: () => void): any; /** Called when the cache has been hydrated with the data from `readData` */ onCacheHydrated?(): any; } /** Set of keys that have been modified or accessed. * @internal */ type Dependencies = Set<string>; /** The type of cache operation being executed. * @internal */ type OperationType = 'read' | 'write'; /** Casts a given object type to have a required typename field. * @internal */ type WithTypename<T extends { __typename?: any; }> = T & { __typename: NonNullable<T['__typename']>; }; export { Cache, CacheExchangeOpts, Data, DataField, DataFields, Dependencies, Directive, DirectivesConfig, Entity, EntityField, FieldArgs, FieldInfo, Fragments, KeyGenerator, KeyInfo, KeyingConfig, Link, Logger, MakeFunctional, NullArray, OperationRequest, OperationType, OptimisticMutationConfig, OptimisticMutationResolver, PossibleTypesConfig, Primitive, QueryInput, ResolveInfo, Resolver, ResolverConfig, ResolverResult, Scalar, ScalarObject, SchemaIntrospector, SerializedEntries, SerializedRequest, StorageAdapter, SystemFields, UpdateResolver, UpdatesConfig, Variables, WithTypename };