@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.2

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 (163) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/utils/constants.d.ts +28 -0
  6. package/dist/utils/constants.d.ts.map +1 -0
  7. package/dist/utils/constants.js +17 -0
  8. package/dist/utils/constants.js.map +1 -0
  9. package/dist/utils/cookieSanitizer.d.ts +87 -0
  10. package/dist/utils/cookieSanitizer.d.ts.map +1 -0
  11. package/dist/utils/cookieSanitizer.js +136 -0
  12. package/dist/utils/cookieSanitizer.js.map +1 -0
  13. package/dist/utils/operationRouter.d.ts +25 -0
  14. package/dist/utils/operationRouter.d.ts.map +1 -0
  15. package/dist/utils/operationRouter.js +120 -0
  16. package/dist/utils/operationRouter.js.map +1 -0
  17. package/dist/utils/types.d.ts +944 -0
  18. package/dist/utils/types.d.ts.map +1 -0
  19. package/dist/utils/types.js +25 -0
  20. package/dist/utils/types.js.map +1 -0
  21. package/dist/vtexClient.d.ts +173 -0
  22. package/dist/vtexClient.d.ts.map +1 -0
  23. package/dist/vtexClient.js +242 -0
  24. package/dist/vtexClient.js.map +1 -0
  25. package/package.json +16 -57
  26. package/src/index.ts +11 -29
  27. package/src/utils/constants.ts +2 -56
  28. package/src/utils/cookieSanitizer.ts +59 -59
  29. package/src/utils/operationRouter.ts +75 -65
  30. package/src/utils/types.ts +709 -1557
  31. package/src/vtexClient.ts +358 -347
  32. package/src/README.md +0 -6
  33. package/src/__conformance__/upstream.test.ts +0 -158
  34. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  35. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  36. package/src/__tests__/schemas.test.ts +0 -71
  37. package/src/actions/address.ts +0 -250
  38. package/src/actions/analytics/sendEvent.ts +0 -86
  39. package/src/actions/auth.ts +0 -333
  40. package/src/actions/checkout.ts +0 -659
  41. package/src/actions/index.ts +0 -11
  42. package/src/actions/masterData.ts +0 -168
  43. package/src/actions/misc.ts +0 -188
  44. package/src/actions/newsletter.ts +0 -105
  45. package/src/actions/orders.ts +0 -34
  46. package/src/actions/profile.ts +0 -201
  47. package/src/actions/session.ts +0 -85
  48. package/src/actions/trigger.ts +0 -43
  49. package/src/actions/wishlist.ts +0 -114
  50. package/src/client.ts +0 -724
  51. package/src/commerceLoaders.ts +0 -267
  52. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  53. package/src/hooks/__tests__/createCart.test.ts +0 -92
  54. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  55. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  56. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  57. package/src/hooks/cartQuery.ts +0 -204
  58. package/src/hooks/createCart.ts +0 -427
  59. package/src/hooks/createUseCart.ts +0 -360
  60. package/src/hooks/createUseUser.ts +0 -153
  61. package/src/hooks/createUseWishlist.ts +0 -242
  62. package/src/hooks/index.ts +0 -25
  63. package/src/hooks/useAutocomplete.ts +0 -83
  64. package/src/hooks/useCart.ts +0 -243
  65. package/src/hooks/useUser.ts +0 -78
  66. package/src/hooks/useWishlist.ts +0 -119
  67. package/src/invoke.ts +0 -235
  68. package/src/loaders/ProductDetailsPage.ts +0 -1
  69. package/src/loaders/ProductList.ts +0 -1
  70. package/src/loaders/ProductListingPage.ts +0 -1
  71. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  72. package/src/loaders/address.ts +0 -115
  73. package/src/loaders/autocomplete.ts +0 -56
  74. package/src/loaders/brands.ts +0 -44
  75. package/src/loaders/cart/attachments.ts +0 -53
  76. package/src/loaders/cart/full.ts +0 -67
  77. package/src/loaders/cart/gifts.ts +0 -43
  78. package/src/loaders/cart/orderFormId.ts +0 -31
  79. package/src/loaders/cart/shipping.test.ts +0 -125
  80. package/src/loaders/cart/shipping.ts +0 -136
  81. package/src/loaders/cart/summary.ts +0 -36
  82. package/src/loaders/cart.ts +0 -52
  83. package/src/loaders/catalog.ts +0 -163
  84. package/src/loaders/collections.ts +0 -55
  85. package/src/loaders/index.ts +0 -20
  86. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  87. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  88. package/src/loaders/intelligentSearch/productList.ts +0 -170
  89. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  90. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  91. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  92. package/src/loaders/legacy/productList.ts +0 -1
  93. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  94. package/src/loaders/legacy.ts +0 -710
  95. package/src/loaders/logistics.ts +0 -111
  96. package/src/loaders/minicart.ts +0 -78
  97. package/src/loaders/navbar.ts +0 -26
  98. package/src/loaders/orders.ts +0 -92
  99. package/src/loaders/pageType.ts +0 -68
  100. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  101. package/src/loaders/payment.ts +0 -97
  102. package/src/loaders/productListFull.ts +0 -157
  103. package/src/loaders/profile.ts +0 -138
  104. package/src/loaders/promotion.ts +0 -30
  105. package/src/loaders/search.ts +0 -124
  106. package/src/loaders/session.ts +0 -83
  107. package/src/loaders/user.ts +0 -87
  108. package/src/loaders/wishlist.ts +0 -89
  109. package/src/loaders/wishlistProducts.ts +0 -69
  110. package/src/loaders/workflow/products.ts +0 -62
  111. package/src/loaders/workflow.ts +0 -316
  112. package/src/logo.png +0 -0
  113. package/src/manifest.gen.ts +0 -89
  114. package/src/middleware.cacheHeaders.test.ts +0 -44
  115. package/src/middleware.ts +0 -240
  116. package/src/mod.ts +0 -173
  117. package/src/registry.ts +0 -9
  118. package/src/schemas.gen.ts +0 -3191
  119. package/src/schemas.ts +0 -30
  120. package/src/types.ts +0 -248
  121. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  122. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  123. package/src/utils/__tests__/fetch.test.ts +0 -80
  124. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  125. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  126. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  127. package/src/utils/__tests__/minicart.test.ts +0 -184
  128. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  129. package/src/utils/__tests__/resilience.test.ts +0 -205
  130. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  131. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  132. package/src/utils/__tests__/sitemap.test.ts +0 -185
  133. package/src/utils/__tests__/slugify.test.ts +0 -31
  134. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  135. package/src/utils/__tests__/transform.test.ts +0 -1111
  136. package/src/utils/accountLoaders.ts +0 -203
  137. package/src/utils/authHelpers.ts +0 -85
  138. package/src/utils/batch.ts +0 -18
  139. package/src/utils/buildOfferShelf.test.ts +0 -59
  140. package/src/utils/cartProjection.ts +0 -96
  141. package/src/utils/cookies.ts +0 -167
  142. package/src/utils/enrichment.ts +0 -556
  143. package/src/utils/fetch.ts +0 -111
  144. package/src/utils/fetchCache.ts +0 -55
  145. package/src/utils/index.ts +0 -19
  146. package/src/utils/instrumentedFetch.ts +0 -98
  147. package/src/utils/intelligentSearch.ts +0 -96
  148. package/src/utils/legacy.ts +0 -139
  149. package/src/utils/minicart.ts +0 -139
  150. package/src/utils/pickAndOmit.ts +0 -25
  151. package/src/utils/proxy.ts +0 -495
  152. package/src/utils/resilience.ts +0 -352
  153. package/src/utils/resourceRange.ts +0 -10
  154. package/src/utils/segment.ts +0 -152
  155. package/src/utils/similars.ts +0 -35
  156. package/src/utils/simulationCache.ts +0 -73
  157. package/src/utils/sitemap.ts +0 -283
  158. package/src/utils/slugCache.ts +0 -28
  159. package/src/utils/slugify.ts +0 -13
  160. package/src/utils/transform.ts +0 -1691
  161. package/src/utils/vtexId.ts +0 -151
  162. package/src/vtexClient.test.ts +0 -204
  163. package/tsconfig.json +0 -7
