import { DocumentNode } from 'graphql'; import { getFragmentQueryDocument } from 'apollo-utilities'; import { DataProxy, Cache } from './types'; import { justTypenameQuery, queryFromPojo, fragmentFromPojo } from './utils'; export type Transaction = (c: ApolloCache) => void; export abstract class ApolloCache implements DataProxy { // required to implement // core API public abstract read( query: Cache.ReadOptions, ): T | null; public abstract write( write: Cache.WriteOptions, ): void; public abstract diff(query: Cache.DiffOptions): Cache.DiffResult; public abstract watch(watch: Cache.WatchOptions): () => void; public abstract evict( query: Cache.EvictOptions, ): Cache.EvictionResult; public abstract reset(): Promise; // intializer / offline / ssr API /** * Replaces existing state in the cache (if any) with the values expressed by * `serializedState`. * * Called when hydrating a cache (server side rendering, or offline storage), * and also (potentially) during hot reloads. */ public abstract restore( serializedState: TSerialized, ): ApolloCache; /** * Exposes the cache's complete state, in a serializable format for later restoration. */ public abstract extract(optimistic?: boolean): TSerialized; // optimistic API public abstract removeOptimistic(id: string): void; // transactional API public abstract performTransaction( transaction: Transaction, ): void; public abstract recordOptimisticTransaction( transaction: Transaction, id: string, ): void; // optional API public transformDocument(document: DocumentNode): DocumentNode { return document; } // experimental public transformForLink(document: DocumentNode): DocumentNode { return document; } // DataProxy API /** * * @param options * @param optimistic */ public readQuery( options: DataProxy.Query, optimistic: boolean = false, ): QueryType | null { return this.read({ query: options.query, variables: options.variables, optimistic, }); } public readFragment( options: DataProxy.Fragment, optimistic: boolean = false, ): FragmentType | null { return this.read({ query: getFragmentQueryDocument(options.fragment, options.fragmentName), variables: options.variables, rootId: options.id, optimistic, }); } public writeQuery( options: Cache.WriteQueryOptions, ): void { this.write({ dataId: 'ROOT_QUERY', result: options.data, query: options.query, variables: options.variables, }); } public writeFragment( options: Cache.WriteFragmentOptions, ): void { this.write({ dataId: options.id, result: options.data, variables: options.variables, query: getFragmentQueryDocument(options.fragment, options.fragmentName), }); } public writeData({ id, data, }: Cache.WriteDataOptions): void { if (typeof id !== 'undefined') { let typenameResult = null; // Since we can't use fragments without having a typename in the store, // we need to make sure we have one. // To avoid overwriting an existing typename, we need to read it out first // and generate a fake one if none exists. try { typenameResult = this.read({ rootId: id, optimistic: false, query: justTypenameQuery, }); } catch (e) { // Do nothing, since an error just means no typename exists } // tslint:disable-next-line const __typename = (typenameResult && typenameResult.__typename) || '__ClientData'; // Add a type here to satisfy the inmemory cache const dataToWrite = Object.assign({ __typename }, data); this.writeFragment({ id, fragment: fragmentFromPojo(dataToWrite, __typename), data: dataToWrite, }); } else { this.writeQuery({ query: queryFromPojo(data), data }); } } }