@trieb.work/nextjs-turbo-redis-cache 1.8.0-beta.4 → 1.8.0-beta.6

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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.8.0-beta.6](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.8.0-beta.5...v1.8.0-beta.6) (2025-06-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * remove general timeoutMs and replace it with getTimeoutMs (for faster non-blocking rendering) ([02deb64](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/02deb649fce40085495c6fec5e8750cba42d2428))
7
+
8
+ # [1.8.0-beta.5](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.8.0-beta.4...v1.8.0-beta.5) (2025-06-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * scan+hscan logging ([56c82c3](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/56c82c3dd62257e8686fedc25dea45a0b7fec18e))
14
+
1
15
  # [1.8.0-beta.4](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.8.0-beta.3...v1.8.0-beta.4) (2025-06-12)
2
16
 
3
17
 
package/README.md CHANGED
@@ -123,22 +123,22 @@ A working example of above can be found in the `test/integration/next-app-custom
123
123
 
124
124
  ## Available Options
125
125
 
126
- | Option | Description | Default Value |
127
- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
128
- | redisUrl | Redis connection url | `process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST ? redis://${process.env.REDISHOST}:${process.env.REDISPORT} : 'redis://localhost:6379'` |
129
- | database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise | `process.env.VERCEL_ENV === 'production' ? 0 : 1` |
130
- | keyPrefix | Prefix added to all Redis keys | `process.env.VERCEL_URL \|\| 'UNDEFINED_URL_'` |
131
- | sharedTagsKey | Key used to store shared tags hash map in Redis | `'__sharedTags__'` |
132
- | timeoutMs | Timeout in milliseconds for Redis operations | `Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5_000 : 5_000` |
133
- | revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map | `250` |
134
- | avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs | `3600000` (1 hour) |
135
- | redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache. | `true` |
136
- | inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely. | `10000` |
137
- | defaultStaleAge | Default stale age in seconds for cached items | `1209600` (14 days) |
138
- | estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age | Production: `staleAge * 2`<br> Other: `staleAge * 1.2` |
139
- | socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`) | `{ connectTimeout: timeoutMs }` |
140
- | clientOptions | Additional Redis client options (e.g., username, password) | `undefined` |
141
- | killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable. | `Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0` |
126
+ | Option | Description | Default Value |
127
+ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
128
+ | redisUrl | Redis connection url | `process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST ? redis://${process.env.REDISHOST}:${process.env.REDISPORT} : 'redis://localhost:6379'` |
129
+ | database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise | `process.env.VERCEL_ENV === 'production' ? 0 : 1` |
130
+ | keyPrefix | Prefix added to all Redis keys | `process.env.VERCEL_URL \|\| 'UNDEFINED_URL_'` |
131
+ | sharedTagsKey | Key used to store shared tags hash map in Redis | `'__sharedTags__'` |
132
+ | getTimeoutMs | Timeout in milliseconds for time critical Redis operations. If Redis get is not fulfilled within this time, returns null to avoid blocking site rendering. | `process.env.REDIS_COMMAND_TIMEOUT_MS ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500) : 500` |
133
+ | revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map | `250` |
134
+ | avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs | `3600000` (1 hour) |
135
+ | redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache. | `true` |
136
+ | inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely. | `10000` |
137
+ | defaultStaleAge | Default stale age in seconds for cached items | `1209600` (14 days) |
138
+ | estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age | Production: `staleAge * 2`<br> Other: `staleAge * 1.2` |
139
+ | socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`) | `{ connectTimeout: timeoutMs }` |
140
+ | clientOptions | Additional Redis client options (e.g., username, password) | `undefined` |
141
+ | killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable. | `Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0` |
142
142
 
143
143
  ## TLS Configuration
144
144
 
package/dist/index.d.mts CHANGED
@@ -20,10 +20,12 @@ type CreateRedisStringsHandlerOptions = {
20
20
  * @default process.env.VERCEL_URL || 'UNDEFINED_URL_'
21
21
  */
22
22
  keyPrefix?: string;
23
- /** Timeout in milliseconds for Redis operations
24
- * @default 5000
23
+ /** Timeout in milliseconds for time critical Redis operations (during cache get, which blocks site rendering).
24
+ * If redis get is not fulfilled within this time, the cache handler will return null so site rendering will
25
+ * not be blocked further and site can fallback to re-render/re-fetch the content.
26
+ * @default 500
25
27
  */
26
- timeoutMs?: number;
28
+ getTimeoutMs?: number;
27
29
  /** Number of entries to query in one batch during full sync of shared tags hash map
28
30
  * @default 250
29
31
  */
@@ -70,17 +72,17 @@ declare class RedisStringsHandler {
70
72
  private sharedTagsMap;
71
73
  private revalidatedTagsMap;
72
74
  private inMemoryDeduplicationCache;
75
+ private getTimeoutMs;
73
76
  private redisGet;
74
77
  private redisDeduplicationHandler;
75
78
  private deduplicatedRedisGet;
76
- private timeoutMs;
77
79
  private keyPrefix;
78
80
  private redisGetDeduplication;
79
81
  private inMemoryCachingTime;
80
82
  private defaultStaleAge;
81
83
  private estimateExpireAge;
82
84
  private killContainerOnErrorThreshold;
83
- constructor({ redisUrl, database, keyPrefix, sharedTagsKey, timeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, }: CreateRedisStringsHandlerOptions);
85
+ constructor({ redisUrl, database, keyPrefix, sharedTagsKey, getTimeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, }: CreateRedisStringsHandlerOptions);
84
86
  resetRequestCache(): void;
85
87
  private clientReadyCalls;
86
88
  private assertClientIsReady;
package/dist/index.d.ts CHANGED
@@ -20,10 +20,12 @@ type CreateRedisStringsHandlerOptions = {
20
20
  * @default process.env.VERCEL_URL || 'UNDEFINED_URL_'
21
21
  */
22
22
  keyPrefix?: string;
23
- /** Timeout in milliseconds for Redis operations
24
- * @default 5000
23
+ /** Timeout in milliseconds for time critical Redis operations (during cache get, which blocks site rendering).
24
+ * If redis get is not fulfilled within this time, the cache handler will return null so site rendering will
25
+ * not be blocked further and site can fallback to re-render/re-fetch the content.
26
+ * @default 500
25
27
  */
26
- timeoutMs?: number;
28
+ getTimeoutMs?: number;
27
29
  /** Number of entries to query in one batch during full sync of shared tags hash map
28
30
  * @default 250
29
31
  */
@@ -70,17 +72,17 @@ declare class RedisStringsHandler {
70
72
  private sharedTagsMap;
71
73
  private revalidatedTagsMap;
72
74
  private inMemoryDeduplicationCache;
75
+ private getTimeoutMs;
73
76
  private redisGet;
74
77
  private redisDeduplicationHandler;
75
78
  private deduplicatedRedisGet;
76
- private timeoutMs;
77
79
  private keyPrefix;
78
80
  private redisGetDeduplication;
79
81
  private inMemoryCachingTime;
80
82
  private defaultStaleAge;
81
83
  private estimateExpireAge;
82
84
  private killContainerOnErrorThreshold;
83
- constructor({ redisUrl, database, keyPrefix, sharedTagsKey, timeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, }: CreateRedisStringsHandlerOptions);
85
+ constructor({ redisUrl, database, keyPrefix, sharedTagsKey, getTimeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, }: CreateRedisStringsHandlerOptions);
84
86
  resetRequestCache(): void;
85
87
  private clientReadyCalls;
86
88
  private assertClientIsReady;
package/dist/index.js CHANGED
@@ -58,7 +58,6 @@ var SyncedMap = class {
58
58
  this.redisKey = options.redisKey;
59
59
  this.syncChannel = `${options.keyPrefix}${SYNC_CHANNEL_SUFFIX}${options.redisKey}`;
60
60
  this.database = options.database;
61
- this.timeoutMs = options.timeoutMs;
62
61
  this.querySize = options.querySize;
63
62
  this.filterKeys = options.filterKeys;
64
63
  this.resyncIntervalMs = options.resyncIntervalMs;
@@ -88,11 +87,13 @@ var SyncedMap = class {
88
87
  const hScanOptions = { COUNT: this.querySize };
89
88
  try {
90
89
  do {
91
- const remoteItems = await this.client.hScan(
92
- getTimeoutRedisCommandOptions(this.timeoutMs),
93
- this.keyPrefix + this.redisKey,
94
- cursor,
95
- hScanOptions
90
+ const remoteItems = await redisErrorHandler(
91
+ "SyncedMap.initialSync(), operation: hScan " + this.syncChannel + " " + this.keyPrefix + " " + this.redisKey + " " + cursor + " " + this.querySize,
92
+ this.client.hScan(
93
+ this.keyPrefix + this.redisKey,
94
+ cursor,
95
+ hScanOptions
96
+ )
96
97
  );
97
98
  for (const { field, value } of remoteItems.tuples) {
98
99
  if (this.filterKeys(field)) {
@@ -114,10 +115,9 @@ var SyncedMap = class {
114
115
  let remoteKeys = [];
115
116
  try {
116
117
  do {
117
- const remoteKeysPortion = await this.client.scan(
118
- getTimeoutRedisCommandOptions(this.timeoutMs),
119
- cursor,
120
- scanOptions
118
+ const remoteKeysPortion = await redisErrorHandler(
119
+ "SyncedMap.cleanupKeysNotInRedis(), operation: scan " + this.keyPrefix,
120
+ this.client.scan(cursor, scanOptions)
121
121
  );
122
122
  remoteKeys = remoteKeys.concat(remoteKeysPortion.keys);
123
123
  cursor = remoteKeysPortion.cursor;
@@ -270,12 +270,10 @@ var SyncedMap = class {
270
270
  return;
271
271
  }
272
272
  if (!this.customizedSync?.withoutRedisHashmap) {
273
- const options = getTimeoutRedisCommandOptions(this.timeoutMs);
274
273
  operations.push(
275
274
  redisErrorHandler(
276
- "SyncedMap.set(), operation: hSet " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
275
+ "SyncedMap.set(), operation: hSet " + this.syncChannel + " " + this.keyPrefix + " " + key,
277
276
  this.client.hSet(
278
- options,
279
277
  this.keyPrefix + this.redisKey,
280
278
  key,
281
279
  JSON.stringify(value)
@@ -290,7 +288,7 @@ var SyncedMap = class {
290
288
  };
291
289
  operations.push(
292
290
  redisErrorHandler(
293
- "SyncedMap.set(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
291
+ "SyncedMap.set(), operation: publish " + this.syncChannel + " " + this.keyPrefix + " " + key,
294
292
  this.client.publish(this.syncChannel, JSON.stringify(insertMessage))
295
293
  )
296
294
  );
@@ -309,11 +307,10 @@ var SyncedMap = class {
309
307
  this.map.delete(key);
310
308
  }
311
309
  if (!this.customizedSync?.withoutRedisHashmap) {
312
- const options = getTimeoutRedisCommandOptions(this.timeoutMs * 10);
313
310
  operations.push(
314
311
  redisErrorHandler(
315
- "SyncedMap.delete(), operation: hDel " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + this.redisKey + " " + keysArray,
316
- this.client.hDel(options, this.keyPrefix + this.redisKey, keysArray)
312
+ "SyncedMap.delete(), operation: hDel " + this.syncChannel + " " + this.keyPrefix + " " + this.redisKey + " " + keysArray,
313
+ this.client.hDel(this.keyPrefix + this.redisKey, keysArray)
317
314
  )
318
315
  );
319
316
  }
@@ -324,7 +321,7 @@ var SyncedMap = class {
324
321
  };
325
322
  operations.push(
326
323
  redisErrorHandler(
327
- "SyncedMap.delete(), operation: publish " + this.syncChannel + " " + this.timeoutMs + "ms " + this.keyPrefix + " " + keysArray,
324
+ "SyncedMap.delete(), operation: publish " + this.syncChannel + " " + this.keyPrefix + " " + keysArray,
328
325
  this.client.publish(
329
326
  this.syncChannel,
330
327
  JSON.stringify(deletionMessage)
@@ -479,9 +476,6 @@ setInterval(() => {
479
476
  }, 1e3);
480
477
  var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
481
478
  var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
482
- function getTimeoutRedisCommandOptions(timeoutMs) {
483
- return (0, import_redis.commandOptions)({ signal: AbortSignal.timeout(timeoutMs) });
484
- }
485
479
  var killContainerOnErrorCount = 0;
486
480
  var RedisStringsHandler = class {
487
481
  constructor({
@@ -489,7 +483,7 @@ var RedisStringsHandler = class {
489
483
  database = process.env.VERCEL_ENV === "production" ? 0 : 1,
490
484
  keyPrefix = process.env.VERCEL_URL || "UNDEFINED_URL_",
491
485
  sharedTagsKey = "__sharedTags__",
492
- timeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS ? Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 5e3 : 5e3,
486
+ getTimeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS ? Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500 : 500,
493
487
  revalidateTagQuerySize = 250,
494
488
  avgResyncIntervalMs = 60 * 60 * 1e3,
495
489
  redisGetDeduplication = true,
@@ -503,12 +497,12 @@ var RedisStringsHandler = class {
503
497
  this.clientReadyCalls = 0;
504
498
  try {
505
499
  this.keyPrefix = keyPrefix;
506
- this.timeoutMs = timeoutMs;
507
500
  this.redisGetDeduplication = redisGetDeduplication;
508
501
  this.inMemoryCachingTime = inMemoryCachingTime;
509
502
  this.defaultStaleAge = defaultStaleAge;
510
503
  this.estimateExpireAge = estimateExpireAge;
511
504
  this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;
505
+ this.getTimeoutMs = getTimeoutMs;
512
506
  try {
513
507
  this.client = (0, import_redis.createClient)({
514
508
  url: redisUrl,
@@ -565,7 +559,6 @@ var RedisStringsHandler = class {
565
559
  keyPrefix,
566
560
  redisKey: sharedTagsKey,
567
561
  database,
568
- timeoutMs,
569
562
  querySize: revalidateTagQuerySize,
570
563
  filterKeys,
571
564
  resyncIntervalMs: avgResyncIntervalMs - avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
@@ -575,7 +568,6 @@ var RedisStringsHandler = class {
575
568
  keyPrefix,
576
569
  redisKey: REVALIDATED_TAGS_KEY,
577
570
  database,
578
- timeoutMs,
579
571
  querySize: revalidateTagQuerySize,
580
572
  filterKeys,
581
573
  resyncIntervalMs: avgResyncIntervalMs + avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
@@ -585,7 +577,6 @@ var RedisStringsHandler = class {
585
577
  keyPrefix,
586
578
  redisKey: "inMemoryDeduplicationCache",
587
579
  database,
588
- timeoutMs,
589
580
  querySize: revalidateTagQuerySize,
590
581
  filterKeys,
591
582
  customizedSync: {
@@ -638,7 +629,7 @@ var RedisStringsHandler = class {
638
629
  "assertClientIsReady: Timeout waiting for Redis maps to be ready"
639
630
  )
640
631
  );
641
- }, this.timeoutMs * 5)
632
+ }, 3e4)
642
633
  )
643
634
  ]);
644
635
  this.clientReadyCalls = 0;
@@ -663,9 +654,9 @@ var RedisStringsHandler = class {
663
654
  await this.assertClientIsReady();
664
655
  const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(key) : this.redisGet;
665
656
  const serializedCacheEntry = await redisErrorHandler(
666
- "RedisStringsHandler.get(), operation: get" + (this.redisGetDeduplication ? "deduplicated" : "") + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
657
+ "RedisStringsHandler.get(), operation: get" + (this.redisGetDeduplication ? "deduplicated" : "") + " " + this.getTimeoutMs + "ms " + this.keyPrefix + " " + key,
667
658
  clientGet(
668
- getTimeoutRedisCommandOptions(this.timeoutMs),
659
+ (0, import_redis.commandOptions)({ signal: AbortSignal.timeout(this.getTimeoutMs) }),
669
660
  this.keyPrefix + key
670
661
  )
671
662
  );
@@ -725,7 +716,7 @@ var RedisStringsHandler = class {
725
716
  const revalidationTime = this.revalidatedTagsMap.get(tag);
726
717
  if (revalidationTime && revalidationTime > cacheEntry.lastModified) {
727
718
  const redisKey = this.keyPrefix + key;
728
- this.client.unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey).catch((err) => {
719
+ this.client.unlink(redisKey).catch((err) => {
729
720
  console.error(
730
721
  "Error occurred while unlinking stale data. Error was:",
731
722
  err
@@ -802,10 +793,9 @@ var RedisStringsHandler = class {
802
793
  data.kind === "FETCH" && data.revalidate || ctx.revalidate || ctx.cacheControl?.revalidate || data?.revalidate
803
794
  );
804
795
  const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
805
- const options = getTimeoutRedisCommandOptions(this.timeoutMs);
806
796
  const setOperation = redisErrorHandler(
807
- "RedisStringsHandler.set(), operation: set" + this.timeoutMs + "ms " + this.keyPrefix + " " + key,
808
- this.client.set(options, this.keyPrefix + key, serializedCacheEntry, {
797
+ "RedisStringsHandler.set(), operation: set " + this.keyPrefix + " " + key,
798
+ this.client.set(this.keyPrefix + key, serializedCacheEntry, {
809
799
  EX: expireAt
810
800
  })
811
801
  );
@@ -899,10 +889,9 @@ var RedisStringsHandler = class {
899
889
  }
900
890
  const redisKeys = Array.from(keysToDelete);
901
891
  const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
902
- const options = getTimeoutRedisCommandOptions(this.timeoutMs);
903
892
  const deleteKeysOperation = redisErrorHandler(
904
- "RedisStringsHandler.revalidateTag(), operation: unlink" + this.timeoutMs + "ms " + this.keyPrefix + " " + fullRedisKeys,
905
- this.client.unlink(options, fullRedisKeys)
893
+ "RedisStringsHandler.revalidateTag(), operation: unlink " + this.keyPrefix + " " + fullRedisKeys,
894
+ this.client.unlink(fullRedisKeys)
906
895
  );
907
896
  if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
908
897
  for (const key of keysToDelete) {