@@ -1,55 +0,0 @@
1
- /**
2
- * VTEX SWR fetch cache — a thin binding over the shared, instrumented
3
- * `createFetchCache` in `@decocms/blocks/sdk/fetchCache`.
4
- *
5
- * The implementation (in-flight dedup, stale-while-revalidate, stale-if-error,
6
- * inflight backstop) now lives ONCE in the framework so every commerce app
7
- * shares it and emits `deco.cache.requests{layer="swr"}` automatically. This
8
- * module just wires VTEX's tuning constants (`./constants`) and the
9
- * `provider: "vtex"` label into a single module-level instance, preserving the
10
- * public surface (`fetchWithCache` / `clearFetchCache` / `getFetchCacheStats` /
11
- * `FetchCacheOptions`) that `client.ts` and existing tests depend on.
12
- */
13
-
14
- import {
15
- createFetchCache,
16
- type FetchCacheOptions as SharedFetchCacheOptions,
17
- } from "@decocms/blocks/sdk/fetchCache";
18
- import {
19
- FETCH_CACHE_FRESH_TTL_MS,
20
- FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
21
- FETCH_CACHE_MAX_ENTRIES,
22
- FETCH_CACHE_STALE_IF_ERROR_MS,
23
- } from "./constants";
24
-
25
- export type FetchCacheOptions = SharedFetchCacheOptions;
26
-
27
- const cache = createFetchCache({
28
- provider: "vtex",
29
- maxEntries: FETCH_CACHE_MAX_ENTRIES,
30
- freshTtlMs: FETCH_CACHE_FRESH_TTL_MS,
31
- staleIfErrorMs: FETCH_CACHE_STALE_IF_ERROR_MS,
32
- inflightBackstopMs: FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
33
- });
34
-
35
- /**
36
- * Wrap a GET fetch call with SWR caching and in-flight dedup.
37
- *
38
- * Returns `null` for non-2xx responses that are cached (e.g. 404).
39
- * 5xx responses throw so the caller can handle them explicitly.
40
- */
41
- export function fetchWithCache<T>(
42
- cacheKey: string,
43
- doFetch: () => Promise<Response>,
44
- opts?: FetchCacheOptions,
45
- ): Promise<T | null> {
46
- return cache.fetchWithCache<T>(cacheKey, doFetch, opts);
47
- }
48
-
49
- export function clearFetchCache() {
50
- cache.clear();
51
- }
52
-
53
- export function getFetchCacheStats() {
54
- return cache.getStats();
55
- }
@@ -1,19 +0,0 @@
1
- export type { PersonalDataOptions } from "./accountLoaders";
2
- export { vtexAccountLoaders } from "./accountLoaders";
3
- export * from "./batch";
4
- export * from "./cookies";
5
- export * from "./enrichment";
6
- export * from "./fetchCache";
7
- export * from "./intelligentSearch";
8
- export * from "./legacy";
9
- export * from "./pickAndOmit";
10
- export * from "./proxy";
11
- export * from "./resourceRange";
12
- export * from "./segment";
13
- export * from "./similars";
14
- export * from "./sitemap";
15
- export * from "./slugCache";
16
- export * from "./slugify";
17
- export * from "./transform";
18
- export * from "./types";
19
- export * from "./vtexId";
@@ -1,98 +0,0 @@
1
- /**
2
- * Pre-wired instrumented fetch factory for VTEX.
3
- *
4
- * Bundles the three pieces a storefront would otherwise have to wire
5
- * by hand:
6
- *
7
- * 1. The `createInstrumentedFetch` boundary from `@decocms/start`
8
- * (spans, traceparent injection, URL redaction, cache-header
9
- * span attributes).
10
- * 2. The `vtexOperationRouter` URL→operation mapping so unannotated
11
- * callsites still get semantic span names + histogram labels.
12
- * 3. An `onComplete` callback that records every call into the
13
- * canonical `http.client.request.duration` histogram via the
14
- * framework's `recordCommerceMetric(...)` helper in
15
- * `@decocms/start/sdk/observability` — `provider`, `operation`,
16
- * `status_class`, and `cached` labels.
17
- *
18
- * Sites opt in once at startup:
19
- *
20
- * ```ts
21
- * import { setVtexFetch } from "@decocms/apps/vtex";
22
- * import { createVtexFetch } from "@decocms/apps/vtex";
23
- *
24
- * setVtexFetch(createVtexFetch());
25
- * ```
26
- *
27
- * Sites that need to wrap a custom underlying fetch (cookie passthrough,
28
- * proxy, retry, etc.) pass it as `baseFetch`. The instrumentation
29
- * still applies — `createInstrumentedFetch` preserves the wrapped
30
- * behavior.
31
- */
32
-
33
- import type { FetchFn } from "@decocms/blocks/sdk/fetchTimeout";
34
- import {
35
- createInstrumentedFetch,
36
- type InstrumentedFetch,
37
- } from "@decocms/blocks/sdk/instrumentedFetch";
38
- import { recordCommerceMetric, statusClassFor } from "@decocms/blocks/sdk/observability";
39
- import { vtexOperationRouter } from "./operationRouter";
40
- import { createResilientFetch, type ResilienceConfig } from "./resilience";
41
-
42
- export interface CreateVtexFetchOptions {
43
- /**
44
- * Underlying fetch to wrap. Defaults to `globalThis.fetch`.
45
- * Pass an existing custom fetch (e.g. one that injects auth cookies
46
- * or routes through a proxy) to preserve its behavior while adding
47
- * the VTEX instrumentation layer on top.
48
- */
49
- baseFetch?: FetchFn;
50
- /**
51
- * Disable the `http.client.request.duration` histogram emission for
52
- * VTEX calls. The framework's span and structured logs still emit.
53
- * Useful when the consumer wants to record its own histogram with a
54
- * custom shape. Default: false.
55
- */
56
- disableHistogram?: boolean;
57
- /**
58
- * Resilience layer (real abort/timeout + per-host circuit breaker +
59
- * idempotent-only retry with a budget). ON by default so every VTEX
60
- * consumer is protected against upstream slowness/outages without
61
- * per-site wiring. Pass a partial config to tune, `false` to opt out,
62
- * or flip the `VTEX_RESILIENCE_DISABLED=true` env var as a runtime
63
- * kill-switch. See {@link createResilientFetch}.
64
- */
65
- resilience?: Partial<ResilienceConfig> | false;
66
- }
67
-
68
- /**
69
- * Construct a pre-wired VTEX `InstrumentedFetch`. Pass the result to
70
- * `setVtexFetch(...)`. See module docstring for details.
71
- */
72
- export function createVtexFetch(options: CreateVtexFetchOptions = {}): InstrumentedFetch {
73
- const { baseFetch, disableHistogram = false, resilience } = options;
74
- // Compose the resilience layer UNDER the instrumentation so spans/histograms
75
- // see the real outcome (including breaker fast-fails and timeouts). A single
76
- // instrumented call = one logical request, which may span several underlying
77
- // attempts. Covers every VTEX egress path — cached GETs, IS, and checkout
78
- // POSTs — because they all funnel through this one fetch.
79
- const resilientBase =
80
- resilience === false
81
- ? baseFetch
82
- : createResilientFetch(baseFetch ?? globalThis.fetch, resilience ?? {});
83
- return createInstrumentedFetch({
84
- name: "vtex",
85
- baseFetch: resilientBase,
86
- resolveOperation: vtexOperationRouter,
87
- onComplete: disableHistogram
88
- ? undefined
89
- : ({ operation, status, durationMs, cached }) => {
90
- recordCommerceMetric(durationMs, {
91
- provider: "vtex",
92
- operation,
93
- status_class: statusClassFor(status),
94
- cached,
95
- });
96
- },
97
- });
98
- }
@@ -1,96 +0,0 @@
1
- import { vtexFetch } from "../client";
2
- import type { PageType, SelectedFacet, SimulationBehavior, Sort } from "./types";
3
-
4
- export const SESSION_COOKIE = "vtex_is_session";
5
- export const ANONYMOUS_COOKIE = "vtex_is_anonymous";
6
-
7
- export const withDefaultFacets = (allFacets: readonly SelectedFacet[]) => {
8
- return [...allFacets];
9
- };
10
-
11
- export const toPath = (facets: SelectedFacet[]) =>
12
- facets.map(({ key, value }) => (key ? `${key}/${value}` : value)).join("/");
13
-
14
- interface Params {
15
- query: string;
16
- page: number;
17
- count: number;
18
- sort: Sort;
19
- fuzzy: string;
20
- locale: string;
21
- hideUnavailableItems: boolean;
22
- simulationBehavior: SimulationBehavior;
23
- }
24
-
25
- export const withDefaultParams = ({
26
- query = "",
27
- page = 0,
28
- count = 12,
29
- sort = "",
30
- fuzzy = "auto",
31
- locale = "pt-BR",
32
- hideUnavailableItems,
33
- simulationBehavior = "default",
34
- }: Partial<Params>) => ({
35
- page: page + 1,
36
- count,
37
- query,
38
- sort,
39
- ...(fuzzy ? { fuzzy } : {}),
40
- locale,
41
- hideUnavailableItems: hideUnavailableItems ?? false,
42
- simulationBehavior,
43
- });
44
-
45
- export const isFilterParam = (keyFilter: string): boolean => keyFilter.startsWith("filter.");
46
-
47
- /**
48
- * Valid VTEX Intelligent Search sort values.
49
- * Anything else (e.g. "orders:desc)" with a trailing paren from legacy URLs)
50
- * causes IS API to return 400.
51
- */
52
- export const VALID_IS_SORTS = new Set([
53
- "",
54
- "orders:desc",
55
- "price:asc",
56
- "price:desc",
57
- "name:asc",
58
- "name:desc",
59
- "release:desc",
60
- "discount:desc",
61
- ]);
62
-
63
- /** Sanitize an IS sort parameter — returns empty string for invalid values. */
64
- export function sanitizeISSort(sort: string): string {
65
- return VALID_IS_SORTS.has(sort) ? sort : "";
66
- }
67
-
68
- const segmentsFromTerm = (term: string) => term.split("/").filter(Boolean);
69
-
70
- const segmentsFromSearchParams = (url: string) => {
71
- const searchParams = new URLSearchParams(url).entries();
72
-
73
- const categories = Array.from(searchParams)
74
- .sort()
75
- .reduce((acc, [key, value]) => {
76
- if (key.includes("filter.category")) {
77
- acc.push(value);
78
- }
79
-
80
- return acc;
81
- }, [] as string[]);
82
-
83
- return categories.length ? categories : segmentsFromTerm(url);
84
- };
85
-
86
- export const pageTypesFromUrl = async (url: string): Promise<PageType[]> => {
87
- const segments = segmentsFromSearchParams(url);
88
-
89
- return await Promise.all(
90
- segments.map((_, index) =>
91
- vtexFetch<PageType>(
92
- `/api/catalog_system/pub/portal/pagetype/${segments.slice(0, index + 1).join("/")}`,
93
- ),
94
- ),
95
- );
96
- };
@@ -1,139 +0,0 @@
1
- import type { Seo } from "@decocms/apps-commerce/types";
2
- import { vtexFetch } from "../client";
3
- import type { WrappedSegment } from "./segment";
4
- import { slugify } from "./slugify";
5
- import type { PageType } from "./types";
6
-
7
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
8
-
9
- export const toSegmentParams = ({ payload: segment }: WrappedSegment) =>
10
- Object.fromEntries(
11
- Object.entries({
12
- utmi_campaign: segment.utmi_campaign ?? undefined,
13
- utm_campaign: segment.utm_campaign ?? undefined,
14
- utm_source: segment.utm_source ?? undefined,
15
- sc: segment.channel ?? undefined,
16
- }).filter(([_, v]) => v !== undefined),
17
- );
18
-
19
- const PAGE_TYPE_TO_MAP_PARAM = {
20
- Brand: "b",
21
- Category: "c",
22
- Department: "c",
23
- SubCategory: "c",
24
- Collection: "productClusterIds",
25
- Cluster: "productClusterIds",
26
- Search: "ft",
27
- FullText: "ft",
28
- Product: "p",
29
- NotFound: null,
30
- };
31
-
32
- const segmentsFromTerm = (term: string) => term.split("/").filter(Boolean);
33
-
34
- export const getValidTypesFromPageTypes = (pagetypes: PageType[]) => {
35
- return pagetypes.filter((type) => PAGE_TYPE_TO_MAP_PARAM[type.pageType]);
36
- };
37
-
38
- export const pageTypesFromPathname = async (term: string): Promise<PageType[]> => {
39
- const segments = segmentsFromTerm(term);
40
-
41
- return await Promise.all(
42
- segments.map((_, index) =>
43
- vtexFetch<PageType>(
44
- `/api/catalog_system/pub/portal/pagetype/${segments.slice(0, index + 1).join("/")}`,
45
- ),
46
- ),
47
- );
48
- };
49
-
50
- export const getMapAndTerm = (pageTypes: PageType[]) => {
51
- const term = pageTypes
52
- .map((type, index) =>
53
- type.url ? segmentsFromTerm(new URL(`http://${type.url}`).pathname)[index] : null,
54
- )
55
- .filter(Boolean)
56
- .join("/");
57
-
58
- const map = pageTypes
59
- .map((type) => PAGE_TYPE_TO_MAP_PARAM[type.pageType])
60
- .filter(Boolean)
61
- .join(",");
62
-
63
- if (map === "ft" && term === "s") {
64
- return ["", ""];
65
- }
66
-
67
- return [map, term];
68
- };
69
-
70
- export const pageTypesToBreadcrumbList = (pages: PageType[], baseUrl: string) => {
71
- const filteredPages = pages.filter(
72
- ({ pageType }) =>
73
- pageType === "Category" || pageType === "Department" || pageType === "SubCategory",
74
- );
75
-
76
- return filteredPages.map((page, index) => {
77
- const position = index + 1;
78
- const slug = filteredPages.slice(0, position).map((x) => slugify(x.name!));
79
-
80
- return {
81
- "@type": "ListItem" as const,
82
- name: page.name!,
83
- item: new URL(`/${slug.join("/")}`, baseUrl).href,
84
- position,
85
- };
86
- });
87
- };
88
-
89
- export const pageTypesToSeo = (
90
- pages: PageType[],
91
- baseUrl: string,
92
- currentPage?: number,
93
- ): Seo | null => {
94
- const current = pages.at(-1);
95
- const url = new URL(baseUrl);
96
- const fullTextSearch = url.searchParams.get("q");
97
- const hasMapTermOrSkuId = !!(url.searchParams.get("map") || url.searchParams.get("skuId"));
98
-
99
- if (
100
- (!current || current.pageType === "Search" || current.pageType === "FullText") &&
101
- fullTextSearch
102
- ) {
103
- return {
104
- title: capitalize(fullTextSearch),
105
- description: capitalize(fullTextSearch),
106
- canonical: url.href,
107
- noIndexing: hasMapTermOrSkuId,
108
- };
109
- }
110
-
111
- if (!current) {
112
- return null;
113
- }
114
-
115
- return {
116
- title: current.title || current.name || "",
117
- description: current.metaTagDescription!,
118
- noIndexing: hasMapTermOrSkuId,
119
- canonical: toCanonical(
120
- new URL(
121
- current.url && current.pageType !== "Collection"
122
- ? current.url.replace(/^[^/]*\//, "/").toLowerCase()
123
- : url,
124
- url,
125
- ),
126
- currentPage,
127
- ),
128
- };
129
- };
130
-
131
- function toCanonical(url: URL, page?: number) {
132
- if (typeof page === "number") {
133
- url.searchParams.set("page", `${page}`);
134
- }
135
-
136
- return url.href;
137
- }
138
-
139
- export { isFilterParam } from "./intelligentSearch";
@@ -1,139 +0,0 @@
1
- /**
2
- * Map a VTEX OrderForm to the canonical `Minicart` contract.
3
- *
4
- * Pure function — no I/O, fully unit-testable. Pricing is converted from
5
- * VTEX's native cents to major units (the canonical unit for `Minicart`).
6
- *
7
- * Locale and currency come from `orderForm.storePreferencesData` and follow
8
- * VTEX's `storePreferencesData.countryCode` / `currencyCode` semantics.
9
- *
10
- * @example
11
- * ```ts
12
- * import { vtexOrderFormToMinicart } from "@decocms/apps/vtex/utils/minicart";
13
- * import { getCart } from "@decocms/apps/vtex/loaders/cart";
14
- *
15
- * const orderForm = await getCart(orderFormId);
16
- * const minicart = vtexOrderFormToMinicart(orderForm, {
17
- * freeShippingTarget: 0,
18
- * checkoutHref: "/checkout",
19
- * });
20
- * ```
21
- */
22
-
23
- import type { Minicart, MinicartItem } from "@decocms/apps-commerce/types";
24
- import type { OrderForm, OrderFormItem, Totalizer } from "../types";
25
-
26
- export interface VtexOrderFormToMinicartOptions {
27
- /** Free-shipping threshold in major units. `0` disables the progress bar. */
28
- freeShippingTarget?: number;
29
- /** Override the OrderForm's `clientPreferencesData.locale` (BCP-47, e.g. `"pt-BR"`). */
30
- locale?: string;
31
- /** Where the checkout button sends the user. Default: `/checkout`. */
32
- checkoutHref?: string;
33
- /** Whether the UI should expose the coupon input. Default: `true`. */
34
- enableCoupon?: boolean;
35
- }
36
-
37
- const CENTS_PER_MAJOR = 100;
38
-
39
- /** Convert VTEX cents to major units. Always returns a finite number. */
40
- function fromCents(cents: number | undefined | null): number {
41
- if (cents == null || !Number.isFinite(cents)) return 0;
42
- return cents / CENTS_PER_MAJOR;
43
- }
44
-
45
- function findTotalizer(totalizers: Totalizer[] | undefined, id: string): number {
46
- if (!totalizers) return 0;
47
- const t = totalizers.find((x) => x.id === id);
48
- return t?.value ?? 0;
49
- }
50
-
51
- /**
52
- * Locale heuristic. VTEX exposes `clientPreferencesData.locale` when set, but
53
- * otherwise we synthesize one from `storePreferencesData.countryCode` so the UI
54
- * always has a usable value for `Intl.NumberFormat`.
55
- */
56
- function inferLocale(orderForm: OrderForm, override?: string): string {
57
- if (override) return override;
58
- const explicit = orderForm.clientPreferencesData?.locale;
59
- if (explicit) return explicit;
60
-
61
- const country = orderForm.storePreferencesData?.countryCode;
62
- if (country === "BRA" || country === "BR") return "pt-BR";
63
- if (country === "USA" || country === "US") return "en-US";
64
- return "en-US";
65
- }
66
-
67
- function vtexItemToMinicartItem(item: OrderFormItem, index: number, coupon?: string): MinicartItem {
68
- const sellingPrice = fromCents(item.sellingPrice ?? item.price);
69
- const listPrice = fromCents(item.listPrice ?? item.price);
70
- const discount = Math.max(0, listPrice - sellingPrice);
71
-
72
- return {
73
- // AnalyticsItem identifier — VTEX uses productId; sites map to numeric SKU
74
- // when needed via `Number(item.item_id)` (see a production Minicart).
75
- item_id: item.id,
76
- item_group_id: item.productId,
77
- item_name: item.name ?? item.skuName ?? "",
78
- item_variant: item.skuName,
79
- item_brand: item.additionalInfo?.brandName ?? undefined,
80
- item_url: item.detailUrl,
81
- coupon,
82
- affiliation: item.seller,
83
- index,
84
- // Cart-required fields
85
- image: item.imageUrl?.replace(/^http:/, "https:") ?? "",
86
- listPrice,
87
- price: sellingPrice,
88
- quantity: item.quantity,
89
- discount: Number(discount.toFixed(2)),
90
- // Platform-specific
91
- seller: item.seller,
92
- attachments: item.attachments as MinicartItem["attachments"],
93
- attachmentOfferings: item.attachmentOfferings as MinicartItem["attachmentOfferings"],
94
- };
95
- }
96
-
97
- /**
98
- * Map a VTEX `OrderForm` to the canonical platform-agnostic `Minicart`.
99
- *
100
- * @param orderForm - Result from `getCart()` or `getOrCreateCart()`.
101
- * @param opts - Storefront-level overrides (free-shipping target, checkout href, ...).
102
- */
103
- export function vtexOrderFormToMinicart(
104
- orderForm: OrderForm,
105
- opts: VtexOrderFormToMinicartOptions = {},
106
- ): Minicart<OrderForm> {
107
- const totalizers = orderForm.totalizers;
108
- const subtotal = fromCents(findTotalizer(totalizers, "Items"));
109
- const discountsRaw = findTotalizer(totalizers, "Discounts");
110
- const discounts = Math.abs(fromCents(discountsRaw));
111
- const shippingRaw = findTotalizer(totalizers, "Shipping");
112
- const shipping = totalizers?.some((t) => t.id === "Shipping")
113
- ? fromCents(shippingRaw)
114
- : undefined;
115
- const total = fromCents(orderForm.value);
116
-
117
- const coupon = orderForm.marketingData?.coupon;
118
- const items = (orderForm.items ?? []).map((item, index) =>
119
- vtexItemToMinicartItem(item, index, coupon),
120
- );
121
-
122
- return {
123
- original: orderForm,
124
- storefront: {
125
- items,
126
- subtotal,
127
- discounts,
128
- shipping,
129
- total,
130
- coupon,
131
- locale: inferLocale(orderForm, opts.locale),
132
- currency: orderForm.storePreferencesData?.currencyCode ?? "BRL",
133
- enableCoupon: opts.enableCoupon ?? true,
134
- freeShippingTarget: opts.freeShippingTarget ?? 0,
135
- checkoutHref: opts.checkoutHref ?? "/checkout",
136
- postalCode: orderForm.shippingData?.address?.postalCode ?? undefined,
137
- },
138
- };
139
- }
@@ -1,25 +0,0 @@
1
- export function pick<T extends object, K extends keyof T = keyof T>(
2
- keys: K[],
3
- obj: T | null | undefined,
4
- ): Pick<T, K> | null {
5
- if (!keys.length || !obj) {
6
- return null;
7
- }
8
-
9
- const entries = keys.map((key) => [key, obj[key]]);
10
-
11
- return Object.fromEntries(entries);
12
- }
13
-
14
- export function omit<T extends object, K extends keyof T>(
15
- keys: K[],
16
- obj: T | null | undefined,
17
- ): Omit<T, K> | null {
18
- if (!keys.length || !obj) {
19
- return null;
20
- }
21
-
22
- const pickedKeys = (Object.keys(obj) as K[]).filter((key) => !keys.includes(key));
23
-
24
- return pick(pickedKeys, obj) as unknown as Omit<T, K>;
25
- }