@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.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.
Files changed (139) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +10 -28
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +75 -65
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +354 -343
  8. package/src/README.md +0 -6
  9. package/src/__conformance__/upstream.test.ts +0 -158
  10. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  11. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  12. package/src/__tests__/schemas.test.ts +0 -71
  13. package/src/actions/address.ts +0 -250
  14. package/src/actions/analytics/sendEvent.ts +0 -86
  15. package/src/actions/auth.ts +0 -333
  16. package/src/actions/checkout.ts +0 -659
  17. package/src/actions/index.ts +0 -11
  18. package/src/actions/masterData.ts +0 -168
  19. package/src/actions/misc.ts +0 -188
  20. package/src/actions/newsletter.ts +0 -105
  21. package/src/actions/orders.ts +0 -34
  22. package/src/actions/profile.ts +0 -201
  23. package/src/actions/session.ts +0 -85
  24. package/src/actions/trigger.ts +0 -43
  25. package/src/actions/wishlist.ts +0 -114
  26. package/src/client.ts +0 -724
  27. package/src/commerceLoaders.ts +0 -267
  28. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  29. package/src/hooks/__tests__/createCart.test.ts +0 -92
  30. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  31. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  32. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  33. package/src/hooks/cartQuery.ts +0 -204
  34. package/src/hooks/createCart.ts +0 -427
  35. package/src/hooks/createUseCart.ts +0 -360
  36. package/src/hooks/createUseUser.ts +0 -153
  37. package/src/hooks/createUseWishlist.ts +0 -242
  38. package/src/hooks/index.ts +0 -25
  39. package/src/hooks/useAutocomplete.ts +0 -83
  40. package/src/hooks/useCart.ts +0 -243
  41. package/src/hooks/useUser.ts +0 -78
  42. package/src/hooks/useWishlist.ts +0 -119
  43. package/src/invoke.ts +0 -235
  44. package/src/loaders/ProductDetailsPage.ts +0 -1
  45. package/src/loaders/ProductList.ts +0 -1
  46. package/src/loaders/ProductListingPage.ts +0 -1
  47. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  48. package/src/loaders/address.ts +0 -115
  49. package/src/loaders/autocomplete.ts +0 -56
  50. package/src/loaders/brands.ts +0 -44
  51. package/src/loaders/cart/attachments.ts +0 -53
  52. package/src/loaders/cart/full.ts +0 -67
  53. package/src/loaders/cart/gifts.ts +0 -43
  54. package/src/loaders/cart/orderFormId.ts +0 -31
  55. package/src/loaders/cart/shipping.test.ts +0 -125
  56. package/src/loaders/cart/shipping.ts +0 -136
  57. package/src/loaders/cart/summary.ts +0 -36
  58. package/src/loaders/cart.ts +0 -52
  59. package/src/loaders/catalog.ts +0 -163
  60. package/src/loaders/collections.ts +0 -55
  61. package/src/loaders/index.ts +0 -20
  62. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  63. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  64. package/src/loaders/intelligentSearch/productList.ts +0 -170
  65. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  66. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  67. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  68. package/src/loaders/legacy/productList.ts +0 -1
  69. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  70. package/src/loaders/legacy.ts +0 -710
  71. package/src/loaders/logistics.ts +0 -111
  72. package/src/loaders/minicart.ts +0 -78
  73. package/src/loaders/navbar.ts +0 -26
  74. package/src/loaders/orders.ts +0 -92
  75. package/src/loaders/pageType.ts +0 -68
  76. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  77. package/src/loaders/payment.ts +0 -97
  78. package/src/loaders/productListFull.ts +0 -157
  79. package/src/loaders/profile.ts +0 -138
  80. package/src/loaders/promotion.ts +0 -30
  81. package/src/loaders/search.ts +0 -124
  82. package/src/loaders/session.ts +0 -83
  83. package/src/loaders/user.ts +0 -87
  84. package/src/loaders/wishlist.ts +0 -89
  85. package/src/loaders/wishlistProducts.ts +0 -69
  86. package/src/loaders/workflow/products.ts +0 -62
  87. package/src/loaders/workflow.ts +0 -316
  88. package/src/logo.png +0 -0
  89. package/src/manifest.gen.ts +0 -89
  90. package/src/middleware.cacheHeaders.test.ts +0 -44
  91. package/src/middleware.ts +0 -240
  92. package/src/mod.ts +0 -173
  93. package/src/registry.ts +0 -9
  94. package/src/schemas.gen.ts +0 -3191
  95. package/src/schemas.ts +0 -30
  96. package/src/types.ts +0 -248
  97. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  98. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  99. package/src/utils/__tests__/fetch.test.ts +0 -80
  100. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  101. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  102. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  103. package/src/utils/__tests__/minicart.test.ts +0 -184
  104. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  105. package/src/utils/__tests__/resilience.test.ts +0 -205
  106. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  107. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  108. package/src/utils/__tests__/sitemap.test.ts +0 -185
  109. package/src/utils/__tests__/slugify.test.ts +0 -31
  110. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  111. package/src/utils/__tests__/transform.test.ts +0 -1111
  112. package/src/utils/accountLoaders.ts +0 -203
  113. package/src/utils/authHelpers.ts +0 -85
  114. package/src/utils/batch.ts +0 -18
  115. package/src/utils/buildOfferShelf.test.ts +0 -59
  116. package/src/utils/cartProjection.ts +0 -96
  117. package/src/utils/cookies.ts +0 -167
  118. package/src/utils/enrichment.ts +0 -556
  119. package/src/utils/fetch.ts +0 -111
  120. package/src/utils/fetchCache.ts +0 -55
  121. package/src/utils/index.ts +0 -19
  122. package/src/utils/instrumentedFetch.ts +0 -98
  123. package/src/utils/intelligentSearch.ts +0 -96
  124. package/src/utils/legacy.ts +0 -139
  125. package/src/utils/minicart.ts +0 -139
  126. package/src/utils/pickAndOmit.ts +0 -25
  127. package/src/utils/proxy.ts +0 -495
  128. package/src/utils/resilience.ts +0 -352
  129. package/src/utils/resourceRange.ts +0 -10
  130. package/src/utils/segment.ts +0 -152
  131. package/src/utils/similars.ts +0 -35
  132. package/src/utils/simulationCache.ts +0 -73
  133. package/src/utils/sitemap.ts +0 -283
  134. package/src/utils/slugCache.ts +0 -28
  135. package/src/utils/slugify.ts +0 -13
  136. package/src/utils/transform.ts +0 -1691
  137. package/src/utils/vtexId.ts +0 -151
  138. package/src/vtexClient.test.ts +0 -204
  139. package/tsconfig.json +0 -7
