@akinon/next 1.93.0-snapshot-ZERO-3586-20250828143733 → 1.93.0-snapshot-ZERO-3586-20250901110545
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 +4 -4
- package/lib/cache-handler.mjs +13 -96
- package/lib/cache.ts +44 -234
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 1.93.0-snapshot-ZERO-3586-
|
|
3
|
+
## 1.93.0-snapshot-ZERO-3586-20250901110545
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- e9541a13: ZERO-2816: Add headers to url
|
|
25
25
|
- 9b7d0de6: ZERO-3393: Improve error handling in checkout middleware to support both object and array error formats
|
|
26
26
|
- 72fd4d67: ZERO-3084: Fix URL search parameters encoding in default middleware
|
|
27
|
-
-
|
|
27
|
+
- c53ef7b95: ZERO-2668: The Link component has been updated to improve the logic for handling href values. Previously, if the href was not a string or started with 'http', it would return the href as is. Now, if the href is not provided, it will default to '#' to prevent any potential errors. Additionally, if the href is a string and does not start with 'http', it will be formatted with the locale and pathname, based on the localeUrlStrategy and defaultLocaleValue. This ensures that the correct href is generated based on the localization settings.
|
|
28
28
|
- a8539c8c: ZERO-3439: Enhance locale handling in middleware and redirect utility
|
|
29
29
|
- 16aff543: ZERO-3431: Add test script for redirect utility in package.json
|
|
30
30
|
- 64699d3f: ZERO-2761: Fix invalid import for plugin module
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
- 72ad7bb1: ZERO-3422: Add Flow Payment to the list of available plugins
|
|
44
44
|
- c39c7000: ZERO-3420: Refactor Modal component
|
|
45
45
|
- e7cd3a5e: ZERO-3435: Add Accept-Language to requestHeaders
|
|
46
|
-
-
|
|
46
|
+
- bbe18b9ff: ZERO-2575: Fix build error
|
|
47
47
|
- 17bfadc4: ZERO-3275: Disable OpenTelemetry monitoring in production environment
|
|
48
48
|
- 35dfb8f8: ZERO-3363: Refactor URL handling in checkout and redirection middlewares to use url.origin instead of process.env.NEXT_PUBLIC_URL
|
|
49
49
|
- 4920742c: Disable getCachedTranslations
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
- 9dc7298a: ZERO-3416: Refactor Accordion component to enhance props and improve styling flexibility
|
|
57
57
|
- 33377cfd: ZERO-3267: Refactor import statement for ROUTES in error-page component
|
|
58
58
|
- 43c182ee: ZERO-3054: Update Redis variable checks to conditionally include CACHE_SECRET
|
|
59
|
-
-
|
|
59
|
+
- c480272c: ZERO-3531: Refactor checkoutApi: Remove unnecessary invalidatesTags property from POST request from sample products
|
|
60
60
|
- b00a90b1: ZERO-3436: Preserve query params on redirect
|
|
61
61
|
- facf1ada: ZERO-3445: Add SameSite and Secure attributes
|
|
62
62
|
- 485e8ef8: ZERO-3422: Refactor parameter handling in wallet complete redirection middleware to use forEach
|
package/lib/cache-handler.mjs
CHANGED
|
@@ -4,7 +4,6 @@ import createRedisHandler from '@neshca/cache-handler/redis-strings';
|
|
|
4
4
|
import { createClient } from 'redis';
|
|
5
5
|
import * as zstdWasm from '@bokuweb/zstd-wasm';
|
|
6
6
|
|
|
7
|
-
// Color utilities for logs
|
|
8
7
|
const colors = {
|
|
9
8
|
red: '\x1b[31m',
|
|
10
9
|
green: '\x1b[32m',
|
|
@@ -18,15 +17,12 @@ const colors = {
|
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
const colorLog = (color, prefix, ...args) => {
|
|
21
|
-
// Always show compression-related logs regardless of debug flag
|
|
22
20
|
// eslint-disable-next-line no-console
|
|
23
21
|
console.log(`${colors.bright}${color}${prefix}${colors.reset}`, ...args);
|
|
24
22
|
};
|
|
25
23
|
|
|
26
|
-
// Initialize ZSTD immediately with IIFE (to avoid top-level await)
|
|
27
24
|
let zstd;
|
|
28
25
|
|
|
29
|
-
// Initialize immediately using IIFE
|
|
30
26
|
(async () => {
|
|
31
27
|
try {
|
|
32
28
|
await zstdWasm.init();
|
|
@@ -51,9 +47,6 @@ const getZstd = () => {
|
|
|
51
47
|
return zstd;
|
|
52
48
|
};
|
|
53
49
|
|
|
54
|
-
// Only ZSTD compression is used now - gzip/deflate removed for simplicity
|
|
55
|
-
|
|
56
|
-
// Compression/decompression functions for cache values
|
|
57
50
|
const compressValue = async (value) => {
|
|
58
51
|
try {
|
|
59
52
|
console_log(
|
|
@@ -63,12 +56,9 @@ const compressValue = async (value) => {
|
|
|
63
56
|
value && typeof value === 'object' ? Object.keys(value) : 'N/A'
|
|
64
57
|
);
|
|
65
58
|
|
|
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
59
|
if (value && typeof value === 'object' && value.value !== undefined) {
|
|
69
60
|
console_log('📦 Next.js cache entry detected, preserving structure');
|
|
70
61
|
|
|
71
|
-
// Debug original lifespan values
|
|
72
62
|
console_log('🔍 Original lifespan values:', {
|
|
73
63
|
expireAge: value.lifespan?.expireAge,
|
|
74
64
|
expireAt: value.lifespan?.expireAt,
|
|
@@ -84,19 +74,17 @@ const compressValue = async (value) => {
|
|
|
84
74
|
: JSON.stringify(nestedValue);
|
|
85
75
|
const originalSize = Buffer.byteLength(serializedNestedValue, 'utf8');
|
|
86
76
|
|
|
87
|
-
// Only compress the nested value if larger than 1KB
|
|
88
77
|
if (originalSize < 1024) {
|
|
89
78
|
colorLog(
|
|
90
79
|
colors.blue,
|
|
91
80
|
'[Cache Handler] ⚡ ',
|
|
92
81
|
`Skipping compression for small value (${originalSize} bytes)`
|
|
93
82
|
);
|
|
94
|
-
// Ensure tags is always an array even for small values
|
|
95
83
|
const result = {
|
|
96
84
|
...value,
|
|
97
85
|
tags: Array.isArray(value.tags) ? value.tags : []
|
|
98
86
|
};
|
|
99
|
-
return result;
|
|
87
|
+
return result;
|
|
100
88
|
}
|
|
101
89
|
|
|
102
90
|
const zstdLib = getZstd();
|
|
@@ -109,7 +97,6 @@ const compressValue = async (value) => {
|
|
|
109
97
|
);
|
|
110
98
|
colorLog(colors.cyan, '[Cache Handler] 🔵 ', `Using ZSTD compression`);
|
|
111
99
|
} else {
|
|
112
|
-
// No fallback compression - if ZSTD fails, don't compress
|
|
113
100
|
colorLog(
|
|
114
101
|
colors.yellow,
|
|
115
102
|
'[Cache Handler] ⚠️ ',
|
|
@@ -133,17 +120,12 @@ const compressValue = async (value) => {
|
|
|
133
120
|
} bytes (${compressionRatio.toFixed(1)}% reduction, method: zstd)`
|
|
134
121
|
);
|
|
135
122
|
|
|
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
123
|
const result = {
|
|
140
|
-
...value,
|
|
141
|
-
tags: Array.isArray(value.tags) ? value.tags : [],
|
|
124
|
+
...value,
|
|
125
|
+
tags: Array.isArray(value.tags) ? value.tags : [],
|
|
142
126
|
lifespan: {
|
|
143
127
|
...value.lifespan,
|
|
144
|
-
// Use revalidate value for expireAge to match project configuration
|
|
145
128
|
expireAge: value.lifespan?.revalidate || value.lifespan?.expireAge,
|
|
146
|
-
// Recalculate expireAt based on lastModifiedAt + revalidate
|
|
147
129
|
expireAt:
|
|
148
130
|
value.lifespan?.lastModifiedAt && value.lifespan?.revalidate
|
|
149
131
|
? value.lifespan.lastModifiedAt + value.lifespan.revalidate
|
|
@@ -158,7 +140,6 @@ const compressValue = async (value) => {
|
|
|
158
140
|
}
|
|
159
141
|
};
|
|
160
142
|
|
|
161
|
-
// Debug final lifespan values to see if they changed
|
|
162
143
|
console_log('🔍 Final lifespan values after compression and fix:', {
|
|
163
144
|
expireAge: result.lifespan?.expireAge,
|
|
164
145
|
expireAt: result.lifespan?.expireAt,
|
|
@@ -170,7 +151,6 @@ const compressValue = async (value) => {
|
|
|
170
151
|
changed: result.lifespan?.expireAge !== value.lifespan?.expireAge
|
|
171
152
|
});
|
|
172
153
|
|
|
173
|
-
// Additional validation for Redis handler requirements
|
|
174
154
|
console_log('🔍 Compressed cache entry validation:', {
|
|
175
155
|
hasLastModified: result.lastModified !== undefined,
|
|
176
156
|
hasLifespan: result.lifespan !== undefined,
|
|
@@ -188,7 +168,6 @@ const compressValue = async (value) => {
|
|
|
188
168
|
return result;
|
|
189
169
|
}
|
|
190
170
|
|
|
191
|
-
// For primitive values or non-cache-entry objects, compress directly
|
|
192
171
|
const serializedValue =
|
|
193
172
|
typeof value === 'string' ? value : JSON.stringify(value);
|
|
194
173
|
const originalSize = Buffer.byteLength(serializedValue, 'utf8');
|
|
@@ -199,8 +178,7 @@ const compressValue = async (value) => {
|
|
|
199
178
|
'[Cache Handler] ⚡ ',
|
|
200
179
|
`Skipping compression for small value (${originalSize} bytes)`
|
|
201
180
|
);
|
|
202
|
-
|
|
203
|
-
// But NEVER modify Next.js cache entries (they have lastModified, lifespan, tags, value)
|
|
181
|
+
|
|
204
182
|
if (
|
|
205
183
|
value &&
|
|
206
184
|
typeof value === 'object' &&
|
|
@@ -208,17 +186,15 @@ const compressValue = async (value) => {
|
|
|
208
186
|
value.lifespan === undefined &&
|
|
209
187
|
value.value === undefined
|
|
210
188
|
) {
|
|
211
|
-
// This is a primitive object, not a Next.js cache entry
|
|
212
189
|
return {
|
|
213
190
|
...value,
|
|
214
|
-
tags: value.tags || [],
|
|
191
|
+
tags: value.tags || [],
|
|
215
192
|
lastModified: Date.now(),
|
|
216
193
|
lifespan: {
|
|
217
194
|
expireAt: Math.floor(Date.now() / 1000) + 3600
|
|
218
195
|
}
|
|
219
196
|
};
|
|
220
197
|
}
|
|
221
|
-
// Fix expireAge for Next.js cache entries to match project's revalidate value
|
|
222
198
|
if (
|
|
223
199
|
value &&
|
|
224
200
|
typeof value === 'object' &&
|
|
@@ -229,9 +205,7 @@ const compressValue = async (value) => {
|
|
|
229
205
|
...value,
|
|
230
206
|
lifespan: {
|
|
231
207
|
...value.lifespan,
|
|
232
|
-
// Use revalidate value for expireAge to match project configuration
|
|
233
208
|
expireAge: value.lifespan.revalidate,
|
|
234
|
-
// Recalculate expireAt based on lastModifiedAt + revalidate
|
|
235
209
|
expireAt:
|
|
236
210
|
value.lifespan.lastModifiedAt && value.lifespan.revalidate
|
|
237
211
|
? value.lifespan.lastModifiedAt + value.lifespan.revalidate
|
|
@@ -239,7 +213,6 @@ const compressValue = async (value) => {
|
|
|
239
213
|
}
|
|
240
214
|
};
|
|
241
215
|
}
|
|
242
|
-
// Return other values unchanged
|
|
243
216
|
return value;
|
|
244
217
|
}
|
|
245
218
|
|
|
@@ -250,14 +223,11 @@ const compressValue = async (value) => {
|
|
|
250
223
|
compressed = zstdLib.compress(Buffer.from(serializedValue, 'utf8'), 3);
|
|
251
224
|
colorLog(colors.cyan, '[Cache Handler] 🔵 ', `Using ZSTD compression`);
|
|
252
225
|
} else {
|
|
253
|
-
// No fallback compression - if ZSTD fails, don't compress
|
|
254
226
|
colorLog(
|
|
255
227
|
colors.yellow,
|
|
256
228
|
'[Cache Handler] ⚠️ ',
|
|
257
229
|
`ZSTD not available, storing uncompressed`
|
|
258
230
|
);
|
|
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
231
|
if (
|
|
262
232
|
value &&
|
|
263
233
|
typeof value === 'object' &&
|
|
@@ -265,17 +235,15 @@ const compressValue = async (value) => {
|
|
|
265
235
|
value.lifespan === undefined &&
|
|
266
236
|
value.value === undefined
|
|
267
237
|
) {
|
|
268
|
-
// This is a primitive object, not a Next.js cache entry
|
|
269
238
|
return {
|
|
270
239
|
...value,
|
|
271
|
-
tags: value.tags || [],
|
|
240
|
+
tags: value.tags || [],
|
|
272
241
|
lastModified: Date.now(),
|
|
273
242
|
lifespan: {
|
|
274
243
|
expireAt: Math.floor(Date.now() / 1000) + 3600
|
|
275
244
|
}
|
|
276
245
|
};
|
|
277
246
|
}
|
|
278
|
-
// Return Next.js cache entries and other values unchanged
|
|
279
247
|
return value;
|
|
280
248
|
}
|
|
281
249
|
|
|
@@ -291,17 +259,15 @@ const compressValue = async (value) => {
|
|
|
291
259
|
} bytes (${compressionRatio.toFixed(1)}% reduction, method: zstd)`
|
|
292
260
|
);
|
|
293
261
|
|
|
294
|
-
// For compressed non-Next.js entries, ensure Redis compatibility
|
|
295
262
|
const compressedResult = {
|
|
296
263
|
__compressed: true,
|
|
297
264
|
__method: 'zstd',
|
|
298
265
|
__originalSize: originalSize,
|
|
299
266
|
__compressedSize: compressed.length,
|
|
300
267
|
__data: compressedBase64,
|
|
301
|
-
|
|
302
|
-
tags: [], // Required array for Redis handler
|
|
268
|
+
tags: [],
|
|
303
269
|
lastModified: Date.now(),
|
|
304
|
-
lifespan: { expireAt: Math.floor(Date.now() / 1000) + 3600 }
|
|
270
|
+
lifespan: { expireAt: Math.floor(Date.now() / 1000) + 3600 }
|
|
305
271
|
};
|
|
306
272
|
|
|
307
273
|
console_log('🔍 Non-Next.js compressed entry validation:', {
|
|
@@ -318,7 +284,7 @@ const compressValue = async (value) => {
|
|
|
318
284
|
'[Cache Handler] Compression failed, storing uncompressed:',
|
|
319
285
|
error.message
|
|
320
286
|
);
|
|
321
|
-
return value;
|
|
287
|
+
return value;
|
|
322
288
|
}
|
|
323
289
|
};
|
|
324
290
|
|
|
@@ -333,7 +299,6 @@ const decompressValue = async (compressedData) => {
|
|
|
333
299
|
: 'N/A'
|
|
334
300
|
);
|
|
335
301
|
|
|
336
|
-
// Check if this is a Next.js cache entry with compressed nested value
|
|
337
302
|
if (
|
|
338
303
|
compressedData &&
|
|
339
304
|
typeof compressedData === 'object' &&
|
|
@@ -365,7 +330,6 @@ const decompressValue = async (compressedData) => {
|
|
|
365
330
|
throw new Error('zstd not available for decompression');
|
|
366
331
|
}
|
|
367
332
|
} else {
|
|
368
|
-
// Legacy support for gzip compressed data, but warn about it
|
|
369
333
|
colorLog(
|
|
370
334
|
colors.yellow,
|
|
371
335
|
'[Cache Handler] ⚠️ ',
|
|
@@ -382,14 +346,12 @@ const decompressValue = async (compressedData) => {
|
|
|
382
346
|
`Decompressed nested value ${compressedNestedValue.__compressedSize} → ${decompressed.length} bytes (method: zstd)`
|
|
383
347
|
);
|
|
384
348
|
|
|
385
|
-
// Restore the original cache entry structure with decompressed nested value
|
|
386
349
|
return {
|
|
387
|
-
...compressedData,
|
|
388
|
-
value: JSON.parse(decompressed)
|
|
350
|
+
...compressedData,
|
|
351
|
+
value: JSON.parse(decompressed)
|
|
389
352
|
};
|
|
390
353
|
}
|
|
391
354
|
|
|
392
|
-
// Check if this is a directly compressed value
|
|
393
355
|
if (
|
|
394
356
|
compressedData &&
|
|
395
357
|
typeof compressedData === 'object' &&
|
|
@@ -413,7 +375,6 @@ const decompressValue = async (compressedData) => {
|
|
|
413
375
|
throw new Error('zstd not available for decompression');
|
|
414
376
|
}
|
|
415
377
|
} else {
|
|
416
|
-
// Legacy support for gzip compressed data, but warn about it
|
|
417
378
|
colorLog(
|
|
418
379
|
colors.yellow,
|
|
419
380
|
'[Cache Handler] ⚠️ ',
|
|
@@ -433,16 +394,14 @@ const decompressValue = async (compressedData) => {
|
|
|
433
394
|
return JSON.parse(decompressed);
|
|
434
395
|
}
|
|
435
396
|
|
|
436
|
-
// No compression detected, return as-is
|
|
437
397
|
console_log('✨ No compression detected, returning value as-is');
|
|
438
398
|
return compressedData;
|
|
439
399
|
} catch (error) {
|
|
440
400
|
console.warn('[Cache Handler] Decompression failed:', error.message);
|
|
441
|
-
return compressedData;
|
|
401
|
+
return compressedData;
|
|
442
402
|
}
|
|
443
403
|
};
|
|
444
404
|
|
|
445
|
-
// Cache configuration
|
|
446
405
|
const CACHE_CONFIG = {
|
|
447
406
|
lru: {
|
|
448
407
|
maxItemCount: 2000
|
|
@@ -461,7 +420,6 @@ const CACHE_CONFIG = {
|
|
|
461
420
|
version: process.env.ACC_APP_VERSION || ''
|
|
462
421
|
};
|
|
463
422
|
|
|
464
|
-
// Use global to persist across module reloads in development
|
|
465
423
|
const globalForRedis = global;
|
|
466
424
|
if (!globalForRedis.redisClient) {
|
|
467
425
|
globalForRedis.redisClient = null;
|
|
@@ -470,7 +428,6 @@ if (!globalForRedis.redisClient) {
|
|
|
470
428
|
globalForRedis.connectionAttempts = 0;
|
|
471
429
|
}
|
|
472
430
|
|
|
473
|
-
// Logging configuration
|
|
474
431
|
const debugValue = process.env.NEXT_PRIVATE_DEBUG_CACHE;
|
|
475
432
|
const debug = debugValue === 'true' || debugValue === '1';
|
|
476
433
|
|
|
@@ -483,19 +440,16 @@ if (debug) {
|
|
|
483
440
|
}
|
|
484
441
|
|
|
485
442
|
async function getRedisClient() {
|
|
486
|
-
// If client exists and is ready, return it
|
|
487
443
|
if (globalForRedis.redisClient?.isReady) {
|
|
488
444
|
console_log('Reusing existing Redis connection');
|
|
489
445
|
return globalForRedis.redisClient;
|
|
490
446
|
}
|
|
491
447
|
|
|
492
|
-
// If we're already connecting, wait a bit and retry
|
|
493
448
|
if (globalForRedis.isConnecting) {
|
|
494
449
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
495
450
|
return getRedisClient();
|
|
496
451
|
}
|
|
497
452
|
|
|
498
|
-
// Start new connection
|
|
499
453
|
globalForRedis.isConnecting = true;
|
|
500
454
|
globalForRedis.connectionAttempts++;
|
|
501
455
|
|
|
@@ -526,7 +480,6 @@ async function getRedisClient() {
|
|
|
526
480
|
});
|
|
527
481
|
|
|
528
482
|
redisClient.on('error', (error) => {
|
|
529
|
-
// Only log the first connection error to avoid spam
|
|
530
483
|
if (!globalForRedis.hasLoggedConnectionError) {
|
|
531
484
|
if (error.code === 'ECONNREFUSED') {
|
|
532
485
|
console.warn(
|
|
@@ -581,7 +534,6 @@ CacheHandler.onCreation(async () => {
|
|
|
581
534
|
'Starting cache handler initialization...'
|
|
582
535
|
);
|
|
583
536
|
|
|
584
|
-
// Force log to appear even if debug is off
|
|
585
537
|
colorLog(
|
|
586
538
|
colors.green,
|
|
587
539
|
'[Cache Handler] 🚀 ',
|
|
@@ -616,7 +568,6 @@ CacheHandler.onCreation(async () => {
|
|
|
616
568
|
'Redis client connected successfully'
|
|
617
569
|
);
|
|
618
570
|
} catch (error) {
|
|
619
|
-
// Error already logged in getRedisClient, just return local handler
|
|
620
571
|
colorLog(
|
|
621
572
|
colors.red,
|
|
622
573
|
'[Cache Handler] ❌ ',
|
|
@@ -630,7 +581,7 @@ CacheHandler.onCreation(async () => {
|
|
|
630
581
|
const redisHandler = createRedisHandler({
|
|
631
582
|
client,
|
|
632
583
|
timeoutMs: CACHE_CONFIG.redis.timeoutMs,
|
|
633
|
-
keyExpirationStrategy: 'EXPIREAT'
|
|
584
|
+
keyExpirationStrategy: 'EXPIREAT'
|
|
634
585
|
});
|
|
635
586
|
|
|
636
587
|
const localHandler = createLruHandler(CACHE_CONFIG.lru);
|
|
@@ -638,32 +589,27 @@ CacheHandler.onCreation(async () => {
|
|
|
638
589
|
const CACHE_VERSION = 'v2';
|
|
639
590
|
const versionPrefix = `${CACHE_VERSION}_`;
|
|
640
591
|
|
|
641
|
-
// Create optimized functions for each scenario
|
|
642
592
|
const versionKeyString = (key) => `${versionPrefix}${key}`;
|
|
643
593
|
const versionKeyObject = (key) => ({
|
|
644
594
|
...key,
|
|
645
595
|
key: `${versionPrefix}${key.key}`
|
|
646
596
|
});
|
|
647
597
|
|
|
648
|
-
// Main version key function that routes to optimized paths
|
|
649
598
|
const versionKey = (key) => {
|
|
650
599
|
return typeof key === 'string'
|
|
651
600
|
? versionKeyString(key)
|
|
652
601
|
: versionKeyObject(key);
|
|
653
602
|
};
|
|
654
603
|
|
|
655
|
-
// Create a custom handler that checks local first, then Redis
|
|
656
604
|
const customHandler = {
|
|
657
605
|
name: 'custom-local-then-redis',
|
|
658
606
|
get: async (key, context) => {
|
|
659
607
|
const vKey = versionKey(key);
|
|
660
608
|
const keyStr = typeof vKey === 'string' ? vKey : vKey?.key;
|
|
661
609
|
|
|
662
|
-
// Force log GET calls to see if handler is being used
|
|
663
610
|
colorLog(colors.white, '[Cache Handler] 🔍 ', `CACHE GET: ${keyStr}`);
|
|
664
611
|
console_log('GET called for key:', keyStr);
|
|
665
612
|
|
|
666
|
-
// Extract and log URL information for better debugging
|
|
667
613
|
if (keyStr) {
|
|
668
614
|
const urlMatch = keyStr.match(/https?%3A%2F%2F[^_]+|\/[^_]*$/);
|
|
669
615
|
const extractedUrl = urlMatch
|
|
@@ -676,13 +622,11 @@ CacheHandler.onCreation(async () => {
|
|
|
676
622
|
);
|
|
677
623
|
}
|
|
678
624
|
|
|
679
|
-
// Check local cache first
|
|
680
625
|
console_log('Checking local cache...');
|
|
681
626
|
const localResult = await localHandler.get(vKey, context);
|
|
682
627
|
|
|
683
628
|
if (localResult) {
|
|
684
629
|
console_log('Found in local cache');
|
|
685
|
-
// Check if it's compressed data (new format or legacy format)
|
|
686
630
|
if (
|
|
687
631
|
localResult &&
|
|
688
632
|
typeof localResult === 'object' &&
|
|
@@ -692,7 +636,6 @@ CacheHandler.onCreation(async () => {
|
|
|
692
636
|
) {
|
|
693
637
|
try {
|
|
694
638
|
const decompressed = await decompressValue(localResult);
|
|
695
|
-
// If it's a string, parse it; otherwise return as-is (it's already parsed)
|
|
696
639
|
return typeof decompressed === 'string'
|
|
697
640
|
? JSON.parse(decompressed)
|
|
698
641
|
: decompressed;
|
|
@@ -705,7 +648,6 @@ CacheHandler.onCreation(async () => {
|
|
|
705
648
|
}
|
|
706
649
|
}
|
|
707
650
|
|
|
708
|
-
// Debug info for non-compressed values
|
|
709
651
|
console_log('🔍 Local result type:', typeof localResult);
|
|
710
652
|
if (
|
|
711
653
|
localResult &&
|
|
@@ -726,7 +668,6 @@ CacheHandler.onCreation(async () => {
|
|
|
726
668
|
|
|
727
669
|
let finalResult = redisResult;
|
|
728
670
|
|
|
729
|
-
// Parse JSON if it's a string (Redis stores as JSON)
|
|
730
671
|
if (typeof redisResult === 'string') {
|
|
731
672
|
try {
|
|
732
673
|
finalResult = JSON.parse(redisResult);
|
|
@@ -740,7 +681,6 @@ CacheHandler.onCreation(async () => {
|
|
|
740
681
|
}
|
|
741
682
|
}
|
|
742
683
|
|
|
743
|
-
// Check if it's compressed data and decompress (new format or legacy format)
|
|
744
684
|
if (
|
|
745
685
|
finalResult &&
|
|
746
686
|
typeof finalResult === 'object' &&
|
|
@@ -750,7 +690,6 @@ CacheHandler.onCreation(async () => {
|
|
|
750
690
|
) {
|
|
751
691
|
try {
|
|
752
692
|
const decompressed = await decompressValue(finalResult);
|
|
753
|
-
// If it's a string, parse it; otherwise return as-is (it's already parsed)
|
|
754
693
|
finalResult =
|
|
755
694
|
typeof decompressed === 'string'
|
|
756
695
|
? JSON.parse(decompressed)
|
|
@@ -760,11 +699,9 @@ CacheHandler.onCreation(async () => {
|
|
|
760
699
|
'[Cache Handler] Failed to decompress Redis cache value:',
|
|
761
700
|
error.message
|
|
762
701
|
);
|
|
763
|
-
// finalResult stays as is
|
|
764
702
|
}
|
|
765
703
|
}
|
|
766
704
|
|
|
767
|
-
// Debug Redis result
|
|
768
705
|
console_log('🔍 Redis result type:', typeof redisResult);
|
|
769
706
|
console_log('🔍 Final result type:', typeof finalResult);
|
|
770
707
|
if (
|
|
@@ -775,7 +712,6 @@ CacheHandler.onCreation(async () => {
|
|
|
775
712
|
console_log('🔍 Final result keys:', Object.keys(finalResult));
|
|
776
713
|
}
|
|
777
714
|
|
|
778
|
-
// Sync back to local cache for faster future access
|
|
779
715
|
try {
|
|
780
716
|
await localHandler.set(vKey, finalResult, context);
|
|
781
717
|
console_log('Synced to local cache');
|
|
@@ -795,11 +731,9 @@ CacheHandler.onCreation(async () => {
|
|
|
795
731
|
const vKey = versionKey(key);
|
|
796
732
|
const keyStr = typeof vKey === 'string' ? vKey : vKey?.key;
|
|
797
733
|
|
|
798
|
-
// Force log SET calls to see if handler is being used
|
|
799
734
|
colorLog(colors.white, '[Cache Handler] 💾 ', `CACHE SET: ${keyStr}`);
|
|
800
735
|
console_log('SET called for key:', keyStr);
|
|
801
736
|
|
|
802
|
-
// Extract and log URL information for better debugging
|
|
803
737
|
if (keyStr) {
|
|
804
738
|
const urlMatch = keyStr.match(/https?%3A%2F%2F[^_]+|\/[^_]*$/);
|
|
805
739
|
const extractedUrl = urlMatch
|
|
@@ -812,7 +746,6 @@ CacheHandler.onCreation(async () => {
|
|
|
812
746
|
);
|
|
813
747
|
}
|
|
814
748
|
|
|
815
|
-
// Log context and value information for debugging
|
|
816
749
|
console_log('SET Debug Info:', {
|
|
817
750
|
contextKeys: context ? Object.keys(context) : 'no context',
|
|
818
751
|
valueKeys:
|
|
@@ -834,7 +767,6 @@ CacheHandler.onCreation(async () => {
|
|
|
834
767
|
: 'unknown'
|
|
835
768
|
});
|
|
836
769
|
|
|
837
|
-
// Debug original value TTL before any processing
|
|
838
770
|
colorLog(
|
|
839
771
|
colors.yellow,
|
|
840
772
|
'[Cache Handler] 🔍 ',
|
|
@@ -850,7 +782,6 @@ CacheHandler.onCreation(async () => {
|
|
|
850
782
|
}
|
|
851
783
|
);
|
|
852
784
|
|
|
853
|
-
// Try to compress the value first
|
|
854
785
|
let compressedValue;
|
|
855
786
|
let shouldUseCompressed = false;
|
|
856
787
|
|
|
@@ -862,7 +793,6 @@ CacheHandler.onCreation(async () => {
|
|
|
862
793
|
);
|
|
863
794
|
compressedValue = await compressValue(value);
|
|
864
795
|
|
|
865
|
-
// Debug compressed value TTL after compression
|
|
866
796
|
colorLog(
|
|
867
797
|
colors.yellow,
|
|
868
798
|
'[Cache Handler] 🔍 ',
|
|
@@ -878,7 +808,6 @@ CacheHandler.onCreation(async () => {
|
|
|
878
808
|
}
|
|
879
809
|
);
|
|
880
810
|
|
|
881
|
-
// Check if compression actually happened (compressValue returns original if no compression)
|
|
882
811
|
shouldUseCompressed =
|
|
883
812
|
compressedValue !== value &&
|
|
884
813
|
(compressedValue?.__compressed ||
|
|
@@ -905,14 +834,9 @@ CacheHandler.onCreation(async () => {
|
|
|
905
834
|
shouldUseCompressed = false;
|
|
906
835
|
}
|
|
907
836
|
|
|
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
837
|
let redisSetResult;
|
|
913
838
|
|
|
914
839
|
if (shouldUseCompressed) {
|
|
915
|
-
// Log before attempting Redis SET with compression
|
|
916
840
|
colorLog(
|
|
917
841
|
colors.blue,
|
|
918
842
|
'[Cache Handler] 💾 ',
|
|
@@ -920,7 +844,6 @@ CacheHandler.onCreation(async () => {
|
|
|
920
844
|
);
|
|
921
845
|
|
|
922
846
|
try {
|
|
923
|
-
// Debug what we're sending to Redis
|
|
924
847
|
colorLog(
|
|
925
848
|
colors.yellow,
|
|
926
849
|
'[Cache Handler] 🔍 ',
|
|
@@ -950,9 +873,7 @@ CacheHandler.onCreation(async () => {
|
|
|
950
873
|
compressionError.message
|
|
951
874
|
);
|
|
952
875
|
|
|
953
|
-
// Fallback to original uncompressed value
|
|
954
876
|
try {
|
|
955
|
-
// Debug what we're sending to Redis as fallback
|
|
956
877
|
colorLog(
|
|
957
878
|
colors.yellow,
|
|
958
879
|
'[Cache Handler] 🔍 ',
|
|
@@ -985,7 +906,6 @@ CacheHandler.onCreation(async () => {
|
|
|
985
906
|
}
|
|
986
907
|
}
|
|
987
908
|
} else {
|
|
988
|
-
// No compression, use original value directly
|
|
989
909
|
colorLog(
|
|
990
910
|
colors.blue,
|
|
991
911
|
'[Cache Handler] 💾 ',
|
|
@@ -993,7 +913,6 @@ CacheHandler.onCreation(async () => {
|
|
|
993
913
|
);
|
|
994
914
|
|
|
995
915
|
try {
|
|
996
|
-
// Debug what we're sending to Redis (no compression)
|
|
997
916
|
colorLog(
|
|
998
917
|
colors.yellow,
|
|
999
918
|
'[Cache Handler] 🔍 ',
|
|
@@ -1026,7 +945,6 @@ CacheHandler.onCreation(async () => {
|
|
|
1026
945
|
}
|
|
1027
946
|
}
|
|
1028
947
|
|
|
1029
|
-
// Always set to local cache with original value
|
|
1030
948
|
let localSetResult;
|
|
1031
949
|
try {
|
|
1032
950
|
await localHandler.set(vKey, value, context);
|
|
@@ -1037,7 +955,6 @@ CacheHandler.onCreation(async () => {
|
|
|
1037
955
|
|
|
1038
956
|
const results = [localSetResult, redisSetResult];
|
|
1039
957
|
|
|
1040
|
-
// Log results
|
|
1041
958
|
console_log('SET Results:', {
|
|
1042
959
|
local: results[0].status,
|
|
1043
960
|
redis: results[1].status,
|
package/lib/cache.ts
CHANGED
|
@@ -5,124 +5,62 @@ import { CacheOptions } from '../types';
|
|
|
5
5
|
import logger from '../utils/log';
|
|
6
6
|
const CACHE_VERSION = 'v2';
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
};
|
|
8
|
+
const compressData = async (data: string): Promise<Uint8Array> => {
|
|
9
|
+
const stream = new CompressionStream('gzip');
|
|
10
|
+
const writer = stream.writable.getWriter();
|
|
11
|
+
const reader = stream.readable.getReader();
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.log(`${colors.bright}${color}${prefix}${colors.reset}`, ...args);
|
|
23
|
-
};
|
|
13
|
+
writer.write(new TextEncoder().encode(data));
|
|
14
|
+
writer.close();
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'[Cache] 🚀 ',
|
|
28
|
-
'Web API compression initialized successfully at module load'
|
|
29
|
-
);
|
|
30
|
-
logger.debug('Web API compression initialized successfully at module load');
|
|
31
|
-
|
|
32
|
-
const extractUrlFromKey = (key: string): string => {
|
|
33
|
-
try {
|
|
34
|
-
const decoded = decodeURIComponent(key);
|
|
35
|
-
const urlMatch =
|
|
36
|
-
decoded.match(/https?:\/\/[^\s]+/) || decoded.match(/\/[^\s]*/);
|
|
37
|
-
return urlMatch ? urlMatch[0] : 'unknown';
|
|
38
|
-
} catch {
|
|
39
|
-
return 'unknown';
|
|
40
|
-
}
|
|
41
|
-
};
|
|
16
|
+
const chunks: Uint8Array[] = [];
|
|
17
|
+
let done = false;
|
|
42
18
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const writer = stream.writable.getWriter();
|
|
49
|
-
const reader = stream.readable.getReader();
|
|
50
|
-
|
|
51
|
-
writer.write(new TextEncoder().encode(data));
|
|
52
|
-
writer.close();
|
|
53
|
-
|
|
54
|
-
const chunks: Uint8Array[] = [];
|
|
55
|
-
let done = false;
|
|
56
|
-
|
|
57
|
-
while (!done) {
|
|
58
|
-
const { value, done: readerDone } = await reader.read();
|
|
59
|
-
done = readerDone;
|
|
60
|
-
if (value) chunks.push(value);
|
|
61
|
-
}
|
|
19
|
+
while (!done) {
|
|
20
|
+
const { value, done: readerDone } = await reader.read();
|
|
21
|
+
done = readerDone;
|
|
22
|
+
if (value) chunks.push(value);
|
|
23
|
+
}
|
|
62
24
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
25
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
26
|
+
const result = new Uint8Array(totalLength);
|
|
27
|
+
let offset = 0;
|
|
66
28
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return result;
|
|
73
|
-
} else {
|
|
74
|
-
// Node.js fallback (server runtime)
|
|
75
|
-
const { deflate } = await import('zlib');
|
|
76
|
-
const { promisify } = await import('util');
|
|
77
|
-
const deflateAsync = promisify(deflate);
|
|
78
|
-
const inputBuffer = Buffer.from(data, 'utf8');
|
|
79
|
-
const compressed = await deflateAsync(inputBuffer);
|
|
80
|
-
return new Uint8Array(
|
|
81
|
-
compressed.buffer,
|
|
82
|
-
compressed.byteOffset,
|
|
83
|
-
compressed.byteLength
|
|
84
|
-
);
|
|
29
|
+
for (const chunk of chunks) {
|
|
30
|
+
result.set(chunk, offset);
|
|
31
|
+
offset += chunk.length;
|
|
85
32
|
}
|
|
33
|
+
|
|
34
|
+
return result;
|
|
86
35
|
};
|
|
87
36
|
|
|
88
|
-
// Web API decompression for edge runtime compatibility
|
|
89
37
|
const decompressData = async (compressed: Uint8Array): Promise<string> => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const writer = stream.writable.getWriter();
|
|
94
|
-
const reader = stream.readable.getReader();
|
|
95
|
-
|
|
96
|
-
writer.write(compressed);
|
|
97
|
-
writer.close();
|
|
98
|
-
|
|
99
|
-
const chunks: Uint8Array[] = [];
|
|
100
|
-
let done = false;
|
|
101
|
-
|
|
102
|
-
while (!done) {
|
|
103
|
-
const { value, done: readerDone } = await reader.read();
|
|
104
|
-
done = readerDone;
|
|
105
|
-
if (value) chunks.push(value);
|
|
106
|
-
}
|
|
38
|
+
const stream = new DecompressionStream('gzip');
|
|
39
|
+
const writer = stream.writable.getWriter();
|
|
40
|
+
const reader = stream.readable.getReader();
|
|
107
41
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let offset = 0;
|
|
42
|
+
writer.write(compressed);
|
|
43
|
+
writer.close();
|
|
111
44
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
45
|
+
const chunks: Uint8Array[] = [];
|
|
46
|
+
let done = false;
|
|
47
|
+
|
|
48
|
+
while (!done) {
|
|
49
|
+
const { value, done: readerDone } = await reader.read();
|
|
50
|
+
done = readerDone;
|
|
51
|
+
if (value) chunks.push(value);
|
|
52
|
+
}
|
|
116
53
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return decompressed.toString('utf8');
|
|
54
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
55
|
+
const result = new Uint8Array(totalLength);
|
|
56
|
+
let offset = 0;
|
|
57
|
+
|
|
58
|
+
for (const chunk of chunks) {
|
|
59
|
+
result.set(chunk, offset);
|
|
60
|
+
offset += chunk.length;
|
|
125
61
|
}
|
|
62
|
+
|
|
63
|
+
return new TextDecoder().decode(result);
|
|
126
64
|
};
|
|
127
65
|
|
|
128
66
|
const hashCacheKey = (object?: Record<string, string>) => {
|
|
@@ -255,9 +193,7 @@ export class Cache {
|
|
|
255
193
|
} else {
|
|
256
194
|
value = null;
|
|
257
195
|
}
|
|
258
|
-
logger.debug('Redis get success', { key, value });
|
|
259
196
|
} catch (error) {
|
|
260
|
-
logger.error('Redis get error', { key, error });
|
|
261
197
|
value = null;
|
|
262
198
|
} finally {
|
|
263
199
|
if (client) {
|
|
@@ -283,9 +219,7 @@ export class Cache {
|
|
|
283
219
|
}
|
|
284
220
|
|
|
285
221
|
success = true;
|
|
286
|
-
logger.debug('Redis set success', { key, value });
|
|
287
222
|
} catch (error) {
|
|
288
|
-
logger.error('Redis set error', { key, error });
|
|
289
223
|
success = false;
|
|
290
224
|
} finally {
|
|
291
225
|
if (client) {
|
|
@@ -325,8 +259,6 @@ export class Cache {
|
|
|
325
259
|
_options.expire = 120;
|
|
326
260
|
}
|
|
327
261
|
|
|
328
|
-
logger.debug('Cache wrap', { key, formattedKey, _options });
|
|
329
|
-
|
|
330
262
|
if (_options.cache) {
|
|
331
263
|
let cachedValue: any;
|
|
332
264
|
|
|
@@ -339,8 +271,6 @@ export class Cache {
|
|
|
339
271
|
}
|
|
340
272
|
|
|
341
273
|
cachedValue = await Cache.proxyRequest('POST', body);
|
|
342
|
-
logger.debug('Cache proxy request success', { key });
|
|
343
|
-
logger.trace('Cache proxy request', { key, cachedValue });
|
|
344
274
|
} else {
|
|
345
275
|
cachedValue = _options.compressed
|
|
346
276
|
? await Cache.getCompressed(formattedKey)
|
|
@@ -352,8 +282,6 @@ export class Cache {
|
|
|
352
282
|
}
|
|
353
283
|
}
|
|
354
284
|
|
|
355
|
-
logger.debug('Redis cache miss. Setting new value...', { key });
|
|
356
|
-
|
|
357
285
|
const data = await handler();
|
|
358
286
|
|
|
359
287
|
if (data && _options.cache) {
|
|
@@ -371,8 +299,6 @@ export class Cache {
|
|
|
371
299
|
body.append('compressed', 'true');
|
|
372
300
|
}
|
|
373
301
|
await Cache.proxyRequest('PUT', body);
|
|
374
|
-
|
|
375
|
-
logger.debug('Cache proxy request', { key, body: body.toString() });
|
|
376
302
|
} catch (error) {
|
|
377
303
|
logger.error('Cache proxy error', error);
|
|
378
304
|
}
|
|
@@ -440,26 +366,11 @@ export class Cache {
|
|
|
440
366
|
results?.filter((result) => result instanceof Error) || [];
|
|
441
367
|
|
|
442
368
|
if (failures.length > 0) {
|
|
443
|
-
logger.warn('Some mset operations failed', {
|
|
444
|
-
totalOperations: results?.length || 0,
|
|
445
|
-
failures: failures.length,
|
|
446
|
-
keys: Object.keys(keyValuePairs)
|
|
447
|
-
});
|
|
448
369
|
success = false;
|
|
449
370
|
} else {
|
|
450
371
|
success = true;
|
|
451
|
-
|
|
452
|
-
logger.debug('Redis mset success', {
|
|
453
|
-
keys: Object.keys(keyValuePairs),
|
|
454
|
-
expire,
|
|
455
|
-
operationsCount: Object.keys(keyValuePairs).length
|
|
456
|
-
});
|
|
457
372
|
}
|
|
458
373
|
} catch (error) {
|
|
459
|
-
logger.error('Redis mset error', {
|
|
460
|
-
keys: Object.keys(keyValuePairs),
|
|
461
|
-
error
|
|
462
|
-
});
|
|
463
374
|
success = false;
|
|
464
375
|
} finally {
|
|
465
376
|
if (client) {
|
|
@@ -476,7 +387,6 @@ export class Cache {
|
|
|
476
387
|
expire?: number
|
|
477
388
|
): Promise<boolean> {
|
|
478
389
|
if (!Cache.validateKey(key)) {
|
|
479
|
-
logger.error('Invalid key in setCompressed', { key });
|
|
480
390
|
return false;
|
|
481
391
|
}
|
|
482
392
|
|
|
@@ -487,27 +397,7 @@ export class Cache {
|
|
|
487
397
|
client = await Cache.getClient();
|
|
488
398
|
const serializedValue = Cache.serializeValue(value);
|
|
489
399
|
|
|
490
|
-
if (serializedValue.length > 1024 * 1024) {
|
|
491
|
-
logger.warn('Large data being compressed', {
|
|
492
|
-
key,
|
|
493
|
-
size: serializedValue.length
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const url = extractUrlFromKey(key);
|
|
498
|
-
|
|
499
|
-
logger.debug('setCompressed called', {
|
|
500
|
-
key,
|
|
501
|
-
url,
|
|
502
|
-
dataSize: serializedValue.length
|
|
503
|
-
});
|
|
504
|
-
|
|
505
400
|
try {
|
|
506
|
-
colorLog(
|
|
507
|
-
colors.magenta,
|
|
508
|
-
'[Cache] 🟣 ',
|
|
509
|
-
`Using GZIP compression for: ${url}`
|
|
510
|
-
);
|
|
511
401
|
const compressed = await compressData(serializedValue);
|
|
512
402
|
const compressedBase64 = Buffer.from(compressed).toString('base64');
|
|
513
403
|
|
|
@@ -518,35 +408,7 @@ export class Cache {
|
|
|
518
408
|
}
|
|
519
409
|
|
|
520
410
|
success = true;
|
|
521
|
-
const compressionRatio = (
|
|
522
|
-
(1 - compressed.length / serializedValue.length) *
|
|
523
|
-
100
|
|
524
|
-
).toFixed(1);
|
|
525
|
-
colorLog(
|
|
526
|
-
colors.magenta,
|
|
527
|
-
'[Cache] 🟣 ',
|
|
528
|
-
`Compressed ${serializedValue.length} → ${compressed.length} bytes (${compressionRatio}% reduction) for: ${url}`
|
|
529
|
-
);
|
|
530
|
-
logger.debug('Redis setCompressed success', {
|
|
531
|
-
key,
|
|
532
|
-
url,
|
|
533
|
-
originalSize: serializedValue.length,
|
|
534
|
-
compressedSize: compressed.length,
|
|
535
|
-
compressionRatio: `${compressionRatio}%`,
|
|
536
|
-
method: 'gzip'
|
|
537
|
-
});
|
|
538
411
|
} catch (compressionError) {
|
|
539
|
-
colorLog(
|
|
540
|
-
colors.yellow,
|
|
541
|
-
'[Cache] ⚠️ ',
|
|
542
|
-
`Compression failed, storing uncompressed for: ${url}`
|
|
543
|
-
);
|
|
544
|
-
logger.warn('Compression failed, storing uncompressed', {
|
|
545
|
-
key,
|
|
546
|
-
url,
|
|
547
|
-
error: compressionError
|
|
548
|
-
});
|
|
549
|
-
|
|
550
412
|
if (expire) {
|
|
551
413
|
await client.set(key, serializedValue, { EX: expire });
|
|
552
414
|
} else {
|
|
@@ -554,14 +416,8 @@ export class Cache {
|
|
|
554
416
|
}
|
|
555
417
|
|
|
556
418
|
success = true;
|
|
557
|
-
logger.debug('Redis set success (uncompressed)', {
|
|
558
|
-
key,
|
|
559
|
-
url,
|
|
560
|
-
size: serializedValue.length
|
|
561
|
-
});
|
|
562
419
|
}
|
|
563
420
|
} catch (error) {
|
|
564
|
-
logger.error('Redis setCompressed error', { key, error });
|
|
565
421
|
success = false;
|
|
566
422
|
} finally {
|
|
567
423
|
if (client) {
|
|
@@ -572,12 +428,8 @@ export class Cache {
|
|
|
572
428
|
return success;
|
|
573
429
|
}
|
|
574
430
|
|
|
575
|
-
static async getCompressed(
|
|
576
|
-
key: string,
|
|
577
|
-
maxDecompressedSize: number = 10 * 1024 * 1024
|
|
578
|
-
): Promise<unknown> {
|
|
431
|
+
static async getCompressed(key: string): Promise<unknown> {
|
|
579
432
|
if (!Cache.validateKey(key)) {
|
|
580
|
-
logger.error('Invalid key in getCompressed', { key });
|
|
581
433
|
return null;
|
|
582
434
|
}
|
|
583
435
|
|
|
@@ -591,67 +443,25 @@ export class Cache {
|
|
|
591
443
|
if (compressed) {
|
|
592
444
|
const compressedBuffer = Buffer.from(compressed, 'base64');
|
|
593
445
|
|
|
594
|
-
const url = extractUrlFromKey(key);
|
|
595
|
-
|
|
596
446
|
try {
|
|
597
|
-
colorLog(
|
|
598
|
-
colors.magenta,
|
|
599
|
-
'[Cache] 🟣 ',
|
|
600
|
-
`Using GZIP decompression for: ${url}`
|
|
601
|
-
);
|
|
602
447
|
const decompressedString = await decompressData(
|
|
603
448
|
new Uint8Array(compressedBuffer)
|
|
604
449
|
);
|
|
605
450
|
value = JSON.parse(decompressedString);
|
|
606
|
-
colorLog(
|
|
607
|
-
colors.magenta,
|
|
608
|
-
'[Cache] 🟣 ',
|
|
609
|
-
`Decompressed ${compressedBuffer.length} → ${decompressedString.length} bytes for: ${url}`
|
|
610
|
-
);
|
|
611
|
-
logger.debug('Redis getCompressed success', {
|
|
612
|
-
key,
|
|
613
|
-
url,
|
|
614
|
-
compressedSize: compressedBuffer.length,
|
|
615
|
-
decompressedSize: decompressedString.length,
|
|
616
|
-
method: 'gzip'
|
|
617
|
-
});
|
|
618
451
|
return value;
|
|
619
452
|
} catch (decompressionError) {
|
|
620
|
-
colorLog(
|
|
621
|
-
colors.yellow,
|
|
622
|
-
'[Cache] ⚠️ ',
|
|
623
|
-
`Decompression failed, trying uncompressed for: ${url}`
|
|
624
|
-
);
|
|
625
|
-
logger.debug('Decompression failed, trying direct JSON parse', {
|
|
626
|
-
key,
|
|
627
|
-
url,
|
|
628
|
-
error: decompressionError
|
|
629
|
-
});
|
|
630
|
-
|
|
631
453
|
try {
|
|
632
454
|
const rawString = compressed;
|
|
633
455
|
const parsedData = JSON.parse(rawString);
|
|
634
|
-
logger.debug('Data read as uncompressed', {
|
|
635
|
-
key,
|
|
636
|
-
url
|
|
637
|
-
});
|
|
638
456
|
return parsedData;
|
|
639
457
|
} catch (jsonError) {
|
|
640
|
-
logger.error('Failed to parse data in all formats', {
|
|
641
|
-
key,
|
|
642
|
-
url,
|
|
643
|
-
decompressionError,
|
|
644
|
-
jsonError
|
|
645
|
-
});
|
|
646
458
|
return null;
|
|
647
459
|
}
|
|
648
460
|
}
|
|
649
461
|
} else {
|
|
650
462
|
value = null;
|
|
651
|
-
logger.debug('Redis getCompressed: key not found', { key });
|
|
652
463
|
}
|
|
653
464
|
} catch (error) {
|
|
654
|
-
logger.error('Redis getCompressed error', { key, error });
|
|
655
465
|
value = null;
|
|
656
466
|
} finally {
|
|
657
467
|
if (client) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.93.0-snapshot-ZERO-3586-
|
|
4
|
+
"version": "1.93.0-snapshot-ZERO-3586-20250901110545",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"set-cookie-parser": "2.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.93.0-snapshot-ZERO-3586-
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.93.0-snapshot-ZERO-3586-20250901110545",
|
|
39
39
|
"@babel/core": "7.26.10",
|
|
40
40
|
"@babel/preset-env": "7.26.9",
|
|
41
41
|
"@babel/preset-typescript": "7.27.0",
|