<commit_before>import {INSERT_QUERY, INSERT_MUTATION, SET_ERROR} from './normalize/duck';
import denormalizeStore from './normalize/denormalizeStore';
import {rebuildOriginalArgs} from './normalize/denormalizeHelpers';
import normalizeResponse from './normalize/normalizeResponse';
import {printMinimalQuery} from './query/printMinimalQuery';
import {shortenNormalizedResponse, invalidateMutationsOnNewQuery} from './query/queryHelpers';
import {isObject, checkMutationInSchema} from './utils';
import mergeStores from './normalize/mergeStores';
import {CachedMutation, CachedQuery, MutationShell} from './helperClasses';
import flushDependencies from './query/flushDependencies';
import {parse, buildExecutionContext, getVariables, clone} from './utils';
import namespaceMutation from './mutate/namespaceMutation';
import createMutationFromQuery from './mutate/createMutationFromQuery';
import removeNamespacing from './mutate/removeNamespacing';
import makeFriendlyStore from './mutate/makeFriendlyStore';
import addDeps from './normalize/addDeps';
import mergeMutations from './mutate/mergeMutations';
import {print} from 'graphql/language/printer';
import ActiveComponentsObj from './mutate/ActiveComponentsObj';

const defaultGetToState = store => store.getState().cashay;
const defaultPaginationWords = {
  before: 'before',
  after: 'after',
  first: 'first',
  last: 'last'
};

class Cashay {
  constructor() {
    // many mutations can share the same mutationName, making it hard to cache stuff without adding complexity
    // we can assume that a mutationName + the components it affects = a unique, reproduceable fullMutation
    // const example = {
    //   [mutationName]: {
    //     activeComponentsObj: {
    //       [component]: key || Boolean
    //     },
    //     fullMutation: MutationString,
    //     variableEnhancers: [variableEnhancerFn],
    //     singles: {
    //       [component]: {
    //          ast: MutationAST,
    //          variableEnhancers: [variableEnhancerFn]
    //       }
    //     }
    //   }
    // }
    this.cachedMutations = {};

    // the object to hold the denormalized query responses
    // const example = {
    //   [component]: {
    //     ast,
    //     refetch: FunctionToRefetchQuery,
    //     response: DenormalizedResponse,
    //     response: {
    //       [key]: DenormalizedResponse // if a key exists
    //     }
    //   }
    // }
    this.cachedQueries = {};

    // const example = {
    //   [subscriptionString]: {
    //     ast: SubscriptionAST,
    //     response: DenormalizedResponseWithUnsub
    //   }
    // };
    this.cachedSubscriptions = {};

    // a flag thrown by the invalidate function and reset when that query is added to the queue
    this._willInvalidateListener = false;

    // const example = {
    //   [mutationName]: {
    //     [component]: mutationHandlerFn
    //   }
    // }
    this.mutationHandlers = {};

    // denormalized deps is an object with entities for keys. 
    // The value of each entity is an object with uids for keys.
    // the value of each UID is a set of components
    // const example = {
    //   Pets: {
    //     1: {
    //       [component]: ['key1', 'key2']
    //     }
    //   }
    // }
    this.denormalizedDeps = {};

    // not stored in _cachedQueries in able to compare old vs new deps
    // const example = {
    //   [component]: Set(...['Pets.1', 'Pets.2']),
    //   [ifKeyComponent]: {
    //     [key]: Set(...['Pets.1', 'Pets.2'])
    //   }
    // }
    this.normalizedDeps = {};
  }

  create({store, transport, schema, getToState = defaultGetToState, paginationWords, idFieldName = 'id'}) {
    // the redux store
    this.store = store || this.store;

    //the cashay state
    this.getState = getToState ? () => getToState(store) : this.getState;

    // the reserved arguments for cusor-based pagination
    this.paginationWords = Object.assign({}, defaultPaginationWords, paginationWords);

    // the field that contains the UID
    this.idFieldName = idFieldName || this.idFieldName;

    // the default function to send the queryString to the server (usually HTTP or WS)
    this.transport = transport || this.transport;

    // the client graphQL schema
    this.schema = schema || this.schema;
  }

  /**
   * a method given to a mutation callback that turns on a global.
   * if true, then we know to queue up a requery
   */
  _invalidate() {
    this._willInvalidateListener = true;
  }

  getTransport() {
    return this.transport;
  }

