@trieb.work/nextjs-turbo-redis-cache 1.8.0-beta.2 → 1.8.0-beta.3
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/CHANGELOG.md +7 -0
- package/dist/index.js +43 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -18
- package/dist/index.mjs.map +1 -1
- package/docker/redis.conf +1 -0
- package/package.json +1 -1
- package/src/RedisStringsHandler.ts +43 -10
- package/src/SyncedMap.ts +60 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.8.0-beta.3](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.8.0-beta.2...v1.8.0-beta.3) (2025-06-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add redis commands debug logging ([4c8a0d7](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/4c8a0d7f82573bbaab63bdd380063244c641735f))
|
|
7
|
+
|
|
1
8
|
# [1.8.0-beta.2](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.8.0-beta.1...v1.8.0-beta.2) (2025-06-11)
|
|
2
9
|
|
|
3
10
|
|
package/dist/index.js
CHANGED
|
@@ -272,11 +272,14 @@ var SyncedMap = class {
|
|
|
272
272
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
273
273
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
274
274
|
operations.push(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this.
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
redisErrorHandler(
|
|
276
|
+
"SyncedMap.set(), operation: hSet " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
277
|
+
this.client.hSet(
|
|
278
|
+
options,
|
|
279
|
+
this.keyPrefix + this.redisKey,
|
|
280
|
+
key,
|
|
281
|
+
JSON.stringify(value)
|
|
282
|
+
)
|
|
280
283
|
)
|
|
281
284
|
);
|
|
282
285
|
}
|
|
@@ -286,7 +289,10 @@ var SyncedMap = class {
|
|
|
286
289
|
value
|
|
287
290
|
};
|
|
288
291
|
operations.push(
|
|
289
|
-
|
|
292
|
+
redisErrorHandler(
|
|
293
|
+
"SyncedMap.set(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
294
|
+
this.client.publish(this.syncChannel, JSON.stringify(insertMessage))
|
|
295
|
+
)
|
|
290
296
|
);
|
|
291
297
|
await Promise.all(operations);
|
|
292
298
|
}
|
|
@@ -305,7 +311,10 @@ var SyncedMap = class {
|
|
|
305
311
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
306
312
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
307
313
|
operations.push(
|
|
308
|
-
|
|
314
|
+
redisErrorHandler(
|
|
315
|
+
"SyncedMap.delete(), operation: hDel " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + keysArray,
|
|
316
|
+
this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray)
|
|
317
|
+
)
|
|
309
318
|
);
|
|
310
319
|
}
|
|
311
320
|
if (!withoutSyncMessage) {
|
|
@@ -314,7 +323,13 @@ var SyncedMap = class {
|
|
|
314
323
|
keys: keysArray
|
|
315
324
|
};
|
|
316
325
|
operations.push(
|
|
317
|
-
|
|
326
|
+
redisErrorHandler(
|
|
327
|
+
"SyncedMap.delete(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + keysArray,
|
|
328
|
+
this.client.publish(
|
|
329
|
+
this.syncChannel,
|
|
330
|
+
JSON.stringify(deletionMessage)
|
|
331
|
+
)
|
|
332
|
+
)
|
|
318
333
|
);
|
|
319
334
|
}
|
|
320
335
|
await Promise.all(operations);
|
|
@@ -438,6 +453,12 @@ function bufferReplacer(_, value) {
|
|
|
438
453
|
}
|
|
439
454
|
|
|
440
455
|
// src/RedisStringsHandler.ts
|
|
456
|
+
function redisErrorHandler(debugInfo, redisCommandResult) {
|
|
457
|
+
return redisCommandResult.catch((error) => {
|
|
458
|
+
console.error("Redis command error", debugInfo, error);
|
|
459
|
+
throw error;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
441
462
|
var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
|
|
442
463
|
var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
|
|
443
464
|
function getTimeoutRedisCommandOptions(timeoutMs) {
|
|
@@ -623,9 +644,12 @@ var RedisStringsHandler = class {
|
|
|
623
644
|
debug("green", "RedisStringsHandler.get() called with", key, ctx);
|
|
624
645
|
await this.assertClientIsReady();
|
|
625
646
|
const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(key) : this.redisGet;
|
|
626
|
-
const serializedCacheEntry = await
|
|
627
|
-
|
|
628
|
-
|
|
647
|
+
const serializedCacheEntry = await redisErrorHandler(
|
|
648
|
+
"RedisStringsHandler.get(), operation: get" + (this.redisGetDeduplication ? "deduplicated" : "") + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
649
|
+
clientGet(
|
|
650
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
651
|
+
this.keyPrefix + key
|
|
652
|
+
)
|
|
629
653
|
);
|
|
630
654
|
debug(
|
|
631
655
|
"green",
|
|
@@ -761,13 +785,11 @@ var RedisStringsHandler = class {
|
|
|
761
785
|
);
|
|
762
786
|
const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
|
|
763
787
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
764
|
-
const setOperation =
|
|
765
|
-
|
|
766
|
-
this.keyPrefix + key,
|
|
767
|
-
serializedCacheEntry,
|
|
768
|
-
{
|
|
788
|
+
const setOperation = redisErrorHandler(
|
|
789
|
+
"RedisStringsHandler.set(), operation: set" + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
790
|
+
this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {
|
|
769
791
|
EX: expireAt
|
|
770
|
-
}
|
|
792
|
+
})
|
|
771
793
|
);
|
|
772
794
|
debug(
|
|
773
795
|
"blue",
|
|
@@ -860,7 +882,10 @@ var RedisStringsHandler = class {
|
|
|
860
882
|
const redisKeys = Array.from(keysToDelete);
|
|
861
883
|
const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
|
|
862
884
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
863
|
-
const deleteKeysOperation =
|
|
885
|
+
const deleteKeysOperation = redisErrorHandler(
|
|
886
|
+
"RedisStringsHandler.revalidateTag(), operation: unlink" + this.timeoutMs + "ms " + this.keyPrefix + " " + fullRedisKeys,
|
|
887
|
+
this.client.unlink(options, fullRedisKeys)
|
|
888
|
+
);
|
|
864
889
|
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
|
865
890
|
for (const key of keysToDelete) {
|
|
866
891
|
this.inMemoryDeduplicationCache.delete(key);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/RedisStringsHandler.ts","../src/utils/debug.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/utils/json.ts","../src/CachedHandler.ts"],"sourcesContent":["import CachedHandler from './CachedHandler';\nexport default CachedHandler;\nimport RedisStringsHandler from './RedisStringsHandler';\nexport { RedisStringsHandler };\n","import { commandOptions, createClient, RedisClientOptions } 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 redisUrl 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 redisUrl?: 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 /** Kill container on Redis client error if error threshold is reached\n * @default 0 (0 means no error threshold)\n */\n killContainerOnErrorThreshold?: number;\n /** Additional Redis client socket options\n * @example { tls: true, rejectUnauthorized: false }\n */\n socketOptions?: RedisClientOptions['socket'];\n /** Additional Redis client options to be passed directly to createClient\n * @example { username: 'user', password: 'pass' }\n */\n clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;\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\nlet killContainerOnErrorCount: number = 0;\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 private killContainerOnErrorThreshold: number;\n\n constructor({\n redisUrl = process.env.REDIS_URL\n ? process.env.REDIS_URL\n : process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\n database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS\n ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5_000)\n : 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 killContainerOnErrorThreshold = process.env\n .KILL_CONTAINER_ON_ERROR_THRESHOLD\n ? (Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0)\n : 0,\n socketOptions,\n clientOptions,\n }: CreateRedisStringsHandlerOptions) {\n try {\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 this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;\n\n try {\n // Create Redis client with properly typed configuration\n this.client = createClient({\n url: redisUrl,\n pingInterval: 10_000, // Useful with Redis deployments that do not use TCP Keep-Alive. Restarts the connection if it is idle for too long.\n ...(database !== 0 ? { database } : {}),\n ...(socketOptions ? { socket: { ...socketOptions } } : {}),\n ...(clientOptions || {}),\n });\n\n this.client.on('error', (error) => {\n console.error(\n 'Redis client error',\n error,\n killContainerOnErrorCount++,\n );\n setTimeout(\n () =>\n this.client.connect().catch((error) => {\n console.error(\n 'Failed to reconnect Redis client after connection loss:',\n error,\n );\n }),\n 1000,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'Redis client error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\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 } catch (error) {\n console.error(\n 'RedisStringsHandler constructor error',\n error,\n killContainerOnErrorCount++,\n );\n if (\n killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler constructor error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n\n resetRequestCache(): void {}\n\n private clientReadyCalls = 0;\n\n private async assertClientIsReady(): Promise<void> {\n if (this.clientReadyCalls > 10) {\n throw new Error(\n 'assertClientIsReady called more than 10 times without being ready.',\n );\n }\n await Promise.race([\n Promise.all([\n this.sharedTagsMap.waitUntilReady(),\n this.revalidatedTagsMap.waitUntilReady(),\n ]),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(\n new Error(\n 'assertClientIsReady: Timeout waiting for Redis maps to be ready',\n ),\n );\n }, this.timeoutMs * 5),\n ),\n ]);\n this.clientReadyCalls = 0;\n if (!this.client.isReady) {\n throw new Error(\n 'assertClientIsReady: Redis client is not ready yet or connection is lost.',\n );\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 try {\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 } catch (error) {\n // This catch block is necessary to handle any errors that may occur during:\n // 1. Redis operations (get, unlink)\n // 2. JSON parsing of cache entries\n // 3. Tag validation and cleanup\n // If any error occurs, we return null to indicate no valid cache entry was found,\n // allowing the application to regenerate the content rather than crash\n console.error(\n 'RedisStringsHandler.get() Error occurred while getting cache entry. Returning null so site can continue to serve content while cache is disabled. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler get() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n return null;\n }\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 try {\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 =\n // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used\n (data.kind === 'FETCH' && data.revalidate) ||\n ctx.revalidate ||\n ctx.cacheControl?.revalidate ||\n (data as { revalidate?: number | false })?.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 } catch (error) {\n console.error(\n 'RedisStringsHandler.set() Error occurred while setting cache entry. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler set() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public async revalidateTag(tagOrTags: string | string[], ...rest: any[]) {\n try {\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.revalidateTag() Error occurred while revalidating tags. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler revalidateTag() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\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 console.error('Failed to connect subscriber client. Retrying...');\n await this.subscriberClient.connect().catch((error) => {\n console.error('Failed to connect subscriber client.', error);\n throw error;\n });\n });\n\n // Check if keyspace event configuration is set correctly\n if (\n (process.env.SKIP_KEYSPACE_CONFIG_CHECK || '').toUpperCase() !== 'TRUE'\n ) {\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 is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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') &&\n keyspaceEventConfig.includes('e')\n )\n ) {\n throw new Error(\n 'Keyspace event configuration is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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\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 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"],"mappings":"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","names":["error"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/RedisStringsHandler.ts","../src/utils/debug.ts","../src/SyncedMap.ts","../src/DeduplicatedRequestHandler.ts","../src/utils/json.ts","../src/CachedHandler.ts"],"sourcesContent":["import CachedHandler from './CachedHandler';\nexport default CachedHandler;\nimport RedisStringsHandler from './RedisStringsHandler';\nexport { RedisStringsHandler };\n","import { commandOptions, createClient, RedisClientOptions } 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 function redisErrorHandler<T extends Promise<unknown>>(\n debugInfo: string,\n redisCommandResult: T,\n): T {\n return redisCommandResult.catch((error) => {\n console.error('Redis command error', debugInfo, error);\n throw error;\n }) as T;\n}\n\nexport type CreateRedisStringsHandlerOptions = {\n /** Redis redisUrl 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 redisUrl?: 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 /** Kill container on Redis client error if error threshold is reached\n * @default 0 (0 means no error threshold)\n */\n killContainerOnErrorThreshold?: number;\n /** Additional Redis client socket options\n * @example { tls: true, rejectUnauthorized: false }\n */\n socketOptions?: RedisClientOptions['socket'];\n /** Additional Redis client options to be passed directly to createClient\n * @example { username: 'user', password: 'pass' }\n */\n clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;\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\nlet killContainerOnErrorCount: number = 0;\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 private killContainerOnErrorThreshold: number;\n\n constructor({\n redisUrl = process.env.REDIS_URL\n ? process.env.REDIS_URL\n : process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\n database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS\n ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5_000)\n : 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 killContainerOnErrorThreshold = process.env\n .KILL_CONTAINER_ON_ERROR_THRESHOLD\n ? (Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0)\n : 0,\n socketOptions,\n clientOptions,\n }: CreateRedisStringsHandlerOptions) {\n try {\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 this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;\n\n try {\n // Create Redis client with properly typed configuration\n this.client = createClient({\n url: redisUrl,\n pingInterval: 10_000, // Useful with Redis deployments that do not use TCP Keep-Alive. Restarts the connection if it is idle for too long.\n ...(database !== 0 ? { database } : {}),\n ...(socketOptions ? { socket: { ...socketOptions } } : {}),\n ...(clientOptions || {}),\n });\n\n this.client.on('error', (error) => {\n console.error(\n 'Redis client error',\n error,\n killContainerOnErrorCount++,\n );\n setTimeout(\n () =>\n this.client.connect().catch((error) => {\n console.error(\n 'Failed to reconnect Redis client after connection loss:',\n error,\n );\n }),\n 1000,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'Redis client error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\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 } catch (error) {\n console.error(\n 'RedisStringsHandler constructor error',\n error,\n killContainerOnErrorCount++,\n );\n if (\n killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler constructor error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n\n resetRequestCache(): void {}\n\n private clientReadyCalls = 0;\n\n private async assertClientIsReady(): Promise<void> {\n if (this.clientReadyCalls > 10) {\n throw new Error(\n 'assertClientIsReady called more than 10 times without being ready.',\n );\n }\n await Promise.race([\n Promise.all([\n this.sharedTagsMap.waitUntilReady(),\n this.revalidatedTagsMap.waitUntilReady(),\n ]),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(\n new Error(\n 'assertClientIsReady: Timeout waiting for Redis maps to be ready',\n ),\n );\n }, this.timeoutMs * 5),\n ),\n ]);\n this.clientReadyCalls = 0;\n if (!this.client.isReady) {\n throw new Error(\n 'assertClientIsReady: Redis client is not ready yet or connection is lost.',\n );\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 try {\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 redisErrorHandler(\n 'RedisStringsHandler.get(), operation: get' +\n (this.redisGetDeduplication ? 'deduplicated' : '') +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n clientGet(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + key,\n ),\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 } catch (error) {\n // This catch block is necessary to handle any errors that may occur during:\n // 1. Redis operations (get, unlink)\n // 2. JSON parsing of cache entries\n // 3. Tag validation and cleanup\n // If any error occurs, we return null to indicate no valid cache entry was found,\n // allowing the application to regenerate the content rather than crash\n console.error(\n 'RedisStringsHandler.get() Error occurred while getting cache entry. Returning null so site can continue to serve content while cache is disabled. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler get() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n return null;\n }\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 try {\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 =\n // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used\n (data.kind === 'FETCH' && data.revalidate) ||\n ctx.revalidate ||\n ctx.cacheControl?.revalidate ||\n (data as { revalidate?: number | false })?.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> = redisErrorHandler(\n 'RedisStringsHandler.set(), operation: set' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.set() Error occurred while setting cache entry. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler set() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public async revalidateTag(tagOrTags: string | string[], ...rest: any[]) {\n try {\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 = redisErrorHandler(\n 'RedisStringsHandler.revalidateTag(), operation: unlink' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n fullRedisKeys,\n this.client.unlink(options, fullRedisKeys),\n );\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.revalidateTag() Error occurred while revalidating tags. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler revalidateTag() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\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 {\n Client,\n getTimeoutRedisCommandOptions,\n redisErrorHandler,\n} 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 console.error('Failed to connect subscriber client. Retrying...');\n await this.subscriberClient.connect().catch((error) => {\n console.error('Failed to connect subscriber client.', error);\n throw error;\n });\n });\n\n // Check if keyspace event configuration is set correctly\n if (\n (process.env.SKIP_KEYSPACE_CONFIG_CHECK || '').toUpperCase() !== 'TRUE'\n ) {\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 is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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') &&\n keyspaceEventConfig.includes('e')\n )\n ) {\n throw new Error(\n 'Keyspace event configuration is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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\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 redisErrorHandler(\n 'SyncedMap.set(), operation: hSet ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\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\n const insertMessage: SyncMessage<V> = {\n type: 'insert',\n key: key as unknown as string,\n value,\n };\n operations.push(\n redisErrorHandler(\n 'SyncedMap.set(), operation: publish ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),\n ),\n );\n await Promise.all(operations);\n }\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 redisErrorHandler(\n 'SyncedMap.delete(), operation: hDel ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n keysArray,\n this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray),\n ),\n );\n }\n\n if (!withoutSyncMessage) {\n const deletionMessage: SyncMessage<V> = {\n type: 'delete',\n keys: keysArray,\n };\n operations.push(\n redisErrorHandler(\n 'SyncedMap.delete(), operation: publish ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n keysArray,\n this.client.publish(\n this.syncChannel,\n JSON.stringify(deletionMessage),\n ),\n ),\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"],"mappings":"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","names":["error"]}
|
package/dist/index.mjs
CHANGED
|
@@ -245,11 +245,14 @@ var SyncedMap = class {
|
|
|
245
245
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
246
246
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
247
247
|
operations.push(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
redisErrorHandler(
|
|
249
|
+
"SyncedMap.set(), operation: hSet " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
250
|
+
this.client.hSet(
|
|
251
|
+
options,
|
|
252
|
+
this.keyPrefix + this.redisKey,
|
|
253
|
+
key,
|
|
254
|
+
JSON.stringify(value)
|
|
255
|
+
)
|
|
253
256
|
)
|
|
254
257
|
);
|
|
255
258
|
}
|
|
@@ -259,7 +262,10 @@ var SyncedMap = class {
|
|
|
259
262
|
value
|
|
260
263
|
};
|
|
261
264
|
operations.push(
|
|
262
|
-
|
|
265
|
+
redisErrorHandler(
|
|
266
|
+
"SyncedMap.set(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
267
|
+
this.client.publish(this.syncChannel, JSON.stringify(insertMessage))
|
|
268
|
+
)
|
|
263
269
|
);
|
|
264
270
|
await Promise.all(operations);
|
|
265
271
|
}
|
|
@@ -278,7 +284,10 @@ var SyncedMap = class {
|
|
|
278
284
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
279
285
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
280
286
|
operations.push(
|
|
281
|
-
|
|
287
|
+
redisErrorHandler(
|
|
288
|
+
"SyncedMap.delete(), operation: hDel " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + keysArray,
|
|
289
|
+
this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray)
|
|
290
|
+
)
|
|
282
291
|
);
|
|
283
292
|
}
|
|
284
293
|
if (!withoutSyncMessage) {
|
|
@@ -287,7 +296,13 @@ var SyncedMap = class {
|
|
|
287
296
|
keys: keysArray
|
|
288
297
|
};
|
|
289
298
|
operations.push(
|
|
290
|
-
|
|
299
|
+
redisErrorHandler(
|
|
300
|
+
"SyncedMap.delete(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + keysArray,
|
|
301
|
+
this.client.publish(
|
|
302
|
+
this.syncChannel,
|
|
303
|
+
JSON.stringify(deletionMessage)
|
|
304
|
+
)
|
|
305
|
+
)
|
|
291
306
|
);
|
|
292
307
|
}
|
|
293
308
|
await Promise.all(operations);
|
|
@@ -411,6 +426,12 @@ function bufferReplacer(_, value) {
|
|
|
411
426
|
}
|
|
412
427
|
|
|
413
428
|
// src/RedisStringsHandler.ts
|
|
429
|
+
function redisErrorHandler(debugInfo, redisCommandResult) {
|
|
430
|
+
return redisCommandResult.catch((error) => {
|
|
431
|
+
console.error("Redis command error", debugInfo, error);
|
|
432
|
+
throw error;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
414
435
|
var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
|
|
415
436
|
var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
|
|
416
437
|
function getTimeoutRedisCommandOptions(timeoutMs) {
|
|
@@ -596,9 +617,12 @@ var RedisStringsHandler = class {
|
|
|
596
617
|
debug("green", "RedisStringsHandler.get() called with", key, ctx);
|
|
597
618
|
await this.assertClientIsReady();
|
|
598
619
|
const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(key) : this.redisGet;
|
|
599
|
-
const serializedCacheEntry = await
|
|
600
|
-
|
|
601
|
-
|
|
620
|
+
const serializedCacheEntry = await redisErrorHandler(
|
|
621
|
+
"RedisStringsHandler.get(), operation: get" + (this.redisGetDeduplication ? "deduplicated" : "") + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
622
|
+
clientGet(
|
|
623
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
624
|
+
this.keyPrefix + key
|
|
625
|
+
)
|
|
602
626
|
);
|
|
603
627
|
debug(
|
|
604
628
|
"green",
|
|
@@ -734,13 +758,11 @@ var RedisStringsHandler = class {
|
|
|
734
758
|
);
|
|
735
759
|
const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
|
|
736
760
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
737
|
-
const setOperation =
|
|
738
|
-
|
|
739
|
-
this.keyPrefix + key,
|
|
740
|
-
serializedCacheEntry,
|
|
741
|
-
{
|
|
761
|
+
const setOperation = redisErrorHandler(
|
|
762
|
+
"RedisStringsHandler.set(), operation: set" + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
|
|
763
|
+
this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {
|
|
742
764
|
EX: expireAt
|
|
743
|
-
}
|
|
765
|
+
})
|
|
744
766
|
);
|
|
745
767
|
debug(
|
|
746
768
|
"blue",
|
|
@@ -833,7 +855,10 @@ var RedisStringsHandler = class {
|
|
|
833
855
|
const redisKeys = Array.from(keysToDelete);
|
|
834
856
|
const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
|
|
835
857
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
836
|
-
const deleteKeysOperation =
|
|
858
|
+
const deleteKeysOperation = redisErrorHandler(
|
|
859
|
+
"RedisStringsHandler.revalidateTag(), operation: unlink" + this.timeoutMs + "ms " + this.keyPrefix + " " + fullRedisKeys,
|
|
860
|
+
this.client.unlink(options, fullRedisKeys)
|
|
861
|
+
);
|
|
837
862
|
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
|
838
863
|
for (const key of keysToDelete) {
|
|
839
864
|
this.inMemoryDeduplicationCache.delete(key);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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, RedisClientOptions } 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 redisUrl 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 redisUrl?: 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 /** Kill container on Redis client error if error threshold is reached\n * @default 0 (0 means no error threshold)\n */\n killContainerOnErrorThreshold?: number;\n /** Additional Redis client socket options\n * @example { tls: true, rejectUnauthorized: false }\n */\n socketOptions?: RedisClientOptions['socket'];\n /** Additional Redis client options to be passed directly to createClient\n * @example { username: 'user', password: 'pass' }\n */\n clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;\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\nlet killContainerOnErrorCount: number = 0;\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 private killContainerOnErrorThreshold: number;\n\n constructor({\n redisUrl = process.env.REDIS_URL\n ? process.env.REDIS_URL\n : process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\n database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS\n ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5_000)\n : 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 killContainerOnErrorThreshold = process.env\n .KILL_CONTAINER_ON_ERROR_THRESHOLD\n ? (Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0)\n : 0,\n socketOptions,\n clientOptions,\n }: CreateRedisStringsHandlerOptions) {\n try {\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 this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;\n\n try {\n // Create Redis client with properly typed configuration\n this.client = createClient({\n url: redisUrl,\n pingInterval: 10_000, // Useful with Redis deployments that do not use TCP Keep-Alive. Restarts the connection if it is idle for too long.\n ...(database !== 0 ? { database } : {}),\n ...(socketOptions ? { socket: { ...socketOptions } } : {}),\n ...(clientOptions || {}),\n });\n\n this.client.on('error', (error) => {\n console.error(\n 'Redis client error',\n error,\n killContainerOnErrorCount++,\n );\n setTimeout(\n () =>\n this.client.connect().catch((error) => {\n console.error(\n 'Failed to reconnect Redis client after connection loss:',\n error,\n );\n }),\n 1000,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'Redis client error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\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 } catch (error) {\n console.error(\n 'RedisStringsHandler constructor error',\n error,\n killContainerOnErrorCount++,\n );\n if (\n killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler constructor error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n\n resetRequestCache(): void {}\n\n private clientReadyCalls = 0;\n\n private async assertClientIsReady(): Promise<void> {\n if (this.clientReadyCalls > 10) {\n throw new Error(\n 'assertClientIsReady called more than 10 times without being ready.',\n );\n }\n await Promise.race([\n Promise.all([\n this.sharedTagsMap.waitUntilReady(),\n this.revalidatedTagsMap.waitUntilReady(),\n ]),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(\n new Error(\n 'assertClientIsReady: Timeout waiting for Redis maps to be ready',\n ),\n );\n }, this.timeoutMs * 5),\n ),\n ]);\n this.clientReadyCalls = 0;\n if (!this.client.isReady) {\n throw new Error(\n 'assertClientIsReady: Redis client is not ready yet or connection is lost.',\n );\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 try {\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 } catch (error) {\n // This catch block is necessary to handle any errors that may occur during:\n // 1. Redis operations (get, unlink)\n // 2. JSON parsing of cache entries\n // 3. Tag validation and cleanup\n // If any error occurs, we return null to indicate no valid cache entry was found,\n // allowing the application to regenerate the content rather than crash\n console.error(\n 'RedisStringsHandler.get() Error occurred while getting cache entry. Returning null so site can continue to serve content while cache is disabled. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler get() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n return null;\n }\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 try {\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 =\n // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used\n (data.kind === 'FETCH' && data.revalidate) ||\n ctx.revalidate ||\n ctx.cacheControl?.revalidate ||\n (data as { revalidate?: number | false })?.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 } catch (error) {\n console.error(\n 'RedisStringsHandler.set() Error occurred while setting cache entry. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler set() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public async revalidateTag(tagOrTags: string | string[], ...rest: any[]) {\n try {\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.revalidateTag() Error occurred while revalidating tags. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler revalidateTag() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\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 console.error('Failed to connect subscriber client. Retrying...');\n await this.subscriberClient.connect().catch((error) => {\n console.error('Failed to connect subscriber client.', error);\n throw error;\n });\n });\n\n // Check if keyspace event configuration is set correctly\n if (\n (process.env.SKIP_KEYSPACE_CONFIG_CHECK || '').toUpperCase() !== 'TRUE'\n ) {\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 is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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') &&\n keyspaceEventConfig.includes('e')\n )\n ) {\n throw new Error(\n 'Keyspace event configuration is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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\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 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":["error"]}
|
|
1
|
+
{"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, RedisClientOptions } 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 function redisErrorHandler<T extends Promise<unknown>>(\n debugInfo: string,\n redisCommandResult: T,\n): T {\n return redisCommandResult.catch((error) => {\n console.error('Redis command error', debugInfo, error);\n throw error;\n }) as T;\n}\n\nexport type CreateRedisStringsHandlerOptions = {\n /** Redis redisUrl 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 redisUrl?: 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 /** Kill container on Redis client error if error threshold is reached\n * @default 0 (0 means no error threshold)\n */\n killContainerOnErrorThreshold?: number;\n /** Additional Redis client socket options\n * @example { tls: true, rejectUnauthorized: false }\n */\n socketOptions?: RedisClientOptions['socket'];\n /** Additional Redis client options to be passed directly to createClient\n * @example { username: 'user', password: 'pass' }\n */\n clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;\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\nlet killContainerOnErrorCount: number = 0;\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 private killContainerOnErrorThreshold: number;\n\n constructor({\n redisUrl = process.env.REDIS_URL\n ? process.env.REDIS_URL\n : process.env.REDISHOST\n ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`\n : 'redis://localhost:6379',\n database = process.env.VERCEL_ENV === 'production' ? 0 : 1,\n keyPrefix = process.env.VERCEL_URL || 'UNDEFINED_URL_',\n sharedTagsKey = '__sharedTags__',\n timeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS\n ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5_000)\n : 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 killContainerOnErrorThreshold = process.env\n .KILL_CONTAINER_ON_ERROR_THRESHOLD\n ? (Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0)\n : 0,\n socketOptions,\n clientOptions,\n }: CreateRedisStringsHandlerOptions) {\n try {\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 this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;\n\n try {\n // Create Redis client with properly typed configuration\n this.client = createClient({\n url: redisUrl,\n pingInterval: 10_000, // Useful with Redis deployments that do not use TCP Keep-Alive. Restarts the connection if it is idle for too long.\n ...(database !== 0 ? { database } : {}),\n ...(socketOptions ? { socket: { ...socketOptions } } : {}),\n ...(clientOptions || {}),\n });\n\n this.client.on('error', (error) => {\n console.error(\n 'Redis client error',\n error,\n killContainerOnErrorCount++,\n );\n setTimeout(\n () =>\n this.client.connect().catch((error) => {\n console.error(\n 'Failed to reconnect Redis client after connection loss:',\n error,\n );\n }),\n 1000,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'Redis client error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\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 } catch (error) {\n console.error(\n 'RedisStringsHandler constructor error',\n error,\n killContainerOnErrorCount++,\n );\n if (\n killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler constructor error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount++,\n );\n process.exit(1);\n }\n throw error;\n }\n }\n\n resetRequestCache(): void {}\n\n private clientReadyCalls = 0;\n\n private async assertClientIsReady(): Promise<void> {\n if (this.clientReadyCalls > 10) {\n throw new Error(\n 'assertClientIsReady called more than 10 times without being ready.',\n );\n }\n await Promise.race([\n Promise.all([\n this.sharedTagsMap.waitUntilReady(),\n this.revalidatedTagsMap.waitUntilReady(),\n ]),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(\n new Error(\n 'assertClientIsReady: Timeout waiting for Redis maps to be ready',\n ),\n );\n }, this.timeoutMs * 5),\n ),\n ]);\n this.clientReadyCalls = 0;\n if (!this.client.isReady) {\n throw new Error(\n 'assertClientIsReady: Redis client is not ready yet or connection is lost.',\n );\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 try {\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 redisErrorHandler(\n 'RedisStringsHandler.get(), operation: get' +\n (this.redisGetDeduplication ? 'deduplicated' : '') +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n clientGet(\n getTimeoutRedisCommandOptions(this.timeoutMs),\n this.keyPrefix + key,\n ),\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 } catch (error) {\n // This catch block is necessary to handle any errors that may occur during:\n // 1. Redis operations (get, unlink)\n // 2. JSON parsing of cache entries\n // 3. Tag validation and cleanup\n // If any error occurs, we return null to indicate no valid cache entry was found,\n // allowing the application to regenerate the content rather than crash\n console.error(\n 'RedisStringsHandler.get() Error occurred while getting cache entry. Returning null so site can continue to serve content while cache is disabled. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler get() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n return null;\n }\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 try {\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 =\n // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used\n (data.kind === 'FETCH' && data.revalidate) ||\n ctx.revalidate ||\n ctx.cacheControl?.revalidate ||\n (data as { revalidate?: number | false })?.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> = redisErrorHandler(\n 'RedisStringsHandler.set(), operation: set' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.set() Error occurred while setting cache entry. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler set() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public async revalidateTag(tagOrTags: string | string[], ...rest: any[]) {\n try {\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 = redisErrorHandler(\n 'RedisStringsHandler.revalidateTag(), operation: unlink' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n fullRedisKeys,\n this.client.unlink(options, fullRedisKeys),\n );\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 } catch (error) {\n console.error(\n 'RedisStringsHandler.revalidateTag() Error occurred while revalidating tags. The original error was:',\n error,\n killContainerOnErrorCount++,\n );\n if (\n this.killContainerOnErrorThreshold > 0 &&\n killContainerOnErrorCount >= this.killContainerOnErrorThreshold\n ) {\n console.error(\n 'RedisStringsHandler revalidateTag() error threshold reached, disconnecting and exiting (please implement a restart process/container watchdog to handle this error)',\n error,\n killContainerOnErrorCount,\n );\n this.client.disconnect();\n this.client.quit();\n setTimeout(() => {\n process.exit(1);\n }, 500);\n }\n throw error;\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 {\n Client,\n getTimeoutRedisCommandOptions,\n redisErrorHandler,\n} 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 console.error('Failed to connect subscriber client. Retrying...');\n await this.subscriberClient.connect().catch((error) => {\n console.error('Failed to connect subscriber client.', error);\n throw error;\n });\n });\n\n // Check if keyspace event configuration is set correctly\n if (\n (process.env.SKIP_KEYSPACE_CONFIG_CHECK || '').toUpperCase() !== 'TRUE'\n ) {\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 is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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') &&\n keyspaceEventConfig.includes('e')\n )\n ) {\n throw new Error(\n 'Keyspace event configuration is set to \"' +\n keyspaceEventConfig +\n \"\\\" but 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\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 redisErrorHandler(\n 'SyncedMap.set(), operation: hSet ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\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\n const insertMessage: SyncMessage<V> = {\n type: 'insert',\n key: key as unknown as string,\n value,\n };\n operations.push(\n redisErrorHandler(\n 'SyncedMap.set(), operation: publish ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n key,\n this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),\n ),\n );\n await Promise.all(operations);\n }\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 redisErrorHandler(\n 'SyncedMap.delete(), operation: hDel ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n keysArray,\n this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray),\n ),\n );\n }\n\n if (!withoutSyncMessage) {\n const deletionMessage: SyncMessage<V> = {\n type: 'delete',\n keys: keysArray,\n };\n operations.push(\n redisErrorHandler(\n 'SyncedMap.delete(), operation: publish ' +\n this.syncChannel +\n ' ' +\n this.timeoutMs +\n 'ms' +\n ' ' +\n this.keyPrefix +\n ' ' +\n keysArray,\n this.client.publish(\n this.syncChannel,\n JSON.stringify(deletionMessage),\n ),\n ),\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":["error"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
notify-keyspace-events Exe
|
package/package.json
CHANGED
|
@@ -13,6 +13,16 @@ export type CacheEntry = {
|
|
|
13
13
|
tags: string[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export function redisErrorHandler<T extends Promise<unknown>>(
|
|
17
|
+
debugInfo: string,
|
|
18
|
+
redisCommandResult: T,
|
|
19
|
+
): T {
|
|
20
|
+
return redisCommandResult.catch((error) => {
|
|
21
|
+
console.error('Redis command error', debugInfo, error);
|
|
22
|
+
throw error;
|
|
23
|
+
}) as T;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
export type CreateRedisStringsHandlerOptions = {
|
|
17
27
|
/** Redis redisUrl to use.
|
|
18
28
|
* @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST
|
|
@@ -352,9 +362,19 @@ export default class RedisStringsHandler {
|
|
|
352
362
|
const clientGet = this.redisGetDeduplication
|
|
353
363
|
? this.deduplicatedRedisGet(key)
|
|
354
364
|
: this.redisGet;
|
|
355
|
-
const serializedCacheEntry = await
|
|
356
|
-
|
|
357
|
-
|
|
365
|
+
const serializedCacheEntry = await redisErrorHandler(
|
|
366
|
+
'RedisStringsHandler.get(), operation: get' +
|
|
367
|
+
(this.redisGetDeduplication ? 'deduplicated' : '') +
|
|
368
|
+
this.timeoutMs +
|
|
369
|
+
'ms' +
|
|
370
|
+
' ' +
|
|
371
|
+
this.keyPrefix +
|
|
372
|
+
' ' +
|
|
373
|
+
key,
|
|
374
|
+
clientGet(
|
|
375
|
+
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
376
|
+
this.keyPrefix + key,
|
|
377
|
+
),
|
|
358
378
|
);
|
|
359
379
|
|
|
360
380
|
debug(
|
|
@@ -595,13 +615,17 @@ export default class RedisStringsHandler {
|
|
|
595
615
|
|
|
596
616
|
// Setting the cache entry in redis
|
|
597
617
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
598
|
-
const setOperation: Promise<string | null> =
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
618
|
+
const setOperation: Promise<string | null> = redisErrorHandler(
|
|
619
|
+
'RedisStringsHandler.set(), operation: set' +
|
|
620
|
+
this.timeoutMs +
|
|
621
|
+
'ms' +
|
|
622
|
+
' ' +
|
|
623
|
+
this.keyPrefix +
|
|
624
|
+
' ' +
|
|
625
|
+
key,
|
|
626
|
+
this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {
|
|
603
627
|
EX: expireAt,
|
|
604
|
-
},
|
|
628
|
+
}),
|
|
605
629
|
);
|
|
606
630
|
|
|
607
631
|
debug(
|
|
@@ -725,7 +749,16 @@ export default class RedisStringsHandler {
|
|
|
725
749
|
const redisKeys = Array.from(keysToDelete);
|
|
726
750
|
const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
|
|
727
751
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
728
|
-
const deleteKeysOperation =
|
|
752
|
+
const deleteKeysOperation = redisErrorHandler(
|
|
753
|
+
'RedisStringsHandler.revalidateTag(), operation: unlink' +
|
|
754
|
+
this.timeoutMs +
|
|
755
|
+
'ms' +
|
|
756
|
+
' ' +
|
|
757
|
+
this.keyPrefix +
|
|
758
|
+
' ' +
|
|
759
|
+
fullRedisKeys,
|
|
760
|
+
this.client.unlink(options, fullRedisKeys),
|
|
761
|
+
);
|
|
729
762
|
|
|
730
763
|
// also delete entries from in-memory deduplication cache if they get revalidated
|
|
731
764
|
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
package/src/SyncedMap.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// SyncedMap.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Client,
|
|
4
|
+
getTimeoutRedisCommandOptions,
|
|
5
|
+
redisErrorHandler,
|
|
6
|
+
} from './RedisStringsHandler';
|
|
3
7
|
import { debugVerbose, debug } from './utils/debug';
|
|
4
8
|
|
|
5
9
|
type CustomizedSync = {
|
|
@@ -304,11 +308,22 @@ export class SyncedMap<V> {
|
|
|
304
308
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
305
309
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
306
310
|
operations.push(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
311
|
+
redisErrorHandler(
|
|
312
|
+
'SyncedMap.set(), operation: hSet ' +
|
|
313
|
+
this.syncChannel +
|
|
314
|
+
' ' +
|
|
315
|
+
this.timeoutMs +
|
|
316
|
+
'ms' +
|
|
317
|
+
' ' +
|
|
318
|
+
this.keyPrefix +
|
|
319
|
+
' ' +
|
|
320
|
+
key,
|
|
321
|
+
this.client.hSet(
|
|
322
|
+
options,
|
|
323
|
+
this.keyPrefix + this.redisKey,
|
|
324
|
+
key as unknown as string,
|
|
325
|
+
JSON.stringify(value),
|
|
326
|
+
),
|
|
312
327
|
),
|
|
313
328
|
);
|
|
314
329
|
}
|
|
@@ -319,7 +334,18 @@ export class SyncedMap<V> {
|
|
|
319
334
|
value,
|
|
320
335
|
};
|
|
321
336
|
operations.push(
|
|
322
|
-
|
|
337
|
+
redisErrorHandler(
|
|
338
|
+
'SyncedMap.set(), operation: publish ' +
|
|
339
|
+
this.syncChannel +
|
|
340
|
+
' ' +
|
|
341
|
+
this.timeoutMs +
|
|
342
|
+
'ms' +
|
|
343
|
+
' ' +
|
|
344
|
+
this.keyPrefix +
|
|
345
|
+
' ' +
|
|
346
|
+
key,
|
|
347
|
+
this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),
|
|
348
|
+
),
|
|
323
349
|
);
|
|
324
350
|
await Promise.all(operations);
|
|
325
351
|
}
|
|
@@ -345,7 +371,18 @@ export class SyncedMap<V> {
|
|
|
345
371
|
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
346
372
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
347
373
|
operations.push(
|
|
348
|
-
|
|
374
|
+
redisErrorHandler(
|
|
375
|
+
'SyncedMap.delete(), operation: hDel ' +
|
|
376
|
+
this.syncChannel +
|
|
377
|
+
' ' +
|
|
378
|
+
this.timeoutMs +
|
|
379
|
+
'ms' +
|
|
380
|
+
' ' +
|
|
381
|
+
this.keyPrefix +
|
|
382
|
+
' ' +
|
|
383
|
+
keysArray,
|
|
384
|
+
this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray),
|
|
385
|
+
),
|
|
349
386
|
);
|
|
350
387
|
}
|
|
351
388
|
|
|
@@ -355,7 +392,21 @@ export class SyncedMap<V> {
|
|
|
355
392
|
keys: keysArray,
|
|
356
393
|
};
|
|
357
394
|
operations.push(
|
|
358
|
-
|
|
395
|
+
redisErrorHandler(
|
|
396
|
+
'SyncedMap.delete(), operation: publish ' +
|
|
397
|
+
this.syncChannel +
|
|
398
|
+
' ' +
|
|
399
|
+
this.timeoutMs +
|
|
400
|
+
'ms' +
|
|
401
|
+
' ' +
|
|
402
|
+
this.keyPrefix +
|
|
403
|
+
' ' +
|
|
404
|
+
keysArray,
|
|
405
|
+
this.client.publish(
|
|
406
|
+
this.syncChannel,
|
|
407
|
+
JSON.stringify(deletionMessage),
|
|
408
|
+
),
|
|
409
|
+
),
|
|
359
410
|
);
|
|
360
411
|
}
|
|
361
412
|
|