@apollo/client/core#InMemoryCache TypeScript Examples

The following examples show how to use @apollo/client/core#InMemoryCache. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: graphql.module.ts    From one-platform with MIT License 6 votes vote down vote up
export function createApollo( httpLink: HttpLink ): any {
  const link = ApolloLink.from( [ cleanTypeName, auth, httpLink.create( { uri } ) ] );
  return {
    link,
    cache: new InMemoryCache(),
    name: 'Search WebApp Client',
    version: '1.0',
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'network-only',
      }
    }
  };
}
Example #2
Source File: test-runner.ts    From balancer-subgraph-v2 with MIT License 6 votes vote down vote up
runTestCases = async (groupName: string, testCases: TestCase[]): void => {
  const linkOptions = { uri: SUBGRAPH_QUERY_ENDPOINT, fetch };
  const link = createHttpLink(linkOptions);
  const cache = new InMemoryCache();
  let aClient = new ApolloClient({ link, cache });

  describe(`${groupName} resolvers`, () => {
    for (let testCase of testCases) {
      it(testCase.id, async () => {
        const res = await aClient.query({
          query: testCase.query,
          variables: testCase.variables || {},
        });
        expect(res).toMatchSnapshot();
      });
    }
  });
}
Example #3
Source File: nftx.service.ts    From runebot with MIT License 6 votes vote down vote up
constructor(
    protected readonly configService: AppConfigService,
    protected readonly cacheService: CacheService,
    protected readonly etherService: EthereumService,
    protected readonly dataStoreService: DataStoreService,
  ) {
    super();
    this._nftxClient = new ApolloClient({
      uri: this.configService.bot.nftxApi,
      cache: new InMemoryCache(),
      defaultOptions: this.apolloDefaultOptions,
    });
    this._sushiClient = new ApolloClient({
      uri: this.configService.bot.sushiApi,
      cache: new InMemoryCache(),
      defaultOptions: this.apolloDefaultOptions,
    });
  }
Example #4
Source File: price-scan.ts    From runebot with MIT License 6 votes vote down vote up
async function getVaultTokens() {
  const ids = [];
  const url = `https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2`;
  const query = `
    query {
      vault(id:"0x87931e7ad81914e7898d07c68f145fc0a553d8fb") {
        holdings (first: 1000) {
          tokenId
        }
      }
    }
  `
  const client = new ApolloClient({
    uri: url,
    cache: new InMemoryCache()
  });
  try {
    const response = await client.query({
      query: gql(query)
    })
    for (const wiz of response.data.vault.holdings) {
      ids.push(wiz.tokenId);
    }
  } catch(error) {
    console.log(error);
  }
  checkVaultIds(ids);
}
Example #5
Source File: client.ts    From vvs-ui with GNU General Public License v3.0 5 votes vote down vote up
blockClient = new ApolloClient({
  link: new HttpLink({
    fetch,
    uri: process.env.BLOCKS_ENDPOINT,
  }),
  cache: new InMemoryCache(),
})
Example #6
Source File: client.ts    From vvs-ui with GNU General Public License v3.0 5 votes vote down vote up
client = new ApolloClient({
  link: new HttpLink({
    fetch,
    uri: process.env.EXCHANGE_ENDPOINT,
  }),
  cache: new InMemoryCache(),
})
Example #7
Source File: ra-auth-jwt.ts    From amplication with Apache License 2.0 5 votes vote down vote up
jwtAuthProvider: AuthProvider = {
  login: async (credentials: Credentials) => {
    const apolloClient = new ApolloClient({
      uri: "/graphql",
      cache: new InMemoryCache(),
    });

    const userData = await apolloClient.mutate<LoginMutateResult>({
      mutation: LOGIN,
      variables: {
        ...credentials,
      },
    });

    if (userData && userData.data?.login.username) {
      localStorage.setItem(
        CREDENTIALS_LOCAL_STORAGE_ITEM,
        createBearerAuthorizationHeader(userData.data.login?.accessToken)
      );
      localStorage.setItem(
        USER_DATA_LOCAL_STORAGE_ITEM,
        JSON.stringify(userData.data)
      );
      return Promise.resolve();
    }
    return Promise.reject();
  },
  logout: () => {
    localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
    return Promise.resolve();
  },
  checkError: ({ status }: any) => {
    if (status === 401 || status === 403) {
      localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
      return Promise.reject();
    }
    return Promise.resolve();
  },
  checkAuth: () => {
    return localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM)
      ? Promise.resolve()
      : Promise.reject();
  },
  getPermissions: () => Promise.reject("Unknown method"),
  getIdentity: () => {
    const str = localStorage.getItem(USER_DATA_LOCAL_STORAGE_ITEM);
    const userData: LoginMutateResult = JSON.parse(str || "");

    return Promise.resolve({
      id: userData.login.username,
      fullName: userData.login.username,
      avatar: undefined,
    });
  },
}
Example #8
Source File: ra-auth-http.ts    From amplication with Apache License 2.0 5 votes vote down vote up
httpAuthProvider: AuthProvider = {
  login: async (credentials: Credentials) => {
    const apolloClient = new ApolloClient({
      uri: "/graphql",
      cache: new InMemoryCache(),
    });

    const userData = await apolloClient.mutate<LoginMutateResult>({
      mutation: LOGIN,
      variables: {
        ...credentials,
      },
    });

    if (userData && userData.data?.login.username) {
      localStorage.setItem(
        CREDENTIALS_LOCAL_STORAGE_ITEM,
        createBasicAuthorizationHeader(
          credentials.username,
          credentials.password
        )
      );
      localStorage.setItem(
        USER_DATA_LOCAL_STORAGE_ITEM,
        JSON.stringify(userData.data)
      );
      return Promise.resolve();
    }
    return Promise.reject();
  },
  logout: () => {
    localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
    return Promise.resolve();
  },
  checkError: ({ status }: any) => {
    if (status === 401 || status === 403) {
      localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
      return Promise.reject();
    }
    return Promise.resolve();
  },
  checkAuth: () => {
    return localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM)
      ? Promise.resolve()
      : Promise.reject();
  },
  getPermissions: () => Promise.reject("Unknown method"),
  getIdentity: () => {
    const str = localStorage.getItem(USER_DATA_LOCAL_STORAGE_ITEM);
    const userData: LoginMutateResult = JSON.parse(str || "");

    return Promise.resolve({
      id: userData.login.username,
      fullName: userData.login.username,
      avatar: undefined,
    });
  },
}
Example #9
Source File: configured-command.ts    From amplication with Apache License 2.0 5 votes vote down vote up
client = new ApolloClient({
    cache: new InMemoryCache(),
  });
