@apollo/client/core#makeReference TypeScript Examples

The following examples show how to use @apollo/client/core#makeReference. 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: utils.ts    From apollo-cache-policies with Apache License 2.0 7 votes vote down vote up
// Returns a query that can be used to watch a normalized cache entity by converting the fragment to a query
// and dynamically adding a type policy that returns the entity.
export function buildWatchFragmentQuery(
  options: WatchFragmentOptions & {
    fieldName: string;
    policies: Policies,
  }
): DocumentNode {
  const { fragment, id, policies, fieldName } = options;
  const fragmentDefinition = fragment.definitions[0] as FragmentDefinitionNode;

  const query = _generateQueryFromFragment({
    fragmentDefinition: fragmentDefinition,
    fieldName,
  });

  // @ts-ignore The getFieldPolicy is private but we need it here to determine
  // if the dynamic type policy we generate for the corresponding fragment has
  // already been added
  if (!policies.getFieldPolicy('Query', fieldName)) {
    policies.addTypePolicies({
      Query: {
        fields: {
          [fieldName]: {
            read(_existing) {
              return makeReference(id);
            }
          }
        }
      }
    });
  }

  return query;
}
Example #2
Source File: CacheResultProcessor.ts    From apollo-cache-policies with Apache License 2.0 6 votes vote down vote up
private processWriteSubResult(result: any) {
    const { cache, invalidationPolicyManager, entityTypeMap } = this.config;
    if (isPlainObject(result)) {
      const { __typename } = result;

      Object.keys(result).forEach((resultField) =>
        this.processWriteSubResult(result[resultField])
      );

      if (__typename) {
        const id = cache.identify(result);

        if (id) {
          const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(
            __typename
          );
          if (
            renewalPolicy === RenewalPolicy.WriteOnly ||
            renewalPolicy === RenewalPolicy.AccessAndWrite
          ) {
            entityTypeMap.renewEntity(id);
          }

          invalidationPolicyManager.runWritePolicy(__typename, {
            parent: {
              id,
              ref: makeReference(id),
            },
          });
        }
      }
    } else if (isArray(result)) {
      result.forEach((resultListItem) =>
        this.processWriteSubResult(resultListItem)
      );
    }
  }
Example #3
Source File: InvalidationPolicyCache.ts    From apollo-cache-policies with Apache License 2.0 6 votes vote down vote up
// 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 #4
Source File: InvalidationPolicyCache.ts    From apollo-cache-policies with Apache License 2.0 5 votes vote down vote up
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;
  }
Example #5
Source File: InvalidationPolicyCache.ts    From apollo-cache-policies with Apache License 2.0 5 votes vote down vote up
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);
  }
Example #6
Source File: InvalidationPolicyCache.ts    From apollo-cache-policies with Apache License 2.0 5 votes vote down vote up
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),
            ];
          }
        }
      });
    }
  }
