{"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"}