Example #10
Source File: create-apollo.ts    From svvs with MIT License 5 votes vote down vote up
export function createApollo(httpLink: HttpLink) {
  return {
    cache: new InMemoryCache(),
    link: httpLink.create({
      uri: environment.graphql.uri,
    }),
  }
}
Example #11
Source File: apollo-link.test.ts    From graphql-mesh with MIT License 5 votes vote down vote up
describe('GraphApolloLink', () => {
  let client: ApolloClient<any>;
  let mesh: MeshInstance;
  beforeEach(async () => {
    mesh = await getTestMesh();
    client = new ApolloClient({
      link: new MeshApolloLink(mesh),
      cache: new InMemoryCache(),
    });
  });
  afterEach(() => {
    mesh?.destroy();
  });
  it('should handle queries correctly', async () => {
    const result = await client.query({
      query: parse(/* GraphQL */ `
        query Greetings {
          greetings
        }
      `),
    });
    expect(result.error).toBeUndefined();
    expect(result.errors?.length).toBeFalsy();
    expect(result.data).toEqual({
      greetings: 'This is the `greetings` field of the root `Query` type',
    });
  });
  it('should handle subscriptions correctly', async () => {
    const observable = client.subscribe({
      query: parse(/* GraphQL */ `
        subscription Time {
          time
        }
      `),
    });
    const asyncIterable =
      observableToAsyncIterable<FetchResult<any, Record<string, any>, Record<string, any>>>(observable);
    let i = 0;
    for await (const result of asyncIterable) {
      i++;
      if (i === 2) {
        break;
      }
      expect(result.errors?.length).toBeFalsy();
      const date = new Date(result?.data?.time);
      expect(date.getFullYear()).toBe(new Date().getFullYear());
    }
    expect(i).toBe(2);
  });
});
Example #12
Source File: graphql.module.ts    From OrchardSkills.OrchardCore.Headless with MIT License 5 votes vote down vote up
// <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
  return {
    link: httpLink.create({uri}),
    cache: new InMemoryCache(),
  };
}
Example #13
Source File: main.ts    From vueconf-london with MIT License 5 votes vote down vote up
apolloClient = new ApolloClient({
  link: combinedHttpAndWebsocketLink,
  cache: new InMemoryCache(),
})
Example #14
Source File: graphql-client.ts    From dt-mergebot with MIT License 5 votes vote down vote up
cache = new InMemoryCache()
Example #15
Source File: EntityStoreWatcher.test.ts    From apollo-cache-policies with Apache License 2.0 4 votes vote down vote up
describe("#EntityStoreWatcher", () => {
  let entityStoreWatcher: EntityStoreWatcher;
  let entityTypeMap: EntityTypeMap;
  let entityStore: EntityStore;
  let policies: Policies;
  let reactiveVarsCache: ReactiveVarsCache;
  let dateNowSpy: any;

  beforeEach(() => {
    dateNowSpy = jest.spyOn(Date, "now").mockReturnValue(0);
    policies = new Policies({ cache: new InMemoryCache() });
    entityTypeMap = new EntityTypeMap();
    entityStore = new EntityStore.Root({
      policies,
    });
    reactiveVarsCache = new ReactiveVarsCache({
      cache: new InvalidationPolicyCache(),
    });
    entityStoreWatcher = new EntityStoreWatcher({
      policies,
      entityTypeMap,
      entityStore,
      reactiveVarsCache,
      updateCollectionField: () => { }
    });
  });

  afterEach(() => {
    dateNowSpy.mockRestore();
  });

  describe("#merge", () => {
    let entityTypeMapSpy: any;

    beforeEach(() => {
      entityTypeMapSpy = jest.spyOn(entityTypeMap, "write");
    });

    afterEach(() => {
      entityTypeMapSpy.mockRestore();
    });

    test("should call the entityStore merge", () => {
      const mergeSpy = jest.spyOn(entityStore, "merge");
      entityStoreWatcher = new EntityStoreWatcher({
        policies,
        entityTypeMap,
        entityStore,
        reactiveVarsCache,
        updateCollectionField: () => { }
      });
      const mergeArgs: [string, StoreObject] = [
        "ROOT_QUERY",
        {
          employees: {
            __typename: "EmployeesResponse",
          },
        },
      ];
      entityStore.merge(...mergeArgs);
      expect(mergeSpy).toHaveBeenCalledWith(...mergeArgs);
      mergeSpy.mockRestore();
    });

    describe("with a query entity", () => {
      test("should call the entityTypeMap write", () => {
        // @ts-ignore
        entityStoreWatcher.merge("ROOT_QUERY", {
          employees: {
            __typename: "EmployeesResponse",
          },
        });
        expect(entityTypeMapSpy).toHaveBeenCalledWith(
          "EmployeesResponse",
          "ROOT_QUERY",
          "employees"
        );
      });
    });

    describe("with a normalized cache entity", () => {
      test("should call the entityTypeMap write", () => {
        entityStore.merge("Employee:1", {
          __typename: "Employee",
          id: 1,
        });
        expect(entityTypeMapSpy).toHaveBeenCalledWith("Employee", "Employee:1");
      });
    });
  });

  describe("#delete", () => {
    let entityTypeMapSpy: any;

    beforeEach(() => {
      entityTypeMapSpy = jest.spyOn(entityTypeMap, "evict");
    });

    afterEach(() => {
      entityTypeMapSpy.mockRestore();
    });

    test("should call the entityStore delete", () => {
      const deleteSpy = jest.spyOn(entityStore, "delete");
      entityStoreWatcher = new EntityStoreWatcher({
        policies,
        entityTypeMap,
        entityStore,
        reactiveVarsCache,
        updateCollectionField: () => { }
      });
      entityStore.delete("ROOT_QUERY", "employees", undefined);
      expect(deleteSpy).toHaveBeenCalledWith(
        "ROOT_QUERY",
        "employees",
        undefined
      );
      deleteSpy.mockRestore();
    });

    describe("with a query entity", () => {
      describe("without variables", () => {
        test("should call the entityTypeMap evict", () => {
          entityStore.delete("ROOT_QUERY", "employees");
          expect(entityTypeMapSpy).toHaveBeenCalledWith(
            "ROOT_QUERY",
            "employees"
          );
        });
      });

      describe("with variables", () => {
        test("should call the entityTypeMap evict", () => {
          entityStore.delete("ROOT_QUERY", "employees", {
            name: "Test",
          });
          // prettier-ignore
          expect(entityTypeMapSpy).toHaveBeenCalledWith("ROOT_QUERY", "employees({\"name\":\"Test\"})");
        });
      });
    });

    describe("with a normalized cache entity", () => {
      test("should call the entityTypeMap evict", () => {
        entityStore.delete("Employee:1");
        expect(entityTypeMapSpy).toHaveBeenCalledWith("Employee:1", undefined);
      });
    });
  });

  describe("#clear", () => {
    test("should call the entityStore clear", () => {
      const clearSpy = jest.spyOn(entityStore, "clear");
      entityStoreWatcher = new EntityStoreWatcher({
        policies,
        entityTypeMap,
        entityStore,
        reactiveVarsCache,
        updateCollectionField: () => { }
      });
      entityStore.clear();
      expect(clearSpy).toHaveBeenCalled();
      clearSpy.mockRestore();
    });

    test("should call the entityTypeMap clear", () => {
      const clearSpy = jest.spyOn(entityTypeMap, "clear");
      entityStore.clear();
      expect(clearSpy).toHaveBeenCalled();
      clearSpy.mockRestore();
    });
  });

  describe("#replace", () => {
    let replaceSpy: any;

    beforeEach(() => {
      replaceSpy = jest.spyOn(entityStore, "replace");
      entityStoreWatcher = new EntityStoreWatcher({
        policies,
        entityTypeMap,
        entityStore,
        reactiveVarsCache,
        updateCollectionField: () => { }
      });
    });

    afterEach(() => {
      replaceSpy.mockRestore();
    });

    test("should call entityStore replace without invalidation data", () => {
      // @ts-ignore
      entityStore.replace({
        invalidation: {
          entitiesById: {},
        },
      });

      expect(replaceSpy).toHaveBeenCalledWith({});
    });

    test("should pause the entityStoreWatcher before calling entityStore replace and then resume", () => {
      // @ts-ignore
      let pauseSpy = jest.spyOn(entityStoreWatcher, "pause");
      // @ts-ignore
      let watchSpy = jest.spyOn(entityStoreWatcher, "watch");
      // @ts-ignore
      entityStore.replace({
        invalidation: {
          entitiesById: {},
        },
      });
      expect(pauseSpy).toHaveBeenCalledBefore(replaceSpy);
      expect(watchSpy).toHaveBeenCalledAfter(replaceSpy);
    });
  });

  describe("#pause and #watch", () => {
    test("should pause entity store proxies and resume them", () => {
      const entityStoreMergeSpy = jest
        .spyOn(entityStore, "merge")
        .mockImplementation();
      const entityStoreDeleteSpy = jest
        .spyOn(entityStore, "delete")
        .mockImplementation();
      const entityStoreClearSpy = jest
        .spyOn(entityStore, "clear")
        .mockImplementation();
      const entityStoreReplaceSpy = jest
        .spyOn(entityStore, "replace")
        .mockImplementation();
      entityStoreWatcher = new EntityStoreWatcher({
        policies,
        entityTypeMap,
        entityStore,
        reactiveVarsCache,
        updateCollectionField: () => { }
      });
      const entityStoreWatcherMergeSpy = jest
        // @ts-ignore
        .spyOn(entityStoreWatcher, "merge")
        .mockImplementation();
      const entityStoreWatcherDeleteSpy = jest
        // @ts-ignore
        .spyOn(entityStoreWatcher, "delete")
        .mockImplementation();
      const entityStoreWatcherClearSpy = jest
        // @ts-ignore
        .spyOn(entityStoreWatcher, "clear")
        .mockImplementation();
      const entityStoreWatcherReplaceSpy = jest
        // @ts-ignore
        .spyOn(entityStoreWatcher, "replace")
        .mockImplementation();

      // @ts-ignore
      entityStoreWatcher.pause();
      entityStore.merge("test", {});
      entityStore.delete("test");
      entityStore.clear();
      entityStore.replace({});
      expect(entityStoreMergeSpy).toHaveBeenCalled();
      expect(entityStoreDeleteSpy).toHaveBeenCalled();
      expect(entityStoreClearSpy).toHaveBeenCalled();
      expect(entityStoreReplaceSpy).toHaveBeenCalled();

      expect(entityStoreWatcherMergeSpy).not.toHaveBeenCalled();
      expect(entityStoreWatcherDeleteSpy).not.toHaveBeenCalled();
      expect(entityStoreWatcherClearSpy).not.toHaveBeenCalled();
      expect(entityStoreWatcherReplaceSpy).not.toHaveBeenCalled();

      // @ts-ignore
      entityStoreWatcher.watch();
      entityStore.merge("test", {});
      entityStore.delete("test");
      entityStore.clear();
      entityStore.replace({});

      expect(entityStoreWatcherMergeSpy).toHaveBeenCalled();
      expect(entityStoreWatcherDeleteSpy).toHaveBeenCalled();
      expect(entityStoreWatcherClearSpy).toHaveBeenCalled();
      expect(entityStoreWatcherReplaceSpy).toHaveBeenCalled();
    });
  });
});
Example #16
Source File: InvalidationPolicyCache.ts    From apollo-cache-policies with Apache License 2.0 4 votes vote down vote up
/**
 * Extension of Apollo in-memory cache which adds support for cache policies.
 */

