Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
apollo-invalidation-policies
Advanced tools
An extension to the InMemoryCache from Apollo for type-based invalidation policies.
An extension of the Apollo 3.0 cache that provides a framework for managing the lifecycle and relationships of cache data through the use of invalidation policies.
npm install apollo-invalidation-policies
import { InvalidationPolicyCache } from 'apollo-invalidation-policies';
const cache = new InvalidationPolicyCache({
typePolicies: {...},
invalidationPolicies: {
timeToLive: Number;
renewalPolicy: RenewalPolicy;
types: {
Typename: {
timeToLive: Number,
renewalPolicy: RenewalPolicy,
PolicyEvent: {
Typename: (PolicyActionCacheOperation, PolicyActionEntity) => {}
__default: (PolicyActionCacheOperation, DefaultPolicyActionEntity) => {}
},
}
}
}
});
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 |
Policy Event | Description | Required |
---|---|---|
onWrite | On writing parent entity into cache, perform action for each type under the parent | false |
onEvict | On evicting parent entity from cache, perform policy action for each type under the parent | false |
Policy Action Cache Operation | Description |
---|---|
evict | evict API from Apollo cache |
modify | modify API from Apollo cache |
readField | readField API from Apollo cache |
Extended cache API | Description | Return Type | Arguments |
---|---|---|---|
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 | N/A |
expiredEntities | Returns all expired entities still present in the cache | String[] - List of expired entities in the cache | N/A |
activePolicyEvents | Returns all active policy events (Read, Write, Evict) | InvalidationPolicyEvent[] - List of active policy events | N/A |
activatePolicyEvents | Activates the provided policy events, defaults to all | void | ...InvalidationPolicyEvent[] |
deactivatePolicyEvents | Dectivates the provided policy events, defaults to all | void | ...InvalidationPolicyEvent[] |
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' } |
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: {} }' |
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { InvalidationPolicyCache } from "apollo-invalidation-policies";
export default new ApolloClient({
uri: "http://localhost:4000",
cache: new InvalidationPolicyCache({
typePolicies: {...},
invalidationPolicies: {
types: {
DeleteEmployeeResponse: {
// Delete an entity from the cache when it is deleted on the server
onWrite: {
Employee: ({ evict, readField }, { id, ref, parent: { variables } }) => {
if (parent.variables.employeeId === readField('id', 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({
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,
}
},
}
}
})
});
The Apollo client cache is a powerful tool for managing client data with support for optimistic data, request retrying, polling and with Apollo 3.0, robust cache modification and eviction.
The client cache stores entries in a normalized data model. A query for fetching a list of employees like this:
import gql from "@apollo/client";
const employeesQuery = gql`
query GetEmployees {
employees {
id
name
}
}
`;
Would be represented in the cache like this:
{
ROOT_QUERY: {
__typename: 'Query',
employees: {
__typename: 'EmployeesResponse',
data: [{ __ref: 'Employee:1' }, { __ref: 'Employee:2' }]
}
},
'Employee:1': {
__typename: 'Employee',
id: 1,
name: 'Alice',
},
'Employee:2': {
__typename: 'Employee',
id: 2,
name: 'Bob',
}
}
Invalidation in the Apollo cache is limited and is a common source of consternation in the Apollo community:
The automatic cache invalidation provided by Apollo is missing two categories of cache invalidation:
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.
The Apollo cache allows clients to handle these scenarios with a query update handler:
const createEntity = await apolloClient.mutate({
mutation: CreateEntity,
variables: newEntityData
update: (cache, { data: createEntityResult }) => {
const cachedEntities = cache.readQuery({ query: GetAllEntities });
cache.writeQuery({
query: GetAllEntities,
data: {
GetAllEntities: {
__typename: 'GetEntityResponse',
entities: [...cachedEntities.entities, createEntityResult.entity],
},
},
});
},
});
This requires the client to specify an update handler at the mutation call site and manually read, modify and write that data back into the cache. While this works, the code does not scale well across multiple usages of the same mutation or for highly relational data where a mutation needs to invalidate various cached entities.
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.
If a cache contains multiple entities like a user's profile, messages, and posts, then deleting their profile should invalidate all cached queries containing their messages and posts.
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.
Apollo links are great tools for watching queries and mutations hitting the network. There even exists a Watched Mutation link which provides some of the desired behavior of this library.
At a high level, links run on the network-bound queries/mutations. Invalidation policies run on the types that are being written and evicted from your cache, which this library believes is a better level at which to manage cache operations.
At a low level, links:
@client
directive queries and mutations.cache.evict
directly, links will not be able to process
anything in relation to what should happen in response to that eviction.This was also something that was explored, and it is possible to do this with custom directives:
type Employee @invalidates(own: [EmployeeMessage, EmployeePost]) {
id
}
type DeleteEmployeeResponse {
success: Boolean!
}
type TotalEmployeesResponse {
count: Number!
}
extend type Query {
totalEmployees(
): TotalEmployeesResponse
}
extend type Mutation {
deleteFinancialPortal(
financialPortalId: ID!
): DeleteFinancialPortalResponse @invalidates(own: [Employee], any: [TotalEmployeesResponse])
}
These schema rules could then be consumable on the client either via a invalidationSchema
introspection query, or just an exported file. We looked into this but found it more limiting for now because of the limited ability of the schema language to express complex scenarios.
FAQs
An extension to the InMemoryCache from Apollo for type-based invalidation policies.
The npm package apollo-invalidation-policies receives a total of 370 weekly downloads. As such, apollo-invalidation-policies popularity was classified as not popular.
We found that apollo-invalidation-policies demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.