@@ -1,427 +0,0 @@
1
- /**
2
- * Cart v2 factory — modular, granular, framework-agnostic client hooks.
3
- *
4
- * Built on the same module-singleton + listener pattern as `createUseCart`
5
- * (no `@tanstack/react-query` required, works identically under Next.js and
6
- * TanStack Start), but redesigned around the Cart v2 goals:
7
- *
8
- * - **Lazy**: nothing hits VTEX on mount. No OrderForm is created until the
9
- * first add-to-cart. The badge shows 0 (or the reconciled count) until then.
10
- * - **Granular**: separate hooks per concern (`useCartSummary` for the badge,
11
- * `useCart` for the drawer, `useShipping`, `useGifts`) so a component pulls
12
- * only what it renders.
13
- * - **Minimal by default + optimistic**: `useAddToCart` bumps the local count
14
- * immediately and reconciles from the server's projected response. The
15
- * optimistic + reconciliation logic lives here so the storefront never
16
- * reimplements it.
17
- *
18
- * Injection: pass your site's `invoke` (the generated server-function proxy).
19
- * The `CreateCartInvoke` interface declares only the v2 methods this factory
20
- * calls — a structural subset, same as `CreateUseCartInvoke`.
21
- *
22
- * @example
23
- * ```ts
24
- * // src/hooks/cart.ts
25
- * import { createCart } from "@decocms/apps/vtex/hooks/createCart";
26
- * import { invoke } from "~/server/invoke";
27
- * export const { useCart, useCartSummary, useAddToCart } = createCart({ invoke });
28
- * ```
29
- */
30
-
31
- import type {
32
- CartProjection,
33
- CartSection,
34
- CartSummary,
35
- CartSummaryWithItems,
36
- Minicart,
37
- } from "@decocms/apps-commerce/types";
38
- import { useEffect, useState } from "react";
39
- import type { CartItemAttachments } from "../loaders/cart/attachments";
40
- import type { CartGifts } from "../loaders/cart/gifts";
41
- import type { CartShipping } from "../loaders/cart/shipping";
42
- import type { OrderForm } from "../types";
43
- import type { VtexCartProjectionResult } from "../utils/cartProjection";
44
-
45
- /** Structural subset of the invoke proxy this factory needs. */
46
- export interface CreateCartInvoke {
47
- vtex: {
48
- actions: {
49
- getOrCreateCartV2: (args: {
50
- data: { orderFormId?: string; projection?: CartProjection; sections?: CartSection[] };
51
- }) => Promise<unknown>;
52
- addItemsToCartV2: (args: {
53
- data: {
54
- orderFormId: string;
55
- orderItems: Array<{ id: string; seller: string; quantity: number }>;
56
- projection?: CartProjection;
57
- sections?: CartSection[];
58
- };
59
- }) => Promise<unknown>;
60
- updateCartItemsV2: (args: {
61
- data: {
62
- orderFormId: string;
63
- orderItems: Array<{ index: number; quantity: number }>;
64
- projection?: CartProjection;
65
- sections?: CartSection[];
66
- };
67
- }) => Promise<unknown>;
68
- addCouponToCartV2: (args: {
69
- data: {
70
- orderFormId: string;
71
- text: string;
72
- projection?: CartProjection;
73
- sections?: CartSection[];
74
- };
75
- }) => Promise<unknown>;
76
- };
77
- loaders: {
78
- cart: {
79
- summary: (args: { data?: { orderFormId?: string } }) => Promise<CartSummary>;
80
- full: (args: {
81
- data?: {
82
- orderFormId?: string;
83
- freeShippingTarget?: number;
84
- locale?: string;
85
- checkoutHref?: string;
86
- enableCoupon?: boolean;
87
- };
88
- }) => Promise<Minicart<OrderForm | null>>;
89
- shipping: (args: {
90
- data: {
91
- items: Array<{ id: string | number; quantity: number; seller: string }>;
92
- postalCode: string;
93
- country?: string;
94
- };
95
- }) => Promise<CartShipping>;
96
- gifts: (args: { data?: { orderFormId?: string } }) => Promise<CartGifts>;
97
- attachments: (args: {
98
- data: { orderFormId?: string; itemIndex: number };
99
- }) => Promise<CartItemAttachments>;
100
- };
101
- };
102
- };
103
- }
104
-
105
- export interface CreateCartOptions {
106
- invoke: CreateCartInvoke;
107
- /** Override the orderFormId cookie name. Default: VTEX standard. */
108
- orderFormCookieName?: string;
109
- /** Override the cookie max-age in seconds. Default: 7 days. */
110
- orderFormCookieMaxAge?: number;
111
- }
112
-
113
- const EMPTY_SUMMARY: CartSummary = { orderFormId: null, totalItems: 0, total: 0 };
114
-
115
- export interface AddToCartOptions {
116
- /** What the server should return. Default: `"summary+items"`. */
117
- projection?: CartProjection;
118
- sections?: CartSection[];
119
- }
120
-
121
- export interface UseCartInclude {
122
- /** Fetch the full minicart (drawer). Default: false. */
123
- full?: boolean;
124
- }
125
-
126
- export interface UseCartV2Options {
127
- include?: UseCartInclude;
128
- freeShippingTarget?: number;
129
- locale?: string;
130
- checkoutHref?: string;
131
- enableCoupon?: boolean;
132
- }
133
-
134
- /** Build a per-site set of Cart v2 hooks. */
135
- export function createCart(opts: CreateCartOptions) {
136
- const { invoke } = opts;
137
- const COOKIE_NAME = opts.orderFormCookieName ?? "checkout.vtex.com__orderFormId";
138
- const COOKIE_MAX_AGE = opts.orderFormCookieMaxAge ?? 7 * 24 * 3600;
139
-
140
- // --- module-singleton state --------------------------------------------
141
- let _summary: CartSummary = EMPTY_SUMMARY;
142
- let _minicart: Minicart<OrderForm | null> | null = null;
143
- let _loading = false;
144
- const _listeners = new Set<() => void>();
145
-
146
- function notify() {
147
- for (const fn of _listeners) fn();
148
- }
149
- function setSummary(s: CartSummary) {
150
- _summary = s;
151
- notify();
152
- }
153
- function setMinicart(m: Minicart<OrderForm | null> | null) {
154
- _minicart = m;
155
- if (m) {
156
- // Keep the badge in sync whenever the full cart is loaded.
157
- _summary = {
158
- orderFormId: m.original?.orderFormId ?? _summary.orderFormId,
159
- totalItems: m.storefront.items.reduce((n, i) => n + (i.quantity ?? 0), 0),
160
- total: m.storefront.total,
161
- };
162
- }
163
- notify();
164
- }
165
- function setLoading(v: boolean) {
166
- _loading = v;
167
- notify();
168
- }
169
-
170
- // --- cookie helpers ----------------------------------------------------
171
- function escapeRegex(s: string): string {
172
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
173
- }
174
- function getOrderFormIdFromCookie(): string | null {
175
- if (typeof document === "undefined") return null;
176
- const re = new RegExp(`${escapeRegex(COOKIE_NAME)}=([^;]*)`);
177
- const match = document.cookie.match(re);
178
- return match ? decodeURIComponent(match[1]) : null;
179
- }
180
- function setOrderFormIdCookie(id: string) {
181
- if (typeof document === "undefined") return;
182
- // biome-ignore lint/suspicious/noDocumentCookie: the orderFormId cookie is intentionally client-readable (VTEX standard, mirrored by createUseCart).
183
- document.cookie = `${COOKIE_NAME}=${encodeURIComponent(id)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;
184
- }
185
-
186
- /**
187
- * Ensure an OrderForm exists. Only called from mutations — never on mount —
188
- * so a browsing-only visitor provisions no cart. If the cookie has an id we
189
- * trust it; otherwise we create one (projection "summary" — we only need the
190
- * id + count back).
191
- */
192
- async function ensureOrderFormId(): Promise<string> {
193
- const existing = _summary.orderFormId ?? getOrderFormIdFromCookie();
194
- if (existing) {
195
- _summary = { ..._summary, orderFormId: existing };
196
- return existing;
197
- }
198
- const created = (await invoke.vtex.actions.getOrCreateCartV2({
199
- data: { projection: "summary" },
200
- })) as CartSummary;
201
- if (created.orderFormId) setOrderFormIdCookie(created.orderFormId);
202
- setSummary(created);
203
- return created.orderFormId ?? "";
204
- }
205
-
206
- /** Reconcile local state from a projected mutation response. */
207
- function reconcile(result: unknown, projection: CartProjection) {
208
- if (projection === "none") return; // optimistic-only; nothing to reconcile
209
- if (projection === "minicart") {
210
- setMinicart(result as Minicart<OrderForm | null>);
211
- return;
212
- }
213
- // "summary" | "summary+items" | "raw" all expose the totals we need.
214
- const r = result as Partial<CartSummaryWithItems> & { value?: number };
215
- if (r && typeof r.totalItems === "number") {
216
- setSummary({
217
- orderFormId: r.orderFormId ?? _summary.orderFormId,
218
- totalItems: r.totalItems,
219
- total: r.total ?? _summary.total,
220
- });
221
- }
222
- }
223
-
224
- // --- shared React subscription ----------------------------------------
225
- function useCartState() {
226
- const [, forceRender] = useState(0);
227
- useEffect(() => {
228
- const listener = () => forceRender((n) => n + 1);
229
- _listeners.add(listener);
230
- return () => {
231
- _listeners.delete(listener);
232
- };
233
- }, []);
234
- }
235
-
236
- // --- hooks -------------------------------------------------------------
237
-
238
- /** Badge hook. Reads the local summary; never triggers a VTEX call by itself. */
239
- function useCartSummary() {
240
- useCartState();
241
- return { summary: _summary, totalItems: _summary.totalItems, loading: _loading };
242
- }
243
-
244
- /**
245
- * Add-to-cart with built-in optimistic count + reconciliation.
246
- * `projection: "none"` → pure 200, optimistic-only (no server data used).
247
- */
248
- function useAddToCart(hookOpts: AddToCartOptions = {}) {
249
- useCartState();
250
- const projection: CartProjection = hookOpts.projection ?? "summary+items";
251
-
252
- async function add(params: {
253
- id: string;
254
- seller: string;
255
- quantity?: number;
256
- }): Promise<VtexCartProjectionResult> {
257
- const qty = params.quantity ?? 1;
258
- // Optimistic: bump the badge immediately.
259
- setSummary({ ..._summary, totalItems: _summary.totalItems + qty });
260
- setLoading(true);
261
- try {
262
- const orderFormId = await ensureOrderFormId();
263
- const result = await invoke.vtex.actions.addItemsToCartV2({
264
- data: {
265
- orderFormId,
266
- orderItems: [{ id: params.id, seller: params.seller, quantity: qty }],
267
- projection,
268
- sections: hookOpts.sections,
269
- },
270
- });
271
- reconcile(result, projection);
272
- // Return the projected payload so callers can drive a toast / analytics
273
- // without a second fetch (e.g. `summary+items` → name/image/price/variant).
274
- return result as VtexCartProjectionResult;
275
- } catch (err) {
276
- // Roll back the optimistic bump on failure.
277
- setSummary({ ..._summary, totalItems: Math.max(0, _summary.totalItems - qty) });
278
- console.error("[cart] addToCart failed:", err);
279
- throw err;
280
- } finally {
281
- setLoading(false);
282
- }
283
- }
284
-
285
- return { add, loading: _loading };
286
- }
287
-
288
- /**
289
- * Composed cart hook for the drawer. Fetches ONLY what `include` enables —
290
- * default is nothing beyond the already-local summary. Call `openDrawer()`
291
- * (or pass `include.full`) to load the full minicart on demand.
292
- */
293
- function useCart(cartOpts: UseCartV2Options = {}) {
294
- useCartState();
295
- const wantFull = cartOpts.include?.full === true;
296
-
297
- // biome-ignore lint/correctness/useExhaustiveDependencies: the drawer loads once when enabled; storefront options are stable per-mount and must not re-trigger the fetch.
298
- useEffect(() => {
299
- if (!wantFull) return;
300
- let cancelled = false;
301
- setLoading(true);
302
- invoke.vtex.loaders.cart
303
- .full({
304
- data: {
305
- freeShippingTarget: cartOpts.freeShippingTarget,
306
- locale: cartOpts.locale,
307
- checkoutHref: cartOpts.checkoutHref,
308
- enableCoupon: cartOpts.enableCoupon,
309
- },
310
- })
311
- .then((m) => {
312
- if (!cancelled) setMinicart(m);
313
- })
314
- .catch((err) => console.error("[cart] load full failed:", err))
315
- .finally(() => {
316
- if (!cancelled) setLoading(false);
317
- });
318
- return () => {
319
- cancelled = true;
320
- };
321
- }, [wantFull]);
322
-
323
- async function refresh() {
324
- const m = await invoke.vtex.loaders.cart.full({
325
- data: {
326
- freeShippingTarget: cartOpts.freeShippingTarget,
327
- locale: cartOpts.locale,
328
- checkoutHref: cartOpts.checkoutHref,
329
- enableCoupon: cartOpts.enableCoupon,
330
- },
331
- });
332
- setMinicart(m);
333
- return m;
334
- }
335
-
336
- async function updateQuantity(index: number, quantity: number) {
337
- const orderFormId = await ensureOrderFormId();
338
- setLoading(true);
339
- try {
340
- const result = await invoke.vtex.actions.updateCartItemsV2({
341
- data: { orderFormId, orderItems: [{ index, quantity }], projection: "minicart" },
342
- });
343
- reconcile(result, "minicart");
344
- return result as Minicart<OrderForm | null>;
345
- } finally {
346
- setLoading(false);
347
- }
348
- }
349
-
350
- async function removeItem(index: number) {
351
- return updateQuantity(index, 0);
352
- }
353
-
354
- async function addCoupon(text: string) {
355
- const orderFormId = await ensureOrderFormId();
356
- setLoading(true);
357
- try {
358
- const result = await invoke.vtex.actions.addCouponToCartV2({
359
- data: { orderFormId, text, projection: "minicart" },
360
- });
361
- reconcile(result, "minicart");
362
- return result as Minicart<OrderForm | null>;
363
- } finally {
364
- setLoading(false);
365
- }
366
- }
367
-
368
- return {
369
- minicart: _minicart,
370
- summary: _summary,
371
- loading: _loading,
372
- refresh,
373
- updateQuantity,
374
- removeItem,
375
- addCoupon,
376
- };
377
- }
378
-
379
- /** On-demand shipping estimate for the drawer. Not cached (see loader note). */
380
- function useShipping() {
381
- useCartState();
382
- async function estimate(params: {
383
- items: Array<{ id: string | number; quantity: number; seller: string }>;
384
- postalCode: string;
385
- country?: string;
386
- }) {
387
- return invoke.vtex.loaders.cart.shipping({ data: params });
388
- }
389
- return { estimate };
390
- }
391
-
392
- /** On-demand selectable-gifts / promotions read. */
393
- function useGifts() {
394
- useCartState();
395
- async function load() {
396
- return invoke.vtex.loaders.cart.gifts({ data: {} });
397
- }
398
- return { load };
399
- }
400
-
401
- /** On-demand attachments/offerings read for a single line (engraving, gift-wrap…). */
402
- function useAttachments() {
403
- useCartState();
404
- async function load(itemIndex: number) {
405
- return invoke.vtex.loaders.cart.attachments({ data: { itemIndex } });
406
- }
407
- return { load };
408
- }
409
-
410
- /** Reset all module-level state (e.g. after logout / order placed). */
411
- function resetCart() {
412
- _summary = EMPTY_SUMMARY;
413
- _minicart = null;
414
- _loading = false;
415
- notify();
416
- }
417
-
418
- return {
419
- useCart,
420
- useCartSummary,
421
- useAddToCart,
422
- useShipping,
423
- useGifts,
424
- useAttachments,
425
- resetCart,
426
- };
427
- }