@scayle/storefront-product-detail 1.9.0 → 1.9.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @scayle/storefront-product-detail
2
2
 
3
+ ## 1.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - **\[Security\]** Use the patched Nuxt for build — `nuxt@3.21.8` (3.x) / `nuxt@4.4.8` (4.x), with matching `@nuxt/kit` and `@nuxt/schema` — which includes the fix for [CVE-2026-53721](https://nvd.nist.gov/vuln/detail/CVE-2026-53721) ([GHSA-mm7m-92g8-7m47](https://github.com/nuxt/nuxt/security/advisories/GHSA-mm7m-92g8-7m47)), a route-rule middleware bypass.
8
+
9
+ Compatibility with previously-supported Nuxt versions is unchanged and remains specified via each package's `peerDependencies` and the Nuxt compatibility flag. `@scayle/storefront-core` is released in lockstep with `@scayle/storefront-nuxt`.
10
+
3
11
  ## 1.9.0
4
12
 
5
13
  ### Minor Changes
@@ -19,19 +27,19 @@
19
27
  params: {
20
28
  productId: 123,
21
29
  limit: 10,
22
- with: { attributes: "all", images: { attributes: "all" } },
30
+ with: { attributes: 'all', images: { attributes: 'all' } },
23
31
  where: {
24
32
  attributes: [
25
33
  {
26
- type: "attributes",
27
- key: "brand",
34
+ type: 'attributes',
35
+ key: 'brand',
28
36
  values: [456],
29
37
  },
30
38
  ],
31
39
  },
32
40
  ignoreSameMasterKey: true,
33
41
  },
34
- });
42
+ })
35
43
  ```
36
44
 
37
45
  ## 1.7.0
@@ -40,7 +48,6 @@
40
48
 
41
49
  - Introduced support for Nuxt 4 while maintaining full backward compatibility with Nuxt 3.
42
50
  This enables consumers to migrate to Nuxt 4 ahead of the Nuxt 3 end of support on 31 Jan 2026.
43
-
44
51
  - **Version Requirements:**
45
52
  - Nuxt 3: `v3.13.0+`
46
53
  - Nuxt 4: `v4.2.0+`
@@ -78,64 +85,64 @@
78
85
  **`sellableTimeframeStartsInFuture`:**
79
86
 
80
87
  ```ts
81
- import { sellableTimeframeStartsInFuture } from "@scayle/storefront-product-detail";
88
+ import { sellableTimeframeStartsInFuture } from '@scayle/storefront-product-detail'
82
89
 
83
90
  // Example timeframe
84
91
  const timeframe = {
85
- sellableFrom: "2099-01-01T00:00:00Z",
86
- sellableTo: "2100-01-01T00:00:00Z",
87
- };
92
+ sellableFrom: '2099-01-01T00:00:00Z',
93
+ sellableTo: '2100-01-01T00:00:00Z',
94
+ }
88
95
 
89
- const inFuture = sellableTimeframeStartsInFuture(timeframe);
90
- console.log(inFuture); // true if current date is before 2099-01-01
96
+ const inFuture = sellableTimeframeStartsInFuture(timeframe)
97
+ console.log(inFuture) // true if current date is before 2099-01-01
91
98
  ```
92
99
 
93
100
  **`sellableTimeframeEndsInPast`:**
94
101
 
95
102
  ```ts
96
- import { sellableTimeframeEndsInPast } from "@scayle/storefront-product-detail";
103
+ import { sellableTimeframeEndsInPast } from '@scayle/storefront-product-detail'
97
104
 
98
105
  const timeframe = {
99
- sellableFrom: "2018-01-01T00:00:00Z",
100
- sellableTo: "2020-01-01T00:00:00Z",
101
- };
106
+ sellableFrom: '2018-01-01T00:00:00Z',
107
+ sellableTo: '2020-01-01T00:00:00Z',
108
+ }
102
109
 
103
- const ended = sellableTimeframeEndsInPast(timeframe);
104
- console.log(ended); // true if current date is after 2020-01-01
110
+ const ended = sellableTimeframeEndsInPast(timeframe)
111
+ console.log(ended) // true if current date is after 2020-01-01
105
112
  ```
106
113
 
107
114
  **`isInSellableTimeframe`:**
108
115
 
109
116
  ```ts