// @ts-ignore: Private API overloads
export default class InvalidationPolicyCache extends InMemoryCache {
  // @ts-ignore: Initialize in parent constructor
  protected entityTypeMap: EntityTypeMap;
  // @ts-ignore: Initialize in parent constructor
  protected entityStoreWatcher: EntityStoreWatcher;
  protected invalidationPolicyManager!: InvalidationPolicyManager;
  protected cacheResultProcessor!: CacheResultProcessor;
  protected entityStoreRoot: any;
  protected isBroadcasting: boolean;
  protected reactiveVarsCache!: ReactiveVarsCache;
  protected invalidationPolicies: InvalidationPolicies;
  protected enableCollections: boolean;
  protected isInitialized: boolean;

  constructor(config: InvalidationPolicyCacheConfig = {}) {
    const { invalidationPolicies = {}, enableCollections = false, ...inMemoryCacheConfig } = config;
    super(inMemoryCacheConfig);

    this.enableCollections = enableCollections;
    this.invalidationPolicies = invalidationPolicies;
    this.isBroadcasting = false;
    this.isInitialized = true;
    this.reactiveVarsCache = initReactiveVarsCache(this);

    // Once the InMemoryCache has called `init()` in the super constructor, we initialize
    // the InvalidationPolicyCache objects.
    this.reinitialize();
  }

