@akinon/next 1.93.0-snapshot-ZERO-3586-20250828143733 → 1.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +39 -1337
  2. package/__tests__/next-config.test.ts +10 -1
  3. package/api/cache.ts +5 -41
  4. package/components/accordion.tsx +5 -20
  5. package/components/file-input.tsx +3 -65
  6. package/components/input.tsx +0 -2
  7. package/components/link.tsx +12 -16
  8. package/components/modal.tsx +16 -32
  9. package/components/plugin-module.tsx +3 -30
  10. package/data/client/checkout.ts +4 -5
  11. package/data/server/category.ts +30 -52
  12. package/data/server/flatpage.ts +13 -20
  13. package/data/server/form.ts +1 -4
  14. package/data/server/landingpage.ts +13 -20
  15. package/data/server/list.ts +14 -25
  16. package/data/server/menu.ts +1 -4
  17. package/data/server/product.ts +40 -68
  18. package/data/server/seo.ts +1 -4
  19. package/data/server/special-page.ts +13 -18
  20. package/data/server/widget.ts +1 -4
  21. package/data/urls.ts +1 -5
  22. package/hocs/server/with-segment-defaults.tsx +2 -5
  23. package/hooks/use-localization.ts +3 -2
  24. package/jest.config.js +1 -7
  25. package/lib/cache-handler.mjs +26 -850
  26. package/lib/cache.ts +13 -432
  27. package/middlewares/checkout-provider.ts +1 -1
  28. package/middlewares/complete-gpay.ts +1 -2
  29. package/middlewares/complete-masterpass.ts +1 -2
  30. package/middlewares/default.ts +13 -50
  31. package/middlewares/locale.ts +1 -9
  32. package/middlewares/pretty-url.ts +1 -2
  33. package/middlewares/redirection-payment.ts +1 -2
  34. package/middlewares/saved-card-redirection.ts +1 -2
  35. package/middlewares/three-d-redirection.ts +1 -2
  36. package/middlewares/url-redirection.ts +15 -9
  37. package/package.json +3 -4
  38. package/plugins.d.ts +0 -8
  39. package/plugins.js +1 -3
  40. package/redux/middlewares/checkout.ts +1 -5
  41. package/sentry/index.ts +17 -54
  42. package/types/commerce/order.ts +0 -1
  43. package/types/index.ts +1 -43
  44. package/utils/app-fetch.ts +2 -7
  45. package/utils/index.ts +10 -34
  46. package/utils/redirect.ts +6 -31
  47. package/with-pz-config.js +5 -2
  48. package/__tests__/redirect.test.ts +0 -319
  49. package/api/image-proxy.ts +0 -75
  50. package/api/similar-product-list.ts +0 -84
  51. package/api/similar-products.ts +0 -120
  52. package/data/server/basket.ts +0 -72
  53. package/utils/redirect-ignore.ts +0 -35
@@ -2,445 +2,6 @@ import { CacheHandler } from '@neshca/cache-handler';
2
2
  import createLruHandler from '@neshca/cache-handler/local-lru';
3
3
  import createRedisHandler from '@neshca/cache-handler/redis-strings';
4
4
  import { createClient } from 'redis';
