@trieb.work/nextjs-turbo-redis-cache 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +12 -0
- package/CHANGELOG.md +15 -0
- package/README.md +1 -0
- package/dist/index.d.mts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +532 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +509 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +13 -10
- package/scripts/vitest-run-staged.cjs +1 -1
- package/tsup.config.ts +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/RedisStringsHandler.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/CachedHandler.ts"],"sourcesContent":["import CachedHandler from \"./CachedHandler\";\nexport default CachedHandler;","import { commandOptions, createClient } from 'redis';\nimport { SyncedMap } from './SyncedMap';\nimport { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';\nimport {\n CacheHandler,\n CacheHandlerValue,\n IncrementalCache,\n} from 'next/dist/server/lib/incremental-cache';\n\nexport type CommandOptions = ReturnType<typeof commandOptions>;\ntype GetParams = Parameters<IncrementalCache['get']>;\ntype SetParams = Parameters<IncrementalCache['set']>;\ntype RevalidateParams = Parameters<IncrementalCache['revalidateTag']>;\nexport type Client = ReturnType<typeof createClient>;\n\nexport type CreateRedisStringsHandlerOptions = {\n database?: number;\n keyPrefix?: string;\n timeoutMs?: number;\n revalidateTagQuerySize?: number;\n sharedTagsKey?: string;\n avgResyncIntervalMs?: number;\n redisGetDeduplication?: boolean;\n inMemoryCachingTime?: number;\n defaultStaleAge?: number;\n estimateExpireAge?: (staleAge: number) => number;\n};\n\nconst NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';\nconst REVALIDATED_TAGS_KEY = '__revalidated_tags__';\n\nfunction isImplicitTag(tag: string): boolean {\n return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID);\n}\n\nexport function getTimeoutRedisCommandOptions(\n timeoutMs: number,\n): CommandOptions {\n return commandOptions({ signal: AbortSignal.timeout(timeoutMs) });\n}\n\nexport default class RedisStringsHandler implements CacheHandler {\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 database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = 5000,\n revalidateTagQuerySize = 250,\n avgResyncIntervalMs = 60 * 60 * 1000,\n redisGetDeduplication = true,\n inMemoryCachingTime = 10_000,\n defaultStaleAge = 60 * 60 * 24 * 14,\n estimateExpireAge = (staleAge) =>\n process.env.VERCEL_ENV === 'preview' ? staleAge * 1.2 : staleAge * 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: process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\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((error) => {\n console.error('Failed to connect Redis client:', error);\n this.client.disconnect();\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 resetRequestCache(...args: never[]): void {\n console.warn('WARNING resetRequestCache() was called', args);\n }\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(key: GetParams[0], ctx: GetParams[1]) {\n await this.assertClientIsReady();\n\n const clientGet = this.redisGetDeduplication\n ? this.deduplicatedRedisGet(key)\n : this.redisGet;\n const result = await clientGet(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + key,\n );\n\n if (!result) {\n return null;\n }\n\n const cacheValue = JSON.parse(result) as\n | (CacheHandlerValue & { lastModified: number })\n | null;\n\n if (!cacheValue) {\n return null;\n }\n\n if (cacheValue.value?.kind === 'FETCH') {\n cacheValue.value.data.body = Buffer.from(\n cacheValue.value.data.body,\n ).toString('base64');\n }\n\n const combinedTags = new Set([\n ...(ctx?.softTags || []),\n ...(ctx?.tags || []),\n ]);\n\n if (combinedTags.size === 0) {\n return cacheValue;\n }\n\n for (const tag of combinedTags) {\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n const revalidationTime = this.revalidatedTagsMap.get(tag);\n if (revalidationTime && revalidationTime > cacheValue.lastModified) {\n const redisKey = this.keyPrefix + key;\n // Do not await here as this can happen in the background while we can already serve the cacheValue\n this.client\n .unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey)\n .catch((err) => {\n console.error(\n 'Error occurred while unlinking stale data. Retrying now. Error was:',\n err,\n );\n this.client.unlink(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n redisKey,\n );\n })\n .finally(async () => {\n await this.sharedTagsMap.delete(key);\n await this.revalidatedTagsMap.delete(tag);\n });\n return null;\n }\n }\n\n return cacheValue;\n }\n public async set(\n key: SetParams[0],\n data: SetParams[1] & { lastModified: number },\n ctx: SetParams[2],\n ) {\n if (data.kind === 'FETCH') {\n console.time('encoding' + key);\n data.data.body = Buffer.from(data.data.body, 'base64').toString();\n console.timeEnd('encoding' + key);\n }\n await this.assertClientIsReady();\n\n data.lastModified = Date.now();\n\n const value = JSON.stringify(data);\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(key, value);\n }\n\n const expireAt =\n ctx.revalidate &&\n Number.isSafeInteger(ctx.revalidate) &&\n ctx.revalidate > 0\n ? this.estimateExpireAge(ctx.revalidate)\n : this.estimateExpireAge(this.defaultStaleAge);\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n const setOperation: Promise<string | null> = this.client.set(\n options,\n this.keyPrefix + key,\n value,\n {\n EX: expireAt,\n },\n );\n\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 await Promise.all([setOperation, setTagsOperation]);\n }\n public async revalidateTag(tagOrTags: RevalidateParams[0]) {\n const tags = new Set([tagOrTags || []].flat());\n await this.assertClientIsReady();\n\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n for (const tag of tags) {\n if (isImplicitTag(tag)) {\n const now = Date.now();\n await this.revalidatedTagsMap.set(tag, now);\n }\n }\n\n const keysToDelete: string[] = [];\n\n for (const [key, sharedTags] of this.sharedTagsMap.entries()) {\n if (sharedTags.some((tag) => tags.has(tag))) {\n keysToDelete.push(key);\n }\n }\n\n if (keysToDelete.length === 0) {\n return;\n }\n\n const fullRedisKeys = keysToDelete.map((key) => this.keyPrefix + key);\n\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n\n const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);\n\n // delete entries from in-memory deduplication cache\n if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {\n for (const key of keysToDelete) {\n this.inMemoryDeduplicationCache.delete(key);\n }\n }\n\n const deleteTagsOperation = this.sharedTagsMap.delete(keysToDelete);\n\n await Promise.all([deleteKeysOperation, deleteTagsOperation]);\n }\n}\n","// SyncedMap.ts\nimport { Client, getTimeoutRedisCommandOptions } from './RedisStringsHandler';\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 (_channel: string, message: string) => {\n const key = message;\n if (key.startsWith(this.keyPrefix)) {\n const keyInMap = key.substring(this.keyPrefix.length);\n if (this.filterKeys(keyInMap)) {\n await this.delete(keyInMap, true);\n }\n }\n };\n\n try {\n await this.subscriberClient.connect();\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 keyspace 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 return this.map.get(key);\n }\n\n public async set(key: string, value: V): Promise<void> {\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 public async delete(\n keys: string[] | string,\n withoutSyncMessage = false,\n ): Promise<void> {\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 await Promise.all(operations);\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 { 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 setTimeout(() => {\n this.inMemoryDeduplicationCache.delete(key);\n }, this.cachingTimeMs);\n }\n\n // Method to handle deduplicated requests\n deduplicatedFunction = (key: string): T => {\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 if (\n self.inMemoryDeduplicationCache &&\n self.inMemoryDeduplicationCache.has(key)\n ) {\n const res = await self.inMemoryDeduplicationCache\n .get(key)!\n .then((v) => structuredClone(v));\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 try {\n const result = await promise;\n return structuredClone(result);\n } finally {\n // Once the promise is resolved/rejected, remove it from the map\n setTimeout(() => {\n self.inMemoryDeduplicationCache.delete(key);\n }, self.cachingTimeMs);\n }\n };\n return dedupedFn as T;\n };\n}\n","import { CacheHandler } from \"next/dist/server/lib/incremental-cache\";\nimport RedisStringsHandler, { CreateRedisStringsHandlerOptions } from \"./RedisStringsHandler\";\n\nlet cachedHandler: RedisStringsHandler;\n\nexport default class CachedHandler implements CacheHandler {\n constructor(options: CreateRedisStringsHandlerOptions) {\n if (!cachedHandler) {\n console.log(\"created cached handler\");\n cachedHandler = new RedisStringsHandler(options);\n }\n }\n get(...args: Parameters<RedisStringsHandler[\"get\"]>): ReturnType<RedisStringsHandler[\"get\"]> {\n return cachedHandler.get(...args);\n }\n set(...args: Parameters<RedisStringsHandler[\"set\"]>): ReturnType<RedisStringsHandler[\"set\"]> {\n return cachedHandler.set(...args);\n }\n revalidateTag(...args: Parameters<RedisStringsHandler[\"revalidateTag\"]>): ReturnType<RedisStringsHandler[\"revalidateTag\"]> {\n return cachedHandler.revalidateTag(...args);\n }\n resetRequestCache(...args: Parameters<RedisStringsHandler[\"resetRequestCache\"]>): ReturnType<RedisStringsHandler[\"resetRequestCache\"]> {\n return cachedHandler.resetRequestCache(...args);\n }\n}"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
// src/RedisStringsHandler.ts
|
|
2
|
+
import { commandOptions, createClient } from "redis";
|
|
3
|
+
|
|
4
|
+
// src/SyncedMap.ts
|
|
5
|
+
var SYNC_CHANNEL_SUFFIX = ":sync-channel:";
|
|
6
|
+
var SyncedMap = class {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.client = options.client;
|
|
9
|
+
this.keyPrefix = options.keyPrefix;
|
|
10
|
+
this.redisKey = options.redisKey;
|
|
11
|
+
this.syncChannel = `${options.keyPrefix}${SYNC_CHANNEL_SUFFIX}${options.redisKey}`;
|
|
12
|
+
this.database = options.database;
|
|
13
|
+
this.timeoutMs = options.timeoutMs;
|
|
14
|
+
this.querySize = options.querySize;
|
|
15
|
+
this.filterKeys = options.filterKeys;
|
|
16
|
+
this.resyncIntervalMs = options.resyncIntervalMs;
|
|
17
|
+
this.customizedSync = options.customizedSync;
|
|
18
|
+
this.map = /* @__PURE__ */ new Map();
|
|
19
|
+
this.subscriberClient = this.client.duplicate();
|
|
20
|
+
this.setupLock = new Promise((resolve) => {
|
|
21
|
+
this.setupLockResolve = resolve;
|
|
22
|
+
});
|
|
23
|
+
this.setup().catch((error) => {
|
|
24
|
+
console.error("Failed to setup SyncedMap:", error);
|
|
25
|
+
throw error;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async setup() {
|
|
29
|
+
let setupPromises = [];
|
|
30
|
+
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
31
|
+
setupPromises.push(this.initialSync());
|
|
32
|
+
this.setupPeriodicResync();
|
|
33
|
+
}
|
|
34
|
+
setupPromises.push(this.setupPubSub());
|
|
35
|
+
await Promise.all(setupPromises);
|
|
36
|
+
this.setupLockResolve();
|
|
37
|
+
}
|
|
38
|
+
async initialSync() {
|
|
39
|
+
let cursor = 0;
|
|
40
|
+
const hScanOptions = { COUNT: this.querySize };
|
|
41
|
+
try {
|
|
42
|
+
do {
|
|
43
|
+
const remoteItems = await this.client.hScan(
|
|
44
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
45
|
+
this.keyPrefix + this.redisKey,
|
|
46
|
+
cursor,
|
|
47
|
+
hScanOptions
|
|
48
|
+
);
|
|
49
|
+
for (const { field, value } of remoteItems.tuples) {
|
|
50
|
+
if (this.filterKeys(field)) {
|
|
51
|
+
const parsedValue = JSON.parse(value);
|
|
52
|
+
this.map.set(field, parsedValue);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
cursor = remoteItems.cursor;
|
|
56
|
+
} while (cursor !== 0);
|
|
57
|
+
await this.cleanupKeysNotInRedis();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("Error during initial sync:", error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async cleanupKeysNotInRedis() {
|
|
64
|
+
let cursor = 0;
|
|
65
|
+
const scanOptions = { COUNT: this.querySize, MATCH: `${this.keyPrefix}*` };
|
|
66
|
+
let remoteKeys = [];
|
|
67
|
+
try {
|
|
68
|
+
do {
|
|
69
|
+
const remoteKeysPortion = await this.client.scan(
|
|
70
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
71
|
+
cursor,
|
|
72
|
+
scanOptions
|
|
73
|
+
);
|
|
74
|
+
remoteKeys = remoteKeys.concat(remoteKeysPortion.keys);
|
|
75
|
+
cursor = remoteKeysPortion.cursor;
|
|
76
|
+
} while (cursor !== 0);
|
|
77
|
+
const remoteKeysSet = new Set(
|
|
78
|
+
remoteKeys.map((key) => key.substring(this.keyPrefix.length))
|
|
79
|
+
);
|
|
80
|
+
const keysToDelete = [];
|
|
81
|
+
for (const key of this.map.keys()) {
|
|
82
|
+
const keyStr = key;
|
|
83
|
+
if (!remoteKeysSet.has(keyStr) && this.filterKeys(keyStr)) {
|
|
84
|
+
keysToDelete.push(keyStr);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (keysToDelete.length > 0) {
|
|
88
|
+
await this.delete(keysToDelete);
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("Error during cleanup of keys not in Redis:", error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
setupPeriodicResync() {
|
|
96
|
+
if (this.resyncIntervalMs && this.resyncIntervalMs > 0) {
|
|
97
|
+
setInterval(() => {
|
|
98
|
+
this.initialSync().catch((error) => {
|
|
99
|
+
console.error("Error during periodic resync:", error);
|
|
100
|
+
});
|
|
101
|
+
}, this.resyncIntervalMs);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async setupPubSub() {
|
|
105
|
+
const syncHandler = async (message) => {
|
|
106
|
+
const syncMessage = JSON.parse(message);
|
|
107
|
+
if (syncMessage.type === "insert") {
|
|
108
|
+
if (syncMessage.key !== void 0 && syncMessage.value !== void 0) {
|
|
109
|
+
this.map.set(syncMessage.key, syncMessage.value);
|
|
110
|
+
}
|
|
111
|
+
} else if (syncMessage.type === "delete") {
|
|
112
|
+
if (syncMessage.keys) {
|
|
113
|
+
for (const key of syncMessage.keys) {
|
|
114
|
+
this.map.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const keyEventHandler = async (_channel, message) => {
|
|
120
|
+
const key = message;
|
|
121
|
+
if (key.startsWith(this.keyPrefix)) {
|
|
122
|
+
const keyInMap = key.substring(this.keyPrefix.length);
|
|
123
|
+
if (this.filterKeys(keyInMap)) {
|
|
124
|
+
await this.delete(keyInMap, true);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
await this.subscriberClient.connect();
|
|
130
|
+
await Promise.all([
|
|
131
|
+
// We use a custom channel for insert/delete For the following reason:
|
|
132
|
+
// With custom channel we can delete multiple entries in one message. If we would listen to unlink / del we
|
|
133
|
+
// could get thousands of messages for one revalidateTag (For example revalidateTag("algolia") would send an enormous amount of network packages)
|
|
134
|
+
// Also we can send the value in the message for insert
|
|
135
|
+
this.subscriberClient.subscribe(this.syncChannel, syncHandler),
|
|
136
|
+
// Subscribe to Redis keyspace notifications for evicted and expired keys
|
|
137
|
+
this.subscriberClient.subscribe(
|
|
138
|
+
`__keyevent@${this.database}__:evicted`,
|
|
139
|
+
keyEventHandler
|
|
140
|
+
),
|
|
141
|
+
this.subscriberClient.subscribe(
|
|
142
|
+
`__keyevent@${this.database}__:expired`,
|
|
143
|
+
keyEventHandler
|
|
144
|
+
)
|
|
145
|
+
]);
|
|
146
|
+
this.subscriberClient.on("error", async (err) => {
|
|
147
|
+
console.error("Subscriber client error:", err);
|
|
148
|
+
try {
|
|
149
|
+
await this.subscriberClient.quit();
|
|
150
|
+
this.subscriberClient = this.client.duplicate();
|
|
151
|
+
await this.setupPubSub();
|
|
152
|
+
} catch (reconnectError) {
|
|
153
|
+
console.error(
|
|
154
|
+
"Failed to reconnect subscriber client:",
|
|
155
|
+
reconnectError
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Error setting up pub/sub client:", error);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async waitUntilReady() {
|
|
165
|
+
await this.setupLock;
|
|
166
|
+
}
|
|
167
|
+
get(key) {
|
|
168
|
+
return this.map.get(key);
|
|
169
|
+
}
|
|
170
|
+
async set(key, value) {
|
|
171
|
+
this.map.set(key, value);
|
|
172
|
+
const operations = [];
|
|
173
|
+
if (this.customizedSync?.withoutSetSync) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
177
|
+
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
178
|
+
operations.push(
|
|
179
|
+
this.client.hSet(
|
|
180
|
+
options,
|
|
181
|
+
this.keyPrefix + this.redisKey,
|
|
182
|
+
key,
|
|
183
|
+
JSON.stringify(value)
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const insertMessage = {
|
|
188
|
+
type: "insert",
|
|
189
|
+
key,
|
|
190
|
+
value
|
|
191
|
+
};
|
|
192
|
+
operations.push(
|
|
193
|
+
this.client.publish(this.syncChannel, JSON.stringify(insertMessage))
|
|
194
|
+
);
|
|
195
|
+
await Promise.all(operations);
|
|
196
|
+
}
|
|
197
|
+
async delete(keys, withoutSyncMessage = false) {
|
|
198
|
+
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
199
|
+
const operations = [];
|
|
200
|
+
for (const key of keysArray) {
|
|
201
|
+
this.map.delete(key);
|
|
202
|
+
}
|
|
203
|
+
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
204
|
+
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
205
|
+
operations.push(
|
|
206
|
+
this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (!withoutSyncMessage) {
|
|
210
|
+
const deletionMessage = {
|
|
211
|
+
type: "delete",
|
|
212
|
+
keys: keysArray
|
|
213
|
+
};
|
|
214
|
+
operations.push(
|
|
215
|
+
this.client.publish(this.syncChannel, JSON.stringify(deletionMessage))
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
await Promise.all(operations);
|
|
219
|
+
}
|
|
220
|
+
has(key) {
|
|
221
|
+
return this.map.has(key);
|
|
222
|
+
}
|
|
223
|
+
entries() {
|
|
224
|
+
return this.map.entries();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/DeduplicatedRequestHandler.ts
|
|
229
|
+
var DeduplicatedRequestHandler = class {
|
|
230
|
+
constructor(fn, cachingTimeMs, inMemoryDeduplicationCache) {
|
|
231
|
+
// Method to handle deduplicated requests
|
|
232
|
+
this.deduplicatedFunction = (key) => {
|
|
233
|
+
const self = this;
|
|
234
|
+
const dedupedFn = async (...args) => {
|
|
235
|
+
if (self.inMemoryDeduplicationCache && self.inMemoryDeduplicationCache.has(key)) {
|
|
236
|
+
const res = await self.inMemoryDeduplicationCache.get(key).then((v) => structuredClone(v));
|
|
237
|
+
return res;
|
|
238
|
+
}
|
|
239
|
+
const promise = self.fn(...args);
|
|
240
|
+
self.inMemoryDeduplicationCache.set(key, promise);
|
|
241
|
+
try {
|
|
242
|
+
const result = await promise;
|
|
243
|
+
return structuredClone(result);
|
|
244
|
+
} finally {
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
self.inMemoryDeduplicationCache.delete(key);
|
|
247
|
+
}, self.cachingTimeMs);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
return dedupedFn;
|
|
251
|
+
};
|
|
252
|
+
this.fn = fn;
|
|
253
|
+
this.cachingTimeMs = cachingTimeMs;
|
|
254
|
+
this.inMemoryDeduplicationCache = inMemoryDeduplicationCache;
|
|
255
|
+
}
|
|
256
|
+
// Method to manually seed a result into the cache
|
|
257
|
+
seedRequestReturn(key, value) {
|
|
258
|
+
const resultPromise = new Promise((res) => res(value));
|
|
259
|
+
this.inMemoryDeduplicationCache.set(key, resultPromise);
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
this.inMemoryDeduplicationCache.delete(key);
|
|
262
|
+
}, this.cachingTimeMs);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/RedisStringsHandler.ts
|
|
267
|
+
var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
|
|
268
|
+
var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
|
|
269
|
+
function isImplicitTag(tag) {
|
|
270
|
+
return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID);
|
|
271
|
+
}
|
|
272
|
+
function getTimeoutRedisCommandOptions(timeoutMs) {
|
|
273
|
+
return commandOptions({ signal: AbortSignal.timeout(timeoutMs) });
|
|
274
|
+
}
|
|
275
|
+
var RedisStringsHandler = class {
|
|
276
|
+
constructor({
|
|
277
|
+
database = process.env.VERCEL_ENV === "production" ? 0 : 1,
|
|
278
|
+
keyPrefix = process.env.VERCEL_URL || "UNDEFINED_URL_",
|
|
279
|
+
sharedTagsKey = "__sharedTags__",
|
|
280
|
+
timeoutMs = 5e3,
|
|
281
|
+
revalidateTagQuerySize = 250,
|
|
282
|
+
avgResyncIntervalMs = 60 * 60 * 1e3,
|
|
283
|
+
redisGetDeduplication = true,
|
|
284
|
+
inMemoryCachingTime = 1e4,
|
|
285
|
+
defaultStaleAge = 60 * 60 * 24 * 14,
|
|
286
|
+
estimateExpireAge = (staleAge) => process.env.VERCEL_ENV === "preview" ? staleAge * 1.2 : staleAge * 2
|
|
287
|
+
}) {
|
|
288
|
+
this.keyPrefix = keyPrefix;
|
|
289
|
+
this.timeoutMs = timeoutMs;
|
|
290
|
+
this.redisGetDeduplication = redisGetDeduplication;
|
|
291
|
+
this.inMemoryCachingTime = inMemoryCachingTime;
|
|
292
|
+
this.defaultStaleAge = defaultStaleAge;
|
|
293
|
+
this.estimateExpireAge = estimateExpireAge;
|
|
294
|
+
try {
|
|
295
|
+
this.client = createClient({
|
|
296
|
+
...database !== 0 ? { database } : {},
|
|
297
|
+
url: process.env.REDISHOST ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}` : "redis://localhost:6379"
|
|
298
|
+
});
|
|
299
|
+
this.client.on("error", (error) => {
|
|
300
|
+
console.error("Redis client error", error);
|
|
301
|
+
});
|
|
302
|
+
this.client.connect().then(() => {
|
|
303
|
+
console.info("Redis client connected.");
|
|
304
|
+
}).catch((error) => {
|
|
305
|
+
console.error("Failed to connect Redis client:", error);
|
|
306
|
+
this.client.disconnect();
|
|
307
|
+
});
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error("Failed to initialize Redis client");
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
const filterKeys = (key) => key !== REVALIDATED_TAGS_KEY && key !== sharedTagsKey;
|
|
313
|
+
this.sharedTagsMap = new SyncedMap({
|
|
314
|
+
client: this.client,
|
|
315
|
+
keyPrefix,
|
|
316
|
+
redisKey: sharedTagsKey,
|
|
317
|
+
database,
|
|
318
|
+
timeoutMs,
|
|
319
|
+
querySize: revalidateTagQuerySize,
|
|
320
|
+
filterKeys,
|
|
321
|
+
resyncIntervalMs: avgResyncIntervalMs - avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
|
|
322
|
+
});
|
|
323
|
+
this.revalidatedTagsMap = new SyncedMap({
|
|
324
|
+
client: this.client,
|
|
325
|
+
keyPrefix,
|
|
326
|
+
redisKey: REVALIDATED_TAGS_KEY,
|
|
327
|
+
database,
|
|
328
|
+
timeoutMs,
|
|
329
|
+
querySize: revalidateTagQuerySize,
|
|
330
|
+
filterKeys,
|
|
331
|
+
resyncIntervalMs: avgResyncIntervalMs + avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
|
|
332
|
+
});
|
|
333
|
+
this.inMemoryDeduplicationCache = new SyncedMap({
|
|
334
|
+
client: this.client,
|
|
335
|
+
keyPrefix,
|
|
336
|
+
redisKey: "inMemoryDeduplicationCache",
|
|
337
|
+
database,
|
|
338
|
+
timeoutMs,
|
|
339
|
+
querySize: revalidateTagQuerySize,
|
|
340
|
+
filterKeys,
|
|
341
|
+
customizedSync: {
|
|
342
|
+
withoutRedisHashmap: true,
|
|
343
|
+
withoutSetSync: true
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
const redisGet = this.client.get.bind(this.client);
|
|
347
|
+
this.redisDeduplicationHandler = new DeduplicatedRequestHandler(
|
|
348
|
+
redisGet,
|
|
349
|
+
inMemoryCachingTime,
|
|
350
|
+
this.inMemoryDeduplicationCache
|
|
351
|
+
);
|
|
352
|
+
this.redisGet = redisGet;
|
|
353
|
+
this.deduplicatedRedisGet = this.redisDeduplicationHandler.deduplicatedFunction;
|
|
354
|
+
}
|
|
355
|
+
resetRequestCache(...args) {
|
|
356
|
+
console.warn("WARNING resetRequestCache() was called", args);
|
|
357
|
+
}
|
|
358
|
+
async assertClientIsReady() {
|
|
359
|
+
await Promise.all([
|
|
360
|
+
this.sharedTagsMap.waitUntilReady(),
|
|
361
|
+
this.revalidatedTagsMap.waitUntilReady()
|
|
362
|
+
]);
|
|
363
|
+
if (!this.client.isReady) {
|
|
364
|
+
throw new Error("Redis client is not ready yet or connection is lost.");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async get(key, ctx) {
|
|
368
|
+
await this.assertClientIsReady();
|
|
369
|
+
const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(key) : this.redisGet;
|
|
370
|
+
const result = await clientGet(
|
|
371
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
372
|
+
this.keyPrefix + key
|
|
373
|
+
);
|
|
374
|
+
if (!result) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const cacheValue = JSON.parse(result);
|
|
378
|
+
if (!cacheValue) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if (cacheValue.value?.kind === "FETCH") {
|
|
382
|
+
cacheValue.value.data.body = Buffer.from(
|
|
383
|
+
cacheValue.value.data.body
|
|
384
|
+
).toString("base64");
|
|
385
|
+
}
|
|
386
|
+
const combinedTags = /* @__PURE__ */ new Set([
|
|
387
|
+
...ctx?.softTags || [],
|
|
388
|
+
...ctx?.tags || []
|
|
389
|
+
]);
|
|
390
|
+
if (combinedTags.size === 0) {
|
|
391
|
+
return cacheValue;
|
|
392
|
+
}
|
|
393
|
+
for (const tag of combinedTags) {
|
|
394
|
+
const revalidationTime = this.revalidatedTagsMap.get(tag);
|
|
395
|
+
if (revalidationTime && revalidationTime > cacheValue.lastModified) {
|
|
396
|
+
const redisKey = this.keyPrefix + key;
|
|
397
|
+
this.client.unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey).catch((err) => {
|
|
398
|
+
console.error(
|
|
399
|
+
"Error occurred while unlinking stale data. Retrying now. Error was:",
|
|
400
|
+
err
|
|
401
|
+
);
|
|
402
|
+
this.client.unlink(
|
|
403
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
404
|
+
redisKey
|
|
405
|
+
);
|
|
406
|
+
}).finally(async () => {
|
|
407
|
+
await this.sharedTagsMap.delete(key);
|
|
408
|
+
await this.revalidatedTagsMap.delete(tag);
|
|
409
|
+
});
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return cacheValue;
|
|
414
|
+
}
|
|
415
|
+
async set(key, data, ctx) {
|
|
416
|
+
if (data.kind === "FETCH") {
|
|
417
|
+
console.time("encoding" + key);
|
|
418
|
+
data.data.body = Buffer.from(data.data.body, "base64").toString();
|
|
419
|
+
console.timeEnd("encoding" + key);
|
|
420
|
+
}
|
|
421
|
+
await this.assertClientIsReady();
|
|
422
|
+
data.lastModified = Date.now();
|
|
423
|
+
const value = JSON.stringify(data);
|
|
424
|
+
if (this.redisGetDeduplication) {
|
|
425
|
+
this.redisDeduplicationHandler.seedRequestReturn(key, value);
|
|
426
|
+
}
|
|
427
|
+
const expireAt = ctx.revalidate && Number.isSafeInteger(ctx.revalidate) && ctx.revalidate > 0 ? this.estimateExpireAge(ctx.revalidate) : this.estimateExpireAge(this.defaultStaleAge);
|
|
428
|
+
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
429
|
+
const setOperation = this.client.set(
|
|
430
|
+
options,
|
|
431
|
+
this.keyPrefix + key,
|
|
432
|
+
value,
|
|
433
|
+
{
|
|
434
|
+
EX: expireAt
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
let setTagsOperation;
|
|
438
|
+
if (ctx.tags && ctx.tags.length > 0) {
|
|
439
|
+
const currentTags = this.sharedTagsMap.get(key);
|
|
440
|
+
const currentIsSameAsNew = currentTags?.length === ctx.tags.length && currentTags.every((v) => ctx.tags.includes(v)) && ctx.tags.every((v) => currentTags.includes(v));
|
|
441
|
+
if (!currentIsSameAsNew) {
|
|
442
|
+
setTagsOperation = this.sharedTagsMap.set(
|
|
443
|
+
key,
|
|
444
|
+
structuredClone(ctx.tags)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
await Promise.all([setOperation, setTagsOperation]);
|
|
449
|
+
}
|
|
450
|
+
async revalidateTag(tagOrTags) {
|
|
451
|
+
const tags = new Set([tagOrTags || []].flat());
|
|
452
|
+
await this.assertClientIsReady();
|
|
453
|
+
for (const tag of tags) {
|
|
454
|
+
if (isImplicitTag(tag)) {
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
await this.revalidatedTagsMap.set(tag, now);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const keysToDelete = [];
|
|
460
|
+
for (const [key, sharedTags] of this.sharedTagsMap.entries()) {
|
|
461
|
+
if (sharedTags.some((tag) => tags.has(tag))) {
|
|
462
|
+
keysToDelete.push(key);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (keysToDelete.length === 0) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const fullRedisKeys = keysToDelete.map((key) => this.keyPrefix + key);
|
|
469
|
+
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
470
|
+
const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);
|
|
471
|
+
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
|
472
|
+
for (const key of keysToDelete) {
|
|
473
|
+
this.inMemoryDeduplicationCache.delete(key);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const deleteTagsOperation = this.sharedTagsMap.delete(keysToDelete);
|
|
477
|
+
await Promise.all([deleteKeysOperation, deleteTagsOperation]);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/CachedHandler.ts
|
|
482
|
+
var cachedHandler;
|
|
483
|
+
var CachedHandler = class {
|
|
484
|
+
constructor(options) {
|
|
485
|
+
if (!cachedHandler) {
|
|
486
|
+
console.log("created cached handler");
|
|
487
|
+
cachedHandler = new RedisStringsHandler(options);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
get(...args) {
|
|
491
|
+
return cachedHandler.get(...args);
|
|
492
|
+
}
|
|
493
|
+
set(...args) {
|
|
494
|
+
return cachedHandler.set(...args);
|
|
495
|
+
}
|
|
496
|
+
revalidateTag(...args) {
|
|
497
|
+
return cachedHandler.revalidateTag(...args);
|
|
498
|
+
}
|
|
499
|
+
resetRequestCache(...args) {
|
|
500
|
+
return cachedHandler.resetRequestCache(...args);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/index.ts
|
|
505
|
+
var index_default = CachedHandler;
|
|
506
|
+
export {
|
|
507
|
+
index_default as default
|
|
508
|
+
};
|
|
509
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RedisStringsHandler.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/CachedHandler.ts","../src/index.ts"],"sourcesContent":["import { commandOptions, createClient } from 'redis';\nimport { SyncedMap } from './SyncedMap';\nimport { DeduplicatedRequestHandler } from './DeduplicatedRequestHandler';\nimport {\n CacheHandler,\n CacheHandlerValue,\n IncrementalCache,\n} from 'next/dist/server/lib/incremental-cache';\n\nexport type CommandOptions = ReturnType<typeof commandOptions>;\ntype GetParams = Parameters<IncrementalCache['get']>;\ntype SetParams = Parameters<IncrementalCache['set']>;\ntype RevalidateParams = Parameters<IncrementalCache['revalidateTag']>;\nexport type Client = ReturnType<typeof createClient>;\n\nexport type CreateRedisStringsHandlerOptions = {\n database?: number;\n keyPrefix?: string;\n timeoutMs?: number;\n revalidateTagQuerySize?: number;\n sharedTagsKey?: string;\n avgResyncIntervalMs?: number;\n redisGetDeduplication?: boolean;\n inMemoryCachingTime?: number;\n defaultStaleAge?: number;\n estimateExpireAge?: (staleAge: number) => number;\n};\n\nconst NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';\nconst REVALIDATED_TAGS_KEY = '__revalidated_tags__';\n\nfunction isImplicitTag(tag: string): boolean {\n return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID);\n}\n\nexport function getTimeoutRedisCommandOptions(\n timeoutMs: number,\n): CommandOptions {\n return commandOptions({ signal: AbortSignal.timeout(timeoutMs) });\n}\n\nexport default class RedisStringsHandler implements CacheHandler {\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 database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = 5000,\n revalidateTagQuerySize = 250,\n avgResyncIntervalMs = 60 * 60 * 1000,\n redisGetDeduplication = true,\n inMemoryCachingTime = 10_000,\n defaultStaleAge = 60 * 60 * 24 * 14,\n estimateExpireAge = (staleAge) =>\n process.env.VERCEL_ENV === 'preview' ? staleAge * 1.2 : staleAge * 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: process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\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((error) => {\n console.error('Failed to connect Redis client:', error);\n this.client.disconnect();\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 resetRequestCache(...args: never[]): void {\n console.warn('WARNING resetRequestCache() was called', args);\n }\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(key: GetParams[0], ctx: GetParams[1]) {\n await this.assertClientIsReady();\n\n const clientGet = this.redisGetDeduplication\n ? this.deduplicatedRedisGet(key)\n : this.redisGet;\n const result = await clientGet(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + key,\n );\n\n if (!result) {\n return null;\n }\n\n const cacheValue = JSON.parse(result) as\n | (CacheHandlerValue & { lastModified: number })\n | null;\n\n if (!cacheValue) {\n return null;\n }\n\n if (cacheValue.value?.kind === 'FETCH') {\n cacheValue.value.data.body = Buffer.from(\n cacheValue.value.data.body,\n ).toString('base64');\n }\n\n const combinedTags = new Set([\n ...(ctx?.softTags || []),\n ...(ctx?.tags || []),\n ]);\n\n if (combinedTags.size === 0) {\n return cacheValue;\n }\n\n for (const tag of combinedTags) {\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n const revalidationTime = this.revalidatedTagsMap.get(tag);\n if (revalidationTime && revalidationTime > cacheValue.lastModified) {\n const redisKey = this.keyPrefix + key;\n // Do not await here as this can happen in the background while we can already serve the cacheValue\n this.client\n .unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey)\n .catch((err) => {\n console.error(\n 'Error occurred while unlinking stale data. Retrying now. Error was:',\n err,\n );\n this.client.unlink(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n redisKey,\n );\n })\n .finally(async () => {\n await this.sharedTagsMap.delete(key);\n await this.revalidatedTagsMap.delete(tag);\n });\n return null;\n }\n }\n\n return cacheValue;\n }\n public async set(\n key: SetParams[0],\n data: SetParams[1] & { lastModified: number },\n ctx: SetParams[2],\n ) {\n if (data.kind === 'FETCH') {\n console.time('encoding' + key);\n data.data.body = Buffer.from(data.data.body, 'base64').toString();\n console.timeEnd('encoding' + key);\n }\n await this.assertClientIsReady();\n\n data.lastModified = Date.now();\n\n const value = JSON.stringify(data);\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(key, value);\n }\n\n const expireAt =\n ctx.revalidate &&\n Number.isSafeInteger(ctx.revalidate) &&\n ctx.revalidate > 0\n ? this.estimateExpireAge(ctx.revalidate)\n : this.estimateExpireAge(this.defaultStaleAge);\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n const setOperation: Promise<string | null> = this.client.set(\n options,\n this.keyPrefix + key,\n value,\n {\n EX: expireAt,\n },\n );\n\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 await Promise.all([setOperation, setTagsOperation]);\n }\n public async revalidateTag(tagOrTags: RevalidateParams[0]) {\n const tags = new Set([tagOrTags || []].flat());\n await this.assertClientIsReady();\n\n // TODO: check how this revalidatedTagsMap is used or if it can be deleted\n for (const tag of tags) {\n if (isImplicitTag(tag)) {\n const now = Date.now();\n await this.revalidatedTagsMap.set(tag, now);\n }\n }\n\n const keysToDelete: string[] = [];\n\n for (const [key, sharedTags] of this.sharedTagsMap.entries()) {\n if (sharedTags.some((tag) => tags.has(tag))) {\n keysToDelete.push(key);\n }\n }\n\n if (keysToDelete.length === 0) {\n return;\n }\n\n const fullRedisKeys = keysToDelete.map((key) => this.keyPrefix + key);\n\n const options = getTimeoutRedisCommandOptions(this.timeoutMs);\n\n const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);\n\n // delete entries from in-memory deduplication cache\n if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {\n for (const key of keysToDelete) {\n this.inMemoryDeduplicationCache.delete(key);\n }\n }\n\n const deleteTagsOperation = this.sharedTagsMap.delete(keysToDelete);\n\n await Promise.all([deleteKeysOperation, deleteTagsOperation]);\n }\n}\n","// SyncedMap.ts\nimport { Client, getTimeoutRedisCommandOptions } from './RedisStringsHandler';\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 (_channel: string, message: string) => {\n const key = message;\n if (key.startsWith(this.keyPrefix)) {\n const keyInMap = key.substring(this.keyPrefix.length);\n if (this.filterKeys(keyInMap)) {\n await this.delete(keyInMap, true);\n }\n }\n };\n\n try {\n await this.subscriberClient.connect();\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 keyspace 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 return this.map.get(key);\n }\n\n public async set(key: string, value: V): Promise<void> {\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 public async delete(\n keys: string[] | string,\n withoutSyncMessage = false,\n ): Promise<void> {\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 await Promise.all(operations);\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 { 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 setTimeout(() => {\n this.inMemoryDeduplicationCache.delete(key);\n }, this.cachingTimeMs);\n }\n\n // Method to handle deduplicated requests\n deduplicatedFunction = (key: string): T => {\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 if (\n self.inMemoryDeduplicationCache &&\n self.inMemoryDeduplicationCache.has(key)\n ) {\n const res = await self.inMemoryDeduplicationCache\n .get(key)!\n .then((v) => structuredClone(v));\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 try {\n const result = await promise;\n return structuredClone(result);\n } finally {\n // Once the promise is resolved/rejected, remove it from the map\n setTimeout(() => {\n self.inMemoryDeduplicationCache.delete(key);\n }, self.cachingTimeMs);\n }\n };\n return dedupedFn as T;\n };\n}\n","import { CacheHandler } from \"next/dist/server/lib/incremental-cache\";\nimport RedisStringsHandler, { CreateRedisStringsHandlerOptions } from \"./RedisStringsHandler\";\n\nlet cachedHandler: RedisStringsHandler;\n\nexport default class CachedHandler implements CacheHandler {\n constructor(options: CreateRedisStringsHandlerOptions) {\n if (!cachedHandler) {\n console.log(\"created cached handler\");\n cachedHandler = new RedisStringsHandler(options);\n }\n }\n get(...args: Parameters<RedisStringsHandler[\"get\"]>): ReturnType<RedisStringsHandler[\"get\"]> {\n return cachedHandler.get(...args);\n }\n set(...args: Parameters<RedisStringsHandler[\"set\"]>): ReturnType<RedisStringsHandler[\"set\"]> {\n return cachedHandler.set(...args);\n }\n revalidateTag(...args: Parameters<RedisStringsHandler[\"revalidateTag\"]>): ReturnType<RedisStringsHandler[\"revalidateTag\"]> {\n return cachedHandler.revalidateTag(...args);\n }\n resetRequestCache(...args: Parameters<RedisStringsHandler[\"resetRequestCache\"]>): ReturnType<RedisStringsHandler[\"resetRequestCache\"]> {\n return cachedHandler.resetRequestCache(...args);\n }\n}","import CachedHandler from \"./CachedHandler\";\nexport default CachedHandler;"],"mappings":"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","names":[]}
|