  // Whenever the InMemoryCache reinitializes the entityStore by calling `init()` again,
  // we must also reinitialize all of the InvalidationPolicyCache objects.
  private reinitialize() {
    // @ts-ignore Data is a private API
    this.entityStoreRoot = this.data;
    this.entityTypeMap = new EntityTypeMap();
    this.entityStoreWatcher = new EntityStoreWatcher({
      entityStore: this.entityStoreRoot,
      entityTypeMap: this.entityTypeMap,
      policies: this.policies,
      reactiveVarsCache: this.reactiveVarsCache,
      updateCollectionField: this.updateCollectionField.bind(this),
    });
    this.invalidationPolicyManager = new InvalidationPolicyManager({
      policies: this.invalidationPolicies,
      entityTypeMap: this.entityTypeMap,
      cacheOperations: {
        evict: (...args) => this.evict(...args),
        modify: (...args) => this.modify(...args),
        readField: (...args) => this.readField(...args),
      },
    });
    this.cacheResultProcessor = new CacheResultProcessor({
      invalidationPolicyManager: this.invalidationPolicyManager,
      // @ts-ignore This field is assigned in the parent constructor
      entityTypeMap: this.entityTypeMap,
      cache: this,
    });
    this.reactiveVarsCache.reset();
  }

  // @ts-ignore private API
  private init() {
    // @ts-ignore private API
    super.init();

    // After init is called, the entity store has been reset so we must
    // reinitialize all invalidation policy objects.
    if (this.isInitialized) {
      this.reinitialize();
    }
  }

