@akinon/next 1.96.0-rc.60 → 1.96.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 (43) hide show
  1. package/CHANGELOG.md +37 -1252
  2. package/__tests__/next-config.test.ts +10 -1
  3. package/components/accordion.tsx +5 -20
  4. package/components/file-input.tsx +3 -65
  5. package/components/input.tsx +0 -2
  6. package/components/link.tsx +12 -16
  7. package/components/modal.tsx +16 -32
  8. package/components/plugin-module.tsx +3 -30
  9. package/data/client/checkout.ts +4 -5
  10. package/data/server/category.ts +24 -44
  11. package/data/server/flatpage.ts +12 -16
  12. package/data/server/landingpage.ts +12 -16
  13. package/data/server/list.ts +13 -23
  14. package/data/server/product.ts +39 -66
  15. package/data/server/special-page.ts +12 -16
  16. package/data/urls.ts +1 -5
  17. package/hocs/server/with-segment-defaults.tsx +2 -5
  18. package/hooks/use-localization.ts +3 -2
  19. package/jest.config.js +1 -7
  20. package/lib/cache.ts +0 -2
  21. package/middlewares/complete-gpay.ts +1 -2
  22. package/middlewares/complete-masterpass.ts +1 -2
  23. package/middlewares/default.ts +13 -50
  24. package/middlewares/locale.ts +1 -9
  25. package/middlewares/redirection-payment.ts +1 -2
  26. package/middlewares/saved-card-redirection.ts +1 -2
  27. package/middlewares/three-d-redirection.ts +1 -2
  28. package/middlewares/url-redirection.ts +14 -8
  29. package/package.json +3 -3
  30. package/plugins.d.ts +0 -8
  31. package/plugins.js +1 -3
  32. package/redux/middlewares/checkout.ts +1 -5
  33. package/types/commerce/order.ts +0 -1
  34. package/types/index.ts +1 -34
  35. package/utils/app-fetch.ts +2 -7
  36. package/utils/redirect.ts +6 -31
  37. package/with-pz-config.js +5 -1
  38. package/__tests__/redirect.test.ts +0 -319
  39. package/api/image-proxy.ts +0 -75
  40. package/api/similar-product-list.ts +0 -84
  41. package/api/similar-products.ts +0 -120
  42. package/data/server/basket.ts +0 -72
  43. package/utils/redirect-ignore.ts +0 -35
