New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

apollo-invalidation-policies

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apollo-invalidation-policies - npm Package Compare versions

Comparing version 1.0.0-beta1 to 1.0.0-beta10

dist/audit/AuditLog.d.ts

39

CHANGELOG.md

@@ -0,3 +1,42 @@

1.0.0-beta10 (Dan Reynolds)
- Add support for a default policy action to perform side effects whenever a specific type is written/evicted from the cache
1.0.0-beta9 (Dan Reynolds)
- Add `expiredEntities` API for accessing the expired entities in the cache without evicting them
1.0.0-beta8 (Dan Reynolds)
- Add test for Write-on-Write policies where the mutation has arguments and fix the Readme to illustrate how to correctly write the invalidation policy in that case
1.0.0-beta7 (Dan Reynolds)
- Fix issue where read policies were attempted to be evaluated for non-normalized entities not yet in the cache if they had a different store field name with the same name
already written in the cache.
1.0.0-beta6 (Dan Reynolds)
- Adds a `storage` dictionary by unique `storeFieldName` for queries or `ID` for normalized entities in the policy action object so that arbitrary meta information can be stored across multiple policy action invocations.
1.0.0-beta5 (Dan Reynolds)
- Adds support for a `renewalPolicy` type and global config option for specifying how type TTLs should be renewed on write vs access
- Adds the `expire` API for evicting all entities that have expired in the cache based on their type's or the global TTL.
1.0.0-beta4 (Dan Reynolds)
- [BREAKING CHANGE] Adds support for a default TTL option that applies to all types
1.0.0-beta3 (Dan Reynolds)
- Ensure that empty field arguments are still passed as an empty object as the variables in the policy event.
1.0.0-beta2 (Dan Reynolds)
- Bumps to latest Apollo version (3.1)
- Adds audit logging for better entity debugging through the type map and invalidation policy manager
1.0.0-beta1 (Dan Reynolds)
- Initial beta release 🚀

50

dist/cache/CacheResultProcessor.js

@@ -13,2 +13,3 @@ "use strict";

const fragments_1 = require("@apollo/client/utilities/graphql/fragments");
const types_1 = require("../policies/types");
var ReadResultStatus;