  protected readField<T>(
    fieldNameOrOptions?: string | ReadFieldOptions,
    from?: StoreObject | Reference
  ) {
    if (!fieldNameOrOptions) {
      return;
    }

    const options = typeof fieldNameOrOptions === "string"
      ? {
        fieldName: fieldNameOrOptions,
        from,
      }
      : fieldNameOrOptions;

    if (void 0 === options.from) {
      options.from = { __ref: 'ROOT_QUERY' };
    }

    return this.policies.readField<T>(
      options,
      {
        store: this.entityStoreRoot,
      }
    );
  }

  protected broadcastWatches() {
    this.isBroadcasting = true;
    super.broadcastWatches();
    this.isBroadcasting = false;
  }

  // Determines whether the cache's data reference is set to the root store. If not, then there is an ongoing optimistic transaction
  // being applied to a new layer.
  isOperatingOnRootData() {
    // @ts-ignore
    return this.data === this.entityStoreRoot;
  }

  modify(options: Cache.ModifyOptions) {
    const modifyResult = super.modify(options);

    if (
      !this.invalidationPolicyManager.isPolicyEventActive(
        InvalidationPolicyEvent.Write
      ) ||
      !modifyResult
    ) {
      return modifyResult;
    }

    const { id = "ROOT_QUERY", fields } = options;

    if (isQuery(id)) {
      Object.keys(fields).forEach((storeFieldName) => {
        const fieldName = fieldNameFromStoreName(storeFieldName);

        const typename = this.entityTypeMap.readEntityById(
          makeEntityId(id, fieldName)
        )?.typename;

        if (!typename) {
          return;
        }

        this.invalidationPolicyManager.runWritePolicy(typename, {
          parent: {
            id,
            fieldName,
            storeFieldName,
            ref: makeReference(id),
          },
        });
      });
    } else {
      const typename = this.entityTypeMap.readEntityById(id)?.typename;

      if (!typename) {
        return modifyResult;
      }

      this.invalidationPolicyManager.runWritePolicy(typename, {
        parent: {
          id,
          ref: makeReference(id),
        },
      });
    }

    if (options.broadcast) {
      this.broadcastWatches();
    }

    return modifyResult;
  }