@@ -35,84 +35,57 @@ const getProductDataHandler = ({
35
35
  .join('&');
36
36
  }
37
37
 
38
- let data: ProductResult;
39
-
40
- try {
41
- data = await appFetch<ProductResult>({
42
- url,
43
- locale,
44
- currency,
45
- init: {
46
- headers: {
47
- Accept: 'application/json',
48
- 'Content-Type': 'application/json',
49
- ...(headers ?? {})
50
- }
38
+ const data = await appFetch<ProductResult>({
39
+ url,
40
+ locale,
41
+ currency,
42
+ init: {
43
+ headers: {
44
+ Accept: 'application/json',
45
+ 'Content-Type': 'application/json',
46
+ ...(headers ?? {})
51
47
  }
52
- });
53
- } catch (error) {
54
- logger.error('Failed to fetch product data', {
55
- handler: 'getProductDataHandler',
56
- pk,
57
- error: error.message,
58
- url
59
- });
60
- return null;
61
- }
48
+ }
49
+ });
62
50
 
63
51
  const categoryUrl = product.categoryUrl(data.product.pk);
64
52
 
65
- let productCategoryData: ProductCategoryResult;
66
- let breadcrumbData: { menu?: unknown } = {};
67
-
68
- try {
69
- productCategoryData = await appFetch<ProductCategoryResult>({
70
- url: categoryUrl,
71
- locale,
72
- currency,
73
- init: {
74
- headers: {
75
- Accept: 'application/json',
76
- 'Content-Type': 'application/json'
77
- }
53
+ const productCategoryData = await appFetch<ProductCategoryResult>({
54
+ url: categoryUrl,
55
+ locale,
56
+ currency,
57
+ init: {
58
+ headers: {
59
+ Accept: 'application/json',
60
+ 'Content-Type': 'application/json'
78
61
  }
79
- });
80
-
81
- const menuItemModel = productCategoryData?.results[0]?.menuitemmodel;
82
-
83
- if (!menuItemModel) {
84
- logger.warn(
85
- 'menuItemModel is undefined, skipping breadcrumbData fetch',
86
- {
87
- handler: 'getProductDataHandler',
88
- pk
89
- }
90
- );
91
- return { data, breadcrumbData: undefined };
92
62
  }
63
+ });
93
64
 
94
- const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
65
+ const menuItemModel = productCategoryData?.results[0]?.menuitemmodel;
95
66
 
96
- breadcrumbData = await appFetch<{ menu?: unknown }>({
97
- url: breadcrumbUrl,
98
- locale,
99
- currency,
100
- init: {
101
- headers: {
102
- Accept: 'application/json',
103
- 'Content-Type': 'application/json'
104
- }
105
- }
106
- });
107
- } catch (error) {
108
- logger.warn('Failed to fetch breadcrumb data', {
67
+ if (!menuItemModel) {
68
+ logger.warn('menuItemModel is undefined, skipping breadcrumbData fetch', {
109
69
  handler: 'getProductDataHandler',
110
- pk,
111
- error: error.message
70
+ pk
112
71
  });
113
- // Continue without breadcrumb data
72
+ return { data, breadcrumbData: undefined };
114
73
  }
115
74
 
75
+ const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
76
+
77
+ const breadcrumbData = await appFetch<any>({
78
+ url: breadcrumbUrl,
79
+ locale,
80
+ currency,
81
+ init: {
82
+ headers: {
83
+ Accept: 'application/json',
84
+ 'Content-Type': 'application/json'
85
+ }
86
+ }
87
+ });
88
+
116
89
  return {
117
90
  data,
118
91
  breadcrumbData: breadcrumbData?.menu
@@ -15,24 +15,20 @@ const getSpecialPageDataHandler = (
15
15
  return async function () {
16
16
  const params = generateCommerceSearchParams(searchParams);
17
17
 
18
- try {
19
- const data: GetCategoryResponse = await appFetch({
20
- url: `${category.getSpecialPageByPk(pk)}${params}`,
21
- locale,
22
- currency,
23
- init: {
24
- headers: {
25
- Accept: 'application/json',
26
- 'Content-Type': 'application/json',
27
- ...(headers ?? {})
28
- }
18
+ const data: GetCategoryResponse = await appFetch({
19
+ url: `${category.getSpecialPageByPk(pk)}${params}`,
20
+ locale,
21
+ currency,
22
+ init: {
23
+ headers: {
24
+ Accept: 'application/json',
25
+ 'Content-Type': 'application/json',
26
+ ...(headers ?? {})
29
27
  }
30
- });
28
+ }
29
+ });
31
30
 
32
- return data;
33
- } catch (error) {
34
- return null;
35
- }
31
+ return data;
36
32
  };
37
33
  };
38
34
 
package/data/urls.ts CHANGED
@@ -183,11 +183,7 @@ export const product = {
183
183
  breadcrumbUrl: (menuitemmodel: string) =>
184
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
185
185
  bundleProduct: (productPk: string, queryString: string) =>
186
- `/bundle-product/${productPk}/?${queryString}`,
187
- similarProducts: (params?: string) =>
188
- `/similar-products${params ? `?${params}` : ''}`,
189
- similarProductsList: (params?: string) =>
190
- `/similar-product-list${params ? `?${params}` : ''}`
186
+ `/bundle-product/${productPk}/?${queryString}`
191
187
  };
192
188
 
193
189
  export const wishlist = {
@@ -72,13 +72,10 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
72
72
  const checkRedisVariables = () => {
73
73
  const requiredVariableValues = [
74
74
  process.env.CACHE_HOST,
75
- process.env.CACHE_PORT
75
+ process.env.CACHE_PORT,
76
+ process.env.CACHE_SECRET
76
77
  ];
77
78
 
78
- if (!settings.usePrettyUrlRoute) {
79
- requiredVariableValues.push(process.env.CACHE_SECRET);
80
- }
81
-
82
79
  if (
83
80
  !requiredVariableValues.every((v) => v) &&
84
81
  process.env.NODE_ENV === 'production'
@@ -4,6 +4,7 @@ import { LocalizationContext } from '../localization/provider';
4
4
  import { useContext } from 'react';
5
5
  import { setCookie, urlLocaleMatcherRegex } from '../utils';
6
6
  import { LocaleUrlStrategy } from '../localization';
7
+ import { useRouter } from 'next/navigation';
7
8
 
8
9
  export const useLocalization = () => {
9
10
  const {
@@ -17,6 +18,8 @@ export const useLocalization = () => {
17
18
  localeUrlStrategy
18
19
  } = useContext(LocalizationContext);
19
20
 
21
+ const router = useRouter();
22
+
20
23
  /**
21
24
  * Sets the locale in the URL.
22
25
  * @param locale Locale value defined in the settings.
@@ -27,8 +30,6 @@ export const useLocalization = () => {
27
30
 
28
31
  let targetUrl;
29
32
 
30
- setCookie('pz-locale', locale);
31
-
32
33
  if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
33
34
  const hostParts = hostname.split('.');
34
35
  const subDomain = hostParts[0];
package/jest.config.js CHANGED
@@ -1,19 +1,13 @@
1
1
  const path = require('path');
2
- const findBaseDir = require('./utils/find-base-dir');
3
-
4
- const baseDir = findBaseDir();
5
2
 
6
3
  module.exports = {
7
4
  preset: 'ts-jest',
8
5
  testEnvironment: 'node',
9
6
  rootDir: path.resolve(__dirname),
7
+ roots: [],
10
8
  testMatch: ['**/*.test.ts'],
11
9
  testPathIgnorePatterns: [],
12
- roots: [path.resolve(__dirname)],
13
10
  transformIgnorePatterns: [],
14
- moduleNameMapper: {
15
- '^settings$': path.resolve(baseDir, 'src/settings.js')
16
- },
17
11
  transform: {
18
12
  '^.+\\.(tsx?|jsx?|mjs?)$': [
19
13
  'ts-jest',
package/lib/cache.ts CHANGED
@@ -31,8 +31,6 @@ export const CacheKey = {
31
31
  `category_${pk}_${encodeURIComponent(
32
32
  JSON.stringify(searchParams)
33
33
  )}${hashCacheKey(headers)}`,
34
- Basket: (namespace?: string) => `basket${namespace ? `_${namespace}` : ''}`,
35
- AllBaskets: () => 'all_baskets',
36
34
  CategorySlug: (slug: string) => `category_${slug}`,
37
35
  SpecialPage: (
38
36
  pk: number,
@@ -148,8 +148,7 @@ const withCompleteGpay =
148
148
  logger.info('Redirecting to order success page', {
149
149
  middleware: 'complete-gpay',
150
150
  redirectUrlWithLocale,
151
- ip,
152
- setCookie: request.headers.get('set-cookie')
151
+ ip
153
152
  });
154
153
 
155
154
  // Using POST method while redirecting causes an error,
@@ -149,8 +149,7 @@ const withCompleteMasterpass =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'complete-masterpass',
151
151
  redirectUrlWithLocale,
152
- ip,
153
- setCookie: request.headers.get('set-cookie')
152
+ ip
154
153
  });
155
154
 
156
155
  // Using POST method while redirecting causes an error,
@@ -302,6 +302,19 @@ const withPzDefault =
302
302
  )}`;
303
303
  }
304
304
 
305
+ if (
306
+ !req.middlewareParams.found &&
307
+ Settings.customNotFoundEnabled
308
+ ) {
309
+ const pathname = url.pathname
310
+ .replace(/\/+$/, '')
311
+ .split('/');
312
+ url.pathname = url.pathname.replace(
313
+ pathname.pop(),
314
+ 'pz-not-found'
315
+ );
316
+ }
317
+
305
318
  Settings.rewrites.forEach((rewrite) => {
306
319
  url.pathname = url.pathname.replace(
307
320
  rewrite.source,
@@ -339,24 +352,6 @@ const withPzDefault =
339
352
  middlewareResult = NextResponse.rewrite(url);
340
353
  }
341
354
 
342
- if (
343
- !req.middlewareParams.found &&
344
- Settings.customNotFoundEnabled
345
- ) {
346
- const pathSegments = url.pathname
347
- .replace(/\/+$/, '')
348
- .split('/');
349
- if (pathSegments.length >= 3) {
350
- url.pathname = `/${pathSegments[1]}/${pathSegments[2]}/pz-not-found`;
351
- } else {
352
- url.pathname = '/pz-not-found';
353
- }
354
-
355
- middlewareResult = NextResponse.rewrite(url, {
356
- status: 404
357
- });
358
- }
359
-
360
355
  const { localeUrlStrategy } =
361
356
  Settings.localization;
362
357
 
@@ -406,38 +401,6 @@ const withPzDefault =
406
401
  }
407
402
  );
408
403
 
409
- if (
410
- !url.pathname.startsWith(
411
- `/${currency}/orders`
412
- )
413
- ) {
414
- const currentCookieLocale =
415
- req.cookies.get('pz-locale')?.value;
416
-
417
- const urlHasExplicitLocale =
418
- url.pathname.match(urlLocaleMatcherRegex);
419
- const shouldUpdateCookie =
420
- !currentCookieLocale ||
421
- urlHasExplicitLocale;
422
-
423
- if (shouldUpdateCookie) {
424
- middlewareResult.cookies.set(
425
- 'pz-locale',
426
- locale?.length > 0
427
- ? locale
428
- : defaultLocaleValue,
429
- {
430
- domain: rootHostname,
431
- sameSite: 'none',
432
- secure: true,
433
- expires: new Date(
434
- Date.now() + 1000 * 60 * 60 * 24 * 7
435
- ) // 7 days
436
- }
437
- );
438
- }
439
- }
440
-
441
404
  if (
442
405
  req.cookies.get('pz-locale') &&
443
406
  req.cookies.get('pz-locale').value !== locale
@@ -23,15 +23,7 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
23
23
  );
24
24
 
25
25
  if (subDomainLocaleMatched && subDomainLocaleMatched[0]) {
26
- const subdomainLocale = subDomainLocaleMatched[0].slice(1);
27
-
28
- const isValidSubdomainLocale = settings.localization.locales.find(
29
- (l) => l.value === subdomainLocale
30
- );
31
-
32
- if (isValidSubdomainLocale) {
33
- matchedLocale = subdomainLocale;
34
- }
26
+ matchedLocale = subDomainLocaleMatched[0].slice(1);
35
27
  }
36
28
  }
37
29
  }
@@ -149,8 +149,7 @@ const withRedirectionPayment =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'redirection-payment',
151
151
  redirectUrlWithLocale,
152
- ip,
153
- setCookie: request.headers.get('set-cookie')
152
+ ip
154
153
  });
155
154
 
156
155
  // Using POST method while redirecting causes an error,
@@ -149,8 +149,7 @@ const withSavedCardRedirection =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'saved-card-redirection',
151
151
  redirectUrlWithLocale,
152
- ip,
153
- setCookie: request.headers.get('set-cookie')
152
+ ip
154
153
  });
155
154
 
156
155
  // Using POST method while redirecting causes an error,
@@ -149,8 +149,7 @@ const withThreeDRedirection =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'three-d-redirection',
151
151
  redirectUrlWithLocale,
152
- ip,
153
- setCookie: request.headers.get('set-cookie')
152
+ ip
154
153
  });
155
154
 
156
155
  // Using POST method while redirecting causes an error,
@@ -4,7 +4,6 @@ import { PzNextRequest } from '.';
4
4
  import logger from '../utils/log';
5
5
  import { urlLocaleMatcherRegex } from '../utils';
6
6
  import { getUrlPathWithLocale } from '../utils/localization';
7
- import { shouldIgnoreRedirect } from '../utils/redirect-ignore';
8
7
  import { ROUTES } from 'routes';
9
8
 
10
9
  // This middleware is used to handle url redirections set in Omnitron
@@ -61,13 +60,20 @@ const withUrlRedirection =
61
60
 
62
61
  const setCookies = request.headers.getSetCookie();
63
62
 
64
- if (
65
- shouldIgnoreRedirect(
66
- url.pathname,
67
- req.middlewareParams.rewrites.locale
68
- )
69
- ) {
70
- return middleware(req, event);
63
+ if (settings.commerceRedirectionIgnoreList) {
64
+ const shouldIgnoreRedirect =
65
+ settings.commerceRedirectionIgnoreList.some((ignorePath) =>
66
+ redirectUrl.pathname.startsWith(
67
+ getUrlPathWithLocale(
68
+ ignorePath,
69
+ req.middlewareParams.rewrites.locale
70
+ )
71
+ )
72
+ );
73
+
74
+ if (shouldIgnoreRedirect) {
75
+ return middleware(req, event);
76
+ }
71
77
  }
72
78
 
73
79
  const response = NextResponse.redirect(redirectUrl.toString(), {
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.96.0-rc.60",
4
+ "version": "1.96.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -17,13 +17,13 @@
17
17
  "test": "jest"
18
18
  },
19
19
  "dependencies": {
20
+ "@neshca/cache-handler": "1.5.1",
20
21
  "@opentelemetry/exporter-trace-otlp-http": "0.46.0",
21
22
  "@opentelemetry/resources": "1.19.0",
22
23
  "@opentelemetry/sdk-node": "0.46.0",
23
24
  "@opentelemetry/sdk-trace-node": "1.19.0",
24
25
  "@opentelemetry/semantic-conventions": "1.19.0",
25
26
  "@reduxjs/toolkit": "1.9.7",
26
- "@neshca/cache-handler": "1.9.0",
27
27
  "@sentry/nextjs": "9.5.0",
28
28
  "cross-spawn": "7.0.3",
29
29
  "generic-pool": "3.9.0",
@@ -34,7 +34,7 @@
34
34
  "set-cookie-parser": "2.6.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@akinon/eslint-plugin-projectzero": "1.96.0-rc.60",
37
+ "@akinon/eslint-plugin-projectzero": "1.96.0",
38
38
  "@babel/core": "7.26.10",
39
39
  "@babel/preset-env": "7.26.9",
40
40
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -38,11 +38,3 @@ declare module '@akinon/pz-iyzico-saved-card' {
38
38
  declare module '@akinon/pz-apple-pay' {}
39
39
 
40
40
  declare module '@akinon/pz-flow-payment' {}
41
-
42
- declare module '@akinon/pz-similar-products' {
43
- export const SimilarProductsModal: any;
44
- export const SimilarProductsFilterSidebar: any;
45
- export const SimilarProductsResultsGrid: any;
46
- export const SimilarProductsPlugin: any;
47
- export const SimilarProductsButtonPlugin: any;
48
- }
package/plugins.js CHANGED
@@ -16,7 +16,5 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
- 'pz-hepsipay',
20
- 'pz-flow-payment',
21
- 'pz-similar-products'
19
+ 'pz-flow-payment'
22
20
  ];
@@ -51,11 +51,7 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
51
51
  const result: CheckoutResult = next(action);
52
52
  const errors = result?.payload?.errors;
53
53
 
54
- if (
55
- !!errors &&
56
- ((typeof errors === 'object' && Object.keys(errors).length > 0) ||
57
- (Array.isArray(errors) && errors.length > 0))
58
- ) {
54
+ if (errors) {
59
55
  dispatch(setErrors(errors));
60
56
  }
61
57
 
@@ -114,7 +114,6 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
- logo: string;
118
117
  [key: string]: any;
119
118
  };
120
119
  }
package/types/index.ts CHANGED
@@ -83,12 +83,6 @@ export interface Settings {
83
83
  };
84
84
  usePrettyUrlRoute?: boolean;
85
85
  commerceUrl: string;
86
- /**
87
- * This option allows you to track Sentry events on the client side, in addition to server and edge environments.
88
- *
89
- * It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
90
- */
91
- sentryDsn?: string;
92
86
  redis: {
93
87
  defaultExpirationTime: number;
94
88
  };
@@ -297,13 +291,7 @@ export interface ButtonProps
297
291
  target?: '_blank' | '_self' | '_parent' | '_top';
298
292
  }
299
293
 
300
- export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
301
- fileClassName?: string;
302
- fileNameWrapperClassName?: string;
303
- fileInputClassName?: string;
304
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
305
- buttonClassName?: string;
306
- }
294
+ export type FileInputProps = React.HTMLProps<HTMLInputElement>;
307
295
 
308
296
  export interface PriceProps {
309
297
  currencyCode?: string;
@@ -324,19 +312,15 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
324
312
 
325
313
  export interface AccordionProps {
326
314
  isCollapse?: boolean;
327
- collapseClassName?: string;
328
315
  title?: string;
329
316
  subTitle?: string;
330
317
  icons?: string[];
331
318
  iconSize?: number;
332
319
  iconColor?: string;
333
320
  children?: ReactNode;
334
- headerClassName?: string;
335
321
  className?: string;
336
322
  titleClassName?: string;
337
- subTitleClassName?: string;
338
323
  dataTestId?: string;
339
- contentClassName?: string;
340
324
  }
341
325
 
342
326
  export interface PluginModuleComponentProps {
@@ -361,20 +345,3 @@ export interface PaginationProps {
361
345
  direction?: 'next' | 'prev';
362
346
  isLoading?: boolean;
363
347
  }
364
-
365
- export interface ModalProps {
366
- portalId: string;
367
- children?: React.ReactNode;
368
- open?: boolean;
369
- setOpen?: (open: boolean) => void;
370
- title?: React.ReactNode;
371
- showCloseButton?: React.ReactNode;
372
- className?: string;
373
- overlayClassName?: string;
374
- headerWrapperClassName?: string;
375
- titleClassName?: string;
376
- closeButtonClassName?: string;
377
- iconName?: string;
378
- iconSize?: number;
379
- iconClassName?: string;
380
- }
@@ -43,12 +43,12 @@ const appFetch = async <T>({
43
43
  const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
44
44
 
45
45
  init.headers = {
46
- cookie: nextCookies.toString(),
47
46
  ...(init.headers ?? {}),
48
47
  ...(ServerVariables.globalHeaders ?? {}),
49
48
  'Accept-Language': currentLocale.apiValue,
50
49
  'x-currency': currency,
51
- 'x-forwarded-for': ip
50
+ 'x-forwarded-for': ip,
51
+ cookie: nextCookies.toString()
52
52
  };
53
53
 
54
54
  init.next = {
@@ -60,11 +60,6 @@ const appFetch = async <T>({
60
60
  status = req.status;
61
61
  logger.debug(`FETCH END ${url}`, { status: req.status, ip });
62
62
 
63
- if (!req.ok) {
64
- const errorMessage = `HTTP ${req.status}: ${req.statusText}`;
65
- throw new Error(errorMessage);
66
- }
67
-
68
63
  if (responseType === FetchResponseType.JSON) {
69
64
  response = (await req.json()) as T;
70
65
  } else {