@revenexx/cover 0.1.10 → 0.1.12

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/app/app.config.ts CHANGED
@@ -24,6 +24,12 @@ export default defineAppConfig({
24
24
  orderService: "mock",
25
25
  inventoryService: "mock",
26
26
  themeService: "mock",
27
+ // Search UX defaults the layer's components read — apps may override,
28
+ // but the layer must ship working values (a missing `search` object
29
+ // crashed SearchInput with "Cannot read … 'autocompleteMinChars'").
30
+ search: {
31
+ autocompleteMinChars: 2,
32
+ },
27
33
  ui: {
28
34
  icons: nuxtUiIcons(DEFAULT_ICON_WEIGHT),
29
35
  },
@@ -16,7 +16,10 @@ interface Suggestion {
16
16
  }
17
17
 
18
18
  const { t } = useI18n();
19
- const { search: searchConfig } = useAppConfig();
19
+ // The layer ships a `search` default, but a consumer that overrides
20
+ // app.config without it must not crash the header — fall back locally.
21
+ const appConfig = useAppConfig();
22
+ const autocompleteMinChars = computed(() => appConfig.search?.autocompleteMinChars ?? 2);
20
23
 
21
24
  const query = ref<string>("");
22
25
  const emit = defineEmits<{ search: [query: string] }>();
@@ -59,7 +62,7 @@ const debouncedFetch = useDebounceFn(fetchSuggestions, 250);
59
62
 
60
63
  watch(query, (val) => {
61
64
  activeIndex.value = -1;
62
- if (!val.trim() || val.trim().length < searchConfig.autocompleteMinChars) {
65
+ if (!val.trim() || val.trim().length < autocompleteMinChars.value) {
63
66
  suggestions.value = [];
64
67
  return;
65
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/cover",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Cover \u2014 revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI components, theming tokens and stores shared by the demo shop, custom storefronts and the Blokkli theme.",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -11,13 +11,7 @@ export default defineEventHandler(async (event) => {
11
11
  if (!detail) {
12
12
  throw createError({ statusCode: 404, message: "Product not found" });
13
13
  }
14
- if (resolvePriceServiceKey(event) === "api") {
15
- detail = await enrichDetailWithLivePrices(event, detail);
16
- }
17
- if (resolveInventoryServiceKey(event) === "api") {
18
- detail = await enrichDetailWithLiveAvailability(event, detail);
19
- }
20
- return detail;
14
+ return await enrichDetailLive(event, detail);
21
15
  }
22
16
  catch (err) {
23
17
  if (err !== null && typeof err === "object" && "statusCode" in err) {
@@ -10,15 +10,8 @@ export default defineEventHandler(async (event) => {
10
10
  if (!product) {
11
11
  throw createError({ status: 404, message: "Product not found" });
12
12
  }
13
- if (resolvePriceServiceKey(event) === "api") {
14
- const [enriched] = await enrichProductsWithLivePrices(event, [product]);
15
- product = enriched ?? product;
16
- }
17
- if (resolveInventoryServiceKey(event) === "api") {
18
- const [enriched] = await enrichProductsWithLiveAvailability(event, [product]);
19
- product = enriched ?? product;
20
- }
21
- return product;
13
+ const [enriched] = await enrichProductsLive(event, [product]);
14
+ return enriched ?? product;
22
15
  }
23
16
  catch (err) {
24
17
  if (err !== null && typeof err === "object" && "statusCode" in err) {
@@ -5,14 +5,8 @@ export default defineEventHandler(async (event) => {
5
5
  };
6
6
 
7
7
  try {
8
- let products = await getProductService(event).listProducts({ category, subcategory, locale });
9
- if (resolvePriceServiceKey(event) === "api") {
10
- products = await enrichProductsWithLivePrices(event, products);
11
- }
12
- if (resolveInventoryServiceKey(event) === "api") {
13
- products = await enrichProductsWithLiveAvailability(event, products);
14
- }
15
- return products;
8
+ const products = await getProductService(event).listProducts({ category, subcategory, locale });
9
+ return await enrichProductsLive(event, products);
16
10
  }
17
11
  catch (err) {
18
12
  getLogService().error("Service error: products/list", toErrorContext(err));
@@ -88,13 +88,8 @@ export default defineEventHandler(async (event) => {
88
88
  perPage,
89
89
  });
90
90
 
91
- let products: ProductList[] = (results.hits ?? []).map(hit => mapLiveDocToProduct(hit.document));
92
- if (resolvePriceServiceKey(event) === "api") {
93
- products = await enrichProductsWithLivePrices(event, products);
94
- }
95
- if (resolveInventoryServiceKey(event) === "api") {
96
- products = await enrichProductsWithLiveAvailability(event, products);
97
- }
91
+ const mapped: ProductList[] = (results.hits ?? []).map(hit => mapLiveDocToProduct(hit.document));
92
+ const products: ProductList[] = await enrichProductsLive(event, mapped);
98
93
  const facets: SearchFacet[] = (results.facet_counts ?? []).map(fc => ({
99
94
  fieldName: fc.field_name,
100
95
  counts: (fc.counts ?? []).map(c => ({ value: c.value, count: c.count })),
@@ -0,0 +1,51 @@
1
+ import type { H3Event } from "h3";
2
+
3
+ import type { Product } from "../../app/composables/useProducts";
4
+ import type { ProductDetail } from "../../app/interfaces/product-detail";
5
+
6
+ /**
7
+ * Live catalog enrichment fan-out: prices and availability come from two
8
+ * independent apps — fetch them IN PARALLEL and merge their fields. The
9
+ * sequential await-chain this replaces paid both round-trips back to back
10
+ * on every PDP and listing request (~2x upstream latency).
11
+ * Respects the per-request service keys; mock domains stay untouched.
12
+ */
13
+ export async function enrichProductsLive<T extends Product>(event: H3Event, products: T[]): Promise<T[]> {
14
+ if (products.length === 0) {
15
+ return products;
16
+ }
17
+ const wantPrices = resolvePriceServiceKey(event) === "api";
18
+ const wantStock = resolveInventoryServiceKey(event) === "api";
19
+ if (!wantPrices && !wantStock) {
20
+ return products;
21
+ }
22
+ const [priced, stocked] = await Promise.all([
23
+ wantPrices ? enrichProductsWithLivePrices(event, products) : null,
24
+ wantStock ? enrichProductsWithLiveAvailability(event, products) : null,
25
+ ]);
26
+ return products.map((product, i) => ({
27
+ ...product,
28
+ ...(priced?.[i] ? { prices: priced[i].prices } : {}),
29
+ ...(stocked?.[i]
30
+ ? { stocks: stocked[i].stocks, isOutOfStock: stocked[i].isOutOfStock }
31
+ : {}),
32
+ }));
33
+ }
34
+
35
+ /** Single-detail variant for the PDP payload. */
36
+ export async function enrichDetailLive(event: H3Event, detail: ProductDetail): Promise<ProductDetail> {
37
+ const wantPrices = resolvePriceServiceKey(event) === "api";
38
+ const wantStock = resolveInventoryServiceKey(event) === "api";
39
+ if (!wantPrices && !wantStock) {
40
+ return detail;
41
+ }
42
+ const [priced, stocked] = await Promise.all([
43
+ wantPrices ? enrichDetailWithLivePrices(event, detail) : null,
44
+ wantStock ? enrichDetailWithLiveAvailability(event, detail) : null,
45
+ ]);
46
+ return {
47
+ ...detail,
48
+ ...(priced ? { prices: priced.prices } : {}),
49
+ ...(stocked ? { stocks: stocked.stocks } : {}),
50
+ };
51
+ }