@decocms/apps 0.23.3 → 0.24.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/commerce/components/Image.tsx +129 -143
  3. package/commerce/components/JsonLd.tsx +192 -201
  4. package/commerce/components/Picture.tsx +65 -75
  5. package/commerce/sdk/analytics.ts +15 -15
  6. package/commerce/sdk/formatPrice.ts +13 -16
  7. package/commerce/sdk/url.ts +7 -7
  8. package/commerce/sdk/useOffer.ts +46 -57
  9. package/commerce/sdk/useVariantPossibilities.ts +25 -25
  10. package/commerce/types/commerce.ts +868 -875
  11. package/commerce/utils/canonical.ts +5 -8
  12. package/commerce/utils/constants.ts +5 -6
  13. package/commerce/utils/filters.ts +4 -4
  14. package/commerce/utils/productToAnalyticsItem.ts +52 -56
  15. package/commerce/utils/stateByZip.ts +42 -42
  16. package/package.json +23 -4
  17. package/shopify/actions/cart/addItems.ts +24 -25
  18. package/shopify/actions/cart/updateCoupons.ts +19 -20
  19. package/shopify/actions/cart/updateItems.ts +19 -20
  20. package/shopify/actions/user/signIn.ts +25 -30
  21. package/shopify/actions/user/signUp.ts +19 -24
  22. package/shopify/client.ts +24 -24
  23. package/shopify/index.ts +20 -18
  24. package/shopify/init.ts +18 -21
  25. package/shopify/loaders/ProductDetailsPage.ts +16 -20
  26. package/shopify/loaders/ProductList.ts +66 -69
  27. package/shopify/loaders/ProductListingPage.ts +150 -158
  28. package/shopify/loaders/RelatedProducts.ts +24 -27
  29. package/shopify/loaders/cart.ts +53 -52
  30. package/shopify/loaders/shop.ts +22 -27
  31. package/shopify/loaders/user.ts +27 -32
  32. package/shopify/utils/admin/admin.ts +33 -34
  33. package/shopify/utils/admin/queries.ts +2 -2
  34. package/shopify/utils/cart.ts +18 -14
  35. package/shopify/utils/cookies.ts +62 -65
  36. package/shopify/utils/enums.ts +424 -424
  37. package/shopify/utils/graphql.ts +44 -55
  38. package/shopify/utils/storefront/queries.ts +24 -29
  39. package/shopify/utils/storefront/storefront.graphql.gen.ts +55 -55
  40. package/shopify/utils/transform.ts +370 -376
  41. package/shopify/utils/types.ts +118 -118
  42. package/shopify/utils/user.ts +11 -11
  43. package/shopify/utils/utils.ts +135 -140
  44. package/vtex/actions/address.ts +86 -86
  45. package/vtex/actions/auth.ts +14 -27
  46. package/vtex/actions/checkout.ts +36 -49
  47. package/vtex/actions/masterData.ts +10 -27
  48. package/vtex/actions/misc.ts +101 -111
  49. package/vtex/actions/newsletter.ts +48 -52
  50. package/vtex/actions/orders.ts +13 -16
  51. package/vtex/actions/profile.ts +55 -55
  52. package/vtex/actions/session.ts +36 -35
  53. package/vtex/actions/trigger.ts +25 -25
  54. package/vtex/actions/wishlist.ts +51 -53
  55. package/vtex/client.ts +14 -42
  56. package/vtex/hooks/index.ts +4 -4
  57. package/vtex/hooks/useAutocomplete.ts +42 -48
  58. package/vtex/hooks/useCart.ts +153 -165
  59. package/vtex/hooks/useUser.ts +40 -40
  60. package/vtex/hooks/useWishlist.ts +70 -70
  61. package/vtex/inline-loaders/productDetailsPage.ts +1 -3
  62. package/vtex/inline-loaders/productList.ts +121 -127
  63. package/vtex/inline-loaders/productListingPage.ts +10 -34
  64. package/vtex/inline-loaders/relatedProducts.ts +1 -3
  65. package/vtex/inline-loaders/suggestions.ts +36 -39
  66. package/vtex/inline-loaders/workflowProducts.ts +45 -49
  67. package/vtex/invoke.ts +159 -194
  68. package/vtex/loaders/address.ts +49 -54
  69. package/vtex/loaders/brands.ts +19 -26
  70. package/vtex/loaders/cart.ts +24 -21
  71. package/vtex/loaders/catalog.ts +51 -53
  72. package/vtex/loaders/collections.ts +25 -27
  73. package/vtex/loaders/legacy.ts +487 -534
  74. package/vtex/loaders/logistics.ts +33 -37
  75. package/vtex/loaders/navbar.ts +5 -8
  76. package/vtex/loaders/orders.ts +28 -39
  77. package/vtex/loaders/pageType.ts +41 -35
  78. package/vtex/loaders/payment.ts +27 -37
  79. package/vtex/loaders/profile.ts +38 -38
  80. package/vtex/loaders/promotion.ts +5 -8
  81. package/vtex/loaders/search.ts +56 -59
  82. package/vtex/loaders/session.ts +22 -30
  83. package/vtex/loaders/user.ts +39 -41
  84. package/vtex/loaders/wishlist.ts +35 -35
  85. package/vtex/loaders/wishlistProducts.ts +3 -15
  86. package/vtex/loaders/workflow.ts +220 -227
  87. package/vtex/middleware.ts +116 -119
  88. package/vtex/types.ts +201 -201
  89. package/vtex/utils/batch.ts +13 -16
  90. package/vtex/utils/cookies.ts +76 -80
  91. package/vtex/utils/enrichment.ts +62 -42
  92. package/vtex/utils/fetchCache.ts +1 -4
  93. package/vtex/utils/index.ts +6 -6
  94. package/vtex/utils/intelligentSearch.ts +48 -57
  95. package/vtex/utils/legacy.ts +108 -124
  96. package/vtex/utils/pickAndOmit.ts +15 -20
  97. package/vtex/utils/proxy.ts +136 -146
  98. package/vtex/utils/resourceRange.ts +3 -3
  99. package/vtex/utils/segment.ts +100 -111
  100. package/vtex/utils/similars.ts +1 -2
  101. package/vtex/utils/sitemap.ts +91 -91
  102. package/vtex/utils/slugCache.ts +2 -6
  103. package/vtex/utils/slugify.ts +9 -9
  104. package/vtex/utils/transform.ts +1012 -1105
  105. package/vtex/utils/types.ts +1381 -1381
  106. package/vtex/utils/vtexId.ts +44 -47
  107. package/.github/workflows/release.yml +0 -34
  108. package/.releaserc.json +0 -28
  109. package/knip.json +0 -19
  110. package/tsconfig.json +0 -11