Example #7
Source File: InvalidationPolicyManager.ts    From apollo-cache-policies with Apache License 2.0 5 votes vote down vote up
private runPolicyEvent(
    typeName: string,
    policyEvent: InvalidationPolicyEvent.Evict | InvalidationPolicyEvent.Write,
    policyMeta: PolicyActionMeta
  ) {
    const { entityTypeMap } = this.config;
    const { mutedCacheOperations } = this;
    const typePolicyForEvent = this.getTypePolicyForEvent(
      typeName,
      policyEvent
    );
    if (!typePolicyForEvent) {
      return;
    }

    let defaultPolicyAction: DefaultPolicyAction | undefined;
    let restTypePolicyTypeNames: Record<string, PolicyAction> = {} as Record<string, PolicyAction>;

    if (typeof typePolicyForEvent === 'object') {
      let { __default: _defaultPolicyAction, ..._restTypePolicyTypeNames } = typePolicyForEvent;
      defaultPolicyAction = _defaultPolicyAction;
      restTypePolicyTypeNames = _restTypePolicyTypeNames;
    } else {
      defaultPolicyAction = typePolicyForEvent;
    }

    if (defaultPolicyAction) {
      defaultPolicyAction(mutedCacheOperations, {
        storage: this.getPolicyActionStorage(`${typeName}__default`),
        ...policyMeta,
      });
    }

    Object.keys(restTypePolicyTypeNames).forEach((typePolicyTypeName: string) => {
      const typeMapEntities = entityTypeMap.readEntitiesByType(typePolicyTypeName) ?? {};
      const policyAction = (typePolicyForEvent as Record<string, PolicyAction>)[typePolicyTypeName];

      Object.values(typeMapEntities).forEach((typeMapEntity) => {
        const { dataId, fieldName, storeFieldNames } = typeMapEntity;
        if (storeFieldNames) {
          Object.keys(storeFieldNames.entries).forEach((storeFieldName) => {
            policyAction(mutedCacheOperations, {
              id: dataId,
              fieldName,
              storeFieldName,
              variables: storeFieldNames.entries[storeFieldName].variables,
              args: storeFieldNames.entries[storeFieldName].args,
              ref: makeReference(dataId),
              storage: this.getPolicyActionStorage(storeFieldName),
              ...policyMeta,
            });
          });
        } else {
          policyAction(mutedCacheOperations, {
            id: dataId,
            storage: this.getPolicyActionStorage(dataId),
            ref: makeReference(dataId),
            ...policyMeta,
          });
        }
      });
    });
  }
Example #8
Source File: CacheResultProcessor.ts    From apollo-cache-policies with Apache License 2.0 4 votes vote down vote up
processWriteResult(options: Cache.WriteOptions<any, any>) {
    const { dataId, variables, result } = options;
    const { entityTypeMap, cache, invalidationPolicyManager } = this.config;

    if (isPlainObject(result)) {
      this.processWriteSubResult(result);
    }

    if (dataId && isQuery(dataId) && isPlainObject(result)) {
      this.getFieldsForQuery(options).forEach((field) => {
        const fieldName = field.name.value;
        const typename = entityTypeMap.readEntityById(
          makeEntityId(dataId, fieldName)
        )?.typename;

        if (typename) {
          const storeFieldName = cache.policies.getStoreFieldName({
            typename,
            field,
            fieldName,
            variables,
          });

          const fieldArgs = argumentsObjectFromField(field, variables);
          const fieldVariables = variables ?? (fieldArgs !== null ? {} : undefined);

          const queryTypename = cache.policies.rootTypenamesById[dataId];
          const storeFieldNameForQuery = cache.policies.getStoreFieldName({
            typename: queryTypename,
            fieldName,
            field,
            variables,
          });

          // Write a query to the entity type map at `write` in addition to `merge` time so that we can keep track of its variables.
          entityTypeMap.write(typename, dataId, storeFieldName, fieldVariables, fieldArgs);
          entityTypeMap.write(typename, dataId, storeFieldNameForQuery, fieldVariables, fieldArgs);

          const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(
            typename
          );
          if (
            renewalPolicy === RenewalPolicy.WriteOnly ||
            renewalPolicy === RenewalPolicy.AccessAndWrite
          ) {
            entityTypeMap.renewEntity(dataId, storeFieldName);
            entityTypeMap.renewEntity(dataId, storeFieldNameForQuery);
          }

          invalidationPolicyManager.runWritePolicy(typename, {
            parent: {
              id: dataId,
              fieldName,
              storeFieldName,
              ref: makeReference(dataId),
              variables: fieldVariables,
              args: fieldArgs,
            },
          });
        }
      });
    } else if (dataId) {
      const typename = entityTypeMap.readEntityById(makeEntityId(dataId))
        ?.typename;

      if (typename) {
        const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(
          typename
        );
        if (
          renewalPolicy === RenewalPolicy.WriteOnly ||
          renewalPolicy === RenewalPolicy.AccessAndWrite
        ) {
          entityTypeMap.renewEntity(dataId);
        }

        invalidationPolicyManager.runWritePolicy(typename, {
          parent: {
            id: dataId,
            ref: makeReference(dataId),
            variables,
          },
        });
      }
    }
  }