  write(options: Cache.WriteOptions<any, any>) {
    const writeResult = super.write(options);

    // Do not trigger a write policy if the current write is being applied to an optimistic data layer since
    // the policy will later be applied when the server data response is received.
    if (
      (!this.invalidationPolicyManager.isPolicyEventActive(
        InvalidationPolicyEvent.Write
      ) &&
        !this.invalidationPolicyManager.isPolicyEventActive(
          InvalidationPolicyEvent.Read
        )) ||
      !this.isOperatingOnRootData()
    ) {
      return writeResult;
    }

    this.cacheResultProcessor.processWriteResult(options);

    if (options.broadcast) {
      this.broadcastWatches();
    }

    return writeResult;
  }

  // Evicts all entities of the given type matching the filter criteria. Returns a list of evicted entities
  // by reference.
  evictWhere<EntityType>(filters: { __typename: string, filter?: FragmentWhereFilter<EntityType> }) {
    const { __typename, filter } = filters;

    const references = this.readReferenceWhere({
      __typename,
      filter,
    });

    references.forEach((ref) => this.evict({ id: ref.__ref, broadcast: false }));
    this.broadcastWatches();

    return references;
  }

  evict(options: Cache.EvictOptions): boolean {
    const { fieldName, args } = options;
    let { id } = options;
    if (!id) {
      if (Object.prototype.hasOwnProperty.call(options, "id")) {
        return false;
      }
      id = "ROOT_QUERY";
    }

    if (
      this.invalidationPolicyManager.isPolicyEventActive(
        InvalidationPolicyEvent.Evict
      )
    ) {
      const { typename } =
        this.entityTypeMap.readEntityById(makeEntityId(id, fieldName)) ?? {};

      if (typename) {
        const storeFieldName =
          isQuery(id) && fieldName
            ? this.policies.getStoreFieldName({
              typename,
              fieldName,
              args,
            })
            : undefined;

        this.invalidationPolicyManager.runEvictPolicy(typename, {
          parent: {
            id,
            fieldName,
            storeFieldName,
            variables: args,
            ref: makeReference(id),
          },
        });
      }
    }

    return super.evict(options);
  }

  protected updateCollectionField(typename: string, dataId: string) {
    // Since colletion support is still experimental, only record entities in collections if enabled
    if (!this.enableCollections) {
      return;
    }

    const collectionEntityId = collectionEntityIdForType(typename);
    const collectionFieldExists = !!this.readField<Record<string, any[]>>('id', makeReference(collectionEntityId));

    // If the collection field for the type does not exist in the cache, then initialize it as
    // an empty array.
    if (!collectionFieldExists) {
      this.writeFragment({
        id: collectionEntityId,
        fragment: gql`
          fragment InitializeCollectionEntity on CacheExtensionsCollectionEntity {
            data
            id
          }
        `,
        data: {
          __typename: cacheExtensionsCollectionTypename,
          id: typename,
          data: [],
        },
      });
    }

    // If the entity does not already exist in the cache, add it to the collection field policy for its type
    if (!this.entityTypeMap.readEntityById(dataId)) {
      this.modify({
        broadcast: false,
        id: collectionEntityId,
        fields: {
          data: (existing, { canRead }) => {
            return [
              ...existing.filter((ref: Reference) => canRead(ref)),
              makeReference(dataId),
            ];
          }
        }
      });
    }
  }

