{"version":3,"sources":["../../src/cache/redis.ts"],"names":[],"mappings":"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","file":"redis.mjs","sourcesContent":["import type { CacheAdapter } from \"../types\";\n\n/**\n * Minimal interface representing a Redis client.\n * Compatible with both `ioredis` and `redis` (node-redis v4+).\n */\nexport interface RedisLikeClient {\n  get(key: string): Promise<string | null>;\n  set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n  del(...keys: (string | string[])[]): Promise<number>;\n  keys(pattern: string): Promise<string[]>;\n  /** Optional SCAN-based iteration — used when available to avoid blocking the server. */\n  scanIterator?(options: { MATCH: string; COUNT?: number }): AsyncIterable<string>;\n}\n\nexport interface RedisCacheOptions {\n  /** A Redis client instance (ioredis or node-redis). */\n  client: RedisLikeClient;\n  /** Key prefix to namespace ani-client entries (default: `\"ani:\"`) */\n  prefix?: string;\n  /** TTL in milliseconds (default: 86 400 000 = 24h) */\n  ttl?: number;\n}\n\n/**\n * Redis-backed cache adapter for AniListClient.\n *\n * @example\n * ```ts\n * import Redis from \"ioredis\";\n * import { AniListClient, RedisCache } from \"ani-client\";\n *\n * const redis = new Redis();\n * const client = new AniListClient({\n *   cacheAdapter: new RedisCache({ client: redis }),\n * });\n * ```\n */\nexport class RedisCache implements CacheAdapter {\n  private readonly client: RedisLikeClient;\n  private readonly prefix: string;\n  private readonly ttl: number;\n\n  constructor(options: RedisCacheOptions) {\n    this.client = options.client;\n    this.prefix = options.prefix ?? \"ani:\";\n    this.ttl = options.ttl !== undefined ? Math.floor(options.ttl / 1000) : 86_400;\n  }\n\n  private prefixedKey(key: string): string {\n    return `${this.prefix}${key}`;\n  }\n\n  async get<T>(key: string): Promise<T | undefined> {\n    const raw = await this.client.get(this.prefixedKey(key));\n    if (raw === null) return undefined;\n    try {\n      return JSON.parse(raw) as T;\n    } catch {\n      return undefined;\n    }\n  }\n\n  async set<T>(key: string, data: T): Promise<void> {\n    await this.client.set(this.prefixedKey(key), JSON.stringify(data), \"EX\", this.ttl);\n  }\n\n  async delete(key: string): Promise<boolean> {\n    const count = await this.client.del(this.prefixedKey(key));\n    return count > 0;\n  }\n\n  /**\n   * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.\n   *\n   * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.\n   * Provide a client with `scanIterator` support for production use.\n   * @internal\n   */\n  private async collectKeys(pattern: string): Promise<string[]> {\n    if (this.client.scanIterator) {\n      const keys: string[] = [];\n      for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {\n        keys.push(key);\n      }\n      return keys;\n    }\n    return this.client.keys(pattern);\n  }\n\n  async clear(): Promise<void> {\n    const keys = await this.collectKeys(`${this.prefix}*`);\n    if (keys.length > 0) {\n      await this.client.del(...keys);\n    }\n  }\n\n  /**\n   * Get the actual number of keys with this prefix in Redis.\n   */\n  get size(): Promise<number> {\n    return this.getSize();\n  }\n\n  /** @internal */\n  private async getSize(): Promise<number> {\n    const keys = await this.collectKeys(`${this.prefix}*`);\n    return keys.length;\n  }\n\n  async keys(): Promise<string[]> {\n    const raw = await this.collectKeys(`${this.prefix}*`);\n    return raw.map((k) => k.slice(this.prefix.length));\n  }\n\n  /**\n   * Remove all entries whose key matches the given pattern.\n   *\n   * - **String**: treated as a substring match (e.g. `\"Media\"` removes all keys containing `\"Media\"`).\n   * - **RegExp**: tested against each key directly.\n   *\n   * @param pattern — A string (substring match) or RegExp.\n   * @returns Number of entries removed.\n   */\n  async invalidate(pattern: string | RegExp): Promise<number> {\n    if (typeof pattern === \"string\") {\n      const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);\n      if (keys.length === 0) return 0;\n      return this.client.del(...keys);\n    }\n\n    const allKeys = await this.collectKeys(`${this.prefix}*`);\n    const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));\n    if (matching.length === 0) return 0;\n    return this.client.del(...matching);\n  }\n}\n"]}