110
- import { isInSellableTimeframe } from "@scayle/storefront-product-detail";
117
+ import { isInSellableTimeframe } from '@scayle/storefront-product-detail'
111
118
 
112
119
  const timeframe = {
113
- sellableFrom: "2023-01-01T00:00:00Z",
114
- sellableTo: "2025-01-01T00:00:00Z",
115
- };
120
+ sellableFrom: '2023-01-01T00:00:00Z',
121
+ sellableTo: '2025-01-01T00:00:00Z',
122
+ }
116
123
 
117
- const active = isInSellableTimeframe(timeframe);
118
- console.log(active); // true if current date is between 2023-01-01 and 2025-01-01
124
+ const active = isInSellableTimeframe(timeframe)
125
+ console.log(active) // true if current date is between 2023-01-01 and 2025-01-01
119
126
  ```
120
127
 
121
128
  **`useSellableTimeFrame`:**
122
129
 
123
130
  ```ts
124
- import { useSellableTimeFrame } from "@scayle/storefront-product-detail";
131
+ import { useSellableTimeFrame } from '@scayle/storefront-product-detail'
125
132
 
126
133
  const timeframe = {
127
- sellableFrom: "2023-01-01T00:00:00Z",
128
- sellableTo: "2025-01-01T00:00:00Z",
129
- };
134
+ sellableFrom: '2023-01-01T00:00:00Z',
135
+ sellableTo: '2025-01-01T00:00:00Z',
136
+ }
130
137
  const {
131
138
  isInSellableTimeframe,
132
139
  sellableTimeframeStartsInFuture,
133
140
  sellableTimeframeEndsInPast,
134
- } = useSellableTimeFrame(timeframe);
141
+ } = useSellableTimeFrame(timeframe)
135
142
 
136
- console.log(isInSellableTimeframe); // true if the timeframe is active
137
- console.log(sellableTimeframeStartsInFuture); // true if the time frame starts in the future
138
- console.log(sellableTimeframeEndsInPast); // true if the timeframe ended in the past
143
+ console.log(isInSellableTimeframe) // true if the timeframe is active
144
+ console.log(sellableTimeframeStartsInFuture) // true if the time frame starts in the future
145
+ console.log(sellableTimeframeEndsInPast) // true if the timeframe ended in the past
139
146
  ```
140
147
 
141
148
  ## 1.5.6
@@ -145,26 +152,25 @@
145
152
  - Enhanced data fetching composables to support reactive keys, aligning with [Nuxt 3.17 data fetching improvements](https://nuxt.com/blog/v3-17#data-fetching-improvements).
146
153
 
147
154
  You can now pass a `ref`, `computed`, or getter function as the `key` parameter to `useRpc` and derived composables (like `useProducts`, `useBrand` etc.). When the reactive key changes, the data will automatically re-fetch.
148
-
149
155
  - **Before:** Keys were static strings.
150
156
  - **After:** Keys can be reactive, enabling dynamic caching and automatic updates.
151
157
 
152
158
  ```ts
153
159
  // Before
154
- const { data } = await useRpc("getProduct", "my-static-product-key", {
160
+ const { data } = await useRpc('getProduct', 'my-static-product-key', {
155
161
  productId: 123,
156
- });
162
+ })
157
163
 
158
164
  // After
159
- const productId = ref(123);
165
+ const productId = ref(123)
160
166
 
161
167
  // The key is now reactive. If `productId` changes, the key updates
162
168
  // to 'product-456' (for example) and triggers a new fetch.
163
169
  const { data } = await useRpc(
164
- "getProduct",
170
+ 'getProduct',
165
171
  () => `product-${productId.value}`,
166
- { productId }
167
- );
172
+ { productId },
173
+ )
168
174
  ```
169
175
 
170
176
  ## 1.5.5
@@ -201,15 +207,15 @@
201
207
 
202
208
  ```ts
203
209
  export default defineNuxtConfig({
204
- modules: ["@scayle/storefront-product-detail"],
210
+ modules: ['@scayle/storefront-product-detail'],
205
211
  runtimeConfig: {
206
212
  public: {
207
- "product-detail": {
213
+ 'product-detail': {
208
214
  maxRecentlyViewedProducts: 15,
209
215
  },
210
216
  },
211
217
  },
212
- });
218
+ })
213
219
  ```
214
220
 
215
221
  ## 1.5.0
@@ -221,15 +227,15 @@
221
227
  Example usage:
222
228
 
223
229
  ```ts
224
- import { useRecentlyViewedProducts } from "#storefront-product-detail/composables";
230
+ import { useRecentlyViewedProducts } from '#storefront-product-detail/composables'
225
231
 
226
232
  const { products, addProduct, loadMissingProducts } =
227
- useRecentlyViewedProducts();
233
+ useRecentlyViewedProducts()
228
234
 
229
235
  onBeforeMount(async () => {
230
- addProductId(1234);
231
- await loadMissingProducts();
232
- });
236
+ addProductId(1234)
237
+ await loadMissingProducts()
238
+ })
233
239
  ```
234
240
 
235
241
  ## 1.4.2
@@ -261,44 +267,44 @@
261
267
 
262
268
  ```ts
263
269
  useProductSeoData({
264
- name: "Name",
265
- brand: "Brand",
266
- productDescription: "Description",
270
+ name: 'Name',
271
+ brand: 'Brand',
272
+ productDescription: 'Description',
267
273
  variants: variants.value.map((variant) => {
268
274
  return generateProductSchema({
269
- productName: "Name",
275
+ productName: 'Name',
270
276
  variant,
271
277
  url: `${$config.public.baseUrl}${route.fullPath}`,
272
278
  size,
273
- });
279
+ })
274
280
  }),
275
281
  images: images.value,
276
282
  productId: product.value?.id || 0,
277
283
  color: formatColors(colors.value),
278
284
  variesBy:
279
- variants.value.length > 1 ? ["https://schema.org/size"] : undefined,
280
- });
285
+ variants.value.length > 1 ? ['https://schema.org/size'] : undefined,
286
+ })
281
287
 
282
288
  // Will become
283
289
 
284
290
  useProductSeoData({
285
- name: "Name",
286
- brand: "Brand",
287
- productDescription: "Description",
291
+ name: 'Name',
292
+ brand: 'Brand',
293
+ productDescription: 'Description',
288
294
  variants: variants.value.map((variant) => {
289
295
  return generateProductSchema({
290
- productName: "Name",
296
+ productName: 'Name',
291
297
  variant,
292
298
  url: `${$config.public.baseUrl}${route.fullPath}`,
293
299
  size,
294
300
  images: images.value[0],
295
- });
301
+ })
296
302
  }),
297
303
  productId: product.value?.id || 0,
298
304
  color: formatColors(colors.value),
299
305
  variesBy:
300
- variants.value.length > 1 ? ["https://schema.org/size"] : undefined,
301
- });
306
+ variants.value.length > 1 ? ['https://schema.org/size'] : undefined,
307
+ })
302
308
  ```
303
309
 
304
310
  ## 1.2.0
@@ -355,7 +361,6 @@
355
361
 
356
362
  - This release introduces the `@scayle/storefront-product-detail` package, decoupling composables and utilities of the Product Detail Page (PDP) functionality from the SCAYLE Storefront Boilerplate for enhanced modularity and integration.
357
363
  This separation empowers developers with greater control over PDP updates and simplifies its integration into existing Storefront-based projects.
358
-
359
364
  - [Discover more about the Product Detail Page for SCAYLE Storefront in our SCAYLE Resource Center.](https://scayle.dev/en/core-documentation/storefront-guide/storefront-application/features/product-detail-page)
360
365
 
361
366
  - This release requires `@scayle/storefront-nuxt@8.x` or higher. Support for `@scayle/storefront-nuxt@7.x` has been discontinued. Please update your dependencies accordingly. The `peerDependency` range has been updated to `^8.0.0`.
@@ -363,7 +368,6 @@
363
368
  ### Minor Changes
364
369
 
365
370
  - Compared to the original implementation within the SCAYLE Storefront Boilerplate, the `@scayle/storefront-product-detail` package has received some improvements and refactoring:
366
-
367
371
  - Introduced the `getCombineWithProductIds` function within the product utility, enabling efficient retrieval of product combinations within product listings.
368
372
  - Added the `getFilteredAttributeGroups` function to the attribute utility, simplifying the retrieval and formatting of filtered product attributes for display in listings.
369
373
  - Introduced the `useProductSeoData` composable, automating the generation of SEO-critical metadata for product pages. This includes dynamically generated canonical links, robots meta tags, product titles, and structured data (`JSON-LD`) for product details and breadcrumbs, enhancing search engine visibility.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@scayle/storefront-product-detail",
3
3
  "configKey": "product-detail",
4
- "version": "1.9.0",
4
+ "version": "1.9.1",
5
5
  "compatibility": {
6
6
  "bridge": false,
7
7
  "nuxt": ">=3.13"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit';
2
2
 
3
3
  const PACKAGE_NAME = "@scayle/storefront-product-detail";
4
- const PACKAGE_VERSION = "1.9.0";
4
+ const PACKAGE_VERSION = "1.9.1";
5
5
  const module$1 = defineNuxtModule({
6
6
  meta: {
7
7
  name: PACKAGE_NAME,
@@ -3,10 +3,5 @@ export function useAllShopProductsForId({
3
3
  params,
4
4
  options
5
5
  } = {}, key = "useAllShopProductsById") {
6
- return useRpc(
7
- "getAllShopProductsForId",
8
- key,
9
- params,
10
- options
11
- );
6
+ return useRpc("getAllShopProductsForId", key, params, options);
12
7
  }
@@ -65,9 +65,7 @@ export function useRecentlyViewedProducts(options) {
65
65
  products.value.push(...result);
66
66
  }
67
67
  const sortOrderMap = new Map(
68
- recentlyViewedProductsIds.value.map(
69
- (id, index) => [id, index]
70
- )
68
+ recentlyViewedProductsIds.value.map((id, index) => [id, index])
71
69
  );
72
70
  products.value = products.value.sort((a, b) => {
73
71
  const aIndex = sortOrderMap.get(a.id);
@@ -1,33 +1,36 @@
1
1
  import { defineRpcHandler } from "@scayle/storefront-nuxt";
2
- export const getAllShopProductsForId = defineRpcHandler(async (options, context) => {
3
- const { runtimeConfiguration, sapiClient, log, cached } = context;
4
- const fetchAllProducts = async () => {
5
- const products = [];
6
- const shops = Object.values(
7
- runtimeConfiguration.storefront.shops
8
- );
9
- await Promise.all(
10
- shops.map(async (shop) => {
11
- try {
12
- const product = await sapiClient.clone({ shopId: shop.shopId }).products.getById(options.id, {
13
- with: options.with
14
- });
15
- products.push({
16
- locale: shop.locale,
17
- path: Array.isArray(shop.path) ? shop.path[0] : shop.path,
18
- product
19
- });
20
- } catch (error) {
21
- log.error(error instanceof Error ? error : String(error));
22
- }
23
- })
24
- );
25
- return products;
26
- };
27
- const result = await cached(fetchAllProducts, {
28
- cacheKeyPrefix: `getAllShopProductsForId-${options.id}`,
29
- ttl: 12 * 60 * 60
30
- // 12 hours
31
- })();
32
- return result;
33
- }, { method: "GET" });
2
+ export const getAllShopProductsForId = defineRpcHandler(
3
+ async (options, context) => {
4
+ const { runtimeConfiguration, sapiClient, log, cached } = context;
5
+ const fetchAllProducts = async () => {
6
+ const products = [];
7
+ const shops = Object.values(
8
+ runtimeConfiguration.storefront.shops
9
+ );
10
+ await Promise.all(
11
+ shops.map(async (shop) => {
12
+ try {
13
+ const product = await sapiClient.clone({ shopId: shop.shopId }).products.getById(options.id, {
14
+ with: options.with
15
+ });
16
+ products.push({
17
+ locale: shop.locale,
18
+ path: Array.isArray(shop.path) ? shop.path[0] : shop.path,
19
+ product
20
+ });
21
+ } catch (error) {
22
+ log.error(error instanceof Error ? error : String(error));
23
+ }
24
+ })
25
+ );
26
+ return products;
27
+ };
28
+ const result = await cached(fetchAllProducts, {
29
+ cacheKeyPrefix: `getAllShopProductsForId-${options.id}`,
30
+ ttl: 12 * 60 * 60
31
+ // 12 hours
32
+ })();
33
+ return result;
34
+ },
35
+ { method: "GET" }
36
+ );
@@ -5,34 +5,34 @@ import {
5
5
  MIN_WITH_PARAMS_PRODUCT,
6
6
  defineRpcHandler
7
7
  } from "@scayle/storefront-nuxt";
8
- export const getSimilarProducts = defineRpcHandler(async (options, context) => {
9
- const { sapiClient, cached, withParams } = context;
10
- const campaignKey = await context.callRpc?.("getCampaignKey");
11
- const { productId, with: withParameter, ...rest } = options;
12
- const fetch = async (productId2, params) => {
13
- try {
14
- return sapiClient.recommendations.getSimilarProducts(productId2, params);
15
- } catch (e) {
16
- if (e instanceof FetchError) {
17
- const response = e.response;
18
- return new ErrorResponse(
19
- response.status,
20
- response.statusText,
21
- "Failed to fetch similar products"
22
- );
8
+ export const getSimilarProducts = defineRpcHandler(
9
+ async (options, context) => {
10
+ const { sapiClient, cached, withParams } = context;
11
+ const campaignKey = await context.callRpc?.("getCampaignKey");
12
+ const { productId, with: withParameter, ...rest } = options;
13
+ const fetch = async (productId2, params) => {
14
+ try {
15
+ return sapiClient.recommendations.getSimilarProducts(productId2, params);
16
+ } catch (e) {
17
+ if (e instanceof FetchError) {
18
+ const response = e.response;
19
+ return new ErrorResponse(
20
+ response.status,
21
+ response.statusText,
22
+ "Failed to fetch similar products"
23
+ );
24
+ }
25
+ throw e;
23
26
  }
24
- throw e;
25
- }
26
- };
27
- return await cached(
28
- fetch,
29
- {
27
+ };
28
+ return await cached(fetch, {
30
29
  cacheKeyPrefix: "getSimilarProducts-products",
31
30
  ttl: 5 * MINUTE
32
- }
33
- )(productId, {
34
- with: withParameter ?? withParams?.product ?? MIN_WITH_PARAMS_PRODUCT,
35
- campaignKey,
36
- ...rest
37
- });
38
- }, { method: "GET" });
31
+ })(productId, {
32
+ with: withParameter ?? withParams?.product ?? MIN_WITH_PARAMS_PRODUCT,
33
+ campaignKey,
34
+ ...rest
35
+ });
36
+ },
37
+ { method: "GET" }
38
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-product-detail",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Collection of essential composables and utilities to work with product detail",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -40,27 +40,27 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@arethetypeswrong/cli": "0.18.2",
43
- "@nuxt/kit": "^3.20.2",
43
+ "@nuxt/kit": "^3.21.7",
44
44
  "@nuxt/module-builder": "1.0.2",
45
- "@nuxt/schema": "^3.20.2",
45
+ "@nuxt/schema": "^3.21.7",
46
46
  "@nuxt/test-utils": "4.0.0",
47
47
  "@scayle/eslint-config-storefront": "^4.8.0",
48
- "@types/node": "22.19.17",
49
- "@vitest/coverage-v8": "4.1.5",
50
- "@vueuse/core": "14.2.1",
48
+ "@types/node": "24.12.2",
49
+ "@vitest/coverage-v8": "4.1.9",
50
+ "@vueuse/core": "14.3.0",
51
51
  "eslint-formatter-gitlab": "7.1.0",
52
- "eslint": "10.2.1",
53
- "happy-dom": "20.9.0",
54
- "nuxt": "^3.20.2",
55
- "publint": "0.3.18",
52
+ "eslint": "10.5.0",
53
+ "happy-dom": "20.10.6",
54
+ "nuxt": "^3.21.7",
55
+ "publint": "0.3.21",
56
56
  "schema-dts": "1.1.5",
57
- "typescript": "5.9.3",
57
+ "typescript": "6.0.3",
58
58
  "unbuild": "3.6.1",
59
- "vitest": "4.1.5",
59
+ "vitest": "4.1.9",
60
60
  "vue-router": "4.6.4",
61
- "vue-tsc": "3.2.6",
62
- "vue": "3.5.32",
63
- "@scayle/storefront-nuxt": "8.61.0",
61
+ "vue-tsc": "3.3.5",
62
+ "vue": "3.5.38",
63
+ "@scayle/storefront-nuxt": "8.62.1",
64
64
  "@scayle/vitest-config-storefront": "1.0.0"
65
65
  },
66
66
  "scripts": {