  /**
   * A method that accepts a GraphQL query and returns a result using only local data.
   * If it cannot complete the request on local data alone, it also asks the server for the data that it does not have.
   *
   * @param {String} queryString The GraphQL query string, exactly as you'd send it to a GraphQL server
   * @param {Object} options The optional objects to include with the query
   *
   * @property {String} options.component A string to match the component.
   * @property {String} options.key A string to uniquely match the component insance.
   * @property {Boolean} options.forceFetch is true if the query is to ignore all local data and fetch new data
   * @property {Function} options.transport The function used to send the data request to GraphQL, if different from default
   * @property {Object} options.variables are the variables sent along with the query
   * @property {Object} options.mutationHandlers the functions used to change the local data when a mutation occurs
   * @property {Object} options.customMutations if mutations are too complex to be autogenerated (rare), write them here
   *
   * @returns {Object} The denormalized object like GraphQL would return, with additional `isComplete` and `firstRun` flags
   *
   */
  query(queryString, options = {}) {
    //if you call forceFetch in a mapStateToProps, you're gonna have a bad time (it'll refresh on EVERY dispatch)
    const {key} = options;
    const forceFetch = Boolean(options.forceFetch);

    // Each component can have only 1 unique queryString/variable combo. This keeps memory use minimal.
    // if 2 components have the same queryString/variable but a different component, it'll fetch twice
    const component = options.component || queryString;

    // get the result, containing a response, queryString, and options to re-call the query
    const fastResult = this.cachedQueries[component];

    // if we got local data cached already, send it back fast
    if (!forceFetch && fastResult && fastResult.response) {
      if (!key) {
        return fastResult.response;
      } else if (fastResult.response[key]) {
        return fastResult.response[key];
      }
    }
    const cashayDataState = this.getState().data;
    
    // save the query so we can call it from anywhere
    if (!fastResult) {
      const refetch = key => {
        this.query(queryString, {
          key,
          forceFetch: true,
          transport: options.transport || this.getTransport()
        });
      };
      this.cachedQueries[component] = new CachedQuery(queryString, this.schema, this.idFieldName, refetch)
      invalidateMutationsOnNewQuery(component, this.cachedMutations);
    }

    const transport = options.transport || this.transport;

    const cachedQuery = this.cachedQueries[component];

    // override singleton defaults with query-specific values
    const variables = getVariables(options.variables, cashayDataState, component, key, cachedQuery.response);

    // create an AST that we can mutate
    const {paginationWords, idFieldName, schema} = this;
    const context = buildExecutionContext(cachedQuery.ast, {
      cashayDataState,<commit_msg>throw error early if required params aren't defined properly handle new getState<commit_after>import {INSERT_QUERY, INSERT_MUTATION, SET_ERROR} from './normalize/duck';
import denormalizeStore from './normalize/denormalizeStore';
import {rebuildOriginalArgs} from './normalize/denormalizeHelpers';
import normalizeResponse from './normalize/normalizeResponse';
import {printMinimalQuery} from './query/printMinimalQuery';
import {shortenNormalizedResponse, invalidateMutationsOnNewQuery} from './query/queryHelpers';
import {isObject, checkMutationInSchema} from './utils';
import mergeStores from './normalize/mergeStores';
import {CachedMutation, CachedQuery, MutationShell} from './helperClasses';
import flushDependencies from './query/flushDependencies';
import {parse, buildExecutionContext, getVariables, clone} from './utils';
import namespaceMutation from './mutate/namespaceMutation';
import createMutationFromQuery from './mutate/createMutationFromQuery';
import removeNamespacing from './mutate/removeNamespacing';
import makeFriendlyStore from './mutate/makeFriendlyStore';
import addDeps from './normalize/addDeps';
import mergeMutations from './mutate/mergeMutations';
import {print} from 'graphql/language/printer';
import ActiveComponentsObj from './mutate/ActiveComponentsObj';

const defaultGetToState = store => store.getState().cashay;
const defaultPaginationWords = {
  before: 'before',
  after: 'after',
  first: 'first',
  last: 'last'
};

class Cashay {
  constructor() {
    // many mutations can share the same mutationName, making it hard to cache stuff without adding complexity
    // we can assume that a mutationName + the components it affects = a unique, reproduceable fullMutation
    // const example = {
    //   [mutationName]: {
    //     activeComponentsObj: {
    //       [component]: key || Boolean
    //     },
    //     fullMutation: MutationString,
    //     variableEnhancers: [variableEnhancerFn],
    //     singles: {
    //       [component]: {
    //          ast: MutationAST,
    //          variableEnhancers: [variableEnhancerFn]
    //       }
    //     }
    //   }
    // }
    this.cachedMutations = {};

    // the object to hold the denormalized query responses
    // const example = {
    //   [component]: {
    //     ast,
    //     refetch: FunctionToRefetchQuery,
    //     response: DenormalizedResponse,
    //     response: {
    //       [key]: DenormalizedResponse // if a key exists
    //     }
    //   }
    // }
    this.cachedQueries = {};

    // const example = {
    //   [subscriptionString]: {
    //     ast: SubscriptionAST,
    //     response: DenormalizedResponseWithUnsub
    //   }
    // };
    this.cachedSubscriptions = {};

    // a flag thrown by the invalidate function and reset when that query is added to the queue
    this._willInvalidateListener = false;

    // const example = {
    //   [mutationName]: {
    //     [component]: mutationHandlerFn
    //   }
    // }
    this.mutationHandlers = {};

    // denormalized deps is an object with entities for keys. 
    // The value of each entity is an object with uids for keys.
    // the value of each UID is a set of components
    // const example = {
    //   Pets: {
    //     1: {
    //       [component]: ['key1', 'key2']
    //     }
    //   }
    // }
    this.denormalizedDeps = {};

    // not stored in _cachedQueries in able to compare old vs new deps
    // const example = {
    //   [component]: Set(...['Pets.1', 'Pets.2']),
    //   [ifKeyComponent]: {
    //     [key]: Set(...['Pets.1', 'Pets.2'])
    //   }
    // }
    this.normalizedDeps = {};
  }

  create({store, transport, schema, getToState, paginationWords, idFieldName = 'id'}) {
    // the redux store
    this.store = store || this.store;

    // if a user-defined function is supplied, use it. otherwise, use what we already have (or the deafult)
    this.getState = getToState ? () => getToState(store) : this.getState || (() => defaultGetToState(store));

    // the reserved arguments for cusor-based pagination
    this.paginationWords = Object.assign({}, defaultPaginationWords, paginationWords);

    // the field that contains the UID
    this.idFieldName = idFieldName || this.idFieldName;

    // the default function to send the queryString to the server (usually HTTP or WS)
    this.transport = transport || this.transport;

    // the client graphQL schema
    this.schema = schema || this.schema;
  }

  /**
   * a method given to a mutation callback that turns on a global.
   * if true, then we know to queue up a requery
   */
  _invalidate() {
    this._willInvalidateListener = true;
  }

  getTransport() {
    return this.transport;
  }

  /**
   * A method that accepts a GraphQL query and returns a result using only local data.
   * If it cannot complete the request on local data alone, it also asks the server for the data that it does not have.
   *
   * @param {String} queryString The GraphQL query string, exactly as you'd send it to a GraphQL server
   * @param {Object} options The optional objects to include with the query
   *
   * @property {String} options.component A string to match the component.
   * @property {String} options.key A string to uniquely match the component insance.
   * @property {Boolean} options.forceFetch is true if the query is to ignore all local data and fetch new data
   * @property {Function} options.transport The function used to send the data request to GraphQL, if different from default
   * @property {Object} options.variables are the variables sent along with the query
   * @property {Object} options.mutationHandlers the functions used to change the local data when a mutation occurs
   * @property {Object} options.customMutations if mutations are too complex to be autogenerated (rare), write them here
   *
   * @returns {Object} The denormalized object like GraphQL would return, with additional `isComplete` and `firstRun` flags
   *
   */
  query(queryString, options = {}) {
    //if you call forceFetch in a mapStateToProps, you're gonna have a bad time (it'll refresh on EVERY dispatch)
    const {key} = options;
    const forceFetch = Boolean(options.forceFetch);

    // Each component can have only 1 unique queryString/variable combo. This keeps memory use minimal.
    // if 2 components have the same queryString/variable but a different component, it'll fetch twice
    const component = options.component || queryString;

    // get the result, containing a response, queryString, and options to re-call the query
    const fastResult = this.cachedQueries[component];

    // if we got local data cached already, send it back fast
    if (!forceFetch && fastResult && fastResult.response) {
      if (!key) {
        return fastResult.response;
      } else if (fastResult.response[key]) {
        return fastResult.response[key];
      }
    }
    const transport = options.transport || this.transport;
    // Make sure we got everything we need
    if (!this.store || !this.schema || !transport) {
      throw new Error('Cashay requires a store, schema, and transport')
    }
    // save the query so we can call it from anywhere
    if (!fastResult) {
      const refetch = key => {
        this.query(queryString, {
          key,
          forceFetch: true,
          transport: options.transport || this.getTransport()
        });
      };
      this.cachedQueries[component] = new CachedQuery(queryString, this.schema, this.idFieldName, refetch);
      invalidateMutationsOnNewQuery(component, this.cachedMutations);
    }

    const cachedQuery = this.cachedQueries[component];

    const cashayDataState = this.getState().data;
    // override singleton defaults with query-specific values
    const variables = getVariables(options.variables, cashayDataState, component, key, cachedQuery.response);

    // create an AST that we can mutate
    const {paginationWords, idFieldName, schema} = this;
    const context = buildExecutionContext(cachedQuery.ast, {
      cashayDataState,
{'fraction_non_alphanumeric': 0.1049888782967906, 'fraction_numerical': 0.001143946615824595, 'mean_word_length': 2.934, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6291608', 'n_tokens_mistral': 4385, 'n_tokens_neox': 4285, 'n_words': 1454}