@credo-ts/core
Version:
<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1
1 lines • 8.28 kB
Source Map (JSON)
{"version":3,"file":"SingleContextStorageLruCache.mjs","names":[],"sources":["../../../../src/modules/cache/singleContextLruCache/SingleContextStorageLruCache.ts"],"sourcesContent":["import LRUMap from 'lru_map'\nimport type { AgentContext } from '../../../agent/context'\nimport { CredoError, RecordDuplicateError } from '../../../error'\nimport type { Cache, CacheOptions } from '../Cache'\nimport type { SingleContextLruCacheItem } from './SingleContextLruCacheRecord'\n\nimport { SingleContextLruCacheRecord } from './SingleContextLruCacheRecord'\nimport { SingleContextLruCacheRepository } from './SingleContextLruCacheRepository'\n\nconst CONTEXT_STORAGE_LRU_CACHE_ID = 'CONTEXT_STORAGE_LRU_CACHE_ID'\n\nexport interface SingleContextStorageLruCacheOptions {\n /** The maximum number of entries allowed in the cache */\n limit: number\n}\n\n/**\n * Cache that leverages the storage associated with the agent context to store cache records.\n * It will keep an in-memory cache of the records to avoid hitting the storage on every read request.\n * Therefore this cache is meant to be used with a single instance of the agent.\n *\n * Due to keeping an in-memory copy of the cache, it is also not meant to be used with multiple\n * agent context instances (meaning multi-tenancy), as they will overwrite the in-memory cache.\n *\n * However, this means the cache is not meant for usage with multiple instances.\n *\n * This cache does not honor {@link CacheOptions.scope}: as it must only ever be used with a single\n * agent context (enforced at runtime), the context and global scopes are equivalent within it.\n */\nexport class SingleContextStorageLruCache implements Cache {\n private limit: number\n private _cache?: LRUMap.LRUMap<string, SingleContextLruCacheItem>\n private _contextCorrelationId?: string\n\n public constructor({ limit }: SingleContextStorageLruCacheOptions) {\n this.limit = limit\n }\n\n public async get<CacheValue>(agentContext: AgentContext, key: string, _options?: CacheOptions) {\n this.assertContextCorrelationId(agentContext)\n\n const cache = await this.getCache(agentContext)\n this.removeExpiredItems(cache)\n\n const item = cache.get(key)\n\n // Does not exist\n if (!item) return null\n\n // Expired\n if (item.expiresAt && Date.now() > item.expiresAt) {\n cache.delete(key)\n await this.persistCache(agentContext)\n return null\n }\n\n return item.value as CacheValue\n }\n\n public async set<CacheValue>(\n agentContext: AgentContext,\n key: string,\n value: CacheValue,\n expiresInSeconds?: number,\n _options?: CacheOptions\n ): Promise<void> {\n this.assertContextCorrelationId(agentContext)\n\n let expiresDate: Date | undefined\n\n if (expiresInSeconds) {\n expiresDate = new Date()\n expiresDate.setSeconds(expiresDate.getSeconds() + expiresInSeconds)\n }\n\n const cache = await this.getCache(agentContext)\n this.removeExpiredItems(cache)\n\n cache.set(key, {\n expiresAt: expiresDate?.getTime(),\n value,\n })\n await this.persistCache(agentContext)\n }\n\n public async remove(agentContext: AgentContext, key: string, _options?: CacheOptions): Promise<void> {\n this.assertContextCorrelationId(agentContext)\n\n const cache = await this.getCache(agentContext)\n this.removeExpiredItems(cache)\n cache.delete(key)\n\n await this.persistCache(agentContext)\n }\n\n private async getCache(agentContext: AgentContext) {\n if (!this._cache) {\n const cacheRecord = await this.fetchCacheRecord(agentContext)\n this._cache = this.lruFromRecord(cacheRecord)\n }\n\n return this._cache\n }\n\n private lruFromRecord(cacheRecord: SingleContextLruCacheRecord) {\n return new LRUMap.LRUMap<string, SingleContextLruCacheItem>(this.limit, cacheRecord.entries.entries())\n }\n\n private async fetchCacheRecord(agentContext: AgentContext) {\n const cacheRepository = agentContext.dependencyManager.resolve(SingleContextLruCacheRepository)\n let cacheRecord = await cacheRepository.findById(agentContext, CONTEXT_STORAGE_LRU_CACHE_ID)\n\n if (!cacheRecord) {\n cacheRecord = new SingleContextLruCacheRecord({\n id: CONTEXT_STORAGE_LRU_CACHE_ID,\n entries: new Map(),\n })\n\n try {\n await cacheRepository.save(agentContext, cacheRecord)\n } catch (error) {\n // This addresses some race conditions issues where we first check if the record exists\n // then we create one if it doesn't, but another process has created one in the meantime\n // Although not the most elegant solution, it addresses the issues\n if (error instanceof RecordDuplicateError) {\n // the record already exists, which is our intended end state\n // we can ignore this error and fetch the existing record\n return cacheRepository.getById(agentContext, CONTEXT_STORAGE_LRU_CACHE_ID)\n }\n throw error\n }\n }\n\n return cacheRecord\n }\n\n private removeExpiredItems(cache: LRUMap.LRUMap<string, SingleContextLruCacheItem>) {\n cache.forEach((value, key) => {\n if (value.expiresAt && Date.now() > value.expiresAt) {\n cache.delete(key)\n }\n })\n }\n\n private async persistCache(agentContext: AgentContext) {\n const cacheRepository = agentContext.dependencyManager.resolve(SingleContextLruCacheRepository)\n const cache = await this.getCache(agentContext)\n\n await cacheRepository.update(\n agentContext,\n new SingleContextLruCacheRecord({\n entries: new Map(cache.toJSON().map(({ key, value }) => [key, value])),\n id: CONTEXT_STORAGE_LRU_CACHE_ID,\n })\n )\n }\n\n /**\n * Asserts this class is not used with multiple agent context instances.\n */\n private assertContextCorrelationId(agentContext: AgentContext) {\n if (!this._contextCorrelationId) {\n this._contextCorrelationId = agentContext.contextCorrelationId\n }\n\n if (this._contextCorrelationId !== agentContext.contextCorrelationId) {\n throw new CredoError(\n 'SingleContextStorageLruCache can not be used with multiple agent context instances. Register a custom cache implementation in the CacheModule.'\n )\n }\n }\n}\n"],"mappings":";;;;;;;AASA,MAAM,+BAA+B;;;;;;;;;;;;;;AAoBrC,IAAa,+BAAb,MAA2D;CAKzD,YAAmB,EAAE,SAA8C;EACjE,KAAK,QAAQ;CACf;CAEA,MAAa,IAAgB,cAA4B,KAAa,UAAyB;EAC7F,KAAK,2BAA2B,YAAY;EAE5C,MAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;EAC9C,KAAK,mBAAmB,KAAK;EAE7B,MAAM,OAAO,MAAM,IAAI,GAAG;EAG1B,IAAI,CAAC,MAAM,OAAO;EAGlB,IAAI,KAAK,aAAa,KAAK,IAAI,IAAI,KAAK,WAAW;GACjD,MAAM,OAAO,GAAG;GAChB,MAAM,KAAK,aAAa,YAAY;GACpC,OAAO;EACT;EAEA,OAAO,KAAK;CACd;CAEA,MAAa,IACX,cACA,KACA,OACA,kBACA,UACe;EACf,KAAK,2BAA2B,YAAY;EAE5C,IAAI;EAEJ,IAAI,kBAAkB;GACpB,8BAAc,IAAI,KAAK;GACvB,YAAY,WAAW,YAAY,WAAW,IAAI,gBAAgB;EACpE;EAEA,MAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;EAC9C,KAAK,mBAAmB,KAAK;EAE7B,MAAM,IAAI,KAAK;GACb,WAAW,aAAa,QAAQ;GAChC;EACF,CAAC;EACD,MAAM,KAAK,aAAa,YAAY;CACtC;CAEA,MAAa,OAAO,cAA4B,KAAa,UAAwC;EACnG,KAAK,2BAA2B,YAAY;EAE5C,MAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;EAC9C,KAAK,mBAAmB,KAAK;EAC7B,MAAM,OAAO,GAAG;EAEhB,MAAM,KAAK,aAAa,YAAY;CACtC;CAEA,MAAc,SAAS,cAA4B;EACjD,IAAI,CAAC,KAAK,QAAQ;GAChB,MAAM,cAAc,MAAM,KAAK,iBAAiB,YAAY;GAC5D,KAAK,SAAS,KAAK,cAAc,WAAW;EAC9C;EAEA,OAAO,KAAK;CACd;CAEA,cAAsB,aAA0C;EAC9D,OAAO,IAAI,OAAO,OAA0C,KAAK,OAAO,YAAY,QAAQ,QAAQ,CAAC;CACvG;CAEA,MAAc,iBAAiB,cAA4B;EACzD,MAAM,kBAAkB,aAAa,kBAAkB,QAAQ,+BAA+B;EAC9F,IAAI,cAAc,MAAM,gBAAgB,SAAS,cAAc,4BAA4B;EAE3F,IAAI,CAAC,aAAa;GAChB,cAAc,IAAI,4BAA4B;IAC5C,IAAI;IACJ,yBAAS,IAAI,IAAI;GACnB,CAAC;GAED,IAAI;IACF,MAAM,gBAAgB,KAAK,cAAc,WAAW;GACtD,SAAS,OAAO;IAId,IAAI,iBAAiB,sBAGnB,OAAO,gBAAgB,QAAQ,cAAc,4BAA4B;IAE3E,MAAM;GACR;EACF;EAEA,OAAO;CACT;CAEA,mBAA2B,OAAyD;EAClF,MAAM,SAAS,OAAO,QAAQ;GAC5B,IAAI,MAAM,aAAa,KAAK,IAAI,IAAI,MAAM,WACxC,MAAM,OAAO,GAAG;EAEpB,CAAC;CACH;CAEA,MAAc,aAAa,cAA4B;EACrD,MAAM,kBAAkB,aAAa,kBAAkB,QAAQ,+BAA+B;EAC9F,MAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;EAE9C,MAAM,gBAAgB,OACpB,cACA,IAAI,4BAA4B;GAC9B,SAAS,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,YAAY,CAAC,KAAK,KAAK,CAAC,CAAC;GACrE,IAAI;EACN,CAAC,CACH;CACF;;;;CAKA,2BAAmC,cAA4B;EAC7D,IAAI,CAAC,KAAK,uBACR,KAAK,wBAAwB,aAAa;EAG5C,IAAI,KAAK,0BAA0B,aAAa,sBAC9C,MAAM,IAAI,WACR,gJACF;CAEJ;AACF"}