{"version":3,"file":"Repository.mjs","names":[],"sources":["../../src/storage/Repository.ts"],"sourcesContent":["import type { AgentContext } from '../agent'\nimport type { EventEmitter } from '../agent/EventEmitter'\nimport { RecordDuplicateError, RecordNotFoundError } from '../error'\nimport { CachedStorageService } from '../modules/cache/CachedStorageService'\nimport { CacheModuleConfig } from '../modules/cache/CacheModuleConfig'\nimport { JsonTransformer } from '../utils'\nimport type { BaseRecord } from './BaseRecord'\nimport type { RecordDeletedEvent, RecordSavedEvent, RecordUpdatedEvent } from './RepositoryEvents'\nimport { RepositoryEventTypes } from './RepositoryEvents'\nimport type { BaseRecordConstructor, Query, QueryOptions, StorageService } from './StorageService'\n\n// biome-ignore lint/suspicious/noExplicitAny: no explanation\nexport class Repository<T extends BaseRecord<any, any, any>> {\n  private storageService: StorageService<T>\n  private recordClass: BaseRecordConstructor<T>\n  private eventEmitter: EventEmitter\n\n  public constructor(\n    recordClass: BaseRecordConstructor<T>,\n    storageService: StorageService<T>,\n    eventEmitter: EventEmitter\n  ) {\n    this.recordClass = recordClass\n    this.storageService = storageService\n    this.eventEmitter = eventEmitter\n  }\n\n  private getStorageService(agentContext: AgentContext): StorageService<T> {\n    try {\n      if (agentContext.dependencyManager.isRegistered(CachedStorageService, true)) {\n        return agentContext.resolve(CachedStorageService<T>)\n      }\n\n      return this.storageService\n    } catch {\n      return this.storageService\n    }\n  }\n\n  public supportsLocking(agentContext: AgentContext): boolean {\n    return this.getStorageService(agentContext).supportsLocking?.(agentContext) ?? false\n  }\n\n  /** @inheritDoc {StorageService#save} */\n  public async save(agentContext: AgentContext, record: T): Promise<void> {\n    await this.getStorageService(agentContext).save(agentContext, record)\n\n    this.eventEmitter.emit<RecordSavedEvent<T>>(agentContext, {\n      type: RepositoryEventTypes.RecordSaved,\n      payload: {\n        // Record in event should be static\n        record: record.clone(),\n      },\n    })\n  }\n\n  /** @inheritDoc {StorageService#update} */\n  public async update(agentContext: AgentContext, record: T): Promise<void> {\n    await this.getStorageService(agentContext).update(agentContext, record)\n\n    this.eventEmitter.emit<RecordUpdatedEvent<T>>(agentContext, {\n      type: RepositoryEventTypes.RecordUpdated,\n      payload: {\n        // Record in event should be static\n        record: record.clone(),\n      },\n    })\n  }\n\n  /** @inheritDoc {StorageService#updateByIdWithLock} */\n  public async updateByIdWithLock(\n    agentContext: AgentContext,\n    id: string,\n    updateCallback: (record: T) => Promise<T>\n  ): Promise<T> {\n    const storageService = this.getStorageService(agentContext)\n\n    // NOTE: we might want to not do this, to allow the caller to handle it, (e.g. they might already\n    // have the record and this would just re-fetch it again). Then we could just return null?\n    // If a backend does not implement this method, we fall back to two separate calls to\n    // getById and update\n    if (!storageService.updateByIdWithLock) {\n      const record = await this.getById(agentContext, id)\n      const updatedRecord = await updateCallback(record)\n      await this.update(agentContext, record)\n      return updatedRecord\n    }\n\n    const updatedRecord = await storageService.updateByIdWithLock(agentContext, this.recordClass, id, updateCallback)\n    this.eventEmitter.emit<RecordUpdatedEvent<T>>(agentContext, {\n      type: RepositoryEventTypes.RecordUpdated,\n      payload: {\n        // Record in event should be static\n        record: updatedRecord.clone(),\n      },\n    })\n\n    return updatedRecord\n  }\n\n  /** @inheritDoc {StorageService#delete} */\n  public async delete(agentContext: AgentContext, record: T): Promise<void> {\n    await this.getStorageService(agentContext).delete(agentContext, record)\n\n    this.eventEmitter.emit<RecordDeletedEvent<T>>(agentContext, {\n      type: RepositoryEventTypes.RecordDeleted,\n      payload: {\n        // Record in event should be static\n        record: record.clone(),\n      },\n    })\n  }\n\n  /**\n   * Delete record by id. Throws {RecordNotFoundError} if no record is found\n   * @param id the id of the record to delete\n   * @returns\n   */\n  public async deleteById(agentContext: AgentContext, id: string): Promise<void> {\n    await this.getStorageService(agentContext).deleteById(agentContext, this.recordClass, id)\n\n    this.eventEmitter.emit<RecordDeletedEvent<T>>(agentContext, {\n      type: RepositoryEventTypes.RecordDeleted,\n      payload: {\n        record: { id, type: this.recordClass.type },\n      },\n    })\n  }\n\n  /** @inheritDoc {StorageService#getById} */\n  public async getById(agentContext: AgentContext, id: string): Promise<T> {\n    return this.getStorageService(agentContext).getById(agentContext, this.recordClass, id)\n  }\n\n  /**\n   * Find record by id. Returns null if no record is found\n   * @param id the id of the record to retrieve\n   * @returns\n   */\n  public async findById(agentContext: AgentContext, id: string): Promise<T | null> {\n    try {\n      return await this.getStorageService(agentContext).getById(agentContext, this.recordClass, id)\n    } catch (error) {\n      if (error instanceof RecordNotFoundError) return null\n\n      throw error\n    }\n  }\n\n  /** @inheritDoc {StorageService#getAll} */\n  public async getAll(agentContext: AgentContext): Promise<T[]> {\n    return this.getStorageService(agentContext).getAll(agentContext, this.recordClass)\n  }\n\n  /** @inheritDoc {StorageService#findByQuery} */\n  public async findByQuery(agentContext: AgentContext, query: Query<T>, queryOptions?: QueryOptions): Promise<T[]> {\n    return this.getStorageService(agentContext).findByQuery(agentContext, this.recordClass, query, queryOptions)\n  }\n\n  /**\n   * Find a single record by query. Returns null if not found.\n   * @param query the query\n   * @param cacheKey optional cache key to use for caching. By default query results are not cached, but if a cache key is provided\n   *                  as well as the record allows caching and the agent has a cached storage service enabled it will use the cache.\n   * @returns the record, or null if not found\n   * @throws {RecordDuplicateError} if multiple records are found for the given query\n   */\n  public async findSingleByQuery(\n    agentContext: AgentContext,\n    query: Query<T>,\n    { cacheKey }: { cacheKey?: string } = {}\n  ): Promise<T | null> {\n    const cache = agentContext.resolve(CacheModuleConfig)\n    const useCacheStorage = cache.useCachedStorageService ?? false\n\n    if (useCacheStorage && cacheKey) {\n      const recordId = (await cache?.cache.get<string>(agentContext, cacheKey)) ?? null\n      if (recordId !== null) {\n        const recordJson = await cache?.cache.get<T>(agentContext, recordId)\n        if (recordJson) return JsonTransformer.fromJSON(recordJson, this.recordClass)\n      }\n    }\n\n    const records = await this.findByQuery(agentContext, query)\n    if (records.length > 1) {\n      throw new RecordDuplicateError(`Multiple records found for given query '${JSON.stringify(query)}'`, {\n        recordType: this.recordClass.type,\n      })\n    }\n\n    if (records.length < 1) {\n      return null\n    }\n\n    if (useCacheStorage && cacheKey) {\n      await cache?.cache.set(agentContext, cacheKey, records[0].id)\n      await cache?.cache.set(agentContext, records[0].id, records[0].toJSON())\n    }\n\n    return records[0]\n  }\n\n  /**\n   * Find a single record by query. Throws if not found\n   * @param query the query\n   * @returns the record\n   * @throws {RecordDuplicateError} if multiple records are found for the given query\n   * @throws {RecordNotFoundError} if no record is found for the given query\n   */\n  public async getSingleByQuery(agentContext: AgentContext, query: Query<T>): Promise<T> {\n    const record = await this.findSingleByQuery(agentContext, query)\n\n    if (!record) {\n      throw new RecordNotFoundError(`No record found for given query '${JSON.stringify(query)}'`, {\n        recordType: this.recordClass.type,\n      })\n    }\n\n    return record\n  }\n}\n"],"mappings":"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"}