  // Returns all expired entities whose cache time exceeds their type's timeToLive or as a fallback
  // the global timeToLive if specified. Evicts the expired entities by default, with an option to only report
  // them.
  private _expire(reportOnly = false) {
    const { entitiesById } = this.entityTypeMap.extract();
    const expiredEntityIds: string[] = [];

    Object.keys(entitiesById).forEach((entityId) => {
      const entity = entitiesById[entityId];
      const { storeFieldNames, dataId, fieldName, typename } = entity;

      if (isQuery(dataId) && storeFieldNames) {
        Object.keys(storeFieldNames.entries).forEach((storeFieldName) => {
          const isExpired = this.invalidationPolicyManager.runReadPolicy({
            typename,
            dataId,
            fieldName,
            storeFieldName,
            reportOnly,
          });
          if (isExpired) {
            expiredEntityIds.push(makeEntityId(dataId, storeFieldName));
          }
        });
      } else {
        const isExpired = this.invalidationPolicyManager.runReadPolicy({
          typename,
          dataId,
          fieldName,
          reportOnly,
        });
        if (isExpired) {
          expiredEntityIds.push(makeEntityId(dataId));
        }
      }
    });

    if (expiredEntityIds.length > 0) {
      this.broadcastWatches();
    }

    return expiredEntityIds;
  }

  // Expires all entities still present in the cache that have exceeded their timeToLive. By default entities are evicted
  // lazily on read if their entity is expired. Use this expire API to eagerly remove expired entities.
  expire() {
    return this._expire(false);
  }

  // Returns all expired entities still present in the cache.
  expiredEntities() {
    return this._expire(true);
  }

  // Activates the provided policy events (on read, on write, on evict) or by default all policy events.
  activatePolicyEvents(...policyEvents: InvalidationPolicyEvent[]) {
    if (policyEvents.length > 0) {
      this.invalidationPolicyManager.activatePolicies(...policyEvents);
    } else {
      this.invalidationPolicyManager.activatePolicies(
        InvalidationPolicyEvent.Read,
        InvalidationPolicyEvent.Write,
        InvalidationPolicyEvent.Evict
      );
    }
  }

  // Deactivates the provided policy events (on read, on write, on evict) or by default all policy events.
  deactivatePolicyEvents(...policyEvents: InvalidationPolicyEvent[]) {
    if (policyEvents.length > 0) {
      this.invalidationPolicyManager.deactivatePolicies(...policyEvents);
    } else {
      this.invalidationPolicyManager.deactivatePolicies(
        InvalidationPolicyEvent.Read,
        InvalidationPolicyEvent.Write,
        InvalidationPolicyEvent.Evict
      );
    }
  }

  // Returns the policy events that are currently active.
  activePolicyEvents() {
    return [
      InvalidationPolicyEvent.Read,
      InvalidationPolicyEvent.Write,
      InvalidationPolicyEvent.Evict
    ].filter(policyEvent => this.invalidationPolicyManager.isPolicyEventActive(policyEvent));
  }

  read<T>(options: Cache.ReadOptions<any>): T | null {
    const result = super.read<T>(options);

    if (
      !this.invalidationPolicyManager.isPolicyEventActive(
        InvalidationPolicyEvent.Read
      )
    ) {
      return result;
    }

    const processedResult = maybeDeepClone(result);
    const processedResultStatus = this.cacheResultProcessor.processReadResult(
      processedResult,
      options
    );

    if (processedResultStatus === ReadResultStatus.Complete) {
      return result;
    }

    this.broadcastWatches();

    return processedResultStatus === ReadResultStatus.Evicted
      ? null
      : processedResult;
  }

  diff<T>(options: Cache.DiffOptions): Cache.DiffResult<T> {
    const cacheDiff = super.diff<T>(options);

    // Diff calls made by `broadcastWatches` should not trigger the read policy
    // as these are internal reads not reflective of client action and can lead to recursive recomputation of cached data which is an error.
    // Instead, diffs will trigger the read policies for client-based reads like `readCache` invocations from watched queries outside
    // the scope of broadcasts.
    if (
      !this.invalidationPolicyManager.isPolicyEventActive(
        InvalidationPolicyEvent.Read
      ) ||
      this.isBroadcasting
    ) {
      return cacheDiff;
    }

    const { result } = cacheDiff;

    const processedResult = maybeDeepClone(result);
    const processedResultStatus = this.cacheResultProcessor.processReadResult(
      processedResult,
      options
    );

    if (processedResultStatus === ReadResultStatus.Complete) {
      return cacheDiff;
    }

    this.broadcastWatches();

    cacheDiff.complete = false;
    cacheDiff.result =
      processedResultStatus === ReadResultStatus.Evicted
        ? undefined
        : processedResult;

    return cacheDiff;
  }