@@ -24,57 +24,62 @@
24
24
  * ```
25
25
  */
26
26
 
27
+ import { ANONYMOUS_COOKIE, SESSION_COOKIE } from "./utils/intelligentSearch";
27
28
  import {
28
- SEGMENT_COOKIE_NAME,
29
- SALES_CHANNEL_COOKIE,
30
- parseSegment,
31
- buildSegmentFromParams,
32
- serializeSegment,
33
- DEFAULT_SEGMENT,
34
- type WrappedSegment,
29
+ buildSegmentFromParams,
30
+ DEFAULT_SEGMENT,
31
+ parseSegment,
32
+ SALES_CHANNEL_COOKIE,
33
+ SEGMENT_COOKIE_NAME,
34
+ serializeSegment,
35
35
  } from "./utils/segment";
36
- import { SESSION_COOKIE, ANONYMOUS_COOKIE } from "./utils/intelligentSearch";
37
- import { isVtexLoggedIn, extractVtexAuthCookie, parseVtexAuthToken } from "./utils/vtexId";
38
36
  import type { Segment } from "./utils/types";
37
+ import { extractVtexAuthCookie, parseVtexAuthToken } from "./utils/vtexId";
39
38
 
40
39
  // -------------------------------------------------------------------------
41
40
  // Types
42
41
  // -------------------------------------------------------------------------
43
42
 
44
43
  export interface VtexRequestContext {
45
- /** Decoded segment from cookie or URL params. */
46
- segment: Partial<Segment>;
47
- /** Serialized segment token for cache key use. */
48
- segmentToken: string;
49
- /** Whether the user has a valid (non-expired) VTEX auth cookie. */
50
- isLoggedIn: boolean;
51
- /** Extracted email from the auth JWT, if available. */
52
- email?: string;
53
- /** Sales channel derived from segment. */
54
- salesChannel: string;
55
- /**
56
- * VTEX region ID from the segment cookie.
57
- * Present when the user has set a postal code (CEP) for regionalization.
58
- * Null when no region is set (anonymous default segment).
59
- */
60
- regionId: string | null;
61
- /** Whether this request carries price tables (B2B). */
62
- hasCustomPricing: boolean;
63
- /** Intelligent Search session cookie. */
64
- isSessionId: string;
65
- /** Intelligent Search anonymous cookie. */
66
- isAnonymousId: string;
44
+ /** Decoded segment from cookie or URL params. */
45
+ segment: Partial<Segment>;
46
+ /** Serialized segment token for cache key use. */
47
+ segmentToken: string;
48
+ /** Whether the user has a valid (non-expired) VTEX auth cookie. */
49
+ isLoggedIn: boolean;
50
+ /** Extracted email from the auth JWT, if available. */
51
+ email?: string;
52
+ /** Sales channel derived from segment. */
53
+ salesChannel: string;
54
+ /**
55
+ * VTEX region ID from the segment cookie.
56
+ * Present when the user has set a postal code (CEP) for regionalization.
57
+ * Null when no region is set (anonymous default segment).
58
+ */
59
+ regionId: string | null;
60
+ /** Whether this request carries price tables (B2B). */
61
+ hasCustomPricing: boolean;
62
+ /** Intelligent Search session cookie. */
63
+ isSessionId: string;
64
+ /** Intelligent Search anonymous cookie. */
65
+ isAnonymousId: string;
67
66
  }
68
67
 
69
68
  // -------------------------------------------------------------------------
70
69
  // Cookie helpers
71
70
  // -------------------------------------------------------------------------
72
71
 
73
- const IS_COOKIE_PREFIX = "vtex_is_";
72
+ const _IS_COOKIE_PREFIX = "vtex_is_";
73
+
74
+ /** Seconds in one day (86 400). Used for cookie Max-Age and stale-if-error. */
75
+ const ONE_DAY_SECONDS = 86_400;
76
+
77
+ /** Seconds in one year (~365 days). Used for long-lived IS cookie Max-Age. */
78
+ const ONE_YEAR_SECONDS = 365 * 24 * 60 * 60;
74
79
 
75
80
  function getCookieValue(cookieHeader: string, name: string): string | null {
76
- const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
77
- return match?.[1] ?? null;
81
+ const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
82
+ return match?.[1] ?? null;
78
83
  }
79
84
 
80
85
  // -------------------------------------------------------------------------
@@ -88,54 +93,52 @@ function getCookieValue(cookieHeader: string, name: string): string | null {
88
93
  * to build a complete picture of the user's VTEX session state.
89
94
  */
90
95
  function generateUUID(): string {
91
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
92
- return crypto.randomUUID();
93
- }
94
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
95
- const r = (Math.random() * 16) | 0;
96
- return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
97
- });
96
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
97
+ return crypto.randomUUID();
98
+ }
99
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
100
+ const r = (Math.random() * 16) | 0;
101
+ return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
102
+ });
98
103
  }
99
104
 
100
105
  export function extractVtexContext(request: Request): VtexRequestContext {
101
- const cookies = request.headers.get("cookie") ?? "";
102
- const url = new URL(request.url);
103
-
104
- const segmentCookie = getCookieValue(cookies, SEGMENT_COOKIE_NAME);
105
- const cookieSegment = segmentCookie ? parseSegment(segmentCookie) : null;
106
-
107
- const paramSegment = buildSegmentFromParams(url.searchParams);
108
-
109
- const vtexsc = getCookieValue(cookies, SALES_CHANNEL_COOKIE);
110
-
111
- const segment: Partial<Segment> = {
112
- ...DEFAULT_SEGMENT,
113
- ...cookieSegment,
114
- ...paramSegment,
115
- };
116
- if (vtexsc) segment.channel = vtexsc;
117
-
118
- const segmentToken = serializeSegment(segment);
119
-
120
- const authToken = extractVtexAuthCookie(cookies);
121
- const authInfo = authToken ? parseVtexAuthToken(authToken) : null;
122
-
123
- const isSessionId = getCookieValue(cookies, SESSION_COOKIE) ?? generateUUID();
124
- const isAnonymousId = getCookieValue(cookies, ANONYMOUS_COOKIE) ?? generateUUID();
125
-
126
- return {
127
- segment,
128
- segmentToken,
129
- isLoggedIn: authInfo?.isLoggedIn ?? false,
130
- email: authInfo?.email,
131
- salesChannel: segment.channel ?? "1",
132
- regionId: segment.regionId ?? null,
133
- hasCustomPricing: Boolean(
134
- segment.priceTables && segment.priceTables.length > 0,
135
- ),
136
- isSessionId,
137
- isAnonymousId,
138
- };
106
+ const cookies = request.headers.get("cookie") ?? "";
107
+ const url = new URL(request.url);
108
+
109
+ const segmentCookie = getCookieValue(cookies, SEGMENT_COOKIE_NAME);
110
+ const cookieSegment = segmentCookie ? parseSegment(segmentCookie) : null;
111
+
112
+ const paramSegment = buildSegmentFromParams(url.searchParams);
113
+
114
+ const vtexsc = getCookieValue(cookies, SALES_CHANNEL_COOKIE);
115
+
116
+ const segment: Partial<Segment> = {
117
+ ...DEFAULT_SEGMENT,
118
+ ...cookieSegment,
119
+ ...paramSegment,
120
+ };
121
+ if (vtexsc) segment.channel = vtexsc;
122
+
123
+ const segmentToken = serializeSegment(segment);
124
+
125
+ const authToken = extractVtexAuthCookie(cookies);
126
+ const authInfo = authToken ? parseVtexAuthToken(authToken) : null;
127
+
128
+ const isSessionId = getCookieValue(cookies, SESSION_COOKIE) ?? generateUUID();
129
+ const isAnonymousId = getCookieValue(cookies, ANONYMOUS_COOKIE) ?? generateUUID();
130
+
131
+ return {
132
+ segment,
133
+ segmentToken,
134
+ isLoggedIn: authInfo?.isLoggedIn ?? false,
135
+ email: authInfo?.email,
136
+ salesChannel: segment.channel ?? "1",
137
+ regionId: segment.regionId ?? null,
138
+ hasCustomPricing: Boolean(segment.priceTables && segment.priceTables.length > 0),
139
+ isSessionId,
140
+ isAnonymousId,
141
+ };
139
142
  }
140
143
 
141
144
  // -------------------------------------------------------------------------
@@ -151,22 +154,22 @@ export function extractVtexContext(request: Request): VtexRequestContext {
151
154
  * - Anonymous default segment: public with CDN caching
152
155
  */
153
156
  export function vtexCacheControl(
154
- ctx: VtexRequestContext,
155
- options?: {
156
- /** Max age for public (anonymous) responses in seconds. @default 60 */
157
- publicMaxAge?: number;
158
- /** Stale-while-revalidate for public responses in seconds. @default 3600 */
159
- publicSWR?: number;
160
- },
157
+ ctx: VtexRequestContext,
158
+ options?: {
159
+ /** Max age for public (anonymous) responses in seconds. @default 60 */
160
+ publicMaxAge?: number;
161
+ /** Stale-while-revalidate for public responses in seconds. @default 3600 */
162
+ publicSWR?: number;
163
+ },
161
164
  ): string {
162
- if (ctx.isLoggedIn || ctx.hasCustomPricing) {
163
- return "private, no-cache, no-store, must-revalidate";
164
- }
165
+ if (ctx.isLoggedIn || ctx.hasCustomPricing) {
166
+ return "private, no-cache, no-store, must-revalidate";
167
+ }
165
168
 
166
- const maxAge = options?.publicMaxAge ?? 60;
167
- const swr = options?.publicSWR ?? 3600;
169
+ const maxAge = options?.publicMaxAge ?? 60;
170
+ const swr = options?.publicSWR ?? 3600;
168
171
 
169
- return `public, s-maxage=${maxAge}, stale-while-revalidate=${swr}, stale-if-error=86400`;
172
+ return `public, s-maxage=${maxAge}, stale-while-revalidate=${swr}, stale-if-error=${ONE_DAY_SECONDS}`;
170
173
  }
171
174
 
172
175
  // -------------------------------------------------------------------------
@@ -180,19 +183,16 @@ export function vtexCacheControl(
180
183
  * If not, new UUIDs from the context are set. This ensures
181
184
  * every user has IS cookies for personalization and analytics.
182
185
  */
183
- export function propagateISCookies(
184
- ctx: VtexRequestContext,
185
- response: Response,
186
- ): void {
187
- const maxAge = 365 * 24 * 60 * 60;
188
- response.headers.append(
189
- "Set-Cookie",
190
- `${SESSION_COOKIE}=${ctx.isSessionId}; Path=/; SameSite=Lax; Max-Age=${maxAge}`,
191
- );
192
- response.headers.append(
193
- "Set-Cookie",
194
- `${ANONYMOUS_COOKIE}=${ctx.isAnonymousId}; Path=/; SameSite=Lax; Max-Age=${maxAge}`,
195
- );
186
+ export function propagateISCookies(ctx: VtexRequestContext, response: Response): void {
187
+ const maxAge = ONE_YEAR_SECONDS;
188
+ response.headers.append(
189
+ "Set-Cookie",
190
+ `${SESSION_COOKIE}=${ctx.isSessionId}; Path=/; SameSite=Lax; Max-Age=${maxAge}`,
191
+ );
192
+ response.headers.append(
193
+ "Set-Cookie",
194
+ `${ANONYMOUS_COOKIE}=${ctx.isAnonymousId}; Path=/; SameSite=Lax; Max-Age=${maxAge}`,
195
+ );
196
196
  }
197
197
 
198
198
  /**
@@ -201,14 +201,11 @@ export function propagateISCookies(
201
201
  * Use this when URL params change the segment (e.g., ?sc=2) so the
202
202
  * browser persists the new segment for subsequent requests.
203
203
  */
204
- export function buildSegmentSetCookie(
205
- segment: Partial<Segment>,
206
- domain?: string,
207
- ): string {
208
- const token = serializeSegment(segment);
209
- let cookie = `${SEGMENT_COOKIE_NAME}=${token}; Path=/; SameSite=Lax; Max-Age=86400`;
210
- if (domain) cookie += `; Domain=${domain}`;
211
- return cookie;
204
+ export function buildSegmentSetCookie(segment: Partial<Segment>, domain?: string): string {
205
+ const token = serializeSegment(segment);
206
+ let cookie = `${SEGMENT_COOKIE_NAME}=${token}; Path=/; SameSite=Lax; Max-Age=${ONE_DAY_SECONDS}`;
207
+ if (domain) cookie += `; Domain=${domain}`;
208
+ return cookie;
212
209
  }
213
210
 
214
211
  // -------------------------------------------------------------------------
@@ -223,16 +220,16 @@ export function buildSegmentSetCookie(
223
220
  * get the same cache key; a logged-in user gets a unique (uncached) key.
224
221
  */
225
222
  export function vtexCacheKeySuffix(ctx: VtexRequestContext): string {
226
- if (ctx.isLoggedIn) return "__vtex_auth";
227
- const parts = [`sc=${ctx.salesChannel}`];
228
- if (ctx.regionId) parts.push(`r=${ctx.regionId}`);
229
- return `__vtex_${parts.join("_")}`;
223
+ if (ctx.isLoggedIn) return "__vtex_auth";
224
+ const parts = [`sc=${ctx.salesChannel}`];
225
+ if (ctx.regionId) parts.push(`r=${ctx.regionId}`);
226
+ return `__vtex_${parts.join("_")}`;
230
227
  }
231
228
 
232
229
  // -------------------------------------------------------------------------
233
230
  // Re-exports for convenience
234
231
  // -------------------------------------------------------------------------
235
232
 
236
- export { isVtexLoggedIn } from "./utils/vtexId";
237
- export type { VtexAuthInfo } from "./utils/vtexId";
238
233
  export type { Segment } from "./utils/types";
234
+ export type { VtexAuthInfo } from "./utils/vtexId";
235
+ export { isVtexLoggedIn } from "./utils/vtexId";