import { isNumber } from 'lodash-es' ; import { InvalidationPolicy , InvalidationPolicyEventActivation , InvalidationPolicyEvent , InvalidationPolicyLifecycleEvent , InvalidationPolicyManagerConfig , PolicyActionCacheOperations , PolicyActionMeta , PolicyActionStorage , PolicyAction , } from "./types" ; import { makeEntityId } from "../helpers" ; import { makeReference } from "@apollo/client/core" ; import { RenewalPolicy } from "./types" ; import { DefaultPolicyAction } from ".." ; /**
 * Executes cache policies for types when they are modified, evicted or read from the cache.
 */ export default class InvalidationPolicyManager { private mutedCacheOperations : PolicyActionCacheOperations ; private activePolicyEvents : InvalidationPolicyEventActivation ; private policyActionStorage : PolicyActionStorage = { } ; constructor ( private config ) { const { cacheOperations : { readField , evict , modify } , } = this . config ; this . mutedCacheOperations = { readField , evict : ( options ) => evict ( { ... options , broadcast : false } ) , modify : ( options ) => modify ( { ... options , broadcast : false } ) , } ; this . activePolicyEvents = this . activateInitialPolicyEvents ( ) ; } private activateInitialPolicyEvents ( ) { const { policies } = this . config ; const { types : policyTypes = { } , timeToLive : defaultTimeToLive } = policies ; return Object . keys ( policyTypes ) . reduce < InvalidationPolicyEventActivation > ( ( acc , type ) => { const policy = policyTypes [ type ] ; acc [ InvalidationPolicyEvent . Read ] = acc [ InvalidationPolicyEvent . Read ] || ! ! policy . timeToLive ; acc [ InvalidationPolicyEvent . Write ] = acc [ InvalidationPolicyEvent . Write ] || ! ! policy [ InvalidationPolicyLifecycleEvent . Write ] ; acc [ InvalidationPolicyEvent . Evict ] = acc [ InvalidationPolicyEvent . Evict ] || ! ! policy [ InvalidationPolicyLifecycleEvent . Evict ] ; return acc ; } , { [ InvalidationPolicyEvent . Read ] : ! ! defaultTimeToLive , [ InvalidationPolicyEvent . Write ] : false , [ InvalidationPolicyEvent . Evict ] : false , } ) ; } private getPolicy ( typeName ) { return this . config . policies ?. types ?. [ typeName ] || null ; } private getPolicyActionStorage ( identifier ) { const existingStorage = this . policyActionStorage [ identifier ] ; if ( ! existingStorage ) { this . policyActionStorage [ identifier ] = { } ; } return this . policyActionStorage [ identifier ] ; } private getTypePolicyForEvent ( typeName , policyEvent ) { const policyForType = this . getPolicy ( typeName ) ; if ( ! policyForType ) { return null ; } return policyForType [ InvalidationPolicyLifecycleEvent [ policyEvent ] ] ; } private runPolicyEvent ( typeName , policyEvent , policyMeta ) { const { entityTypeMap } = this . config ; const { mutedCacheOperations } = this ; const typePolicyForEvent = this . getTypePolicyForEvent ( typeName , policyEvent ) ; if ( ! typePolicyForEvent ) { return ; } let defaultPolicyAction ; let restTypePolicyTypeNames = { } 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 ) => { 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 , } ) ; } } ) ; } ) ; } getRenewalPolicyForType ( typename ) { const { policies } = this . config ; return ( policies . types ?. [ typename ] ?. renewalPolicy ?? policies . renewalPolicy ?? RenewalPolicy . WriteOnly ) ; } runWritePolicy ( typeName , policyMeta ) { return this . runPolicyEvent ( typeName , InvalidationPolicyEvent . Write , policyMeta ) ; } runEvictPolicy ( typeName , policyMeta ) { return this . runPolicyEvent ( typeName , InvalidationPolicyEvent . Evict , policyMeta ) ; } runReadPolicy ( { typename , dataId , fieldName , storeFieldName , reportOnly = false , } ) { const { cacheOperations , entityTypeMap , policies } = this . config ; const entityId = makeEntityId ( dataId , fieldName ) ; const typeMapEntity = entityTypeMap . readEntityById ( entityId ) ; if ( ! typeMapEntity ) { return false ; } let entityCacheTime ; if ( storeFieldName && ! ! typeMapEntity . storeFieldNames ) { const entityForStoreFieldName = typeMapEntity . storeFieldNames . entries [ storeFieldName ] ; if ( ! entityForStoreFieldName ) { return false ; } entityCacheTime = entityForStoreFieldName . cacheTime ; } else { entityCacheTime = typeMapEntity . cacheTime ; } const timeToLive = this . getPolicy ( typename ) ?. timeToLive || policies . timeToLive ; if ( isNumber ( entityCacheTime ) && timeToLive && Date . now ( ) > entityCacheTime + timeToLive ) { if ( ! reportOnly ) { cacheOperations . evict ( { id : dataId , fieldName : storeFieldName , broadcast : false , } ) ; } return true ; } return false ; } activatePolicies ( ... policyEvents ) { policyEvents . forEach ( policyEvent => this . activePolicyEvents [ policyEvent ] = true ) ; } deactivatePolicies ( ... policyEvents ) { policyEvents . forEach ( policyEvent => this . activePolicyEvents [ policyEvent ] = false ) ; } isPolicyEventActive ( policyEvent ) { return this . activePolicyEvents [ policyEvent ] ; } }