@akinon/next 1.96.0-rc.57 → 1.96.0-snapshot-ZERO-35861-20250908151109

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 +41 -1246
  2. package/__tests__/next-config.test.ts +10 -1
  3. package/api/cache.ts +39 -5
  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 +32 -50
  12. package/data/server/flatpage.ts +16 -17
  13. package/data/server/form.ts +4 -1
  14. package/data/server/landingpage.ts +12 -16
  15. package/data/server/list.ts +15 -24
  16. package/data/server/menu.ts +5 -2
  17. package/data/server/product.ts +41 -67
  18. package/data/server/special-page.ts +12 -16
  19. package/data/server/widget.ts +4 -1
  20. package/data/urls.ts +1 -5
  21. package/hocs/server/with-segment-defaults.tsx +2 -5
  22. package/hooks/use-localization.ts +3 -2
  23. package/jest.config.js +1 -7
  24. package/lib/cache-handler.mjs +365 -87
  25. package/lib/cache.ts +252 -25
  26. package/middlewares/complete-gpay.ts +1 -2
  27. package/middlewares/complete-masterpass.ts +1 -2
  28. package/middlewares/default.ts +13 -50
  29. package/middlewares/locale.ts +1 -9
  30. package/middlewares/pretty-url.ts +2 -1
  31. package/middlewares/redirection-payment.ts +1 -2
  32. package/middlewares/saved-card-redirection.ts +1 -2
  33. package/middlewares/three-d-redirection.ts +1 -2
  34. package/middlewares/url-redirection.ts +14 -8
  35. package/package.json +4 -3
  36. package/plugins.d.ts +0 -8
  37. package/plugins.js +1 -3
  38. package/redux/middlewares/checkout.ts +1 -5
  39. package/types/commerce/order.ts +0 -1
  40. package/types/index.ts +2 -34
  41. package/utils/app-fetch.ts +2 -7
  42. package/utils/redirect.ts +6 -31
  43. package/with-pz-config.js +5 -1
  44. package/__tests__/redirect.test.ts +0 -319
  45. package/api/image-proxy.ts +0 -75
  46. package/api/similar-product-list.ts +0 -84
  47. package/api/similar-products.ts +0 -120
  48. package/data/server/basket.ts +0 -72
  49. package/utils/redirect-ignore.ts +0 -35
