{"version":3,"file":"StorageService.mjs","names":[],"sources":["../../src/storage/StorageService.ts"],"sourcesContent":["import type { AgentContext } from '../agent'\nimport { JsonEncoder } from '../utils'\nimport type { Constructor } from '../utils/mixins'\nimport type { BaseRecord, TagsBase } from './BaseRecord'\n\n// https://stackoverflow.com/questions/51954558/how-can-i-remove-a-wider-type-from-a-union-type-without-removing-its-subtypes-in/51955852#51955852\n// biome-ignore lint/suspicious/noExplicitAny: no explanation\nexport type SimpleQuery<T extends BaseRecord<any, any, any>> =\n  T extends BaseRecord<infer DefaultTags, infer CustomTags>\n    ? DefaultTags extends TagsBase\n      ? Partial<ReturnType<T['getTags']>> & TagsBase\n      : CustomTags extends TagsBase\n        ? Partial<ReturnType<T['getTags']>> & TagsBase\n        : Partial<DefaultTags & CustomTags> & TagsBase\n    : Partial<ReturnType<T['getTags']>> & TagsBase\n\n// biome-ignore lint/suspicious/noExplicitAny: no explanation\ninterface AdvancedQuery<T extends BaseRecord<any, any, any>> {\n  $and?: Query<T>[]\n  $or?: Query<T>[]\n  $not?: Query<T>\n}\n\nexport type QueryOptions = {\n  limit?: number\n  offset?: number\n  /**\n   * Cursor and offset cannot be used together.\n   * In case both are present 'cursor' based filtering is used.\n   *\n   * Cursor based pagination is currently only supported for records stored in drizzle-storage\n   */\n  cursor?: {\n    before?: string\n    after?: string\n  }\n}\n\ntype InternalCursor = {\n  createdAt: Date\n  id: string\n}\n\nexport type RecordToCursorBody = {\n  createdAt: string | Date\n  id: string\n}\n\nexport function recordToCursor(record: RecordToCursorBody): string {\n  return encodeCursor({\n    createdAt: record.createdAt instanceof Date ? record.createdAt : new Date(record.createdAt),\n    id: record.id,\n  })\n}\n\nexport function encodeCursor(cursor: InternalCursor): string {\n  return JsonEncoder.toBase64Url(cursor)\n}\n\nexport function decodeCursor(cursor: string): InternalCursor | null {\n  try {\n    const decoded = JsonEncoder.fromBase64Url(cursor)\n\n    if (!decoded || typeof decoded !== 'object') return null\n    if (typeof decoded.id !== 'string') return null\n    if (!decoded.createdAt) return null\n\n    const createdAt = new Date(decoded?.createdAt)\n    if (Number.isNaN(createdAt.getTime())) return null\n\n    return {\n      id: decoded?.id,\n      createdAt,\n    }\n  } catch {\n    return null\n  }\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: no explanation\nexport type Query<T extends BaseRecord<any, any, any>> = AdvancedQuery<T> | SimpleQuery<T>\n\nexport interface BaseRecordConstructor<T> extends Constructor<T> {\n  type: string\n  allowCache: boolean\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: no explanation\nexport interface StorageService<T extends BaseRecord<any, any, any>> {\n  supportsCursorPagination: boolean\n  /**\n   * Save record in storage\n   *\n   * @param record the record to store\n   * @throws {RecordDuplicateError} if a record with this id already exists\n   */\n  save(agentContext: AgentContext, record: T): Promise<void>\n\n  /**\n   * Update record in storage\n   *\n   * @param record the record to update\n   * @throws {RecordNotFoundError} if a record with this id and type does not exist\n   */\n  update(agentContext: AgentContext, record: T): Promise<void>\n\n  /**\n   * Delete record from storage\n   *\n   * @param record the record to delete\n   * @throws {RecordNotFoundError} if a record with this id and type does not exist\n   */\n  delete(agentContext: AgentContext, record: T): Promise<void>\n\n  /**\n   * Delete record by id.\n   *\n   * @param recordClass the record class to delete the record for\n   * @param id the id of the record to delete from storage\n   * @throws {RecordNotFoundError} if a record with this id and type does not exist\n   */\n  deleteById(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>, id: string): Promise<void>\n\n  /**\n   * Get record by id.\n   *\n   * @param recordClass the record class to get the record for\n   * @param id the id of the record to retrieve from storage\n   * @throws {RecordNotFoundError} if a record with this id and type does not exist\n   */\n  getById(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>, id: string): Promise<T>\n\n  /**\n   * Retrieve the record with by id, and provide it in the callback for update.\n   * The returned record will be stored.\n   *\n   * The purpose of this method is to allow storage services that support locking\n   * to lock the record, preventing concurrent processes from overwriting updates\n   * to the record.\n   *\n   * Note that locking a record can result in deadlocks, and slow down processes.\n   * It's recommended to minimize the side effects performed in the `updateCallback`\n   *\n   * TODO: should we allow partial updates for backend that support it? E.g. with drizzle\n   * we can update just a value which makes locking less needed in some cases.\n   */\n  updateByIdWithLock?(\n    agentContext: AgentContext,\n    recordClass: BaseRecordConstructor<T>,\n    id: string,\n    updateCallback: (record: T) => Promise<T>\n  ): Promise<T>\n\n  /**\n   * Whether the storage service supports locking. This may be dependant on\n   * the agent context. If the method is not implemented it is assumed the\n   * storage service does not support locking\n   */\n  supportsLocking?(agentContext: AgentContext): boolean\n\n  /**\n   * Get all records by specified record class.\n   *\n   * @param recordClass the record class to get records for\n   */\n  getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>): Promise<T[]>\n\n  /**\n   * Find all records by specified record class and query.\n   *\n   * @param recordClass the record class to find records for\n   * @param query the query to use for finding records\n   * @param queryOptions optional parameters to customize the query execution (e.g., limit, offset)\n   *\n   */\n  findByQuery(\n    agentContext: AgentContext,\n    recordClass: BaseRecordConstructor<T>,\n    query: Query<T>,\n    queryOptions?: QueryOptions\n  ): Promise<T[]>\n}\n"],"mappings":";;;;;;AAgDA,SAAgB,eAAe,QAAoC;AACjE,QAAO,aAAa;EAClB,WAAW,OAAO,qBAAqB,OAAO,OAAO,YAAY,IAAI,KAAK,OAAO,UAAU;EAC3F,IAAI,OAAO;EACZ,CAAC;;AAGJ,SAAgB,aAAa,QAAgC;AAC3D,QAAO,YAAY,YAAY,OAAO;;AAGxC,SAAgB,aAAa,QAAuC;AAClE,KAAI;EACF,MAAM,UAAU,YAAY,cAAc,OAAO;AAEjD,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,MAAI,OAAO,QAAQ,OAAO,SAAU,QAAO;AAC3C,MAAI,CAAC,QAAQ,UAAW,QAAO;EAE/B,MAAM,YAAY,IAAI,KAAK,SAAS,UAAU;AAC9C,MAAI,OAAO,MAAM,UAAU,SAAS,CAAC,CAAE,QAAO;AAE9C,SAAO;GACL,IAAI,SAAS;GACb;GACD;SACK;AACN,SAAO"}