@decocms/apps-vtex 8.0.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 (133) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +12 -15
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +76 -72
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +444 -0
  8. package/src/README.md +0 -6
  9. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  10. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  11. package/src/__tests__/schemas.test.ts +0 -71
  12. package/src/actions/address.ts +0 -250
  13. package/src/actions/analytics/sendEvent.ts +0 -86
  14. package/src/actions/auth.ts +0 -333
  15. package/src/actions/checkout.ts +0 -659
  16. package/src/actions/index.ts +0 -11
  17. package/src/actions/masterData.ts +0 -168
  18. package/src/actions/misc.ts +0 -188
  19. package/src/actions/newsletter.ts +0 -105
  20. package/src/actions/orders.ts +0 -34
  21. package/src/actions/profile.ts +0 -201
  22. package/src/actions/session.ts +0 -85
  23. package/src/actions/trigger.ts +0 -43
  24. package/src/actions/wishlist.ts +0 -114
  25. package/src/client.ts +0 -667
  26. package/src/commerceLoaders.ts +0 -267
  27. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  28. package/src/hooks/__tests__/createCart.test.ts +0 -92
  29. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  30. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  31. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  32. package/src/hooks/cartQuery.ts +0 -204
  33. package/src/hooks/createCart.ts +0 -427
  34. package/src/hooks/createUseCart.ts +0 -360
  35. package/src/hooks/createUseUser.ts +0 -153
  36. package/src/hooks/createUseWishlist.ts +0 -242
  37. package/src/hooks/index.ts +0 -25
  38. package/src/hooks/useAutocomplete.ts +0 -83
  39. package/src/hooks/useCart.ts +0 -243
  40. package/src/hooks/useUser.ts +0 -78
  41. package/src/hooks/useWishlist.ts +0 -119
  42. package/src/invoke.ts +0 -235
  43. package/src/loaders/ProductDetailsPage.ts +0 -1
  44. package/src/loaders/ProductList.ts +0 -1
  45. package/src/loaders/ProductListingPage.ts +0 -1
  46. package/src/loaders/address.ts +0 -115
  47. package/src/loaders/autocomplete.ts +0 -58
  48. package/src/loaders/brands.ts +0 -44
  49. package/src/loaders/cart/attachments.ts +0 -53
  50. package/src/loaders/cart/full.ts +0 -67
  51. package/src/loaders/cart/gifts.ts +0 -43
  52. package/src/loaders/cart/orderFormId.ts +0 -31
  53. package/src/loaders/cart/shipping.test.ts +0 -125
  54. package/src/loaders/cart/shipping.ts +0 -136
  55. package/src/loaders/cart/summary.ts +0 -36
  56. package/src/loaders/cart.ts +0 -52
  57. package/src/loaders/catalog.ts +0 -163
  58. package/src/loaders/collections.ts +0 -55
  59. package/src/loaders/index.ts +0 -20
  60. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  61. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -113
  62. package/src/loaders/intelligentSearch/productList.ts +0 -154
  63. package/src/loaders/intelligentSearch/productListingPage.ts +0 -526
  64. package/src/loaders/intelligentSearch/suggestions.ts +0 -46
  65. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  66. package/src/loaders/legacy/productList.ts +0 -1
  67. package/src/loaders/legacy/relatedProductsLoader.ts +0 -81
  68. package/src/loaders/legacy.ts +0 -635
  69. package/src/loaders/logistics.ts +0 -111
  70. package/src/loaders/minicart.ts +0 -78
  71. package/src/loaders/navbar.ts +0 -26
  72. package/src/loaders/orders.ts +0 -92
  73. package/src/loaders/pageType.ts +0 -68
  74. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  75. package/src/loaders/payment.ts +0 -97
  76. package/src/loaders/productListFull.ts +0 -159
  77. package/src/loaders/profile.ts +0 -138
  78. package/src/loaders/promotion.ts +0 -30
  79. package/src/loaders/search.ts +0 -124
  80. package/src/loaders/session.ts +0 -83
  81. package/src/loaders/user.ts +0 -87
  82. package/src/loaders/wishlist.ts +0 -89
  83. package/src/loaders/wishlistProducts.ts +0 -69
  84. package/src/loaders/workflow/products.ts +0 -64
  85. package/src/loaders/workflow.ts +0 -316
  86. package/src/logo.png +0 -0
  87. package/src/manifest.gen.ts +0 -89
  88. package/src/middleware.ts +0 -240
  89. package/src/mod.ts +0 -157
  90. package/src/registry.ts +0 -9
  91. package/src/schemas.gen.ts +0 -3191
  92. package/src/schemas.ts +0 -30
  93. package/src/types.ts +0 -248
  94. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  95. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  96. package/src/utils/__tests__/fetch.test.ts +0 -80
  97. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  98. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  99. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  100. package/src/utils/__tests__/minicart.test.ts +0 -184
  101. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  102. package/src/utils/__tests__/resilience.test.ts +0 -205
  103. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  104. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  105. package/src/utils/__tests__/sitemap.test.ts +0 -185
  106. package/src/utils/__tests__/slugify.test.ts +0 -31
  107. package/src/utils/__tests__/transform.test.ts +0 -769
  108. package/src/utils/accountLoaders.ts +0 -203
  109. package/src/utils/authHelpers.ts +0 -85
  110. package/src/utils/batch.ts +0 -18
  111. package/src/utils/cartProjection.ts +0 -96
  112. package/src/utils/cookies.ts +0 -167
  113. package/src/utils/enrichment.ts +0 -560
  114. package/src/utils/fetch.ts +0 -107
  115. package/src/utils/fetchCache.ts +0 -199
  116. package/src/utils/index.ts +0 -19
  117. package/src/utils/instrumentedFetch.ts +0 -97
  118. package/src/utils/intelligentSearch.ts +0 -96
  119. package/src/utils/legacy.ts +0 -139
  120. package/src/utils/minicart.ts +0 -139
  121. package/src/utils/pickAndOmit.ts +0 -25
  122. package/src/utils/proxy.ts +0 -435
  123. package/src/utils/resilience.ts +0 -352
  124. package/src/utils/resourceRange.ts +0 -10
  125. package/src/utils/segment.ts +0 -152
  126. package/src/utils/similars.ts +0 -37
  127. package/src/utils/simulationCache.ts +0 -73
  128. package/src/utils/sitemap.ts +0 -282
  129. package/src/utils/slugCache.ts +0 -28
  130. package/src/utils/slugify.ts +0 -13
  131. package/src/utils/transform.ts +0 -1522
  132. package/src/utils/vtexId.ts +0 -138
  133. 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
- }