@akinon/next 2.0.23-beta.0 → 2.0.23
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 +11 -2
- package/data/server/category.ts +3 -0
- package/data/server/list.ts +3 -0
- package/data/server/product.ts +4 -1
- package/data/server/special-page.ts +3 -0
- package/package.json +2 -2
- package/utils/index.ts +1 -0
- package/utils/normalize-search-params.ts +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 2.0.23
|
|
3
|
+
## 2.0.23
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 079fc67: ZERO-4522: Normalize searchParams at the entry of server data functions so v2 brand SSR no longer drops query params and no longer collides on a single Redis cache entry.
|
|
8
|
+
|
|
9
|
+
`withSegmentDefaults` converts Next 16's plain searchParams into a `URLSearchParams` instance for v1 brand compatibility. The instance then flowed into `getProductData` / `getCategoryData` / `getListData` / `getSpecialPageData` and broke two things:
|
|
10
|
+
|
|
11
|
+
- `JSON.stringify(new URLSearchParams(...))` returns `"{}"`, so every cache key for the same `pk` collapsed onto one Redis entry. The first user's filtered/variant response was served to every subsequent visitor.
|
|
12
|
+
- `getProductData`'s outbound URL builder used `Object.keys(searchParams).map(...)`, which returns `[]` for `URLSearchParams`. The backend product fetch silently dropped every query param — variant/attribute selections on the PDP never reached the commerce backend, and the add-to-cart button stayed stuck in its loading state.
|
|
13
|
+
|
|
14
|
+
Add `normalizeSearchParams` (`packages/akinon-next/utils/normalize-search-params.ts`) and call it once at the entry of each public data function. Multi-value URLSearchParams keys (`?color=red&color=blue`) collapse to `string[]` so multi-select filters survive. v1 brand pages keep receiving `URLSearchParams` from the HOC — only the internal data layer canonicalizes. No public signature change.
|
|
15
|
+
|
|
16
|
+
Side note: v1 brand PDP requests will now actually forward `?` params to the commerce backend instead of silently dropping them. Any v1 brand that relied on PDP requests ignoring query strings should smoke-test variant fetches.
|
|
8
17
|
|
|
9
18
|
## 2.0.22
|
|
10
19
|
|
package/data/server/category.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GetCategoryResponse, SearchParams } from '../../types';
|
|
2
2
|
import { generateCommerceSearchParams } from '../../utils';
|
|
3
|
+
import { normalizeSearchParams } from '../../utils/normalize-search-params';
|
|
3
4
|
import appFetch, { FetchResponseType } from '../../utils/app-fetch';
|
|
4
5
|
import { category, product } from '../urls';
|
|
5
6
|
import { Cache, CacheKey } from '../../lib/cache';
|
|
@@ -93,6 +94,8 @@ export const getCategoryData = ({
|
|
|
93
94
|
searchParams?: SearchParams;
|
|
94
95
|
headers?: Record<string, string>;
|
|
95
96
|
}) => {
|
|
97
|
+
searchParams = normalizeSearchParams(searchParams);
|
|
98
|
+
|
|
96
99
|
return Cache.wrap(
|
|
97
100
|
CacheKey.Category(pk, searchParams, headers),
|
|
98
101
|
locale,
|
package/data/server/list.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Cache, CacheKey } from '../../lib/cache';
|
|
|
2
2
|
import { category } from '../urls';
|
|
3
3
|
import { GetCategoryResponse, SearchParams } from '../../types';
|
|
4
4
|
import { generateCommerceSearchParams } from '../../utils';
|
|
5
|
+
import { normalizeSearchParams } from '../../utils/normalize-search-params';
|
|
5
6
|
import appFetch, { FetchResponseType } from '../../utils/app-fetch';
|
|
6
7
|
import { parse } from 'lossless-json';
|
|
7
8
|
import logger from '../../utils/log';
|
|
@@ -66,6 +67,8 @@ export const getListData = async ({
|
|
|
66
67
|
searchParams: SearchParams;
|
|
67
68
|
headers?: Record<string, string>;
|
|
68
69
|
}) => {
|
|
70
|
+
searchParams = normalizeSearchParams(searchParams);
|
|
71
|
+
|
|
69
72
|
return Cache.wrap(
|
|
70
73
|
CacheKey.List(searchParams, headers),
|
|
71
74
|
locale,
|
package/data/server/product.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Cache, CacheKey } from '../../lib/cache';
|
|
|
2
2
|
import { product } from '../urls';
|
|
3
3
|
import { ProductCategoryResult, ProductResult, SearchParams } from '../../types';
|
|
4
4
|
import appFetch from '../../utils/app-fetch';
|
|
5
|
+
import { normalizeSearchParams } from '../../utils/normalize-search-params';
|
|
5
6
|
import { ServerVariables } from '../../utils/server-variables';
|
|
6
7
|
import logger from '../../utils/log';
|
|
7
8
|
|
|
@@ -133,13 +134,15 @@ export const getProductData = async ({
|
|
|
133
134
|
groupProduct,
|
|
134
135
|
headers
|
|
135
136
|
}: GetProduct) => {
|
|
137
|
+
searchParams = normalizeSearchParams(searchParams);
|
|
138
|
+
|
|
136
139
|
// Convert pk to number for cache key if it's a string
|
|
137
140
|
const numericPkForCache = typeof pk === 'string' ? parseInt(pk, 10) : pk;
|
|
138
141
|
|
|
139
142
|
const result = await Cache.wrap(
|
|
140
143
|
CacheKey[groupProduct ? 'GroupProduct' : 'Product'](
|
|
141
144
|
numericPkForCache,
|
|
142
|
-
searchParams ??
|
|
145
|
+
searchParams ?? {}
|
|
143
146
|
),
|
|
144
147
|
locale,
|
|
145
148
|
getProductDataHandler({
|
|
@@ -2,6 +2,7 @@ import { Cache, CacheKey } from '../../lib/cache';
|
|
|
2
2
|
import { category } from '../urls';
|
|
3
3
|
import { GetCategoryResponse, SearchParams } from '../../types';
|
|
4
4
|
import { generateCommerceSearchParams } from '../../utils';
|
|
5
|
+
import { normalizeSearchParams } from '../../utils/normalize-search-params';
|
|
5
6
|
import appFetch from '../../utils/app-fetch';
|
|
6
7
|
import { ServerVariables } from '../../utils/server-variables';
|
|
7
8
|
|
|
@@ -45,6 +46,8 @@ export const getSpecialPageData = async ({
|
|
|
45
46
|
searchParams: SearchParams;
|
|
46
47
|
headers?: Record<string, string>;
|
|
47
48
|
}) => {
|
|
49
|
+
searchParams = normalizeSearchParams(searchParams);
|
|
50
|
+
|
|
48
51
|
return Cache.wrap(
|
|
49
52
|
CacheKey.SpecialPage(pk, searchParams, headers),
|
|
50
53
|
locale,
|
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": "2.0.23
|
|
4
|
+
"version": "2.0.23",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"set-cookie-parser": "2.6.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@akinon/eslint-plugin-projectzero": "2.0.23
|
|
39
|
+
"@akinon/eslint-plugin-projectzero": "2.0.23",
|
|
40
40
|
"@babel/core": "7.26.10",
|
|
41
41
|
"@babel/preset-env": "7.26.9",
|
|
42
42
|
"@babel/preset-typescript": "7.27.0",
|
package/utils/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import getRootHostname from './get-root-hostname';
|
|
|
6
6
|
export * from './get-currency';
|
|
7
7
|
export * from './menu-generator';
|
|
8
8
|
export * from './generate-commerce-search-params';
|
|
9
|
+
export * from './normalize-search-params';
|
|
9
10
|
export * from './get-currency-label';
|
|
10
11
|
export * from './pz-segments';
|
|
11
12
|
export * from './get-checkout-path';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SearchParams } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize SearchParams to a plain Record<string, string | string[]>.
|
|
5
|
+
*
|
|
6
|
+
* Accepts either a URLSearchParams instance (Next 16 HOC output / v2 brands)
|
|
7
|
+
* or a plain Record (v1 brands / direct callers) and always returns a plain
|
|
8
|
+
* object. Downstream code can then safely:
|
|
9
|
+
* - JSON.stringify it for stable cache keys (avoids the URLSearchParams
|
|
10
|
+
* `JSON.stringify === "{}"` cache collision)
|
|
11
|
+
* - iterate with Object.keys / Object.entries (avoids the dropped query
|
|
12
|
+
* param bug on the PDP outbound URL builder)
|
|
13
|
+
* - spread with { ...obj }
|
|
14
|
+
*
|
|
15
|
+
* Repeated keys in URLSearchParams (e.g. ?color=red&color=blue) collapse to
|
|
16
|
+
* a string[] value so multi-value filter semantics survive.
|
|
17
|
+
*
|
|
18
|
+
* Returns undefined when the input is undefined so downstream
|
|
19
|
+
* `if (searchParams)` guards keep working unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export const normalizeSearchParams = (
|
|
22
|
+
searchParams?: SearchParams
|
|
23
|
+
): Record<string, string | string[]> | undefined => {
|
|
24
|
+
if (!searchParams) return undefined;
|
|
25
|
+
|
|
26
|
+
if (searchParams instanceof URLSearchParams) {
|
|
27
|
+
const out: Record<string, string | string[]> = {};
|
|
28
|
+
for (const key of new Set(searchParams.keys())) {
|
|
29
|
+
const all = searchParams.getAll(key);
|
|
30
|
+
out[key] = all.length > 1 ? all : all[0];
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Plain object — drop undefined values so cache key serialization is stable
|
|
36
|
+
// across calls that omit some keys.
|
|
37
|
+
const out: Record<string, string | string[]> = {};
|
|
38
|
+
for (const [key, value] of Object.entries(searchParams)) {
|
|
39
|
+
if (value !== undefined) out[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
};
|