{"version":3,"file":"index.mjs","names":["#client","#keyPrefix","#getCollectionsSetKey","#getDocumentKey","documents: Document[]"],"sources":["../src/index.ts"],"sourcesContent":["// oxlint-disable no-explicit-any\n// oxlint-disable no-unsafe-member-access\n\nimport {\n  DatabaseAdapterErrors,\n  type DatabaseAdapter,\n  type DatabaseAdapterOptions,\n  type DatabaseDocumentListOptions,\n  type StoryBookerDatabaseDocument,\n} from \"@storybooker/core/adapter\";\nimport type { RedisClientType } from \"redis\";\n\n/**\n * Redis database adapter for StoryBooker.\n * Uses key prefixes to simulate collections since Redis is key-value store.\n *\n * Key structure: `collection:{collectionId}:doc:{documentId}`\n * Collections list key: `collections`\n *\n * Compatible with all Redis services (Redis Cloud, AWS ElastiCache, Azure Cache, etc.)\n */\nexport class RedisDatabaseAdapter implements DatabaseAdapter {\n  #client: RedisClientType;\n  #keyPrefix: string;\n\n  constructor(client: RedisClientType, keyPrefix = \"sbr\") {\n    this.#client = client;\n    this.#keyPrefix = keyPrefix;\n  }\n\n  metadata: DatabaseAdapter[\"metadata\"] = { name: \"Redis\" };\n\n  init: DatabaseAdapter[\"init\"] = async (_options) => {\n    // Ensure Redis connection is ready\n    if (!this.#client.isReady) {\n      try {\n        await this.#client.connect();\n      } catch (error) {\n        throw new DatabaseAdapterErrors.DatabaseNotInitializedError(error);\n      }\n    }\n  };\n\n  // Helper methods for key generation\n  //   #getCollectionKey(collectionId: string): string {\n  //     return `${this.#keyPrefix}:collection:${collectionId}`;\n  //   }\n\n  #getDocumentKey(collectionId: string, documentId: string): string {\n    return `${this.#keyPrefix}:collection:${collectionId}:doc:${documentId}`;\n  }\n\n  #getCollectionsSetKey(): string {\n    return `${this.#keyPrefix}:collections`;\n  }\n\n  listCollections: DatabaseAdapter[\"listCollections\"] = async (_options) => {\n    const collections = await this.#client.sMembers(this.#getCollectionsSetKey());\n    return collections;\n  };\n\n  createCollection: DatabaseAdapter[\"createCollection\"] = async (collectionId, _options) => {\n    try {\n      // Add collection to the set of collections\n      await this.#client.sAdd(this.#getCollectionsSetKey(), collectionId);\n    } catch (error) {\n      throw new DatabaseAdapterErrors.CollectionAlreadyExistsError(collectionId, error);\n    }\n  };\n\n  hasCollection: DatabaseAdapter[\"hasCollection\"] = async (collectionId, _options) => {\n    const exists = await this.#client.sIsMember(this.#getCollectionsSetKey(), collectionId);\n    return exists > 0;\n  };\n\n  deleteCollection: DatabaseAdapter[\"deleteCollection\"] = async (collectionId, _options) => {\n    try {\n      // Get all document keys for this collection\n      const pattern = this.#getDocumentKey(collectionId, \"*\");\n      const keys = await this.#client.keys(pattern);\n\n      // Delete all documents in the collection\n      if (keys.length > 0) {\n        await this.#client.del(keys);\n      }\n\n      // Remove collection from the set\n      await this.#client.sRem(this.#getCollectionsSetKey(), collectionId);\n    } catch (error) {\n      throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n    }\n  };\n\n  listDocuments: DatabaseAdapter[\"listDocuments\"] = async <\n    Document extends StoryBookerDatabaseDocument,\n  >(\n    collectionId: string,\n    listOptions: DatabaseDocumentListOptions<Document>,\n    _options: DatabaseAdapterOptions,\n  ): Promise<Document[]> => {\n    const pattern = this.#getDocumentKey(collectionId, \"*\");\n    const keys = await this.#client.keys(pattern);\n\n    if (keys.length === 0) {\n      return [];\n    }\n\n    // Get all documents\n    const values = await this.#client.mGet(keys);\n    const documents: Document[] = values\n      .filter((value): value is string => value !== null)\n      .map((value) => JSON.parse(value) as Document);\n\n    // Apply filtering\n    let filteredDocs = documents;\n    const filterFn =\n      listOptions?.filter && typeof listOptions.filter === \"function\"\n        ? listOptions.filter\n        : undefined;\n    if (filterFn) {\n      filteredDocs = documents.filter((doc) => filterFn(doc));\n    }\n    // String filters could be implemented as key pattern matching if needed\n\n    // Apply sorting\n    if (listOptions?.sort) {\n      if (typeof listOptions.sort === \"function\") {\n        filteredDocs.sort(listOptions.sort);\n      } else if (listOptions.sort === \"latest\") {\n        // Assume documents have a createdAt or similar timestamp field\n        filteredDocs.sort((docA, docB) => {\n          const aTime = (docA as any).createdAt ?? (docA as any).timestamp ?? 0;\n          const bTime = (docB as any).createdAt ?? (docB as any).timestamp ?? 0;\n          return bTime - aTime; // Descending order (latest first)\n        });\n      }\n    }\n\n    // Apply limit\n    if (listOptions?.limit && listOptions.limit > 0) {\n      filteredDocs = filteredDocs.slice(0, listOptions.limit);\n    }\n\n    // // Apply select (field projection)\n    // if (listOptions?.select && listOptions.select.length > 0) {\n    //   filteredDocs = filteredDocs.map((doc) => {\n    //     const projected = { id: doc.id } as Document;\n    //     // oxlint-disable-next-line no-non-null-assertion\n    //     for (const field of listOptions.select!) {\n    //       if (field in doc) {\n    //         projected[field] = (doc as any)[field];\n    //       }\n    //     }\n    //     return projected;\n    //   });\n    // }\n\n    return filteredDocs;\n  };\n\n  getDocument: DatabaseAdapter[\"getDocument\"] = async <\n    Document extends StoryBookerDatabaseDocument,\n  >(\n    collectionId: string,\n    documentId: string,\n    _options: DatabaseAdapterOptions,\n  ): Promise<Document> => {\n    const key = this.#getDocumentKey(collectionId, documentId);\n    const value = await this.#client.get(key);\n\n    if (!value) {\n      throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n    }\n\n    const document: Document = JSON.parse(value);\n    return document;\n  };\n\n  hasDocument: DatabaseAdapter[\"hasDocument\"] = async (collectionId, documentId, _options) => {\n    const key = this.#getDocumentKey(collectionId, documentId);\n    const exists = await this.#client.exists(key);\n    return exists === 1;\n  };\n\n  createDocument: DatabaseAdapter[\"createDocument\"] = async (\n    collectionId,\n    documentData,\n    _options,\n  ) => {\n    // Ensure collection exists\n    try {\n      await this.#client.sAdd(this.#getCollectionsSetKey(), collectionId);\n    } catch (error) {\n      throw new DatabaseAdapterErrors.CollectionDoesNotExistError(collectionId, error);\n    }\n\n    try {\n      const key = this.#getDocumentKey(collectionId, documentData.id);\n      const value = JSON.stringify(documentData);\n      await this.#client.set(key, value);\n    } catch (error) {\n      throw new DatabaseAdapterErrors.DocumentAlreadyExistsError(\n        collectionId,\n        documentData.id,\n        error,\n      );\n    }\n  };\n\n  updateDocument: DatabaseAdapter[\"updateDocument\"] = async (\n    collectionId,\n    documentId,\n    documentData,\n  ) => {\n    const key = this.#getDocumentKey(collectionId, documentId);\n\n    // Get existing document\n    const existingValue = await this.#client.get(key);\n    if (!existingValue) {\n      throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n    }\n\n    const existingDoc = JSON.parse(existingValue);\n    const updatedDoc = { ...existingDoc, ...documentData };\n\n    const value = JSON.stringify(updatedDoc);\n    await this.#client.set(key, value);\n  };\n\n  deleteDocument: DatabaseAdapter[\"deleteDocument\"] = async (\n    collectionId,\n    documentId,\n    _options,\n  ) => {\n    const key = this.#getDocumentKey(collectionId, documentId);\n    const deleted = await this.#client.del(key);\n\n    if (deleted === 0) {\n      throw new DatabaseAdapterErrors.DocumentDoesNotExistError(collectionId, documentId);\n    }\n  };\n}\n"],"mappings":"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"}