@akinon/next 1.96.0-snapshot-ZERO-35861-20250908151109 → 1.96.0-snapshot-ZERO-3620-20250915165755

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 (49) hide show
  1. package/CHANGELOG.md +1395 -45
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -0
  4. package/api/cache.ts +5 -39
  5. package/api/image-proxy.ts +75 -0
  6. package/api/similar-product-list.ts +84 -0
  7. package/api/similar-products.ts +120 -0
  8. package/components/accordion.tsx +20 -5
  9. package/components/file-input.tsx +65 -3
  10. package/components/input.tsx +2 -0
  11. package/components/link.tsx +16 -12
  12. package/components/modal.tsx +32 -16
  13. package/components/plugin-module.tsx +30 -3
  14. package/data/client/checkout.ts +5 -4
  15. package/data/server/basket.ts +72 -0
  16. package/data/server/category.ts +50 -32
  17. package/data/server/flatpage.ts +17 -16
  18. package/data/server/form.ts +1 -4
  19. package/data/server/landingpage.ts +16 -12
  20. package/data/server/list.ts +24 -15
  21. package/data/server/menu.ts +2 -5
  22. package/data/server/product.ts +67 -41
  23. package/data/server/special-page.ts +16 -12
  24. package/data/server/widget.ts +1 -4
  25. package/data/urls.ts +5 -1
  26. package/hocs/server/with-segment-defaults.tsx +5 -2
  27. package/hooks/use-localization.ts +2 -3
  28. package/jest.config.js +7 -1
  29. package/lib/cache-handler.mjs +87 -365
  30. package/lib/cache.ts +25 -252
  31. package/middlewares/complete-gpay.ts +2 -1
  32. package/middlewares/complete-masterpass.ts +2 -1
  33. package/middlewares/default.ts +50 -13
  34. package/middlewares/locale.ts +9 -1
  35. package/middlewares/pretty-url.ts +1 -2
  36. package/middlewares/redirection-payment.ts +2 -1
  37. package/middlewares/saved-card-redirection.ts +2 -1
  38. package/middlewares/three-d-redirection.ts +2 -1
  39. package/middlewares/url-redirection.ts +8 -14
  40. package/package.json +3 -4
  41. package/plugins.d.ts +8 -0
  42. package/plugins.js +3 -1
  43. package/redux/middlewares/checkout.ts +5 -1
  44. package/types/commerce/order.ts +1 -0
  45. package/types/index.ts +34 -2
  46. package/utils/app-fetch.ts +7 -2
  47. package/utils/redirect-ignore.ts +35 -0
  48. package/utils/redirect.ts +31 -6
  49. package/with-pz-config.js +1 -5
@@ -3,261 +3,7 @@ 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
5
 