@@ -3,7 +3,261 @@ 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
- // Cache configuration
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
+
7
261
  const CACHE_CONFIG = {
8
262
  lru: {
9
263
  maxItemCount: 2000
@@ -22,7 +276,6 @@ const CACHE_CONFIG = {
22
276
  version: process.env.ACC_APP_VERSION || ''
23
277
  };
24
278
 
25
- // Use global to persist across module reloads in development
26
279
  const globalForRedis = global;
27
280
  if (!globalForRedis.redisClient) {
28
281
  globalForRedis.redisClient = null;
@@ -31,42 +284,22 @@ if (!globalForRedis.redisClient) {
31
284
  globalForRedis.connectionAttempts = 0;
32
285
  }
33
286
 
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
-
46
287
  async function getRedisClient() {
47
- // If client exists and is ready, return it
48
288
  if (globalForRedis.redisClient?.isReady) {
49
- console_log('Reusing existing Redis connection');
50
289
  return globalForRedis.redisClient;
51
290
  }
52
291
 
53
- // If we're already connecting, wait a bit and retry
54
292
  if (globalForRedis.isConnecting) {
55
293
  await new Promise((resolve) => setTimeout(resolve, 100));
56
294
  return getRedisClient();
57
295
  }
58
296
 
59
- // Start new connection
60
297
  globalForRedis.isConnecting = true;
61
298
  globalForRedis.connectionAttempts++;
62
299
 
63
300
  try {
64
301
  const redisUrl = `redis://${CACHE_CONFIG.host}:${CACHE_CONFIG.port}/${CACHE_CONFIG.bucket}`;
65
302
 
66
- if (globalForRedis.connectionAttempts === 1) {
67
- console_log('Creating Redis connection:', redisUrl);
68
- }
69
-
70
303
  const redisClient = createClient({
71
304
  url: redisUrl,
72
305
  socket: {
@@ -87,7 +320,6 @@ async function getRedisClient() {
87
320
  });
88
321
 
89
322
  redisClient.on('error', (error) => {
90
- // Only log the first connection error to avoid spam
91
323
  if (!globalForRedis.hasLoggedConnectionError) {
92
324
  if (error.code === 'ECONNREFUSED') {
93
325
  console.warn(
@@ -101,12 +333,10 @@ async function getRedisClient() {
101
333
  });
102
334
 
103
335
  redisClient.on('connect', () => {
104
- console_log('Redis connected');
105
336
  globalForRedis.hasLoggedConnectionError = false;
106
337
  });
107
338
 
108
339
  redisClient.on('ready', () => {
109
- console_log('Redis ready');
110
340
  globalForRedis.hasLoggedConnectionError = false;
111
341
  });
112
342
 
@@ -115,16 +345,6 @@ async function getRedisClient() {
115
345
  return redisClient;
116
346
  } catch (error) {
117
347
  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
- }
128
348
  globalForRedis.hasLoggedConnectionError = true;
129
349
  }
130
350
  globalForRedis.redisClient = null;
@@ -135,13 +355,10 @@ async function getRedisClient() {
135
355
  }
136
356
 
137
357
  CacheHandler.onCreation(async () => {
138
- console_log('Initializing cache handlers...');
139
-
140
358
  let client;
141
359
  try {
142
360
  client = await getRedisClient();
143
361
  } catch (error) {
144
- // Error already logged in getRedisClient, just return local handler
145
362
  return {
146
363
  handlers: [createLruHandler(CACHE_CONFIG.lru)]
147
364
  };
@@ -150,98 +367,161 @@ CacheHandler.onCreation(async () => {
150
367
  const redisHandler = createRedisHandler({
151
368
  client,
152
369
  timeoutMs: CACHE_CONFIG.redis.timeoutMs,
153
- keyExpirationStrategy: 'EXAT'
370
+ keyExpirationStrategy: 'EXPIREAT'
154
371
  });
155
372
 
156
373
  const localHandler = createLruHandler(CACHE_CONFIG.lru);
157
374
 
158
- // Pre-compute version prefix if exists
159
- const versionPrefix = CACHE_CONFIG.version ? `${CACHE_CONFIG.version}:` : '';
160
-
161
- // Create optimized functions for each scenario
162
- const versionKeyString = versionPrefix
163
- ? (key) => `${versionPrefix}${key}`
164
- : (key) => key;
375
+ const CACHE_VERSION = 'v2';
376
+ const versionPrefix = `${CACHE_VERSION}_`;
165
377
 
166
- const versionKeyObject = versionPrefix
167
- ? (key) => ({ ...key, key: `${versionPrefix}${key.key}` })
168
- : (key) => key;
378
+ const versionKeyString = (key) => `${versionPrefix}${key}`;
379
+ const versionKeyObject = (key) => ({
380
+ ...key,
381
+ key: `${versionPrefix}${key.key}`
382
+ });
169
383
 
170
- // Main version key function that routes to optimized paths
171
384
  const versionKey = (key) => {
172
385
  return typeof key === 'string'
173
386
  ? versionKeyString(key)
174
387
  : versionKeyObject(key);
175
388
  };
176
389
 
177
- // Create a custom handler that checks local first, then Redis
178
390
  const customHandler = {
179
391
  name: 'custom-local-then-redis',
180
392
  get: async (key, context) => {
181
393
  const vKey = versionKey(key);
182
- console_log(
183
- 'GET called for key:',
184
- typeof vKey === 'string' ? vKey : vKey?.key
185
- );
186
394
 
187
- // Check local cache first
188
- console_log('Checking local cache...');
189
395
  const localResult = await localHandler.get(vKey, context);
190
396
 
191
397
  if (localResult) {
192
- console_log('Found in local cache');
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
+
193
415
  return localResult;
194
416
  }
195
417
 
196
- console_log('Not found in local, checking Redis...');
197
418
  try {
198
419
  const redisResult = await redisHandler.get(vKey, context);
199
420
 
200
421
  if (redisResult) {
201
- console_log('Found in Redis');
202
- // Sync back to local cache for faster future access
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
+
203
450
  try {
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);
451
+ await localHandler.set(vKey, finalResult, context);
452
+ } catch (_) {
453
+ return finalResult;
208
454
  }
209
- return redisResult;
455
+ return finalResult;
210
456
  }
211
- } catch (error) {
212
- console_log('Redis error:', error.message);
457
+ } catch (_) {
458
+ return undefined;
213
459
  }
214
460
 
215
- console_log('Not found in any cache');
216
461
  return undefined;
217
462
  },
218
463
  set: async (key, value, context) => {
219
464
  const vKey = versionKey(key);
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
- ]);
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
+ }
231
515
  },
232
516
  delete: async (key, context) => {
233
517
  const vKey = versionKey(key);
234
- console_log(
235
- 'DELETE called for key:',
236
- typeof vKey === 'string' ? vKey : vKey?.key
237
- );
518
+
238
519
  await Promise.allSettled([
239
520
  localHandler.delete?.(vKey, context),
240
521
  redisHandler.delete?.(vKey, context)
241
522
  ]);
242
523
  },
243
524
  revalidateTag: async (tags, context) => {
244
- console_log('REVALIDATE_TAG called for tags:', tags);
245
525
  await Promise.allSettled([
246
526
  localHandler.revalidateTag?.(tags, context),
247
527
  redisHandler.revalidateTag?.(tags, context)
@@ -249,8 +529,6 @@ CacheHandler.onCreation(async () => {
249
529
  }
250
530
  };
251
531
 
252
- console_log('[Cache Handler] Handlers initialized successfully');
253
-
254
532
  return {
255
533
  handlers: [customHandler]
256
534
  };