@@ -44,3 +45,3 @@ (function (ReadResultStatus) {

processReadSubResult(parentResult, fieldNameOrIndex) {
const { cache, invalidationPolicyManager } = this.config;
const { cache, invalidationPolicyManager, entityTypeMap } = this.config;
const result = lodash_1.default.isUndefined(fieldNameOrIndex)

@@ -56,3 +57,11 @@ ? parentResult

if (id) {
const evicted = invalidationPolicyManager.runReadPolicy(__typename, id);
const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(__typename);
if (renewalPolicy === types_1.RenewalPolicy.AccessAndWrite ||
renewalPolicy === types_1.RenewalPolicy.AccessOnly) {
entityTypeMap.renewEntity(id);
}
const evicted = invalidationPolicyManager.runReadPolicy({
typename: __typename,
dataId: id
});
if (evicted) {

@@ -107,3 +116,13 @@ if (lodash_1.default.isPlainObject(parentResult) && fieldNameOrIndex) {

});
const evicted = invalidationPolicyManager.runReadPolicy(typename, dataId, fieldName, storeFieldName);
const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(typename);
if (renewalPolicy === types_1.RenewalPolicy.AccessAndWrite ||
renewalPolicy === types_1.RenewalPolicy.AccessOnly) {
entityTypeMap.renewEntity(dataId, storeFieldName);
}
const evicted = invalidationPolicyManager.runReadPolicy({
typename,
dataId,
fieldName,
storeFieldName
});
if (evicted) {

@@ -127,3 +146,3 @@ delete result[fieldName];

processWriteSubResult(result) {
const { cache, invalidationPolicyManager } = this.config;
const { cache, invalidationPolicyManager, entityTypeMap } = this.config;
if (lodash_1.default.isPlainObject(result)) {

@@ -135,2 +154,7 @@ const { __typename } = result;

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

@@ -158,3 +182,3 @@ parent: {

this.getFieldsForQuery(options).forEach((field) => {
var _a;
var _a, _b, _c;
const fieldName = storeUtils_1.resultKeyNameFromField(field);

@@ -169,4 +193,11 @@ const typename = (_a = entityTypeMap.readEntityById(helpers_1.makeEntityId(dataId, fieldName))) === null || _a === void 0 ? void 0 : _a.typename;

});
const hasFieldArgs = ((_c = (_b = field === null || field === void 0 ? void 0 : field.arguments) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0;
const fieldVariables = variables !== null && variables !== void 0 ? variables : (hasFieldArgs ? {} : undefined);
// 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, variables);
entityTypeMap.write(typename, dataId, storeFieldName, fieldVariables);
const renewalPolicy = invalidationPolicyManager.getRenewalPolicyForType(typename);
if (renewalPolicy === types_1.RenewalPolicy.WriteOnly ||
renewalPolicy === types_1.RenewalPolicy.AccessAndWrite) {
entityTypeMap.renewEntity(dataId, storeFieldName);
}
invalidationPolicyManager.runWritePolicy(typename, {

@@ -178,3 +209,3 @@ parent: {

ref: client_1.makeReference(dataId),
variables,
variables: fieldVariables,
},

@@ -188,2 +219,7 @@ });

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

@@ -190,0 +226,0 @@ parent: {

24

dist/cache/InvalidationPolicyCache.d.ts

@@ -1,3 +0,8 @@

import { InMemoryCache, Cache, NormalizedCacheObject, Reference } from "@apollo/client";
import { InMemoryCache, Cache, NormalizedCacheObject, Reference, StoreObject } from "@apollo/client";
import { ReadFieldOptions } from "@apollo/client/cache/core/types/common";
import { EntityStore } from "@apollo/client/cache/inmemory/entityStore";
import InvalidationPolicyManager from "../policies/InvalidationPolicyManager";
import { EntityTypeMap } from "../entity-store";
import { InvalidationPolicyCacheConfig } from "./types";
import { CacheResultProcessor } from "./CacheResultProcessor";
/**

@@ -7,14 +12,17 @@ * Extension of Apollo in-memory cache which adds support for invalidation policies.

export default class InvalidationPolicyCache extends InMemoryCache {
private entityTypeMap;
private invalidationPolicyManager;
private cacheResultProcessor;
private entityStoreRoot;
private isBroadcasting;
protected entityTypeMap: EntityTypeMap;
protected invalidationPolicyManager: InvalidationPolicyManager;
protected cacheResultProcessor: CacheResultProcessor;
protected entityStoreRoot: EntityStore.Root;
protected isBroadcasting: boolean;
constructor(config?: InvalidationPolicyCacheConfig);
private readField;
protected readField<T>(fieldNameOrOptions?: string | ReadFieldOptions, from?: StoreObject | Reference): import("@apollo/client/cache/core/types/common").SafeReadonly<T> | undefined;
protected broadcastWatches(): void;
isOperatingonRootData(): boolean;
isOperatingOnRootData(): boolean;
modify(options: Cache.ModifyOptions): boolean;
write(options: Cache.WriteOptions<any, any>): Reference | undefined;
evict(options: Cache.EvictOptions): boolean;
_expire(reportOnly?: boolean): string[];
expire(): string[];
expiredEntities(): string[];
read<T>(options: Cache.ReadOptions<any>): T | null;

@@ -21,0 +29,0 @@ diff<T>(options: Cache.DiffOptions): Cache.DiffResult<T>;

@@ -76,3 +76,3 @@ "use strict";

// being applied to a new layer.
isOperatingonRootData() {
isOperatingOnRootData() {
// @ts-ignore

@@ -128,4 +128,5 @@ return this.data === this.entityStoreRoot;

// the policy will later be applied when the server data response is received.
if (!this.invalidationPolicyManager.isPolicyActive(types_1.InvalidationPolicyEvent.Write) ||
!this.isOperatingonRootData()) {
if ((!this.invalidationPolicyManager.isPolicyActive(types_1.InvalidationPolicyEvent.Write) &&
!this.invalidationPolicyManager.isPolicyActive(types_1.InvalidationPolicyEvent.Read)) ||
!this.isOperatingOnRootData()) {
return writeResult;

@@ -172,2 +173,48 @@ }

}
// 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.
_expire(reportOnly = false) {
const { entitiesById } = this.entityTypeMap.extract();
const expiredEntityIds = [];
Object.keys(entitiesById).forEach((entityId) => {
const entity = entitiesById[entityId];
const { storeFieldNames, dataId, fieldName, typename } = entity;
if (helpers_2.isQuery(dataId) && storeFieldNames) {
Object.keys(storeFieldNames.entries).forEach((storeFieldName) => {
const isExpired = this.invalidationPolicyManager.runReadPolicy({
typename,
dataId,
fieldName,
storeFieldName,
reportOnly,
});
if (isExpired) {
expiredEntityIds.push(helpers_2.makeEntityId(dataId, storeFieldName));
}
});
}
else {
const isExpired = this.invalidationPolicyManager.runReadPolicy({
typename,
dataId,
fieldName,
reportOnly,
});
if (isExpired) {
expiredEntityIds.push(helpers_2.makeEntityId(dataId));
}
}
});
if (expiredEntityIds.length > 0) {
this.broadcastWatches();
}
return expiredEntityIds;
}
expire() {
return this._expire(false);
}
expiredEntities() {
return this._expire(true);
}
read(options) {

@@ -192,3 +239,3 @@ const result = super.read(options);

// 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 swill trigger the read policies for client-based reads like `readCache` invocations from watched queries outside
// Instead, diffs will trigger the read policies for client-based reads like `readCache` invocations from watched queries outside
// the scope of broadcasts.

@@ -216,6 +263,5 @@ if (!this.invalidationPolicyManager.isPolicyActive(types_1.InvalidationPolicyEvent.Read) ||

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

@@ -222,0 +268,0 @@ return extractedCache;

@@ -98,2 +98,3 @@ import { EntitiesById, ExtractedTypeMap, TypeMapEntities, TypeMapEntity } from "./types";

readEntityById(entityId: string): TypeMapEntity | null;
renewEntity(dataId: string, storeFieldName?: string): void;
restore(entitiesById: EntitiesById): void;

@@ -100,0 +101,0 @@ extract(): ExtractedTypeMap;

@@ -108,7 +108,22 @@ "use strict";

const entityId = helpers_2.makeEntityId(dataId, fieldName);
const typeMapEntity = this.readEntityById(entityId);
let cacheTime = Date.now();
let newEntity = null;
if (helpers_2.isQuery(dataId) && storeFieldName) {
if (!typeMapEntity) {
const existingTypeMapEntity = this.readEntityById(entityId);
if (existingTypeMapEntity) {
if (helpers_2.isQuery(dataId) && storeFieldName) {
const storeFieldNameEntry = existingTypeMapEntity.storeFieldNames
.entries[storeFieldName];
if (storeFieldNameEntry) {
storeFieldNameEntry.variables = variables;
}
else {
existingTypeMapEntity.storeFieldNames.entries[storeFieldName] = {
variables,
};
existingTypeMapEntity.storeFieldNames.__size++;
}
}
}
else {
let newEntity;
const cacheTime = Date.now();
if (helpers_2.isQuery(dataId) && storeFieldName) {
newEntity = {

@@ -121,3 +136,3 @@ dataId,

entries: {
[storeFieldName]: { cacheTime, variables },
[storeFieldName]: { variables, cacheTime },
},

@@ -128,24 +143,8 @@ },

else {
const storeFieldNameEntry = typeMapEntity.storeFieldNames.entries[storeFieldName];
if (storeFieldNameEntry) {
storeFieldNameEntry.cacheTime = cacheTime;
storeFieldNameEntry.variables = variables;
}
else {
typeMapEntity.storeFieldNames.entries[storeFieldName] = {
cacheTime,
variables,
};
typeMapEntity.storeFieldNames.__size++;
}
newEntity = {
dataId,
typename,
cacheTime,
};
}
}
else {
newEntity = {
dataId,
typename,
cacheTime,
};
}
if (newEntity) {
lodash_1.default.set(this.entitiesByType, [typename, entityId], newEntity);

@@ -191,2 +190,17 @@ this.entitiesById[entityId] = newEntity;

}
renewEntity(dataId, storeFieldName) {
const fieldName = storeFieldName
? helpers_1.fieldNameFromStoreName(storeFieldName)
: undefined;
const entity = this.entitiesById[helpers_2.makeEntityId(dataId, fieldName)];
if (entity) {
const cacheTime = Date.now();
if (helpers_2.isQuery(dataId) && storeFieldName) {
entity.storeFieldNames.entries[storeFieldName].cacheTime = cacheTime;
}
else {
entity.cacheTime = cacheTime;
}
}
}
restore(entitiesById) {

@@ -204,6 +218,6 @@ this.entitiesById = entitiesById;

const { entitiesById, entitiesByType } = this;
return lodash_1.default.cloneDeep({
return {
entitiesById,
entitiesByType,
});
};
}

@@ -210,0 +224,0 @@ clear() {

import { NormalizedCacheObject } from "@apollo/client";
import { InvalidationPolicies } from "../policies/types";
export interface EntityTypeMapConfig {
policies: InvalidationPolicies;
}
export interface TypeMapEntity {

@@ -11,3 +15,3 @@ dataId: string;

[index: string]: {
cacheTime: number;
cacheTime?: number;
variables?: Record<string, any>;

@@ -14,0 +18,0 @@ };

export { InvalidationPolicyCache } from "./cache";
export { InvalidationPolicies, PolicyActionEntity, PolicyActionFields, } from "./policies/types";
export { InvalidationPolicyCacheAuditor } from "./audit";
export { InvalidationPolicies, PolicyActionEntity, PolicyActionFields, RenewalPolicy, } from "./policies/types";
export { InvalidationPolicyCacheConfig } from "./cache/types";
//# sourceMappingURL=index.d.ts.map

@@ -5,2 +5,6 @@ "use strict";

exports.InvalidationPolicyCache = cache_1.InvalidationPolicyCache;
var audit_1 = require("./audit");
exports.InvalidationPolicyCacheAuditor = audit_1.InvalidationPolicyCacheAuditor;
var types_1 = require("./policies/types");
exports.RenewalPolicy = types_1.RenewalPolicy;
//# sourceMappingURL=index.js.map
import { InvalidationPolicyEvent, InvalidationPolicyManagerConfig, PolicyActionMeta } from "./types";
import { RenewalPolicy } from "./types";
/**

@@ -9,12 +10,21 @@ * Executes invalidation policies for types when they are modified, evicted or read from the cache.

private policyActivation;
private policyActionStorage;
constructor(config: InvalidationPolicyManagerConfig);
private activatePolicies;
private getPolicy;
private getPolicyActionStorage;
private getTypePolicyForEvent;
private runPolicyEvent;
getRenewalPolicyForType(typename: string): RenewalPolicy.AccessOnly | RenewalPolicy;
runWritePolicy(typeName: string, policyMeta: PolicyActionMeta): void;
runEvictPolicy(typeName: string, policyMeta: PolicyActionMeta): void;
runReadPolicy(typename: string, dataId: string, fieldName?: string, storeFieldName?: string): boolean;
runReadPolicy({ typename, dataId, fieldName, storeFieldName, reportOnly, }: {
typename: string;
dataId: string;
fieldName?: string;
storeFieldName?: string;
reportOnly?: boolean;
}): boolean;
isPolicyActive(policyEvent: InvalidationPolicyEvent): boolean;
}
//# sourceMappingURL=InvalidationPolicyManager.d.ts.map
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -10,2 +21,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

const client_1 = require("@apollo/client");
const types_2 = require("./types");
/**

@@ -17,2 +29,3 @@ * Executes invalidation policies for types when they are modified, evicted or read from the cache.

this.config = config;
this.policyActionStorage = {};
const { cacheOperations: { readField, evict, modify }, } = this.config;

@@ -30,4 +43,5 @@ // Watch broadcasts by evict and modify operations called by policy actions

const { policies } = this.config;
return Object.keys(policies).reduce((acc, type) => {
const policy = policies[type];
const { types: policyTypes = {}, timeToLive: defaultTimeToLive } = policies;
return Object.keys(policyTypes).reduce((acc, type) => {
const policy = policyTypes[type];
acc[types_1.InvalidationPolicyEvent.Read] =

@@ -43,3 +57,3 @@ acc[types_1.InvalidationPolicyEvent.Read] || !!policy.timeToLive;

}, {
[types_1.InvalidationPolicyEvent.Read]: false,
[types_1.InvalidationPolicyEvent.Read]: !!defaultTimeToLive,
[types_1.InvalidationPolicyEvent.Write]: false,

@@ -50,4 +64,12 @@ [types_1.InvalidationPolicyEvent.Evict]: false,

getPolicy(typeName) {
return this.config.policies[typeName] || null;
var _a, _b;
return ((_b = (_a = this.config.policies) === null || _a === void 0 ? void 0 : _a.types) === null || _b === void 0 ? void 0 : _b[typeName]) || null;
}
getPolicyActionStorage(identifier) {
const existingStorage = this.policyActionStorage[identifier];
if (!existingStorage) {
this.policyActionStorage[identifier] = {};
}
return this.policyActionStorage[identifier];
}
getTypePolicyForEvent(typeName, policyEvent) {

@@ -67,6 +89,10 @@ const policyForType = this.getPolicy(typeName);

}
Object.keys(typePolicyForEvent).forEach((typeName) => {
const { __default: defaultPolicyAction } = typePolicyForEvent, restTypePolicyTypeNames = __rest(typePolicyForEvent, ["__default"]);
if (defaultPolicyAction) {
defaultPolicyAction(mutedCacheOperations, Object.assign({ storage: this.getPolicyActionStorage(`${typeName}__default`) }, policyMeta));
}
Object.keys(restTypePolicyTypeNames).forEach((typePolicyTypeName) => {
var _a;
const typeMapEntities = (_a = entityTypeMap.readEntitiesByType(typeName)) !== null && _a !== void 0 ? _a : {};
const policyAction = typePolicyForEvent[typeName];
const typeMapEntities = (_a = entityTypeMap.readEntitiesByType(typePolicyTypeName)) !== null && _a !== void 0 ? _a : {};
const policyAction = typePolicyForEvent[typePolicyTypeName];
Object.values(typeMapEntities).forEach((typeMapEntity) => {

@@ -77,7 +103,7 @@ const { dataId, fieldName, storeFieldNames } = typeMapEntity;

policyAction(mutedCacheOperations, Object.assign({ id: dataId, fieldName,
storeFieldName, variables: storeFieldNames.entries[storeFieldName].variables, ref: client_1.makeReference(dataId) }, policyMeta));
storeFieldName, variables: storeFieldNames.entries[storeFieldName].variables, ref: client_1.makeReference(dataId), storage: this.getPolicyActionStorage(storeFieldName) }, policyMeta));
});
}
else {
policyAction(mutedCacheOperations, Object.assign({ id: dataId, ref: client_1.makeReference(dataId) }, policyMeta));
policyAction(mutedCacheOperations, Object.assign({ id: dataId, storage: this.getPolicyActionStorage(dataId), ref: client_1.makeReference(dataId) }, policyMeta));
}

@@ -87,2 +113,7 @@ });

}
getRenewalPolicyForType(typename) {
var _a, _b, _c, _d;
const { policies } = this.config;
return ((_d = (_c = (_b = (_a = policies.types) === null || _a === void 0 ? void 0 : _a[typename]) === null || _b === void 0 ? void 0 : _b.renewalPolicy) !== null && _c !== void 0 ? _c : policies.renewalPolicy) !== null && _d !== void 0 ? _d : types_2.RenewalPolicy.WriteOnly);
}
runWritePolicy(typeName, policyMeta) {

@@ -94,22 +125,38 @@ return this.runPolicyEvent(typeName, types_1.InvalidationPolicyEvent.Write, policyMeta);

}
runReadPolicy(typename, dataId, fieldName, storeFieldName) {
// Runs the read poliy on the entity, returning whether its TTL was expired.
runReadPolicy({ typename, dataId, fieldName, storeFieldName, reportOnly = false, }) {
var _a;
const { cacheOperations, entityTypeMap } = this.config;
const { cacheOperations, entityTypeMap, policies } = this.config;
const entityId = helpers_1.makeEntityId(dataId, fieldName);
const typeMapEntity = entityTypeMap.readEntityById(entityId);
if (!typeMapEntity) {
return true;
return false;
}
const entityCacheTime = storeFieldName && typeMapEntity.storeFieldNames
? typeMapEntity.storeFieldNames.entries[storeFieldName].cacheTime
: typeMapEntity.cacheTime;
const typeTimeToLive = (_a = this.getPolicy(typename)) === null || _a === void 0 ? void 0 : _a.timeToLive;
let entityCacheTime;
// If a read is done against an entity before it has ever been written, it would not be present in the cache yet and should not attempt
// to have read policy eviction run on it. This can occur in the case of fetching a query field over the network for example, where first
// before it has come back from the network, the Apollo Client tries to diff it against the store to see what the existing value is for it,
// but on first fetch it would not exist.
if (storeFieldName && !!typeMapEntity.storeFieldNames) {
const entityForStoreFieldName = typeMapEntity.storeFieldNames.entries[storeFieldName];
if (!entityForStoreFieldName) {
return false;
}
entityCacheTime = entityForStoreFieldName.cacheTime;
}
else {
entityCacheTime = typeMapEntity.cacheTime;
}
const timeToLive = ((_a = this.getPolicy(typename)) === null || _a === void 0 ? void 0 : _a.timeToLive) || policies.timeToLive;
if (lodash_1.default.isNumber(entityCacheTime) &&
typeTimeToLive &&
Date.now() > entityCacheTime + typeTimeToLive) {
return cacheOperations.evict({
id: dataId,
fieldName: storeFieldName,
broadcast: false,
});
timeToLive &&
Date.now() > entityCacheTime + timeToLive) {
if (!reportOnly) {
cacheOperations.evict({
id: dataId,
fieldName: storeFieldName,
broadcast: false,
});
}
return true;
}

@@ -116,0 +163,0 @@ return false;

@@ -13,5 +13,16 @@ import { Cache, Reference, StoreObject, StoreValue } from "@apollo/client";

}
export declare enum RenewalPolicy {
AccessOnly = "access-only",
AccessAndWrite = "access-and-write",
WriteOnly = "write-only",
None = "none"
}
export interface InvalidationPolicies {
[typeName: string]: InvalidationPolicy;
timeToLive?: number;
renewalPolicy?: RenewalPolicy;
types?: {
[typeName: string]: InvalidationPolicy;
};
}
export declare type PolicyActionStorage = Record<string, Record<string, any>>;
export interface PolicyActionFields {

@@ -22,2 +33,3 @@ id: string;

storeFieldName?: string;
storage: PolicyActionStorage;
variables?: Record<string, any>;

@@ -27,11 +39,15 @@ }

export interface PolicyActionMeta {
parent: PolicyActionFields;
parent: Omit<PolicyActionFields, 'storage'>;
}
export declare type PolicyAction = (cacheOperations: PolicyActionCacheOperations, entity: PolicyActionEntity) => void;
export declare type DefaultPolicyAction = (cacheOperations: PolicyActionCacheOperations, entity: Pick<PolicyActionEntity, 'storage' | 'parent'>) => void;
export declare type InvalidationPolicy = {
[lifecycleEvent in InvalidationPolicyLifecycleEvent]?: {
[typeName: string]: PolicyAction;
} & {
__default?: DefaultPolicyAction;
};
} & {
timeToLive?: number;
renewalPolicy?: RenewalPolicy;
};

@@ -41,3 +57,3 @@ export declare type CacheOperations = {

modify: (options: Cache.ModifyOptions) => boolean;
readField: (fieldNameOrOptions: string | ReadFieldOptions | undefined, from: StoreObject | Reference) => StoreValue | undefined;
readField: (fieldNameOrOptions?: string | ReadFieldOptions | undefined, from?: StoreObject | Reference) => StoreValue | undefined;
};

@@ -47,3 +63,3 @@ export declare type PolicyActionCacheOperations = {

modify: (options: Omit<Cache.ModifyOptions, "broadcast">) => boolean;
readField: (fieldNameOrOptions: string | ReadFieldOptions | undefined, from: StoreObject | Reference) => StoreValue | undefined;
readField: (fieldNameOrOptions?: string | ReadFieldOptions | undefined, from?: StoreObject | Reference) => StoreValue | undefined;
};

@@ -50,0 +66,0 @@ export interface InvalidationPolicyManagerConfig {

@@ -14,2 +14,9 @@ "use strict";

})(InvalidationPolicyLifecycleEvent = exports.InvalidationPolicyLifecycleEvent || (exports.InvalidationPolicyLifecycleEvent = {}));
var RenewalPolicy;
(function (RenewalPolicy) {
RenewalPolicy["AccessOnly"] = "access-only";
RenewalPolicy["AccessAndWrite"] = "access-and-write";
RenewalPolicy["WriteOnly"] = "write-only";
RenewalPolicy["None"] = "none";
})(RenewalPolicy = exports.RenewalPolicy || (exports.RenewalPolicy = {}));
//# sourceMappingURL=types.js.map
{
"name": "apollo-invalidation-policies",
"version": "1.0.0-beta1",
"version": "1.0.0-beta10",
"description": "An extension to the InMemoryCache from Apollo for type-based invalidation policies.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"scripts": {

@@ -17,3 +18,3 @@ "test": "jest",

"type": "git",
"url": "git+https://github.com/NerdWallet/apollo-cache-invalidation.git"
"url": "git+https://github.com/NerdWalletOSS/apollo-invalidation-policies.git"
},

@@ -26,7 +27,7 @@ "keywords": [

"bugs": {
"url": "https://github.com/NerdWallet/apollo-cache-invalidation/issues"
"url": "https://github.com/NerdWalletOSS/apollo-invalidation-policies/issues"
},
"homepage": "https://github.com/NerdWallet/apollo-cache-invalidation#readme",
"homepage": "https://github.com/NerdWalletOSS/apollo-invalidation-policies#readme",
"peerDependencies": {
"@apollo/client": "^3.0.0"
"@apollo/client": "^3.3.0"
},

@@ -43,3 +44,3 @@ "dependencies": {

"devDependencies": {
"@apollo/client": " 3.0.0-rc.11",
"@apollo/client": "^3.3.0",
"@types/jest": "^25.1.5",

@@ -49,4 +50,6 @@ "jest": "^25.2.6",

"prettier": "2.0.5",
"react": "^16.13.1",
"subscriptions-transport-ws": "^0.9.17",
"ts-jest": "^25.4.0"
}
}

@@ -1,2 +0,2 @@

![Build](https://github.com/NerdWallet/apollo-cache-invalidation/workflows/Build/badge.svg)
![Build](https://github.com/NerdWalletOSS/apollo-invalidation-policies/workflows/Build/badge.svg)

@@ -16,11 +16,17 @@ # Apollo Invalidation Policies

```javascript
import InvalidationPolicyCache from 'apollo-invalidation-policies';
import { InvalidationPolicyCache } from 'apollo-invalidation-policies';
const cache = new InvalidationPolicyCache({
typePolicies: {...},
invalidationPolicies: {
Typename: {
timeToLive: Number,
PolicyEvent: {
Typename: (PolicyActionCacheOperation, PolicyActionEntity) => {}
},
timeToLive: Number;
renewalPolicy: RenewalPolicy;
types: {
Typename: {
timeToLive: Number,
renewalPolicy: RenewalPolicy,
PolicyEvent: {
Typename: (PolicyActionCacheOperation, PolicyActionEntity) => {}
__default: (PolicyActionCacheOperation, DefaultPolicyActionEntity) => {}
},
}
}

@@ -31,2 +37,15 @@ }

| Config | Description | Required | Default |
| ----------------| -------------------------------------------------------------------------------------------|----------|-----------|
| `timeToLive` | The global time to live in milliseconds for all types in the cache | false | None |
| `types` | The types for which invalidation policies have been defined | false | None |
| `renewalPolicy` | The policy for renewing an entity's time to live in the cache | false | WriteOnly |
### Renewal policies:
* **AccessOnly** - After first write, the entity in the cache will renew its TTL on read
* **AccessAndWrite** - After first write, the entity will renew its TTL on read or write
* **WriteOnly** - After first write, the entity in the cache will renew its TTL on write
* **None** - After first write, the entity in the cache will never renew its TTL on reads or writes.
| Policy Event | Description | Required |

@@ -43,14 +62,25 @@ | ---------------| -------------------------------------------------------------------------------------------|----------|

| Extended cache API | Description | Return Type |
| -------------------| -------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| `expire` | Evicts all expired entities from the cache based on their type's or the global timeToLive. | String[] - List of expired entity IDs evicted from the cache. |
| `expiredEntities` | Returns all expired entities still present in the cache | String[] - List of expired entities in the cache. |
| Policy Action Entity | Description | Type | Example |
| ---------------------| --------------------------------------------------------|--------------------| --------------------------------------------------------------------------------------------|
| `id` | The id of the entity in the Apollo cache | string | `Employee:1`, `ROOT_QUERY` |
| `ref` | The reference object for the entity in the Apollo cache | Reference | `{ __ref: 'Employee:1' }`, `{ __ref: 'ROOT_QUERY' }` |
| `fieldName` | The field for the entity in the Apollo cache | string? | `employees` |
| `storeFieldName` | The `fieldName` combined with its distinct variables | string? | `employees({ location: 'US' })` |
| `variables` | The variables the entity was written with | Object? | `{ location: 'US' }` |
| `parent` | The parent entity that triggered the PolicyEvent | PolicyActionEntity | `{ id: 'ROOT_QUERY', fieldName: 'deleteEmployees', storeFieldName: 'deleteEmployees({}), ref: { __ref: 'ROOT_QUERY' }, variables: {} }'` |
| ---------------------| --------------------------------------------------------|--------------------| ---------------------------------------------------------------------------------------------|
| `id` | The id of the entity in the Apollo cache | string | `Employee:1`, `ROOT_QUERY` |
| `ref` | The reference object for the entity in the Apollo cache | Reference | `{ __ref: 'Employee:1' }`, `{ __ref: 'ROOT_QUERY' }` |
| `fieldName` | The field for the entity in the Apollo cache | string? | `employees` |
| `storeFieldName` | The `fieldName` combined with its distinct variables | string? | `employees({ location: 'US' })` |
| `variables` | The variables the entity was written with | Object? | `{ location: 'US' }` |
| `storage` | An object for storing unique entity metadata across policy action invocations | Object | `{}` |
| `parent` | The parent entity that triggered the PolicyEvent | PolicyActionEntity | `{ id: 'ROOT_QUERY', fieldName: 'deleteEmployees', storeFieldName: 'deleteEmployees({}), ref: { __ref: 'ROOT_QUERY' }, variables: {} }'` |
| Default Policy Action Entity | Description | Type | Example |
| -----------------------------| ------------------------------------------------------------------------------|---------------------| ---------------------------------------------------------------------------------------------|
| `storage` | An object for storing unique entity metadata across policy action invocations | Object | `{}` |
| `parent` | The parent entity that triggered the PolicyEvent | PolicyActionEntity | `{ id: 'ROOT_QUERY', fieldName: 'deleteEmployees', storeFieldName: 'deleteEmployees({}), ref: { __ref: 'ROOT_QUERY' }, variables: {} }'` |
```javascript
import { ApolloClient, InMemoryCache } from "@apollo/client";
import InvalidationPolicyCache from "apollo-invalidation-policies";
import { InvalidationPolicyCache } from "apollo-invalidation-policies";

@@ -62,3 +92,4 @@ export default new ApolloClient({

invalidationPolicies: {
DeleteEmployeeResponse: {
types: {
DeleteEmployeeResponse: {
// Delete an entity from the cache when it is deleted on the server

@@ -72,38 +103,51 @@ onWrite: {

}
},
Employee: {
// Evict every message in the cache for an employee when they are evicted
onEvict: {
EmployeeMessage: ({ readField, evict }, { id, ref, parent }) => {
if (readField('employee_id', ref) === readField('id', parent.ref)) {
evict({ id });
}
},
Employee: {
// Evict every message in the cache for an employee when they are evicted
onEvict: {
EmployeeMessage: ({ readField, evict }, { id, ref, parent }) => {
if (readField('employee_id', ref) === readField('id', parent.ref)) {
evict({ id });
}
},
}
},
EmployeeMessage: {
// Perform a side-effect whenever an employee message is evicted
onEvict: {
__default: (_cacheOperations, { parent: { id } }) => {
console.log(`Employee message ${id} was evicted`);
},
},
}
},
CreateEmployeeResponse: {
// Add an entity to a cached query when the parent type is written
onWrite: {
EmployeesResponse: ({ readField, modify }, { storeFieldName, parent }) => {
modify({
fields: {
[storeFieldName]: (employeesResponse) => {
const createdEmployeeResponse = readField(parent.storeFieldName, parent.ref);
return {
...employeesResponse,
data: [
...employeesResponse.data,
createdEmployeesResponse.data,
]
},
CreateEmployeeResponse: {
// Add an entity to a cached query when the parent type is written
onWrite: {
EmployeesResponse: ({ readField, modify }, { storeFieldName, parent }) => {
modify({
fields: {
[storeFieldName]: (employeesResponse) => {
const createdEmployeeResponse = readField({
fieldName: parent.fieldName,
args: parent.variables,
from: parent.ref,
});
return {
...employeesResponse,
data: [
...employeesResponse.data,
createdEmployeesResponse.data,
]
}
}
}
}
});
});
},
},
}
},
EmployeesResponse: {
// Assign a time-to-live for types in the store. If accessed beyond their TTL,
// they are evicted and no data is returned.
timeToLive: 3600,
EmployeesResponse: {
// Assign a time-to-live for types in the store. If accessed beyond their TTL,
// they are evicted and no data is returned.
timeToLive: 3600,
}
},
}

@@ -169,3 +213,3 @@ }

1. Creating/deleting entities
### 1. Creating/deleting entities

@@ -197,3 +241,3 @@ Because it uses a normalized data cache, any updates to entities in the cache will be consistent across cached queries that contain them such as in lists or nested data objects. This does not work when creating or deleting entities, however, since it does not know to add any new entities to cached queries or remove them when a mutation deletes an entity from the server.

2. Cache dependencies
### 2. Cache dependencies

@@ -211,7 +255,7 @@ The Apollo cache has powerful utilities for interacting with the cache, but does not have a framework for managing the lifecycle and dependencies between entities in the cache.

1. What use cases is this project targetting?
### What use cases is this project targetting?
The Apollo cache is not a relational datastore and as an extension of it, these invalidation policies are not going to be the best solution for every project. At its core it's a for loop that runs for each child x of type T when a matching policy event occurs for parent entity y of type T2. If your cache will consist of thousands of x's and y's dependent on each other with frequent policy triggers, then something like a client-side database would be a better choice. Our goal has been decreasing developer overhead when having to manage the invalidation of multiple of distinct, dependent cached queries.
2. Why a new cache and not a link?
### Why a new cache and not a link?

@@ -231,3 +275,3 @@ Apollo links are great tools for watching queries and mutations hitting the network. There even exists a [Watched Mutation](https://github.com/afafafafafaf/apollo-link-watched-mutation) link which provides some of the desired behavior of this library.

3. Why not establish schema relationships on the server?
### Why not establish schema relationships on the server?

@@ -259,2 +303,2 @@ This was also something that was explored, and it is possible to do this with custom directives:

</details>
</details>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc