@scayle/storefront-product-detail 1.5.2 → 1.5.3

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,11 @@
1
1
  # @scayle/storefront-product-detail
2
2
 
3
+ ## 1.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - **\[FIX\]** Added logic to prevent simultaneously loading the same products when `loadMissingProducts` from `useRecentlyViewedProducts` is called multiple times concurrently. This ensures that products are not added to the recently viewed list more than once.
8
+
3
9
  ## 1.5.2
4
10
 
5
11
  ### Patch Changes
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.5.2",
4
+ "version": "1.5.3",
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.5.2";
4
+ const PACKAGE_VERSION = "1.5.3";
5
5
  const module = defineNuxtModule({
6
6
  meta: {
7
7
  name: PACKAGE_NAME,
@@ -16,6 +16,10 @@ export function useRecentlyViewedProducts(options) {
16
16
  () => "idle"
17
17
  );
18
18
  const products = useState("recently-viewed-products", () => []);
19
+ const loadingPromise = useState(
20
+ "recently-viewed-products-loading-promise",
21
+ () => /* @__PURE__ */ new Map()
22
+ );
19
23
  const addProductId = (productId) => {
20
24
  if (import.meta.server) {
21
25
  throw new Error(
@@ -38,46 +42,56 @@ export function useRecentlyViewedProducts(options) {
38
42
  "useRecentlyViewedProducts uses local storage. fetchProducts is therefore not available on the server."
39
43
  );
40
44
  }
41
- loading.value = true;
42
- status.value = "pending";
43
- try {
44
- const loadedProductIds = new Set(
45
- products.value.map((product) => product.id)
46
- );
47
- const missingProductIds = recentlyViewedProductsIds.value.filter(
48
- (id) => !loadedProductIds.has(id)
49
- );
50
- if (missingProductIds.length > 0) {
51
- const result = await fetchProductsRpc({
52
- ids: missingProductIds,
53
- with: options?.with,
54
- pricePromotionKey: options?.pricePromotionKey
55
- });
56
- products.value.push(...result);
57
- }
58
- const sortOrderMap = new Map(
59
- recentlyViewedProductsIds.value.map(
60
- (id, index) => [id, index]
61
- )
62
- );
63
- products.value = products.value.sort((a, b) => {
64
- const aIndex = sortOrderMap.get(a.id);
65
- const bIndex = sortOrderMap.get(b.id);
66
- if (aIndex === void 0) {
67
- return 1;
68
- }
69
- if (bIndex === void 0) {
70
- return -1;
71
- }
72
- return aIndex - bIndex;
73
- }).slice(0, maxRecentlyViewedProducts);
74
- status.value = "success";
75
- } catch (e) {
76
- error.value = e;
77
- status.value = "error";
78
- } finally {
79
- loading.value = false;
45
+ const loadedProductIds = new Set(
46
+ products.value.map((product) => product.id)
47
+ );
48
+ const missingProductIds = recentlyViewedProductsIds.value.filter(
49
+ (id) => !loadedProductIds.has(id)
50
+ );
51
+ const promiseKey = missingProductIds.join(",");
52
+ if (loadingPromise.value.has(promiseKey)) {
53
+ return loadingPromise.value.get(promiseKey);
80
54
  }
55
+ const load = async () => {
56
+ loading.value = true;
57
+ status.value = "pending";
58
+ try {
59
+ if (missingProductIds.length > 0) {
60
+ const result = await fetchProductsRpc({
61
+ ids: missingProductIds,
62
+ with: options?.with,
63
+ pricePromotionKey: options?.pricePromotionKey
64
+ });
65
+ products.value.push(...result);
66
+ }
67
+ const sortOrderMap = new Map(
68
+ recentlyViewedProductsIds.value.map(
69
+ (id, index) => [id, index]
70
+ )
71
+ );
72
+ products.value = products.value.sort((a, b) => {
73
+ const aIndex = sortOrderMap.get(a.id);
74
+ const bIndex = sortOrderMap.get(b.id);
75
+ if (aIndex === void 0) {
76
+ return 1;
77
+ }
78
+ if (bIndex === void 0) {
79
+ return -1;
80
+ }
81
+ return aIndex - bIndex;
82
+ }).slice(0, maxRecentlyViewedProducts);
83
+ status.value = "success";
84
+ } catch (e) {
85
+ error.value = e;
86
+ status.value = "error";
87
+ } finally {
88
+ loading.value = false;
89
+ loadingPromise.value.delete(promiseKey);
90
+ }
91
+ };
92
+ const promise = load();
93
+ loadingPromise.value.set(promiseKey, promise);
94
+ return promise;
81
95
  };
82
96
  return {
83
97
  addProductId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-product-detail",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Collection of essential composables and utilities to work with product detail",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -41,23 +41,23 @@
41
41
  "@nuxt/module-builder": "1.0.2",
42
42
  "@nuxt/schema": "3.19.2",
43
43
  "@nuxt/test-utils": "3.19.2",
44
- "@types/node": "22.18.3",
44
+ "@types/node": "22.18.6",
45
45
  "@vitest/coverage-v8": "3.2.4",
46
46
  "@vueuse/core": "13.9.0",
47
47
  "dprint": "0.50.2",
48
48
  "eslint-formatter-gitlab": "6.0.1",
49
- "eslint": "9.35.0",
49
+ "eslint": "9.36.0",
50
50
  "happy-dom": "18.0.1",
51
51
  "nuxt": "3.19.2",
52
- "publint": "0.3.12",
52
+ "publint": "0.3.13",
53
53
  "schema-dts": "1.1.5",
54
54
  "typescript": "5.9.2",
55
55
  "unbuild": "3.6.1",
56
56
  "vitest": "3.2.4",
57
57
  "vue-router": "4.5.1",
58
- "vue-tsc": "3.0.7",
58
+ "vue-tsc": "3.1.0",
59
59
  "vue": "3.5.21",
60
- "@scayle/eslint-config-storefront": "4.7.8",
60
+ "@scayle/eslint-config-storefront": "4.7.9",
61
61
  "@scayle/storefront-nuxt": "8.44.1",
62
62
  "@scayle/vitest-config-storefront": "1.0.0"
63
63
  },