Example #9
Source File: InvalidationPolicyManager.test.ts    From apollo-cache-policies with Apache License 2.0 4 votes vote down vote up
describe("InvalidationPolicyManager", () => {
  let invalidationPolicyManager: InvalidationPolicyManager;
  let cacheOperations: PolicyActionCacheOperations;
  let entityTypeMap: EntityTypeMap;
  let policies: InvalidationPolicies;

  beforeEach(() => {
    policies = {};
    entityTypeMap = new EntityTypeMap();
    cacheOperations = {
      evict: jest.fn((..._args: any[]): any => { }),
      modify: jest.fn((..._args: any[]): any => { }),
      readField: jest.fn((..._args: any[]): any => { }),
    };
    invalidationPolicyManager = new InvalidationPolicyManager({
      entityTypeMap,
      policies,
      cacheOperations,
    });
  });

  describe("#runPolicyEvent", () => {
    let employeePolicyActionSpy: any;
    let employeesResponsePolicyActionSpy: any;
    let actionMeta: PolicyActionMeta;
    let mutedCacheOperations: PolicyActionCacheOperations;

    beforeEach(() => {
      actionMeta = {
        parent: {
          id: "ROOT_QUERY",
          fieldName: "createEmployee",
          storeFieldName: "createEmployees({new: true})",
          ref: makeReference("ROOT_QUERY"),
          variables: { new: true },
        },
      };
      employeePolicyActionSpy = jest.fn();
      employeesResponsePolicyActionSpy = jest.fn();
      policies = {
        types: {
          CreateEmployeeResponse: {
            onEvict: {
              Employee: (...args) => employeePolicyActionSpy(...args),
              EmployeesResponse: (...args) => employeesResponsePolicyActionSpy(...args),
            },
          },
        },
      };
      invalidationPolicyManager = new InvalidationPolicyManager({
        entityTypeMap,
        policies,
        cacheOperations,
      });
      mutedCacheOperations =
        // @ts-ignore
        invalidationPolicyManager.mutedCacheOperations;
      entityTypeMap.write("Employee", "Employee:1");
      entityTypeMap.write("Employee", "Employee:2");
      entityTypeMap.write(
        "EmployeesResponse",
        "ROOT_QUERY",
        "employees({country: 'US'})",
        { country: "US" }
      );
      entityTypeMap.write(
        "EmployeesResponse",
        "ROOT_QUERY",
        "employees({country: 'CAN'})",
        { country: "CAN" }
      );
    });

    test("should call the policy action handlers with the correct arguments", () => {
      invalidationPolicyManager.runEvictPolicy(
        "CreateEmployeeResponse",
        actionMeta
      );
      expect(employeePolicyActionSpy).toHaveBeenCalledTimes(2);
      expect(employeesResponsePolicyActionSpy).toHaveBeenCalledTimes(2);

      expect(employeePolicyActionSpy.mock.calls[0][0]).toEqual(
        // @ts-ignore
        invalidationPolicyManager.mutedCacheOperations
      );
      expect(employeePolicyActionSpy.mock.calls[0][1]).toEqual({
        id: "Employee:1",
        ref: makeReference("Employee:1"),
        storage: {},
        parent: actionMeta.parent,
      });

      expect(employeePolicyActionSpy.mock.calls[1][0]).toEqual(
        mutedCacheOperations
      );
      expect(employeePolicyActionSpy.mock.calls[1][1]).toEqual({
        id: "Employee:2",
        ref: makeReference("Employee:2"),
        storage: {},
        parent: actionMeta.parent,
      });

      expect(employeesResponsePolicyActionSpy.mock.calls[0][0]).toEqual(
        mutedCacheOperations
      );
      expect(employeesResponsePolicyActionSpy.mock.calls[0][1]).toEqual({
        id: "ROOT_QUERY",
        fieldName: "employees",
        storeFieldName: "employees({country: 'US'})",
        ref: makeReference("ROOT_QUERY"),
        variables: { country: "US" },
        storage: {},
        parent: actionMeta.parent,
      });

      expect(employeesResponsePolicyActionSpy.mock.calls[1][0]).toEqual(
        mutedCacheOperations
      );
      expect(employeesResponsePolicyActionSpy.mock.calls[1][1]).toEqual({
        id: "ROOT_QUERY",
        fieldName: "employees",
        ref: makeReference("ROOT_QUERY"),
        storeFieldName: "employees({country: 'CAN'})",
        variables: { country: "CAN" },
        storage: {},
        parent: actionMeta.parent,
      });
    });

    test('should persist storage per identifier across multiple policy events', () => {
      employeePolicyActionSpy = jest.fn((_cacheOperations, policyAction) => {
        policyAction.storage.count = (policyAction.storage?.count ?? 0) + 1;
      });
      invalidationPolicyManager.runEvictPolicy(
        "CreateEmployeeResponse",
        actionMeta
      );
      expect(employeePolicyActionSpy.mock.calls[0][1]).toEqual(
        {
          id: "Employee:1",
          ref: makeReference("Employee:1"),
          storage: { count: 1 },
          parent: actionMeta.parent,
        }
      );
      expect(employeePolicyActionSpy.mock.calls[1][1]).toEqual(
        {
          id: "Employee:2",
          ref: makeReference("Employee:2"),
          storage: { count: 1 },
          parent: actionMeta.parent,
        }
      );

      invalidationPolicyManager.runEvictPolicy(
        "CreateEmployeeResponse",
        actionMeta
      );

      expect(employeePolicyActionSpy.mock.calls[2][1]).toEqual(
        {
          id: "Employee:1",
          ref: makeReference("Employee:1"),
          storage: { count: 2 },
          parent: actionMeta.parent,
        }
      );
      expect(employeePolicyActionSpy.mock.calls[3][1]).toEqual(
        {
          id: "Employee:2",
          ref: makeReference("Employee:2"),
          storage: { count: 2 },
          parent: actionMeta.parent,
        }
      );
    });
  });

  describe("#activatePolicies", () => {
    test("should activate all policies with config options", () => {
      policies = {
        types: {
          Employee: {
            timeToLive: 5,
          },
          EmployeeResponse: {
            onWrite: {
              Employee: () => { },
            },
            onEvict: {
              Employee: () => { },
            },
          },
        },
      };
      invalidationPolicyManager = new InvalidationPolicyManager({
        entityTypeMap,
        policies,
        cacheOperations,
      });

      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Read)
      ).toEqual(true);
      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Write)
      ).toEqual(true);
      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Evict)
      ).toEqual(true);
    });

    test("should not activate policies without config options", () => {
      policies = {
        types: {
          Employee: {},
          EmployeeResponse: {},
        },
      };
      invalidationPolicyManager = new InvalidationPolicyManager({
        entityTypeMap,
        policies,
        cacheOperations,
      });

      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Read)
      ).toEqual(false);
      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Write)
      ).toEqual(false);
      expect(
        invalidationPolicyManager.isPolicyEventActive(InvalidationPolicyEvent.Evict)
      ).toEqual(false);
    });
  });

  describe('#runReadPolicy', () => {
    test('should not evaluate a read policy for normalized entities that are not in the cache', () => {
      const employee = Employee();
      expect(invalidationPolicyManager.runReadPolicy({
        typename: employee.__typename,
        dataId: employee.id
      })).toEqual(false);
    });

    test('should not evaluate a read policy for non-normalized entities that are not in the cache', () => {
      entityTypeMap.write('EmployeesResponse', 'ROOT_QUERY', 'employees({"name":"Tester1"})');
      expect(invalidationPolicyManager.runReadPolicy({
        typename: 'EmployeesResponse',
        dataId: 'ROOT_QUERY',
        fieldName: 'employees',
        storeFieldName: 'employees({"name":"Tester2"})',
      })).toEqual(false);
    });
  })
});