6
- let zstd;
7
-
8
- (async () => {
9
- try {
10
- const { compress, decompress } = await import('@mongodb-js/zstd');
11
- zstd = { compress, decompress, type: 'native' };
12
- } catch (_) {
13
- zstd = false;
14
- }
15
- })();
16
-
17
- const getZstd = () => {
18
- return zstd;
19
- };
20
-
21
- const compressValue = async (value) => {
22
- try {
23
- if (value && typeof value === 'object' && value.value !== undefined) {
24
- const nestedValue = value.value;
25
- const serializedNestedValue =
26
- typeof nestedValue === 'string'
27
- ? nestedValue
28
- : JSON.stringify(nestedValue);
29
- const originalSize = Buffer.byteLength(serializedNestedValue, 'utf8');
30
-
31
- if (originalSize < 1024) {
32
- const result = {
33
- ...value,
34
- tags: Array.isArray(value.tags) ? value.tags : []
35
- };
36
- return result;
37
- }
38
-
39
- const zstdLib = getZstd();
40
- let compressed;
41
-
42
- if (zstdLib && zstdLib !== false) {
43
- const inputBuffer = Buffer.from(serializedNestedValue, 'utf8');
44
-
45
- if (
46
- typeof zstdLib.compress === 'function' &&
47
- zstdLib.compress.constructor.name === 'AsyncFunction'
48
- ) {
49
- compressed = await zstdLib.compress(inputBuffer, 3);
50
- } else {
51
- compressed = zstdLib.compress(inputBuffer, 3);
52
- }
53
- } else {
54
- return {
55
- ...value,
56
- tags: Array.isArray(value.tags) ? value.tags : []
57
- };
58
- }
59
-
60
- const compressedBase64 = Buffer.from(compressed).toString('base64');
61
-
62
- const result = {
63
- ...value,
64
- tags: Array.isArray(value.tags) ? value.tags : [],
65
- lifespan: {
66
- ...value.lifespan,
67
- expireAge: value.lifespan?.revalidate || value.lifespan?.expireAge,
68
- expireAt:
69
- value.lifespan?.lastModifiedAt && value.lifespan?.revalidate
70
- ? value.lifespan.lastModifiedAt + value.lifespan.revalidate
71
- : value.lifespan?.expireAt
72
- },
73
- value: {
74
- __compressed: true,
75
- __method: 'zstd',
76
- __originalSize: originalSize,
77
- __compressedSize: compressed.length,
78
- __data: compressedBase64
79
- }
80
- };
81
-
82
- return result;
83
- }
84
-
85
- const serializedValue =
86
- typeof value === 'string' ? value : JSON.stringify(value);
87
- const originalSize = Buffer.byteLength(serializedValue, 'utf8');
88
-
89
- if (originalSize < 1024) {
90
- if (
91
- value &&
92
- typeof value === 'object' &&
93
- value.lastModified === undefined &&
94
- value.lifespan === undefined &&
95
- value.value === undefined
96
- ) {
97
- return {
98
- ...value,
99
- tags: value.tags || [],
100
- lastModified: Date.now(),
101
- lifespan: {
102
- expireAt: Math.floor(Date.now() / 1000) + 3600
103
- }
104
- };
105
- }
106
- if (
107
- value &&
108
- typeof value === 'object' &&
109
- value.lifespan &&
110
- value.lifespan.revalidate
111
- ) {
112
- return {
113
- ...value,
114
- lifespan: {
115
- ...value.lifespan,
116
- expireAge: value.lifespan.revalidate,
117
- expireAt:
118
- value.lifespan.lastModifiedAt && value.lifespan.revalidate
119
- ? value.lifespan.lastModifiedAt + value.lifespan.revalidate
120
- : value.lifespan.expireAt
121
- }
122
- };
123
- }
124
- return value;
125
- }
126
-
127
- const zstdLib = getZstd();
128
- let compressed;
129
-
130
- if (zstdLib && zstdLib !== false) {
131
- const inputBuffer = Buffer.from(serializedValue, 'utf8');
132
-
133
- if (
134
- typeof zstdLib.compress === 'function' &&
135
- zstdLib.compress.constructor.name === 'AsyncFunction'
136
- ) {
137
- compressed = await zstdLib.compress(inputBuffer, 3);
138
- } else {
139
- compressed = zstdLib.compress(inputBuffer, 3);
140
- }
141
- } else {
142
- if (
143
- value &&
144
- typeof value === 'object' &&
145
- value.lastModified === undefined &&
146
- value.lifespan === undefined &&
147
- value.value === undefined
148
- ) {
149
- return {
150
- ...value,
151
- tags: value.tags || [],
152
- lastModified: Date.now(),
153
- lifespan: {
154
- expireAt: Math.floor(Date.now() / 1000) + 3600
155
- }
156
- };
157
- }
158
- return value;
159
- }
160
-
161
- const compressedBase64 = Buffer.from(compressed).toString('base64');
162
-
163
- const compressedResult = {
164
- __compressed: true,
165
- __method: 'zstd',
166
- __originalSize: originalSize,
167
- __compressedSize: compressed.length,
168
- __data: compressedBase64,
169
- tags: [],
170
- lastModified: Date.now(),
171
- lifespan: { expireAt: Math.floor(Date.now() / 1000) + 3600 }
172
- };
173
-
174
- return compressedResult;
175
- } catch (_) {
176
- return value;
177
- }
178
- };
179
-
180
- const decompressValue = async (compressedData) => {
181
- try {
182
- if (
183
- compressedData &&
184
- typeof compressedData === 'object' &&
185
- compressedData.value &&
186
- typeof compressedData.value === 'object' &&
187
- compressedData.value.__compressed
188
- ) {
189
- const compressedNestedValue = compressedData.value;
190
- const compressedBuffer = Buffer.from(
191
- compressedNestedValue.__data,
192
- 'base64'
193
- );
194
- let decompressed;
195
-
196
- if (compressedNestedValue.__method === 'zstd') {
197
- const zstdLib = getZstd();
198
- if (zstdLib && zstdLib !== false) {
199
- if (
200
- typeof zstdLib.decompress === 'function' &&
201
- zstdLib.decompress.constructor.name === 'AsyncFunction'
202
- ) {
203
- const decompressedBuffer = await zstdLib.decompress(
204
- compressedBuffer
205
- );
206
- decompressed = decompressedBuffer.toString('utf8');
207
- } else {
208
- decompressed = zstdLib
209
- .decompress(compressedBuffer)
210
- .toString('utf8');
211
- }
212
- } else {
213
- throw new Error('zstd not available for decompression');
214
- }
215
- }
216
-
217
- return {
218
- ...compressedData,
219
- value: JSON.parse(decompressed)
220
- };
221
- }
222
-
223
- if (
224
- compressedData &&
225
- typeof compressedData === 'object' &&
226
- compressedData.__compressed
227
- ) {
228
- const compressedBuffer = Buffer.from(compressedData.__data, 'base64');
229
- let decompressed;
230
-
231
- if (compressedData.__method === 'zstd') {
232
- const zstdLib = getZstd();
233
- if (zstdLib && zstdLib !== false) {
234
- if (
235
- typeof zstdLib.decompress === 'function' &&
236
- zstdLib.decompress.constructor.name === 'AsyncFunction'
237
- ) {
238
- const decompressedBuffer = await zstdLib.decompress(
239
- compressedBuffer
240
- );
241
- decompressed = decompressedBuffer.toString('utf8');
242
- } else {
243
- decompressed = zstdLib
244
- .decompress(compressedBuffer)
245
- .toString('utf8');
246
- }
247
- } else {
248
- throw new Error('zstd not available for decompression');
249
- }
250
- }
251
-
252
- return JSON.parse(decompressed);
253
- }
254
-
255
- return compressedData;
256
- } catch (error) {
257
- return compressedData;
258
- }
259
- };
260
-
6
+ // Cache configuration
261
7
  const CACHE_CONFIG = {
262
8
  lru: {
263
9
  maxItemCount: 2000
@@ -276,6 +22,7 @@ const CACHE_CONFIG = {
276
22
  version: process.env.ACC_APP_VERSION || ''
277
23
  };
278
24
 
25
+ // Use global to persist across module reloads in development
279
26
  const globalForRedis = global;
280
27
  if (!globalForRedis.redisClient) {
281
28
  globalForRedis.redisClient = null;
@@ -284,22 +31,42 @@ if (!globalForRedis.redisClient) {
284
31
  globalForRedis.connectionAttempts = 0;
285
32
  }
286
33
 
34
+ // Logging configuration
35
+ const debugValue = process.env.NEXT_PRIVATE_DEBUG_CACHE;
36
+ const debug = debugValue === 'true' || debugValue === '1';
37
+
38
+ let console_log;
39
+ if (debug) {
40
+ // eslint-disable-next-line no-console
41
+ console_log = (...args) => console.log('[Cache Handler]', ...args);
42
+ } else {
43
+ console_log = () => {};
44
+ }
45
+
287
46
  async function getRedisClient() {
47
+ // If client exists and is ready, return it
288
48
  if (globalForRedis.redisClient?.isReady) {
49
+ console_log('Reusing existing Redis connection');
289
50
  return globalForRedis.redisClient;
290
51
  }
291
52
 
53
+ // If we're already connecting, wait a bit and retry
292
54
  if (globalForRedis.isConnecting) {
293
55
  await new Promise((resolve) => setTimeout(resolve, 100));
294
56
  return getRedisClient();
295
57
  }
296
58
 
59
+ // Start new connection
297
60
  globalForRedis.isConnecting = true;
298
61
  globalForRedis.connectionAttempts++;
299
62
 
300
63
  try {
301
64
  const redisUrl = `redis://${CACHE_CONFIG.host}:${CACHE_CONFIG.port}/${CACHE_CONFIG.bucket}`;
302
65
 
66
+ if (globalForRedis.connectionAttempts === 1) {
67
+ console_log('Creating Redis connection:', redisUrl);
68
+ }
69
+
303
70
  const redisClient = createClient({
304
71
  url: redisUrl,
305
72
  socket: {
@@ -320,6 +87,7 @@ async function getRedisClient() {
320
87
  });
321
88
 
322
89
  redisClient.on('error', (error) => {
90
+ // Only log the first connection error to avoid spam
323
91
  if (!globalForRedis.hasLoggedConnectionError) {
324
92
  if (error.code === 'ECONNREFUSED') {
325
93
  console.warn(
@@ -333,10 +101,12 @@ async function getRedisClient() {
333
101
  });
334
102
 
335
103
  redisClient.on('connect', () => {
104
+ console_log('Redis connected');
336
105
  globalForRedis.hasLoggedConnectionError = false;
337
106
  });
338
107
 
339
108
  redisClient.on('ready', () => {
109
+ console_log('Redis ready');
340
110
  globalForRedis.hasLoggedConnectionError = false;
341
111
  });
342
112
 
@@ -345,6 +115,16 @@ async function getRedisClient() {
345
115
  return redisClient;
346
116
  } catch (error) {
347
117
  if (!globalForRedis.hasLoggedConnectionError) {
118
+ if (error.code === 'ECONNREFUSED') {
119
+ console.warn(
120
+ '[Cache Handler] Could not connect to Redis - using local cache only'
121
+ );
122
+ } else {
123
+ console.error(
124
+ '[Cache Handler] Failed to connect to Redis:',
125
+ error.message
126
+ );
127
+ }
348
128
  globalForRedis.hasLoggedConnectionError = true;
349
129
  }
350
130
  globalForRedis.redisClient = null;
@@ -355,10 +135,13 @@ async function getRedisClient() {
355
135
  }
356
136
 
357
137
  CacheHandler.onCreation(async () => {
138
+ console_log('Initializing cache handlers...');
139
+
358
140
  let client;
359
141
  try {
360
142
  client = await getRedisClient();
361
143
  } catch (error) {
144
+ // Error already logged in getRedisClient, just return local handler
362
145
  return {
363
146
  handlers: [createLruHandler(CACHE_CONFIG.lru)]
364
147
  };
@@ -367,161 +150,98 @@ CacheHandler.onCreation(async () => {
367
150
  const redisHandler = createRedisHandler({
368
151
  client,
369
152
  timeoutMs: CACHE_CONFIG.redis.timeoutMs,
370
- keyExpirationStrategy: 'EXPIREAT'
153
+ keyExpirationStrategy: 'EXAT'
371
154
  });
372
155
 
373
156
  const localHandler = createLruHandler(CACHE_CONFIG.lru);
374
157
 
375
- const CACHE_VERSION = 'v2';
376
- const versionPrefix = `${CACHE_VERSION}_`;
158
+ // Pre-compute version prefix if exists
159
+ const versionPrefix = CACHE_CONFIG.version ? `${CACHE_CONFIG.version}:` : '';
377
160
 
378
- const versionKeyString = (key) => `${versionPrefix}${key}`;
379
- const versionKeyObject = (key) => ({
380
- ...key,
381
- key: `${versionPrefix}${key.key}`
382
- });
161
+ // Create optimized functions for each scenario
162
+ const versionKeyString = versionPrefix
163
+ ? (key) => `${versionPrefix}${key}`
164
+ : (key) => key;
383
165
 
166
+ const versionKeyObject = versionPrefix
167
+ ? (key) => ({ ...key, key: `${versionPrefix}${key.key}` })
168
+ : (key) => key;
169
+
170
+ // Main version key function that routes to optimized paths
384
171
  const versionKey = (key) => {
385
172
  return typeof key === 'string'
386
173
  ? versionKeyString(key)
387
174
  : versionKeyObject(key);
388
175
  };
389
176
 
177
+ // Create a custom handler that checks local first, then Redis
390
178
  const customHandler = {
391
179
  name: 'custom-local-then-redis',
392
180
  get: async (key, context) => {
393
181
  const vKey = versionKey(key);
182
+ console_log(
183
+ 'GET called for key:',
184
+ typeof vKey === 'string' ? vKey : vKey?.key
185
+ );
394
186
 
187
+ // Check local cache first
188
+ console_log('Checking local cache...');
395
189
  const localResult = await localHandler.get(vKey, context);
396
190
 
397
191
  if (localResult) {
398
- if (
399
- localResult &&
400
- typeof localResult === 'object' &&
401
- (localResult.__compressed ||
402
- (localResult.value && localResult.value.__compressed) ||
403
- localResult.compressed !== undefined)
404
- ) {
405
- try {
406
- const decompressed = await decompressValue(localResult);
407
- return typeof decompressed === 'string'
408
- ? JSON.parse(decompressed)
409
- : decompressed;
410
- } catch (_) {
411
- return localResult;
412
- }
413
- }
414
-
192
+ console_log('Found in local cache');
415
193
  return localResult;
416
194
  }
417
195
 
196
+ console_log('Not found in local, checking Redis...');
418
197
  try {
419
198
  const redisResult = await redisHandler.get(vKey, context);
420
199
 
421
200
  if (redisResult) {
422
- let finalResult = redisResult;
423
-
424
- if (typeof redisResult === 'string') {
425
- try {
426
- finalResult = JSON.parse(redisResult);
427
- } catch (parseError) {
428
- finalResult = redisResult;
429
- }
430
- }
431
-
432
- if (
433
- finalResult &&
434
- typeof finalResult === 'object' &&
435
- (finalResult.__compressed ||
436
- (finalResult.value && finalResult.value.__compressed) ||
437
- finalResult.compressed !== undefined)
438
- ) {
439
- try {
440
- const decompressed = await decompressValue(finalResult);
441
- finalResult =
442
- typeof decompressed === 'string'
443
- ? JSON.parse(decompressed)
444
- : decompressed;
445
- } catch (_) {
446
- return finalResult;
447
- }
448
- }
449
-
201
+ console_log('Found in Redis');
202
+ // Sync back to local cache for faster future access
450
203
  try {
451
- await localHandler.set(vKey, finalResult, context);
452
- } catch (_) {
453
- return finalResult;
204
+ await localHandler.set(vKey, redisResult, context);
205
+ console_log('Synced to local cache');
206
+ } catch (error) {
207
+ console_log('Failed to sync to local:', error.message);
454
208
  }
455
- return finalResult;
209
+ return redisResult;
456
210
  }
457
- } catch (_) {
458
- return undefined;
211
+ } catch (error) {
212
+ console_log('Redis error:', error.message);
459
213
  }
460
214
 
215
+ console_log('Not found in any cache');
461
216
  return undefined;
462
217
  },
463
218
  set: async (key, value, context) => {
464
219
  const vKey = versionKey(key);
465
-
466
- let compressedValue;
467
- let shouldUseCompressed = false;
468
-
469
- try {
470
- compressedValue = await compressValue(value);
471
-
472
- shouldUseCompressed =
473
- compressedValue !== value &&
474
- (compressedValue?.__compressed ||
475
- compressedValue?.value?.__compressed);
476
- } catch (error) {
477
- compressedValue = value;
478
- shouldUseCompressed = false;
479
- }
480
-
481
- let redisSetResult;
482
-
483
- if (shouldUseCompressed) {
484
- try {
485
- await redisHandler.set(vKey, compressedValue, context);
486
-
487
- redisSetResult = { status: 'fulfilled' };
488
- } catch (compressionError) {
489
- try {
490
- await redisHandler.set(vKey, value, context);
491
-
492
- redisSetResult = { status: 'fulfilled' };
493
- } catch (fallbackError) {
494
- redisSetResult = { status: 'rejected', reason: fallbackError };
495
- }
496
- }
497
- } else {
498
- try {
499
- await redisHandler.set(vKey, value, context);
500
- redisSetResult = { status: 'fulfilled' };
501
- } catch (error) {
502
- redisSetResult = { status: 'rejected', reason: error };
503
- return redisSetResult;
504
- }
505
- }
506
-
507
- let localSetResult;
508
- try {
509
- await localHandler.set(vKey, value, context);
510
- localSetResult = { status: 'fulfilled' };
511
- } catch (error) {
512
- localSetResult = { status: 'rejected', reason: error };
513
- return localSetResult;
514
- }
220
+ console_log(
221
+ 'SET called for key:',
222
+ typeof vKey === 'string' ? vKey : vKey?.key
223
+ );
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
+ ]);
515
231
  },
516
232
  delete: async (key, context) => {
517
233
  const vKey = versionKey(key);
518
-
234
+ console_log(
235
+ 'DELETE called for key:',
236
+ typeof vKey === 'string' ? vKey : vKey?.key
237
+ );
519
238
  await Promise.allSettled([
520
239
  localHandler.delete?.(vKey, context),
521
240
  redisHandler.delete?.(vKey, context)
522
241
  ]);
523
242
  },
524
243
  revalidateTag: async (tags, context) => {
244
+ console_log('REVALIDATE_TAG called for tags:', tags);
525
245
  await Promise.allSettled([
526
246
  localHandler.revalidateTag?.(tags, context),
527
247
  redisHandler.revalidateTag?.(tags, context)
@@ -529,6 +249,8 @@ CacheHandler.onCreation(async () => {
529
249
  }
530
250
  };
531
251
 
252
+ console_log('[Cache Handler] Handlers initialized successfully');
253
+
532
254
  return {
533
255
  handlers: [customHandler]
534
256
  };