{"version":3,"sources":["../src/RedisStringsHandler.ts","../src/utils/debug.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/utils/json.ts","../src/CachedHandler.ts","../src/index.ts"],"sourcesContent":["import { commandOptions, createClient } from 'redis';\nimport { SyncedMap } from './SyncedMap';\nimport { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';\nimport { debug } from './utils/debug';\nimport { bufferReviver, bufferReplacer } from './utils/json';\n\nexport type CommandOptions = ReturnType<typeof commandOptions>;\nexport type Client = ReturnType<typeof createClient>;\n\nexport type CacheEntry = {\n  value: unknown;\n  lastModified: number;\n  tags: string[];\n};\n\nexport type CreateRedisStringsHandlerOptions = {\n  /** Redis redis_url to use.\n   * @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST\n          ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n          : 'redis://localhost:6379'\n   */\n  redis_url?:string;\n  /** Redis database number to use. Uses DB 0 for production, DB 1 otherwise\n   * @default process.env.VERCEL_ENV === 'production' ? 0 : 1\n   */\n  database?: number;\n  /** Prefix added to all Redis keys\n   * @default process.env.VERCEL_URL || 'UNDEFINED_URL_'\n   */\n  keyPrefix?: string;\n  /** Timeout in milliseconds for Redis operations\n   * @default 5000\n   */\n  timeoutMs?: number;\n  /** Number of entries to query in one batch during full sync of shared tags hash map\n   * @default 250\n   */\n  revalidateTagQuerySize?: number;\n  /** Key used to store shared tags hash map in Redis\n   * @default '__sharedTags__'\n   */\n  sharedTagsKey?: string;\n  /** Average interval in milliseconds between tag map full re-syncs\n   * @default 3600000 (1 hour)\n   */\n  avgResyncIntervalMs?: number;\n  /** Enable deduplication of Redis get requests via internal in-memory cache\n   * @default true\n   */\n  redisGetDeduplication?: boolean;\n  /** Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely\n   * @default 10000\n   */\n  inMemoryCachingTime?: number;\n  /** Default stale age in seconds for cached items\n   * @default 1209600 (14 days)\n   */\n  defaultStaleAge?: number;\n  /** Function to calculate expire age (redis TTL value) from stale age\n   * @default Production: staleAge * 2, Other: staleAge * 1.2\n   */\n  estimateExpireAge?: (staleAge: number) => number;\n};\n\n// Identifier prefix used by Next.js to mark automatically generated cache tags\n// These tags are created internally by Next.js for route-based invalidation\nconst NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';\n\n// Redis key used to store a map of tags and their last revalidation timestamps\n// This helps track when specific tags were last invalidated\nconst REVALIDATED_TAGS_KEY = '__revalidated_tags__';\n\nexport function getTimeoutRedisCommandOptions(\n  timeoutMs: number,\n): CommandOptions {\n  return commandOptions({ signal: AbortSignal.timeout(timeoutMs) });\n}\n\nexport default class RedisStringsHandler {\n  private client: Client;\n  private sharedTagsMap: SyncedMap<string[]>;\n  private revalidatedTagsMap: SyncedMap<number>;\n  private inMemoryDeduplicationCache: SyncedMap<\n    Promise<ReturnType<Client['get']>>\n  >;\n  private redisGet: Client['get'];\n  private redisDeduplicationHandler: DeduplicatedRequestHandler<\n    Client['get'],\n    string | Buffer | null\n  >;\n  private deduplicatedRedisGet: (key: string) => Client['get'];\n  private timeoutMs: number;\n  private keyPrefix: string;\n  private redisGetDeduplication: boolean;\n  private inMemoryCachingTime: number;\n  private defaultStaleAge: number;\n  private estimateExpireAge: (staleAge: number) => number;\n\n  constructor({\n    redis_url = process.env.REDIS_URL? process.env.REDIS_URL : (\n        process.env.REDISHOST\n            ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n            : 'redis://localhost:6379'\n    ),\n    database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n    keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n    sharedTagsKey = '__sharedTags__',\n    timeoutMs = 5_000,\n    revalidateTagQuerySize = 250,\n    avgResyncIntervalMs = 60 * 60 * 1_000,\n    redisGetDeduplication = true,\n    inMemoryCachingTime = 10_000,\n    defaultStaleAge = 60 * 60 * 24 * 14,\n    estimateExpireAge = (staleAge) =>\n      process.env.VERCEL_ENV === 'production' ? staleAge * 2 : staleAge * 1.2,\n  }: CreateRedisStringsHandlerOptions) {\n    this.keyPrefix = keyPrefix;\n    this.timeoutMs = timeoutMs;\n    this.redisGetDeduplication = redisGetDeduplication;\n    this.inMemoryCachingTime = inMemoryCachingTime;\n    this.defaultStaleAge = defaultStaleAge;\n    this.estimateExpireAge = estimateExpireAge;\n\n    try {\n      this.client = createClient({\n        ...(database !== 0 ? { database } : {}),\n        url: redis_url,\n      });\n\n      this.client.on('error', (error) => {\n        console.error('Redis client error', error);\n      });\n\n      this.client\n        .connect()\n        .then(() => {\n          console.info('Redis client connected.');\n        })\n        .catch(() => {\n          this.client.connect().catch((error) => {\n            console.error('Failed to connect Redis client:', error);\n            this.client.disconnect();\n            throw error;\n          });\n        });\n    } catch (error: unknown) {\n      console.error('Failed to initialize Redis client');\n      throw error;\n    }\n\n    const filterKeys = (key: string): boolean =>\n      key !== REVALIDATED_TAGS_KEY && key !== sharedTagsKey;\n\n    this.sharedTagsMap = new SyncedMap<string[]>({\n      client: this.client,\n      keyPrefix,\n      redisKey: sharedTagsKey,\n      database,\n      timeoutMs,\n      querySize: revalidateTagQuerySize,\n      filterKeys,\n      resyncIntervalMs:\n        avgResyncIntervalMs -\n        avgResyncIntervalMs / 10 +\n        Math.random() * (avgResyncIntervalMs / 10),\n    });\n\n    this.revalidatedTagsMap = new SyncedMap<number>({\n      client: this.client,\n      keyPrefix,\n      redisKey: REVALIDATED_TAGS_KEY,\n      database,\n      timeoutMs,\n      querySize: revalidateTagQuerySize,\n      filterKeys,\n      resyncIntervalMs:\n        avgResyncIntervalMs +\n        avgResyncIntervalMs / 10 +\n        Math.random() * (avgResyncIntervalMs / 10),\n    });\n\n    this.inMemoryDeduplicationCache = new SyncedMap({\n      client: this.client,\n      keyPrefix,\n      redisKey: 'inMemoryDeduplicationCache',\n      database,\n      timeoutMs,\n      querySize: revalidateTagQuerySize,\n      filterKeys,\n      customizedSync: {\n        withoutRedisHashmap: true,\n        withoutSetSync: true,\n      },\n    });\n\n    const redisGet: Client['get'] = this.client.get.bind(this.client);\n    this.redisDeduplicationHandler = new DeduplicatedRequestHandler(\n      redisGet,\n      inMemoryCachingTime,\n      this.inMemoryDeduplicationCache,\n    );\n    this.redisGet = redisGet;\n    this.deduplicatedRedisGet =\n      this.redisDeduplicationHandler.deduplicatedFunction;\n  }\n\n  resetRequestCache(): void {}\n\n  private async assertClientIsReady(): Promise<void> {\n    await Promise.all([\n      this.sharedTagsMap.waitUntilReady(),\n      this.revalidatedTagsMap.waitUntilReady(),\n    ]);\n    if (!this.client.isReady) {\n      throw new Error('Redis client is not ready yet or connection is lost.');\n    }\n  }\n\n  public async get(\n    key: string,\n    ctx:\n      | {\n          kind: 'APP_ROUTE' | 'APP_PAGE';\n          isRoutePPREnabled: boolean;\n          isFallback: boolean;\n        }\n      | {\n          kind: 'FETCH';\n          revalidate: number;\n          fetchUrl: string;\n          fetchIdx: number;\n          tags: string[];\n          softTags: string[];\n          isFallback: boolean;\n        },\n  ): Promise<CacheEntry | null> {\n    if (\n      ctx.kind !== 'APP_ROUTE' &&\n      ctx.kind !== 'APP_PAGE' &&\n      ctx.kind !== 'FETCH'\n    ) {\n      console.warn(\n        'RedisStringsHandler.get() called with',\n        key,\n        ctx,\n        ' this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ',\n        (ctx as { kind: string })?.kind,\n      );\n    }\n\n    debug('green', 'RedisStringsHandler.get() called with', key, ctx);\n    await this.assertClientIsReady();\n\n    const clientGet = this.redisGetDeduplication\n      ? this.deduplicatedRedisGet(key)\n      : this.redisGet;\n    const serializedCacheEntry = await clientGet(\n      getTimeoutRedisCommandOptions(this.timeoutMs),\n      this.keyPrefix + key,\n    );\n\n    debug(\n      'green',\n      'RedisStringsHandler.get() finished with result (serializedCacheEntry)',\n      serializedCacheEntry?.substring(0, 200),\n    );\n\n    if (!serializedCacheEntry) {\n      return null;\n    }\n\n    const cacheEntry: CacheEntry | null = JSON.parse(\n      serializedCacheEntry,\n      bufferReviver,\n    );\n\n    debug(\n      'green',\n      'RedisStringsHandler.get() finished with result (cacheEntry)',\n      JSON.stringify(cacheEntry).substring(0, 200),\n    );\n\n    if (!cacheEntry) {\n      return null;\n    }\n\n    if (!cacheEntry?.tags) {\n      console.warn(\n        'RedisStringsHandler.get() called with',\n        key,\n        ctx,\n        'cacheEntry is mall formed (missing tags)',\n      );\n    }\n    if (!cacheEntry?.value) {\n      console.warn(\n        'RedisStringsHandler.get() called with',\n        key,\n        ctx,\n        'cacheEntry is mall formed (missing value)',\n      );\n    }\n    if (!cacheEntry?.lastModified) {\n      console.warn(\n        'RedisStringsHandler.get() called with',\n        key,\n        ctx,\n        'cacheEntry is mall formed (missing lastModified)',\n      );\n    }\n\n    if (ctx.kind === 'FETCH') {\n      const combinedTags = new Set([\n        ...(ctx?.softTags || []),\n        ...(ctx?.tags || []),\n      ]);\n\n      if (combinedTags.size === 0) {\n        return cacheEntry;\n      }\n\n      // INFO: implicit tags (revalidate of nested fetch in api route/page on revalidatePath call of the page/api route). See revalidateTag() for more information\n      //\n      // This code checks if any of the cache tags associated with this entry (normally the internal tag of the parent page/api route containing the fetch request)\n      // have been revalidated since the entry was last modified. If any tag was revalidated more recently than the entry's\n      // lastModified timestamp, then the cached content is considered stale (therefore return null) and should be removed.\n      for (const tag of combinedTags) {\n        // Get the last revalidation time for this tag from our revalidatedTagsMap\n        const revalidationTime = this.revalidatedTagsMap.get(tag);\n\n        // If we have a revalidation time for this tag and it's more recent than when\n        // this cache entry was last modified, the entry is stale\n        if (revalidationTime && revalidationTime > cacheEntry.lastModified) {\n          const redisKey = this.keyPrefix + key;\n\n          // We don't await this cleanup since it can happen asynchronously in the background.\n          // The cache entry is already considered invalid at this point.\n          this.client\n            .unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey)\n            .catch((err) => {\n              // If the first unlink fails, only log the error\n              // Never implement a retry here as the cache entry will be updated directly after this get request\n              console.error(\n                'Error occurred while unlinking stale data. Error was:',\n                err,\n              );\n            })\n            .finally(async () => {\n              // Clean up our tag tracking maps after the Redis key is removed\n              await this.sharedTagsMap.delete(key);\n              await this.revalidatedTagsMap.delete(tag);\n            });\n\n          debug(\n            'green',\n            'RedisStringsHandler.get() found revalidation time for tag. Cache entry is stale and will be deleted and \"null\" will be returned.',\n            tag,\n            redisKey,\n            revalidationTime,\n            cacheEntry,\n          );\n\n          // Return null to indicate no valid cache entry was found\n          return null;\n        }\n      }\n    }\n\n    return cacheEntry;\n  }\n  public async set(\n    key: string,\n    data:\n      | {\n          kind: 'APP_PAGE';\n          status: number;\n          headers: {\n            'x-nextjs-stale-time': string; // timestamp in ms\n            'x-next-cache-tags': string; // comma separated paths (tags)\n          };\n          html: string;\n          rscData: Buffer;\n          segmentData: unknown;\n          postboned: unknown;\n        }\n      | {\n          kind: 'APP_ROUTE';\n          status: number;\n          headers: {\n            'cache-control'?: string;\n            'x-nextjs-stale-time': string; // timestamp in ms\n            'x-next-cache-tags': string; // comma separated paths (tags)\n          };\n          body: Buffer;\n        }\n      | {\n          kind: 'FETCH';\n          data: {\n            headers: Record<string, string>;\n            body: string; // base64 encoded\n            status: number;\n            url: string;\n          };\n          revalidate: number | false;\n        },\n    ctx: {\n      isRoutePPREnabled: boolean;\n      isFallback: boolean;\n      tags?: string[];\n      // Different versions of Next.js use different arguments for the same functionality\n      revalidate?: number | false; // Version 15.0.3\n      cacheControl?: { revalidate: 5; expire: undefined }; // Version 15.0.3\n    },\n  ) {\n    if (\n      data.kind !== 'APP_ROUTE' &&\n      data.kind !== 'APP_PAGE' &&\n      data.kind !== 'FETCH'\n    ) {\n      console.warn(\n        'RedisStringsHandler.set() called with',\n        key,\n        ctx,\n        data,\n        ' this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ',\n        (data as { kind: string })?.kind,\n      );\n    }\n\n    await this.assertClientIsReady();\n\n    if (data.kind === 'APP_PAGE' || data.kind === 'APP_ROUTE') {\n      const tags = data.headers['x-next-cache-tags']?.split(',');\n      ctx.tags = [...(ctx.tags || []), ...(tags || [])];\n    }\n\n    // Constructing and serializing the value for storing it in redis\n    const cacheEntry: CacheEntry = {\n      lastModified: Date.now(),\n      tags: ctx?.tags || [],\n      value: data,\n    };\n    const serializedCacheEntry = JSON.stringify(cacheEntry, bufferReplacer);\n\n    // pre seed data into deduplicated get client. This will reduce redis load by not requesting\n    // the same value from redis which was just set.\n    if (this.redisGetDeduplication) {\n      this.redisDeduplicationHandler.seedRequestReturn(\n        key,\n        serializedCacheEntry,\n      );\n    }\n\n    // TODO: implement expiration based on cacheControl.expire argument, -> probably relevant for cacheLife and \"use cache\" etc.: https://nextjs.org/docs/app/api-reference/functions/cacheLife\n    // Constructing the expire time for the cache entry\n    const revalidate = ctx.revalidate || ctx.cacheControl?.revalidate;\n    const expireAt =\n      revalidate && Number.isSafeInteger(revalidate) && revalidate > 0\n        ? this.estimateExpireAge(revalidate)\n        : this.estimateExpireAge(this.defaultStaleAge);\n\n    // Setting the cache entry in redis\n    const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n    const setOperation: Promise<string | null> = this.client.set(\n      options,\n      this.keyPrefix + key,\n      serializedCacheEntry,\n      {\n        EX: expireAt,\n      },\n    );\n\n    debug(\n      'blue',\n      'RedisStringsHandler.set() will set the following serializedCacheEntry',\n      this.keyPrefix,\n      key,\n      data,\n      ctx,\n      serializedCacheEntry?.substring(0, 200),\n      expireAt,\n    );\n\n    // Setting the tags for the cache entry in the sharedTagsMap (locally stored hashmap synced via redis)\n    let setTagsOperation: Promise<void> | undefined;\n    if (ctx.tags && ctx.tags.length > 0) {\n      const currentTags = this.sharedTagsMap.get(key);\n      const currentIsSameAsNew =\n        currentTags?.length === ctx.tags.length &&\n        currentTags.every((v) => ctx.tags!.includes(v)) &&\n        ctx.tags.every((v) => currentTags.includes(v));\n\n      if (!currentIsSameAsNew) {\n        setTagsOperation = this.sharedTagsMap.set(\n          key,\n          structuredClone(ctx.tags) as string[],\n        );\n      }\n    }\n\n    debug(\n      'blue',\n      'RedisStringsHandler.set() will set the following sharedTagsMap',\n      key,\n      ctx.tags as string[],\n    );\n\n    await Promise.all([setOperation, setTagsOperation]);\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  public async revalidateTag(tagOrTags: string | string[], ...rest: any[]) {\n    debug(\n      'red',\n      'RedisStringsHandler.revalidateTag() called with',\n      tagOrTags,\n      rest,\n    );\n    const tags = new Set([tagOrTags || []].flat());\n    await this.assertClientIsReady();\n\n    // find all keys that are related to this tag\n    const keysToDelete: Set<string> = new Set();\n\n    for (const tag of tags) {\n      // INFO: implicit tags (revalidate of nested fetch in api route/page on revalidatePath call of the page/api route)\n      //\n      // Invalidation logic for fetch requests that are related to a invalidated page.\n      // revalidateTag is called for the page tag (_N_T_...) and the fetch request needs to be invalidated as well\n      // unfortunately this is not possible since the revalidateTag is not called with any data that would allow us to find the cache entry of the fetch request\n      // in case of a fetch request get method call, the get method of the cache handler is called with some information about the pages/routes the fetch request is inside\n      // therefore we only mark the page/route as stale here (with help of the revalidatedTagsMap)\n      // and delete the cache entry of the fetch request on the next request to the get function\n      if (tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID)) {\n        const now = Date.now();\n        debug(\n          'red',\n          'RedisStringsHandler.revalidateTag() set revalidation time for tag',\n          tag,\n          'to',\n          now,\n        );\n        await this.revalidatedTagsMap.set(tag, now);\n      }\n    }\n\n    // Scan the whole sharedTagsMap for keys that are dependent on any of the revalidated tags\n    for (const [key, sharedTags] of this.sharedTagsMap.entries()) {\n      if (sharedTags.some((tag) => tags.has(tag))) {\n        keysToDelete.add(key);\n      }\n    }\n\n    debug(\n      'red',\n      'RedisStringsHandler.revalidateTag() found',\n      keysToDelete,\n      'keys to delete',\n    );\n\n    // exit early if no keys are related to this tag\n    if (keysToDelete.size === 0) {\n      return;\n    }\n\n    // prepare deletion of all keys in redis that are related to this tag\n    const redisKeys = Array.from(keysToDelete);\n    const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);\n    const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n    const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);\n\n    // also delete entries from in-memory deduplication cache if they get revalidated\n    if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {\n      for (const key of keysToDelete) {\n        this.inMemoryDeduplicationCache.delete(key);\n      }\n    }\n\n    // prepare deletion of entries from shared tags map if they get revalidated so that the map will not grow indefinitely\n    const deleteTagsOperation = this.sharedTagsMap.delete(redisKeys);\n\n    // execute keys and tag maps deletion\n    await Promise.all([deleteKeysOperation, deleteTagsOperation]);\n    debug(\n      'red',\n      'RedisStringsHandler.revalidateTag() finished delete operations',\n    );\n  }\n}\n","export function debug(\n  color:\n    | 'red'\n    | 'blue'\n    | 'green'\n    | 'yellow'\n    | 'cyan'\n    | 'white'\n    | 'none' = 'none',\n  ...args: unknown[]\n): void {\n  const colorCode = {\n    red: '\\x1b[31m',\n    blue: '\\x1b[34m',\n    green: '\\x1b[32m',\n    yellow: '\\x1b[33m',\n    cyan: '\\x1b[36m',\n    white: '\\x1b[37m',\n    none: '',\n  };\n  if (process.env.DEBUG_CACHE_HANDLER) {\n    console.log(colorCode[color], 'DEBUG CACHE HANDLER: ', ...args);\n  }\n}\n\nexport function debugVerbose(color: string, ...args: unknown[]) {\n  if (process.env.DEBUG_CACHE_HANDLER_VERBOSE_VERBOSE) {\n    console.log('\\x1b[35m', 'DEBUG SYNCED MAP: ', ...args);\n  }\n}\n","// SyncedMap.ts\nimport { Client, getTimeoutRedisCommandOptions } from './RedisStringsHandler';\nimport { debugVerbose, debug } from './utils/debug';\n\ntype CustomizedSync = {\n  withoutRedisHashmap?: boolean;\n  withoutSetSync?: boolean;\n};\n\ntype SyncedMapOptions = {\n  client: Client;\n  keyPrefix: string;\n  redisKey: string; // Redis Hash key\n  database: number;\n  timeoutMs: number;\n  querySize: number;\n  filterKeys: (key: string) => boolean;\n  resyncIntervalMs?: number;\n  customizedSync?: CustomizedSync;\n};\n\nexport type SyncMessage<V> = {\n  type: 'insert' | 'delete';\n  key?: string;\n  value?: V;\n  keys?: string[];\n};\n\nconst SYNC_CHANNEL_SUFFIX = ':sync-channel:';\nexport class SyncedMap<V> {\n  private client: Client;\n  private subscriberClient: Client;\n  private map: Map<string, V>;\n  private keyPrefix: string;\n  private syncChannel: string;\n  private redisKey: string;\n  private database: number;\n  private timeoutMs: number;\n  private querySize: number;\n  private filterKeys: (key: string) => boolean;\n  private resyncIntervalMs?: number;\n  private customizedSync?: CustomizedSync;\n\n  private setupLock: Promise<void>;\n  private setupLockResolve!: () => void;\n\n  constructor(options: SyncedMapOptions) {\n    this.client = options.client;\n    this.keyPrefix = options.keyPrefix;\n    this.redisKey = options.redisKey;\n    this.syncChannel = `${options.keyPrefix}${SYNC_CHANNEL_SUFFIX}${options.redisKey}`;\n    this.database = options.database;\n    this.timeoutMs = options.timeoutMs;\n    this.querySize = options.querySize;\n    this.filterKeys = options.filterKeys;\n    this.resyncIntervalMs = options.resyncIntervalMs;\n    this.customizedSync = options.customizedSync;\n\n    this.map = new Map<string, V>();\n    this.subscriberClient = this.client.duplicate();\n    this.setupLock = new Promise<void>((resolve) => {\n      this.setupLockResolve = resolve;\n    });\n\n    this.setup().catch((error) => {\n      console.error('Failed to setup SyncedMap:', error);\n      throw error;\n    });\n  }\n\n  private async setup() {\n    let setupPromises: Promise<void>[] = [];\n    if (!this.customizedSync?.withoutRedisHashmap) {\n      setupPromises.push(this.initialSync());\n      this.setupPeriodicResync();\n    }\n    setupPromises.push(this.setupPubSub());\n    await Promise.all(setupPromises);\n    this.setupLockResolve();\n  }\n\n  private async initialSync() {\n    let cursor = 0;\n    const hScanOptions = { COUNT: this.querySize };\n\n    try {\n      do {\n        const remoteItems = await this.client.hScan(\n          getTimeoutRedisCommandOptions(this.timeoutMs),\n          this.keyPrefix + this.redisKey,\n          cursor,\n          hScanOptions,\n        );\n        for (const { field, value } of remoteItems.tuples) {\n          if (this.filterKeys(field)) {\n            const parsedValue = JSON.parse(value);\n            this.map.set(field, parsedValue);\n          }\n        }\n        cursor = remoteItems.cursor;\n      } while (cursor !== 0);\n\n      // Clean up keys not in Redis\n      await this.cleanupKeysNotInRedis();\n    } catch (error) {\n      console.error('Error during initial sync:', error);\n      throw error;\n    }\n  }\n\n  private async cleanupKeysNotInRedis() {\n    let cursor = 0;\n    const scanOptions = { COUNT: this.querySize, MATCH: `${this.keyPrefix}*` };\n    let remoteKeys: string[] = [];\n    try {\n      do {\n        const remoteKeysPortion = await this.client.scan(\n          getTimeoutRedisCommandOptions(this.timeoutMs),\n          cursor,\n          scanOptions,\n        );\n        remoteKeys = remoteKeys.concat(remoteKeysPortion.keys);\n        cursor = remoteKeysPortion.cursor;\n      } while (cursor !== 0);\n\n      const remoteKeysSet = new Set(\n        remoteKeys.map((key) => key.substring(this.keyPrefix.length)),\n      );\n\n      const keysToDelete: string[] = [];\n      for (const key of this.map.keys()) {\n        const keyStr = key as unknown as string;\n        if (!remoteKeysSet.has(keyStr) && this.filterKeys(keyStr)) {\n          keysToDelete.push(keyStr);\n        }\n      }\n\n      if (keysToDelete.length > 0) {\n        await this.delete(keysToDelete);\n      }\n    } catch (error) {\n      console.error('Error during cleanup of keys not in Redis:', error);\n      throw error;\n    }\n  }\n\n  private setupPeriodicResync() {\n    if (this.resyncIntervalMs && this.resyncIntervalMs > 0) {\n      setInterval(() => {\n        this.initialSync().catch((error) => {\n          console.error('Error during periodic resync:', error);\n        });\n      }, this.resyncIntervalMs);\n    }\n  }\n\n  private async setupPubSub() {\n    const syncHandler = async (message: string) => {\n      const syncMessage: SyncMessage<V> = JSON.parse(message);\n      if (syncMessage.type === 'insert') {\n        if (syncMessage.key !== undefined && syncMessage.value !== undefined) {\n          this.map.set(syncMessage.key, syncMessage.value);\n        }\n      } else if (syncMessage.type === 'delete') {\n        if (syncMessage.keys) {\n          for (const key of syncMessage.keys) {\n            this.map.delete(key);\n          }\n        }\n      }\n    };\n\n    const keyEventHandler = async (key: string, message: string) => {\n      debug(\n        'yellow',\n        'SyncedMap.keyEventHandler() called with message',\n        this.redisKey,\n        message,\n        key,\n      );\n      // const key = message;\n      if (key.startsWith(this.keyPrefix)) {\n        const keyInMap = key.substring(this.keyPrefix.length);\n        if (this.filterKeys(keyInMap)) {\n          debugVerbose(\n            'SyncedMap.keyEventHandler() key matches filter and will be deleted',\n            this.redisKey,\n            message,\n            key,\n          );\n          await this.delete(keyInMap, true);\n        }\n      } else {\n        debugVerbose(\n          'SyncedMap.keyEventHandler() key does not have prefix',\n          this.redisKey,\n          message,\n          key,\n        );\n      }\n    };\n\n    try {\n      await this.subscriberClient.connect().catch(async () => {\n        await this.subscriberClient.connect();\n      });\n\n      // Check if keyspace event configuration is set correctly\n      const keyspaceEventConfig = (\n        await this.subscriberClient.configGet('notify-keyspace-events')\n      )?.['notify-keyspace-events'];\n      if (!keyspaceEventConfig.includes('E')) {\n        throw new Error(\n          \"Keyspace event configuration has to include 'E' for Keyevent events, published with __keyevent@<db>__ prefix. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`\",\n        );\n      }\n      if (\n        !keyspaceEventConfig.includes('A') &&\n        !(\n          keyspaceEventConfig.includes('x') && keyspaceEventConfig.includes('e')\n        )\n      ) {\n        throw new Error(\n          \"Keyspace event configuration has to include 'A' or 'x' and 'e' for expired and evicted events. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`\",\n        );\n      }\n\n      await Promise.all([\n        // We use a custom channel for insert/delete For the following reason:\n        // With custom channel we can delete multiple entries in one message. If we would listen to unlink / del we\n        // could get thousands of messages for one revalidateTag (For example revalidateTag(\"algolia\") would send an enormous amount of network packages)\n        // Also we can send the value in the message for insert\n        this.subscriberClient.subscribe(this.syncChannel, syncHandler),\n        // Subscribe to Redis keyevent notifications for evicted and expired keys\n        this.subscriberClient.subscribe(\n          `__keyevent@${this.database}__:evicted`,\n          keyEventHandler,\n        ),\n        this.subscriberClient.subscribe(\n          `__keyevent@${this.database}__:expired`,\n          keyEventHandler,\n        ),\n      ]);\n\n      // Error handling for reconnection\n      this.subscriberClient.on('error', async (err) => {\n        console.error('Subscriber client error:', err);\n        try {\n          await this.subscriberClient.quit();\n          this.subscriberClient = this.client.duplicate();\n          await this.setupPubSub();\n        } catch (reconnectError) {\n          console.error(\n            'Failed to reconnect subscriber client:',\n            reconnectError,\n          );\n        }\n      });\n    } catch (error) {\n      console.error('Error setting up pub/sub client:', error);\n      throw error;\n    }\n  }\n\n  public async waitUntilReady() {\n    await this.setupLock;\n  }\n\n  public get(key: string): V | undefined {\n    debugVerbose(\n      'SyncedMap.get() called with key',\n      key,\n      JSON.stringify(this.map.get(key))?.substring(0, 100),\n    );\n    return this.map.get(key);\n  }\n\n  public async set(key: string, value: V): Promise<void> {\n    debugVerbose(\n      'SyncedMap.set() called with key',\n      key,\n      JSON.stringify(value)?.substring(0, 100),\n    );\n    this.map.set(key, value);\n    const operations = [];\n\n    // This is needed if we only want to sync delete commands. This is especially useful for non serializable data like a promise map\n    if (this.customizedSync?.withoutSetSync) {\n      return;\n    }\n    if (!this.customizedSync?.withoutRedisHashmap) {\n      const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n      operations.push(\n        this.client.hSet(\n          options,\n          this.keyPrefix + this.redisKey,\n          key as unknown as string,\n          JSON.stringify(value),\n        ),\n      );\n    }\n\n    const insertMessage: SyncMessage<V> = {\n      type: 'insert',\n      key: key as unknown as string,\n      value,\n    };\n    operations.push(\n      this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),\n    );\n    await Promise.all(operations);\n  }\n\n  // /api/revalidated-fetch\n  // true\n\n  public async delete(\n    keys: string[] | string,\n    withoutSyncMessage = false,\n  ): Promise<void> {\n    debugVerbose(\n      'SyncedMap.delete() called with keys',\n      this.redisKey,\n      keys,\n      withoutSyncMessage,\n    );\n\n    const keysArray = Array.isArray(keys) ? keys : [keys];\n    const operations = [];\n\n    for (const key of keysArray) {\n      this.map.delete(key);\n    }\n\n    if (!this.customizedSync?.withoutRedisHashmap) {\n      const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n      operations.push(\n        this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray),\n      );\n    }\n\n    if (!withoutSyncMessage) {\n      const deletionMessage: SyncMessage<V> = {\n        type: 'delete',\n        keys: keysArray,\n      };\n      operations.push(\n        this.client.publish(this.syncChannel, JSON.stringify(deletionMessage)),\n      );\n    }\n\n    await Promise.all(operations);\n    debugVerbose(\n      'SyncedMap.delete() finished operations',\n      this.redisKey,\n      keys,\n      operations.length,\n    );\n  }\n\n  public has(key: string): boolean {\n    return this.map.has(key);\n  }\n\n  public entries(): IterableIterator<[string, V]> {\n    return this.map.entries();\n  }\n}\n","import { debugVerbose } from './utils/debug';\nimport { SyncedMap } from './SyncedMap';\nexport class DeduplicatedRequestHandler<\n  T extends (...args: [never, never]) => Promise<K>,\n  K,\n> {\n  private inMemoryDeduplicationCache: SyncedMap<Promise<K>>;\n  private cachingTimeMs: number;\n  private fn: T;\n\n  constructor(\n    fn: T,\n    cachingTimeMs: number,\n    inMemoryDeduplicationCache: SyncedMap<Promise<K>>,\n  ) {\n    this.fn = fn;\n    this.cachingTimeMs = cachingTimeMs;\n    this.inMemoryDeduplicationCache = inMemoryDeduplicationCache;\n  }\n\n  // Method to manually seed a result into the cache\n  seedRequestReturn(key: string, value: K): void {\n    const resultPromise = new Promise<K>((res) => res(value));\n    this.inMemoryDeduplicationCache.set(key, resultPromise);\n\n    debugVerbose(\n      'DeduplicatedRequestHandler.seedRequestReturn() seeded result ',\n      key,\n      (value as string).substring(0, 200),\n    );\n\n    setTimeout(() => {\n      this.inMemoryDeduplicationCache.delete(key);\n    }, this.cachingTimeMs);\n  }\n\n  // Method to handle deduplicated requests\n  deduplicatedFunction = (key: string): T => {\n    debugVerbose(\n      'DeduplicatedRequestHandler.deduplicatedFunction() called with',\n      key,\n    );\n    //eslint-disable-next-line @typescript-eslint/no-this-alias\n    const self = this;\n    const dedupedFn = async (...args: [never, never]): Promise<K> => {\n      // If there's already a pending request with the same key, return it\n      debugVerbose(\n        'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn called with',\n        key,\n      );\n      if (\n        self.inMemoryDeduplicationCache &&\n        self.inMemoryDeduplicationCache.has(key)\n      ) {\n        debugVerbose(\n          'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ',\n          key,\n          'found key in inMemoryDeduplicationCache',\n        );\n        const res = await self.inMemoryDeduplicationCache\n          .get(key)!\n          .then((v) => structuredClone(v));\n        debugVerbose(\n          'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ',\n          key,\n          'found key in inMemoryDeduplicationCache and served result from there',\n          JSON.stringify(res).substring(0, 200),\n        );\n        return res;\n      }\n\n      // If no pending request, call the original function and store the promise\n      const promise = self.fn(...args);\n      self.inMemoryDeduplicationCache.set(key, promise);\n\n      debugVerbose(\n        'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ',\n        key,\n        'did not found key in inMemoryDeduplicationCache. Setting it now and waiting for promise to resolve',\n      );\n\n      try {\n        const ts = performance.now();\n        const result = await promise;\n        debugVerbose(\n          'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ',\n          key,\n          'promise resolved (in ',\n          performance.now() - ts,\n          'ms). Returning result',\n          JSON.stringify(result).substring(0, 200),\n        );\n        return structuredClone(result);\n      } finally {\n        // Once the promise is resolved/rejected and caching timeout is over, remove it from the map\n        setTimeout(() => {\n          debugVerbose(\n            'DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ',\n            key,\n            'deleting key from inMemoryDeduplicationCache after ',\n            self.cachingTimeMs,\n            'ms',\n          );\n          self.inMemoryDeduplicationCache.delete(key);\n        }, self.cachingTimeMs);\n      }\n    };\n    return dedupedFn as T;\n  };\n}\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function bufferReviver(_: string, value: any): any {\n  if (value && typeof value === 'object' && typeof value.$binary === 'string') {\n    return Buffer.from(value.$binary, 'base64');\n  }\n  return value;\n}\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function bufferReplacer(_: string, value: any): any {\n  if (Buffer.isBuffer(value)) {\n    return {\n      $binary: value.toString('base64'),\n    };\n  }\n  if (\n    value &&\n    typeof value === 'object' &&\n    value?.type === 'Buffer' &&\n    Array.isArray(value.data)\n  ) {\n    return {\n      $binary: Buffer.from(value.data).toString('base64'),\n    };\n  }\n  return value;\n}\n","import RedisStringsHandler, {\n  CreateRedisStringsHandlerOptions,\n} from './RedisStringsHandler';\nimport { debugVerbose } from './utils/debug';\n\nlet cachedHandler: RedisStringsHandler;\n\nexport default class CachedHandler {\n  constructor(options: CreateRedisStringsHandlerOptions) {\n    if (!cachedHandler) {\n      console.log('created cached handler');\n      cachedHandler = new RedisStringsHandler(options);\n    }\n  }\n  get(\n    ...args: Parameters<RedisStringsHandler['get']>\n  ): ReturnType<RedisStringsHandler['get']> {\n    debugVerbose('CachedHandler.get called with', args);\n    return cachedHandler.get(...args);\n  }\n  set(\n    ...args: Parameters<RedisStringsHandler['set']>\n  ): ReturnType<RedisStringsHandler['set']> {\n    debugVerbose('CachedHandler.set called with', args);\n    return cachedHandler.set(...args);\n  }\n  revalidateTag(\n    ...args: Parameters<RedisStringsHandler['revalidateTag']>\n  ): ReturnType<RedisStringsHandler['revalidateTag']> {\n    debugVerbose('CachedHandler.revalidateTag called with', args);\n    return cachedHandler.revalidateTag(...args);\n  }\n  resetRequestCache(\n    ...args: Parameters<RedisStringsHandler['resetRequestCache']>\n  ): ReturnType<RedisStringsHandler['resetRequestCache']> {\n    // debug(\"CachedHandler.resetRequestCache called with\", args);\n    return cachedHandler.resetRequestCache(...args);\n  }\n}\n","import CachedHandler from './CachedHandler';\nexport default CachedHandler;\nimport RedisStringsHandler from './RedisStringsHandler';\nexport { RedisStringsHandler };\n"],"mappings":"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","names":[]}