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