  extract(optimistic = false, withInvalidation = true): NormalizedCacheObject {
    const extractedCache = super.extract(optimistic);

    if (withInvalidation) {
      // The entitiesById are sufficient alone for reconstructing the type map, so to
      // minimize payload size only inject the entitiesById object into the extracted cache
      extractedCache.invalidation = pick(
        this.entityTypeMap.extract(),
        "entitiesById"
      );
    }

    return extractedCache;
  }

  // Supports reading a collection of entities by type from the cache and filtering them by the given fields. Returns
  // a list of the dereferenced matching entities from the cache based on the given fragment.
  readFragmentWhere<FragmentType, TVariables = any>(options: Cache.ReadFragmentOptions<FragmentType, TVariables> & {
    filter?: FragmentWhereFilter<FragmentType>;
  }): FragmentType[] {
    const { fragment, filter, ...restOptions } = options;
    const fragmentDefinition = fragment.definitions[0] as FragmentDefinitionNode;
    const __typename = fragmentDefinition.typeCondition.name.value;

    const matchingRefs = this.readReferenceWhere(
      {
        __typename,
        filter
      }
    );

    const matchingFragments = matchingRefs.map(ref => this.readFragment({
      ...restOptions,
      fragment,
      id: ref.__ref,
    }));

    return compact(matchingFragments);
  }

  // Supports reading a collection of references by type from the cache and filtering them by the given fields. Returns a
  // list of the matching references.
  readReferenceWhere<T>(options: {
    __typename: string,
    filter?: FragmentWhereFilter<T>;
  }) {
    const { __typename, filter } = options;
    const collectionEntityName = collectionEntityIdForType(__typename);
    const entityReferences = this.readField<Reference[]>('data', makeReference(collectionEntityName));

    if (!entityReferences) {
      return [];
    }

    if (!filter) {
      return entityReferences;
    }

    return entityReferences.filter(ref => {
      if (isFunction(filter)) {
        return filter(ref, this.readField.bind(this));
      }

      const entityFilterResults = Object.keys(filter).map(filterField => {
        // @ts-ignore
        const filterValue = filter[filterField];
        const entityValueForFilter = this.readField(filterField, ref);

        return filterValue === entityValueForFilter;
      });

      return every(entityFilterResults, Boolean);
    });
  }
}
Example #17
Source File: graphql.module.ts    From one-platform with MIT License 4 votes vote down vote up
export function createApollo(): ApolloClientOptions<any> {
  const wsClient = new WebSocketLink({
    uri: environment.WS_URL,
    options: {
      reconnect: true,
      inactivityTimeout: 0,
      reconnectionAttempts: 10,
      connectionParams: {
        Authorization: `Bearer ${window.OpAuthHelper.jwtToken}`,
      },
    },
  });

  const httpLink = createHttpLink({
    uri: environment.API_URL,
  });

  const authLink = setContext((_, { headers }) => {
    // get the authentication token from local storage if it exists
    const token = window.OpAuthHelper.jwtToken;
    // return the headers to the context so httpLink can read them
    return {
      headers: {
        ...headers,
        Authorization: token ? `Bearer ${token}` : '',
      },
    };
  });

  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === 'OperationDefinition' &&
        definition.operation === 'subscription'
      );
    },
    wsClient,
    authLink.concat(httpLink)
  );
  const retry = new RetryLink({
    delay: {
      initial: 500,
      max: Infinity,
      jitter: false,
    },
    attempts: {
      max: 5,
      retryIf: (_error, _operation) => !!_error,
    },
  }) as any;

  const error = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      graphQLErrors.map(({ message, locations, path }) =>
        console.log(
          `[GraphQL error]: Message: ${JSON.stringify(
            message
          )}, Location: ${JSON.stringify(locations)}, Path: ${JSON.stringify(
            path
          )}`
        )
      );
    }
    if (networkError && networkError['status'] === 0) {
      this.isCertificateError.next(true);
      console.log(`[Network error]: ${JSON.stringify(networkError)}`);
    }
  });

  const link = WebSocketLink.from([retry, error, splitLink]);

  return {
    name: 'Lighthouse GraphQL Client',
    version: '0.0.1',
    link,
    cache: new InMemoryCache({
      addTypename: false,
    }),
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'cache-and-network',
        errorPolicy: 'all',
      },
    },
  };
}