5
- import * as zstdWasm from '@bokuweb/zstd-wasm';
6
-
7
- // Color utilities for logs
8
- const colors = {
9
- red: '\x1b[31m',
10
- green: '\x1b[32m',
11
- yellow: '\x1b[33m',
12
- blue: '\x1b[34m',
13
- magenta: '\x1b[35m',
14
- cyan: '\x1b[36m',
15
- white: '\x1b[37m',
16
- reset: '\x1b[0m',
17
- bright: '\x1b[1m'
18
- };
19
-
20
- const colorLog = (color, prefix, ...args) => {
21
- // Always show compression-related logs regardless of debug flag
22
- // eslint-disable-next-line no-console
23
- console.log(`${colors.bright}${color}${prefix}${colors.reset}`, ...args);
24
- };
25
-
26
- // Initialize ZSTD immediately with IIFE (to avoid top-level await)
27
- let zstd;
28
-
29
- // Initialize immediately using IIFE
30
- (async () => {
31
- try {
32
- await zstdWasm.init();
33
- zstd = zstdWasm;
34
- colorLog(
35
- colors.green,
36
- '[Cache Handler] 🚀 ',
37
- 'ZSTD compression initialized successfully at module load'
38
- );
39
- } catch (error) {
40
- colorLog(
41
- colors.yellow,
42
- '[Cache Handler] ⚠️ ',
43
- 'ZSTD compression failed to initialize:',
44
- error.message
45
- );
46
- zstd = false;
47
- }
48
- })();
49
-
50
- const getZstd = () => {
51
- return zstd;
52
- };
53
-
54
- // Only ZSTD compression is used now - gzip/deflate removed for simplicity
55
-
56
- // Compression/decompression functions for cache values
57
- const compressValue = async (value) => {
58
- try {
59
- console_log(
60
- '🔍 Compressing value type:',
61
- typeof value,
62
- 'keys:',
63
- value && typeof value === 'object' ? Object.keys(value) : 'N/A'
64
- );
65
-
66
- // For Next.js cache entries, we need to preserve the entire structure
67
- // and only compress the nested 'value' field if it exists
68
- if (value && typeof value === 'object' && value.value !== undefined) {
69
- console_log('📦 Next.js cache entry detected, preserving structure');
70
-
71
- // Debug original lifespan values
72
- console_log('🔍 Original lifespan values:', {
73
- expireAge: value.lifespan?.expireAge,
74
- expireAt: value.lifespan?.expireAt,
75
- revalidate: value.lifespan?.revalidate,
76
- staleAge: value.lifespan?.staleAge,
77
- currentTime: Math.floor(Date.now() / 1000)
78
- });
79
-
80
- const nestedValue = value.value;
81
- const serializedNestedValue =
82
- typeof nestedValue === 'string'
83
- ? nestedValue
84
- : JSON.stringify(nestedValue);
85
- const originalSize = Buffer.byteLength(serializedNestedValue, 'utf8');
86
-
87
- // Only compress the nested value if larger than 1KB
88
- if (originalSize < 1024) {
89
- colorLog(
90
- colors.blue,
91
- '[Cache Handler] ⚡ ',
92
- `Skipping compression for small value (${originalSize} bytes)`
93
- );
94
- // Ensure tags is always an array even for small values
95
- const result = {
96
- ...value,
97
- tags: Array.isArray(value.tags) ? value.tags : []
98
- };
99
- return result; // Return original structure with guaranteed array tags
100
- }
101
-
102
- const zstdLib = getZstd();
103
- let compressed;
104
-
105
- if (zstdLib && zstdLib !== false) {
106
- compressed = zstdLib.compress(
107
- Buffer.from(serializedNestedValue, 'utf8'),
108
- 3
109
- );
110
- colorLog(colors.cyan, '[Cache Handler] 🔵 ', `Using ZSTD compression`);
111
- } else {
112
- // No fallback compression - if ZSTD fails, don't compress
113
- colorLog(
114
- colors.yellow,
115
- '[Cache Handler] ⚠️ ',
116
- `ZSTD not available, storing uncompressed`
117
- );
118
- return {
119
- ...value,
120
- tags: Array.isArray(value.tags) ? value.tags : []
121
- };
122
- }
123
-
124
- const compressedBase64 = Buffer.from(compressed).toString('base64');
125
- const compressionRatio =
126
- ((originalSize - compressed.length) / originalSize) * 100;
127
-
128
- colorLog(
129
- colors.cyan,
130
- '[Cache Handler] 🔵 ',
131
- `Compressed nested value ${originalSize} → ${
132
- compressed.length
133
- } bytes (${compressionRatio.toFixed(1)}% reduction, method: zstd)`
134
- );
135
-
136
- // Return the cache entry with compressed nested value but preserved structure
137
- // Ensure tags is always an array for Redis handler compatibility
138
- // Fix expireAge to match project's revalidate value (300) instead of Next.js calculated value (450)
139
- const result = {
140
- ...value, // Preserve all metadata (lastModified, expiresAt, tags, etc.)
141
- tags: Array.isArray(value.tags) ? value.tags : [], // Ensure tags is always an array
142
- lifespan: {
143
- ...value.lifespan,
144
- // Use revalidate value for expireAge to match project configuration
145
- expireAge: value.lifespan?.revalidate || value.lifespan?.expireAge,
146
- // Recalculate expireAt based on lastModifiedAt + revalidate
147
- expireAt:
148
- value.lifespan?.lastModifiedAt && value.lifespan?.revalidate
149
- ? value.lifespan.lastModifiedAt + value.lifespan.revalidate
150
- : value.lifespan?.expireAt
151
- },
152
- value: {
153
- __compressed: true,
154
- __method: 'zstd',
155
- __originalSize: originalSize,
156
- __compressedSize: compressed.length,
157
- __data: compressedBase64
158
- }
159
- };
160
-
161
- // Debug final lifespan values to see if they changed
162
- console_log('🔍 Final lifespan values after compression and fix:', {
163
- expireAge: result.lifespan?.expireAge,
164
- expireAt: result.lifespan?.expireAt,
165
- revalidate: result.lifespan?.revalidate,
166
- staleAge: result.lifespan?.staleAge,
167
- originalExpireAge: value.lifespan?.expireAge,
168
- fixedToMatchRevalidate:
169
- result.lifespan?.expireAge === result.lifespan?.revalidate,
170
- changed: result.lifespan?.expireAge !== value.lifespan?.expireAge
171
- });
172
-
173
- // Additional validation for Redis handler requirements
174
- console_log('🔍 Compressed cache entry validation:', {
175
- hasLastModified: result.lastModified !== undefined,
176
- hasLifespan: result.lifespan !== undefined,
177
- lifespanExpireAtType: typeof result.lifespan?.expireAt,
178
- lifespanExpireAtValue: result.lifespan?.expireAt,
179
- tagsIsArray: Array.isArray(result.tags),
180
- tagsLength: result.tags?.length,
181
- isValidForRedis:
182
- result.lastModified !== undefined &&
183
- result.lifespan?.expireAt !== undefined &&
184
- typeof result.lifespan.expireAt === 'number' &&
185
- Array.isArray(result.tags)
186
- });
187
-
188
- return result;
189
- }
190
-
191
- // For primitive values or non-cache-entry objects, compress directly
192
- const serializedValue =
193
- typeof value === 'string' ? value : JSON.stringify(value);
194
- const originalSize = Buffer.byteLength(serializedValue, 'utf8');
195
-
196
- if (originalSize < 1024) {
197
- colorLog(
198
- colors.blue,
199
- '[Cache Handler] ⚡ ',
200
- `Skipping compression for small value (${originalSize} bytes)`
201
- );
202
- // For non-Next.js cache entries, ensure they have required Redis fields if they're objects
203
- // But NEVER modify Next.js cache entries (they have lastModified, lifespan, tags, value)
204
- if (
205
- value &&
206
- typeof value === 'object' &&
207
- value.lastModified === undefined &&
208
- value.lifespan === undefined &&
209
- value.value === undefined
210
- ) {
211
- // This is a primitive object, not a Next.js cache entry
212
- return {
213
- ...value,
214
- tags: value.tags || [], // Preserve existing tags or add empty array
215
- lastModified: Date.now(),
216
- lifespan: {
217
- expireAt: Math.floor(Date.now() / 1000) + 3600
218
- }
219
- };
220
- }
221
- // Fix expireAge for Next.js cache entries to match project's revalidate value
222
- if (
223
- value &&
224
- typeof value === 'object' &&
225
- value.lifespan &&
226
- value.lifespan.revalidate
227
- ) {
228
- return {
229
- ...value,
230
- lifespan: {
231
- ...value.lifespan,
232
- // Use revalidate value for expireAge to match project configuration
233
- expireAge: value.lifespan.revalidate,
234
- // Recalculate expireAt based on lastModifiedAt + revalidate
235
- expireAt:
236
- value.lifespan.lastModifiedAt && value.lifespan.revalidate
237
- ? value.lifespan.lastModifiedAt + value.lifespan.revalidate
238
- : value.lifespan.expireAt
239
- }
240
- };
241
- }
242
- // Return other values unchanged
243
- return value;
244
- }
245
-
246
- const zstdLib = getZstd();
247
- let compressed;
248
-
249
- if (zstdLib && zstdLib !== false) {
250
- compressed = zstdLib.compress(Buffer.from(serializedValue, 'utf8'), 3);
251
- colorLog(colors.cyan, '[Cache Handler] 🔵 ', `Using ZSTD compression`);
252
- } else {
253
- // No fallback compression - if ZSTD fails, don't compress
254
- colorLog(
255
- colors.yellow,
256
- '[Cache Handler] ⚠️ ',
257
- `ZSTD not available, storing uncompressed`
258
- );
259
- // For non-Next.js cache entries, ensure they have required Redis fields if they're objects
260
- // But NEVER modify Next.js cache entries (they have lastModified, lifespan, tags, value)
261
- if (
262
- value &&
263
- typeof value === 'object' &&
264
- value.lastModified === undefined &&
265
- value.lifespan === undefined &&
266
- value.value === undefined
267
- ) {
268
- // This is a primitive object, not a Next.js cache entry
269
- return {
270
- ...value,
271
- tags: value.tags || [], // Preserve existing tags or add empty array
272
- lastModified: Date.now(),
273
- lifespan: {
274
- expireAt: Math.floor(Date.now() / 1000) + 3600
275
- }
276
- };
277
- }
278
- // Return Next.js cache entries and other values unchanged
279
- return value;
280
- }
281
-
282
- const compressedBase64 = Buffer.from(compressed).toString('base64');
283
- const compressionRatio =
284
- ((originalSize - compressed.length) / originalSize) * 100;
285
-
286
- colorLog(
287
- colors.cyan,
288
- '[Cache Handler] 🔵 ',
289
- `Compressed ${originalSize} → ${
290
- compressed.length
291
- } bytes (${compressionRatio.toFixed(1)}% reduction, method: zstd)`
292
- );
293
-
294
- // For compressed non-Next.js entries, ensure Redis compatibility
295
- const compressedResult = {
296
- __compressed: true,
297
- __method: 'zstd',
298
- __originalSize: originalSize,
299
- __compressedSize: compressed.length,
300
- __data: compressedBase64,
301
- // Add required Redis fields only for truly primitive values (not cache entries)
302
- tags: [], // Required array for Redis handler
303
- lastModified: Date.now(),
304
- lifespan: { expireAt: Math.floor(Date.now() / 1000) + 3600 } // Default 1 hour for primitive values only
305
- };
306
-
307
- console_log('🔍 Non-Next.js compressed entry validation:', {
308
- hasRequiredFields:
309
- Array.isArray(compressedResult.tags) &&
310
- compressedResult.lastModified !== undefined &&
311
- compressedResult.lifespan?.expireAt !== undefined &&
312
- typeof compressedResult.lifespan.expireAt === 'number'
313
- });
314
-
315
- return compressedResult;
316
- } catch (error) {
317
- console.warn(
318
- '[Cache Handler] Compression failed, storing uncompressed:',
319
- error.message
320
- );
321
- return value; // Return original value on error
322
- }
323
- };
324
-
325
- const decompressValue = async (compressedData) => {
326
- try {
327
- console_log(
328
- '🔍 Decompressing value type:',
329
- typeof compressedData,
330
- 'keys:',
331
- compressedData && typeof compressedData === 'object'
332
- ? Object.keys(compressedData)
333
- : 'N/A'
334
- );
335
-
336
- // Check if this is a Next.js cache entry with compressed nested value
337
- if (
338
- compressedData &&
339
- typeof compressedData === 'object' &&
340
- compressedData.value &&
341
- typeof compressedData.value === 'object' &&
342
- compressedData.value.__compressed
343
- ) {
344
- console_log(
345
- '📦 Next.js cache entry with compressed nested value detected'
346
- );
347
-
348
- const compressedNestedValue = compressedData.value;
349
- const compressedBuffer = Buffer.from(
350
- compressedNestedValue.__data,
351
- 'base64'
352
- );
353
- let decompressed;
354
-
355
- if (compressedNestedValue.__method === 'zstd') {
356
- const zstdLib = getZstd();
357
- if (zstdLib && zstdLib !== false) {
358
- decompressed = zstdLib.decompress(compressedBuffer).toString('utf8');
359
- colorLog(
360
- colors.cyan,
361
- '[Cache Handler] 🔵 ',
362
- 'Using ZSTD decompression for nested value'
363
- );
364
- } else {
365
- throw new Error('zstd not available for decompression');
366
- }
367
- } else {
368
- // Legacy support for gzip compressed data, but warn about it
369
- colorLog(
370
- colors.yellow,
371
- '[Cache Handler] ⚠️ ',
372
- 'Found legacy gzip compressed data, consider cache invalidation'
373
- );
374
- throw new Error(
375
- 'gzip decompression no longer supported - please invalidate cache'
376
- );
377
- }
378
-
379
- colorLog(
380
- colors.cyan,
381
- '[Cache Handler] 🔵 ',
382
- `Decompressed nested value ${compressedNestedValue.__compressedSize} → ${decompressed.length} bytes (method: zstd)`
383
- );
384
-
385
- // Restore the original cache entry structure with decompressed nested value
386
- return {
387
- ...compressedData, // Preserve all metadata (lastModified, expiresAt, tags, etc.)
388
- value: JSON.parse(decompressed) // Restore original nested value
389
- };
390
- }
391
-
392
- // Check if this is a directly compressed value
393
- if (
394
- compressedData &&
395
- typeof compressedData === 'object' &&
396
- compressedData.__compressed
397
- ) {
398
- console_log('📄 Directly compressed value detected');
399
-
400
- const compressedBuffer = Buffer.from(compressedData.__data, 'base64');
401
- let decompressed;
402
-
403
- if (compressedData.__method === 'zstd') {
404
- const zstdLib = getZstd();
405
- if (zstdLib && zstdLib !== false) {
406
- decompressed = zstdLib.decompress(compressedBuffer).toString('utf8');
407
- colorLog(
408
- colors.cyan,
409
- '[Cache Handler] 🔵 ',
410
- 'Using ZSTD decompression'
411
- );
412
- } else {
413
- throw new Error('zstd not available for decompression');
414
- }
415
- } else {
416
- // Legacy support for gzip compressed data, but warn about it
417
- colorLog(
418
- colors.yellow,
419
- '[Cache Handler] ⚠️ ',
420
- 'Found legacy gzip compressed data, consider cache invalidation'
421
- );
422
- throw new Error(
423
- 'gzip decompression no longer supported - please invalidate cache'
424
- );
425
- }
426
-
427
- colorLog(
428
- colors.cyan,
429
- '[Cache Handler] 🔵 ',
430
- `Decompressed ${compressedData.__compressedSize} → ${decompressed.length} bytes (method: zstd)`
431
- );
432
-
433
- return JSON.parse(decompressed);
434
- }
435
-
436
- // No compression detected, return as-is
437
- console_log('✨ No compression detected, returning value as-is');
438
- return compressedData;
439
- } catch (error) {
440
- console.warn('[Cache Handler] Decompression failed:', error.message);
441
- return compressedData; // Return original data on error
442
- }
443
- };
444
5
 
