All files / src/data queries.ts

87.5% Statements 56/64
85.37% Branches 35/41
92.31% Functions 12/13
88.89% Lines 56/63
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 18410x                   10x   252x   10x 1x   10x 1799x   10x 308x 308x     308x 308x 308x     40x 8x 8x       308x 8x   300x 20x   280x 23x     257x   308x                   308x 7x       10x 246x 5x   241x 241x   241x 241x 241x 5x     10x 23x 3x   20x 20x 20x 2x     10x 46x     46x 46x 46x       10x 185x   10x 12x 12x   8x     6x 6x     10x                                                                                                                                                  
import { DocumentNode, GraphQLError, ExecutionResult } from 'graphql';
import { isEqual } from 'apollo-utilities';

import { NetworkStatus } from '../core/networkStatus';

export type QueryStoreValue = {
  queryString: string;
  document: DocumentNode;
  variables: Object;
  previousVariables?: Object | null;
  networkStatus: NetworkStatus;
  networkError?: Error | null;
  graphQLErrors?: GraphQLError[];
  metadata: any;
};
 
export class QueryStore {
  private store: { [queryId: string]: QueryStoreValue } = {};
 
  public getStore(): { [queryId: string]: QueryStoreValue } {
    return this.store;
  }
I
  public get(queryId: string): QueryStoreValue {
    return this.store[queryId];
  }
 
  public initQuery(query: {
    queryId: string;
    queryString: string;
    document: DocumentNode;
    storePreviousVariables: boolean;
    variables: Object;
    isPoll: boolean;
    isRefetch: boolean;
    metadata: any;
    fetchMoreForQueryId: string | undefined;
  }) {
    const previousQuery = this.store[query.queryId];
 
    if (previousQuery && previousQuery.queryString !== query.queryString) {
      // XXX we're throwing an error here to catch bugs where a query gets overwritten by a new one.
      // we should implement a separate action for refetching so that QUERY_INIT may never overwrite
      // an existing query (see also: https://github.com/apollostack/apollo-client/issues/732)
      throw new Error(
        'Internal Error: may not update existing query string in store',
      );
    }
 
    let isSetVariables = false;
 
    let previousVariables: Object | null = null;
    if (
      query.storePreviousVariables &&
      previousQuery &&
      previousQuery.networkStatus !== NetworkStatus.loading
      // if the previous query was still loading, we don't want to remember it at all.
    ) {
      if (!isEqual(previousQuery.variables, query.variables)) {
        isSetVariables = true;
        previousVariables = previousQuery.variables;
      }
    }
 
    // TODO break this out into a separate function
    let networkStatus;
    if (isSetVariables) {
      networkStatus = NetworkStatus.setVariables;
    } else if (query.isPoll) {
      networkStatus = NetworkStatus.poll;
    } else if (query.isRefetch) {
      networkStatus = NetworkStatus.refetch;
      // TODO: can we determine setVariables here if it's a refetch and the variables have changed?
    } else {
      networkStatus = NetworkStatus.loading;
    }
 
    // XXX right now if QUERY_INIT is fired twice, like in a refetch situation, we just overwrite
    // the store. We probably want a refetch action instead, because I suspect that if you refetch
    // before the initial fetch is done, you'll get an error.
    this.store[query.queryId] = {
      queryString: query.queryString,
      document: query.document,
      variables: query.variables,
      previousVariables,
      networkError: null,
      graphQLErrors: [],
      neItworkStatus,
      metadata: query.metadata,
    };
 
    // If the action had a `moreForQueryId` property then we need to set the
    // network status on that query as well to `fetchMore`.
    //
    // We have a complement to this if statement in the query result and query
    // error action branch, but importantly *not* in the client result branch.
    // This is because the implementation of `fetchMore` *always* sets
    // `fetchPolicy` to `network-only` so we would never have a client result.
    if (typeof query.fetchMoreForQueryId === 'string') {
      this.store[query.fetchMoreForQueryId].networkStatus =
        NetworkStatus.fetchMore;
    }
  }
 
  public markQueryResult(
    queryId: string,
    result: ExecutionResult,
    fetchMoreForQueryId: string | undefined,
  ) {
    if (!this.store[queryId]) {
      return;
    }
 
    this.store[queryId].networkError = null;
    this.store[queryId].graphQLErrors =
      result.errors && result.errors.length ? result.errors : [];
    this.store[queryId].previousVariables = null;
    this.store[queryId].networkStatus = NetworkStatus.ready;
 
    // If we have a `fetchMoreForQueryId` then we need to update the network
    // status for that query. See the branch for query initialization for more
    // explanation about this process.
    if (typeof fetchMoreForQueryId === 'string') {
      this.store[fetchMoreForQueryId].networkStatus = NetworkStatus.ready;
    }
  }
 
  public markQueryError(
    queryId: string,
    error: Error,
    fetchMoreForQueryId: string | undefined,
  ) {
    if (!this.store[queryId]) {
      return;
    }
 
    this.store[queryId].networkError = error;
    this.store[queryId].networkStatus = NetworkStatus.error;
 
    // If we have a `fetchMoreForQueryId` then we need to update the network
    // status for that query. See the branch for query initialization for more
    // explanation about this process.
    if (typeof fetchMoreForQueryId === 'string') {
      this.markQueryError(fetchMoreForQueryId, error, undefined);
    }
  }
 
  public markQueryResultClient(queryId: string, complete: boolean) {
    if (!this.store[queryId]) {
      return;
    }
 
    this.store[queryId].networkError = null;
    this.store[queryId].previousVariables = null;
    this.store[queryId].networkStatus = complete
      ? NetworkStatus.ready
      : NetworkStatus.loading;
  }
 
  public stopQuery(queryId: string) {
    delete this.store[queryId];
  }
 
  public reset(observableQueryIds: string[]) {
    // keep only the queries with query ids that are associated with observables
    this.store = Object.keys(this.store)
      .filter(queryId => {
        return observableQueryIds.indexOf(queryId) > -1;
      })
      .reduce(
        (res, key) => {
          // XXX set loading to true so listeners don't trigger unless they want results with partial data
          res[key] = {
            ...this.store[key],
            networkStatus: NetworkStatus.loading,
          };
 
          return res;
        },
        {} as { [queryId: string]: QueryStoreValue },
      );
  }
}