445
6
  // Cache configuration
446
7
  const CACHE_CONFIG = {
@@ -575,53 +136,12 @@ async function getRedisClient() {
575
136
 
576
137
  CacheHandler.onCreation(async () => {
577
138
  console_log('Initializing cache handlers...');
578
- colorLog(
579
- colors.blue,
580
- '[Cache Handler] 🔧 ',
581
- 'Starting cache handler initialization...'
582
- );
583
-
584
- // Force log to appear even if debug is off
585
- colorLog(
586
- colors.green,
587
- '[Cache Handler] 🚀 ',
588
- 'CACHE HANDLER INITIALIZATION STARTING'
589
- );
590
- colorLog(colors.blue, '[Cache Handler] 🔧 ', 'Environment check:');
591
- colorLog(
592
- colors.blue,
593
- '[Cache Handler] 🔧 ',
594
- '- CACHE_HOST:',
595
- process.env.CACHE_HOST
596
- );
597
- colorLog(
598
- colors.blue,
599
- '[Cache Handler] 🔧 ',
600
- '- CACHE_PORT:',
601
- process.env.CACHE_PORT
602
- );
603
- colorLog(
604
- colors.blue,
605
- '[Cache Handler] 🔧 ',
606
- '- NODE_ENV:',
607
- process.env.NODE_ENV
608
- );
609
139
 
610
140
  let client;
611
141
  try {
612
142
  client = await getRedisClient();
613
- colorLog(
614
- colors.green,
615
- '[Cache Handler] ✅ ',
616
- 'Redis client connected successfully'
617
- );
618
143
  } catch (error) {
619
144
  // Error already logged in getRedisClient, just return local handler
620
- colorLog(
621
- colors.red,
622
- '[Cache Handler] ❌ ',
623
- 'Redis connection failed, using local cache only'
624
- );
625
145
  return {
626
146
  handlers: [createLruHandler(CACHE_CONFIG.lru)]
627
147
  };
@@ -630,20 +150,22 @@ CacheHandler.onCreation(async () => {
630
150
  const redisHandler = createRedisHandler({
631
151
  client,
632
152
  timeoutMs: CACHE_CONFIG.redis.timeoutMs,
633
- keyExpirationStrategy: 'EXPIREAT' // Use EXPIREAT for Redis 6.0.x compatibility
153
+ keyExpirationStrategy: 'EXAT'
634
154
  });
635
155
 
636
156
  const localHandler = createLruHandler(CACHE_CONFIG.lru);
637
157
 
638
- const CACHE_VERSION = 'v2';
639
- const versionPrefix = `${CACHE_VERSION}_`;
158
+ // Pre-compute version prefix if exists
159
+ const versionPrefix = CACHE_CONFIG.version ? `${CACHE_CONFIG.version}:` : '';
640
160
 
641
161
  // Create optimized functions for each scenario
642
- const versionKeyString = (key) => `${versionPrefix}${key}`;
643
- const versionKeyObject = (key) => ({
644
- ...key,
645
- key: `${versionPrefix}${key.key}`
646
- });
162
+ const versionKeyString = versionPrefix
163
+ ? (key) => `${versionPrefix}${key}`
164
+ : (key) => key;
165
+
166
+ const versionKeyObject = versionPrefix
167
+ ? (key) => ({ ...key, key: `${versionPrefix}${key.key}` })
168
+ : (key) => key;
647
169
 
648
170
  // Main version key function that routes to optimized paths
649
171
  const versionKey = (key) => {
@@ -657,24 +179,10 @@ CacheHandler.onCreation(async () => {
657
179
  name: 'custom-local-then-redis',
658
180
  get: async (key, context) => {
659
181
  const vKey = versionKey(key);
660
- const keyStr = typeof vKey === 'string' ? vKey : vKey?.key;
661
-
662
- // Force log GET calls to see if handler is being used
663
- colorLog(colors.white, '[Cache Handler] 🔍 ', `CACHE GET: ${keyStr}`);
664
- console_log('GET called for key:', keyStr);
665
-
666
- // Extract and log URL information for better debugging
667
- if (keyStr) {
668
- const urlMatch = keyStr.match(/https?%3A%2F%2F[^_]+|\/[^_]*$/);
669
- const extractedUrl = urlMatch
670
- ? decodeURIComponent(urlMatch[0])
671
- : 'unknown';
672
- colorLog(
673
- colors.white,
674
- '[Cache Handler] 🌐 ',
675
- `Cache GET for URL: ${extractedUrl}`
676
- );
677
- }
182
+ console_log(
183
+ 'GET called for key:',
184
+ typeof vKey === 'string' ? vKey : vKey?.key
185
+ );
678
186
 
679
187
  // Check local cache first
680
188
  console_log('Checking local cache...');
@@ -682,38 +190,6 @@ CacheHandler.onCreation(async () => {
682
190
 
683
191
  if (localResult) {
684
192
  console_log('Found in local cache');
685
- // Check if it's compressed data (new format or legacy format)
686
- if (
687
- localResult &&
688
- typeof localResult === 'object' &&
689
- (localResult.__compressed ||
690
- (localResult.value && localResult.value.__compressed) ||
691
- localResult.compressed !== undefined)
692
- ) {
693
- try {
694
- const decompressed = await decompressValue(localResult);
695
- // If it's a string, parse it; otherwise return as-is (it's already parsed)
696
- return typeof decompressed === 'string'
697
- ? JSON.parse(decompressed)
698
- : decompressed;
699
- } catch (error) {
700
- console.warn(
701
- '[Cache Handler] Failed to decompress local cache value:',
702
- error.message
703
- );
704
- return localResult;
705
- }
706
- }
707
-
708
- // Debug info for non-compressed values
709
- console_log('🔍 Local result type:', typeof localResult);
710
- if (
711
- localResult &&
712
- typeof localResult === 'object' &&
713
- localResult.constructor === Object
714
- ) {
715
- console_log('🔍 Local result keys:', Object.keys(localResult));
716
- }
717
193
  return localResult;
718
194
  }
719
195
 
@@ -723,66 +199,14 @@ CacheHandler.onCreation(async () => {
723
199
 
724
200
  if (redisResult) {
725
201
  console_log('Found in Redis');
726
-
727
- let finalResult = redisResult;
728
-
729
- // Parse JSON if it's a string (Redis stores as JSON)
730
- if (typeof redisResult === 'string') {
731
- try {
732
- finalResult = JSON.parse(redisResult);
733
- console_log('🔄 Parsed JSON from Redis');
734
- } catch (parseError) {
735
- console.warn(
736
- '⚠️ Failed to parse Redis JSON, using raw string:',
737
- parseError.message
738
- );
739
- finalResult = redisResult;
740
- }
741
- }
742
-
743
- // Check if it's compressed data and decompress (new format or legacy format)
744
- if (
745
- finalResult &&
746
- typeof finalResult === 'object' &&
747
- (finalResult.__compressed ||
748
- (finalResult.value && finalResult.value.__compressed) ||
749
- finalResult.compressed !== undefined)
750
- ) {
751
- try {
752
- const decompressed = await decompressValue(finalResult);
753
- // If it's a string, parse it; otherwise return as-is (it's already parsed)
754
- finalResult =
755
- typeof decompressed === 'string'
756
- ? JSON.parse(decompressed)
757
- : decompressed;
758
- } catch (error) {
759
- console.warn(
760
- '[Cache Handler] Failed to decompress Redis cache value:',
761
- error.message
762
- );
763
- // finalResult stays as is
764
- }
765
- }
766
-
767
- // Debug Redis result
768
- console_log('🔍 Redis result type:', typeof redisResult);
769
- console_log('🔍 Final result type:', typeof finalResult);
770
- if (
771
- finalResult &&
772
- typeof finalResult === 'object' &&
773
- finalResult.constructor === Object
774
- ) {
775
- console_log('🔍 Final result keys:', Object.keys(finalResult));
776
- }
777
-
778
202
  // Sync back to local cache for faster future access
779
203
  try {
780
- await localHandler.set(vKey, finalResult, context);
204
+ await localHandler.set(vKey, redisResult, context);
781
205
  console_log('Synced to local cache');
782
206
  } catch (error) {
783
207
  console_log('Failed to sync to local:', error.message);
784
208
  }
785
- return finalResult;
209
+ return redisResult;
786
210
  }
787
211
  } catch (error) {
788
212
  console_log('Redis error:', error.message);
@@ -793,260 +217,17 @@ CacheHandler.onCreation(async () => {
793
217
  },
794
218
  set: async (key, value, context) => {
795
219
  const vKey = versionKey(key);
796
- const keyStr = typeof vKey === 'string' ? vKey : vKey?.key;
797
-
798
- // Force log SET calls to see if handler is being used
799
- colorLog(colors.white, '[Cache Handler] 💾 ', `CACHE SET: ${keyStr}`);
800
- console_log('SET called for key:', keyStr);
801
-
802
- // Extract and log URL information for better debugging
803
- if (keyStr) {
804
- const urlMatch = keyStr.match(/https?%3A%2F%2F[^_]+|\/[^_]*$/);
805
- const extractedUrl = urlMatch
806
- ? decodeURIComponent(urlMatch[0])
807
- : 'unknown';
808
- colorLog(
809
- colors.white,
810
- '[Cache Handler] 🌐 ',
811
- `Cache SET for URL: ${extractedUrl}`
812
- );
813
- }
814
-
815
- // Log context and value information for debugging
816
- console_log('SET Debug Info:', {
817
- contextKeys: context ? Object.keys(context) : 'no context',
818
- valueKeys:
819
- value && typeof value === 'object'
820
- ? Object.keys(value)
821
- : 'primitive value',
822
- valueType: typeof value,
823
- contextRevalidatedAt: context?.revalidatedAt,
824
- contextExpiresAt: context?.expiresAt,
825
- contextLifespan: context?.lifespan,
826
- valueTags: value?.tags,
827
- valueLastModified: value?.lastModified,
828
- valueExpiresAt: value?.expiresAt,
829
- valueLifespan: value?.lifespan,
830
- lifespanExpireAt: value?.lifespan?.expireAt,
831
- currentTime: Math.floor(Date.now() / 1000),
832
- ttlRemaining: value?.lifespan?.expireAt
833
- ? value.lifespan.expireAt - Math.floor(Date.now() / 1000)
834
- : 'unknown'
835
- });
836
-
837
- // Debug original value TTL before any processing
838
- colorLog(
839
- colors.yellow,
840
- '[Cache Handler] 🔍 ',
841
- 'ORIGINAL VALUE TTL DEBUG:',
842
- {
843
- originalExpireAt: value?.lifespan?.expireAt,
844
- originalExpireAge: value?.lifespan?.expireAge,
845
- originalRevalidate: value?.lifespan?.revalidate,
846
- currentTime: Math.floor(Date.now() / 1000),
847
- ttlRemaining: value?.lifespan?.expireAt
848
- ? value.lifespan.expireAt - Math.floor(Date.now() / 1000)
849
- : 'N/A'
850
- }
220
+ console_log(
221
+ 'SET called for key:',
222
+ typeof vKey === 'string' ? vKey : vKey?.key
851
223
  );
852
-
853
- // Try to compress the value first
854
- let compressedValue;
855
- let shouldUseCompressed = false;
856
-
857
- try {
858
- colorLog(
859
- colors.blue,
860
- '[Cache Handler] 🧪 ',
861
- `Attempting compression for ${keyStr}`
862
- );
863
- compressedValue = await compressValue(value);
864
-
865
- // Debug compressed value TTL after compression
866
- colorLog(
867
- colors.yellow,
868
- '[Cache Handler] 🔍 ',
869
- 'COMPRESSED VALUE TTL DEBUG:',
870
- {
871
- compressedExpireAt: compressedValue?.lifespan?.expireAt,
872
- compressedExpireAge: compressedValue?.lifespan?.expireAge,
873
- compressedRevalidate: compressedValue?.lifespan?.revalidate,
874
- ttlChanged:
875
- value?.lifespan?.expireAt !== compressedValue?.lifespan?.expireAt,
876
- originalTTL: value?.lifespan?.expireAt,
877
- compressedTTL: compressedValue?.lifespan?.expireAt
878
- }
879
- );
880
-
881
- // Check if compression actually happened (compressValue returns original if no compression)
882
- shouldUseCompressed =
883
- compressedValue !== value &&
884
- (compressedValue?.__compressed ||
885
- compressedValue?.value?.__compressed);
886
- colorLog(
887
- colors.green,
888
- '[Cache Handler] ✅ ',
889
- `Compression ${
890
- shouldUseCompressed ? 'SUCCESS' : 'SKIPPED'
891
- } for ${keyStr}`
892
- );
893
- } catch (error) {
894
- colorLog(
895
- colors.red,
896
- '[Cache Handler] ❌ ',
897
- `Compression failed for ${keyStr}:`,
898
- error.message
899
- );
900
- console.warn(
901
- '[Cache Handler] Compression failed, using original value:',
902
- error.message
903
- );
904
- compressedValue = value;
905
- shouldUseCompressed = false;
906
- }
907
-
908
- // Set to both caches - local gets original, Redis gets compressed (if successful)
909
- // IMPORTANT: Both calls must use the same context to preserve TTL
910
-
911
- // Try Redis with compressed value first, fallback to original on error
912
- let redisSetResult;
913
-
914
- if (shouldUseCompressed) {
915
- // Log before attempting Redis SET with compression
916
- colorLog(
917
- colors.blue,
918
- '[Cache Handler] 💾 ',
919
- `Attempting Redis SET with compression for: ${keyStr}`
920
- );
921
-
922
- try {
923
- // Debug what we're sending to Redis
924
- colorLog(
925
- colors.yellow,
926
- '[Cache Handler] 🔍 ',
927
- 'SENDING TO REDIS (COMPRESSED):',
928
- {
929
- keyStr,
930
- hasLifespan: compressedValue?.lifespan !== undefined,
931
- expireAt: compressedValue?.lifespan?.expireAt,
932
- expireAge: compressedValue?.lifespan?.expireAge,
933
- contextExpireAt: context?.expiresAt,
934
- contextLifespan: context?.lifespan
935
- }
936
- );
937
-
938
- await redisHandler.set(vKey, compressedValue, context);
939
- colorLog(
940
- colors.green,
941
- '[Cache Handler] ✅ ',
942
- `Redis SET with compression successful for: ${keyStr}`
943
- );
944
- redisSetResult = { status: 'fulfilled' };
945
- } catch (compressionError) {
946
- colorLog(
947
- colors.yellow,
948
- '[Cache Handler] ⚠️ ',
949
- `Redis SET with compression failed for ${keyStr}, trying uncompressed fallback:`,
950
- compressionError.message
951
- );
952
-
953
- // Fallback to original uncompressed value
954
- try {
955
- // Debug what we're sending to Redis as fallback
956
- colorLog(
957
- colors.yellow,
958
- '[Cache Handler] 🔍 ',
959
- 'SENDING TO REDIS (FALLBACK UNCOMPRESSED):',
960
- {
961
- keyStr,
962
- hasLifespan: value?.lifespan !== undefined,
963
- expireAt: value?.lifespan?.expireAt,
964
- expireAge: value?.lifespan?.expireAge,
965
- contextExpireAt: context?.expiresAt,
966
- contextLifespan: context?.lifespan
967
- }
968
- );
969
-
970
- await redisHandler.set(vKey, value, context);
971
- colorLog(
972
- colors.green,
973
- '[Cache Handler] ✅ ',
974
- `Redis SET fallback (uncompressed) successful for: ${keyStr}`
975
- );
976
- redisSetResult = { status: 'fulfilled' };
977
- } catch (fallbackError) {
978
- colorLog(
979
- colors.red,
980
- '[Cache Handler] ❌ ',
981
- `Redis SET fallback also failed for: ${keyStr}`,
982
- fallbackError.message
983
- );
984
- redisSetResult = { status: 'rejected', reason: fallbackError };
985
- }
986
- }
987
- } else {
988
- // No compression, use original value directly
989
- colorLog(
990
- colors.blue,
991
- '[Cache Handler] 💾 ',
992
- `Attempting Redis SET (uncompressed) for: ${keyStr}`
993
- );
994
-
995
- try {
996
- // Debug what we're sending to Redis (no compression)
997
- colorLog(
998
- colors.yellow,
999
- '[Cache Handler] 🔍 ',
1000
- 'SENDING TO REDIS (NO COMPRESSION):',
1001
- {
1002
- keyStr,
1003
- hasLifespan: value?.lifespan !== undefined,
1004
- expireAt: value?.lifespan?.expireAt,
1005
- expireAge: value?.lifespan?.expireAge,
1006
- contextExpireAt: context?.expiresAt,
1007
- contextLifespan: context?.lifespan
1008
- }
1009
- );
1010
-
1011
- await redisHandler.set(vKey, value, context);
1012
- colorLog(
1013
- colors.green,
1014
- '[Cache Handler] ✅ ',
1015
- `Redis SET (uncompressed) successful for: ${keyStr}`
1016
- );
1017
- redisSetResult = { status: 'fulfilled' };
1018
- } catch (error) {
1019
- colorLog(
1020
- colors.red,
1021
- '[Cache Handler] ❌ ',
1022
- `Redis SET (uncompressed) failed for: ${keyStr}`,
1023
- error.message
1024
- );
1025
- redisSetResult = { status: 'rejected', reason: error };
1026
- }
1027
- }
1028
-
1029
- // Always set to local cache with original value
1030
- let localSetResult;
1031
- try {
1032
- await localHandler.set(vKey, value, context);
1033
- localSetResult = { status: 'fulfilled' };
1034
- } catch (error) {
1035
- localSetResult = { status: 'rejected', reason: error };
1036
- }
1037
-
1038
- const results = [localSetResult, redisSetResult];
1039
-
1040
- // Log results
1041
- console_log('SET Results:', {
1042
- local: results[0].status,
1043
- redis: results[1].status,
1044
- localError:
1045
- results[0].status === 'rejected' ? results[0].reason?.message : null,
1046
- redisError:
1047
- results[1].status === 'rejected' ? results[1].reason?.message : null,
1048
- compressionUsed: shouldUseCompressed
1049
- });
224
+ // Set to both caches
225
+ await Promise.allSettled([
226
+ localHandler.set(vKey, value, context),
227
+ redisHandler
228
+ .set(vKey, value, context)
229
+ .catch((error) => console_log('Redis SET error:', error.message))
230
+ ]);
1050
231
  },
1051
232
  delete: async (key, context) => {
1052
233
  const vKey = versionKey(key);
@@ -1069,11 +250,6 @@ CacheHandler.onCreation(async () => {
1069
250
  };
1070
251
 
1071
252
  console_log('[Cache Handler] Handlers initialized successfully');
1072
- colorLog(
1073
- colors.green,
1074
- '[Cache Handler] 🎉 ',
1075
- 'Cache handlers ready with Redis + Local LRU + Compression'
1076
- );
1077
253
 
1078
254
  return {
1079
255
  handlers: [customHandler]