@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
package/src/vtexClient.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * per-shopper input (cookies, region) as arguments, and never reads the
19
19
  * incoming request, caches, or converts. Converters, hooks, cart/session/sign-in
20
20
  * flows and loaders live in the VTEX platform template and site code; upstream
21
- * caching lives in the framework binding (/next/caching#upstream-data), keyed
21
+ * caching lives in the site (/next/caching#upstream-data), keyed
22
22
  * by everything a response depends on, such as `regionId`.
23
23
  *
24
24
  * Retries and a circuit breaker are ON by default for VTEX (and only for
@@ -26,124 +26,124 @@
26
26
  * never repeated. Pass `retry: false` / `circuitBreaker: false` to turn them off.
27
27
  */
28
28
  import { createInstrumentedFetch } from "@decocms/blocks/fetch";
29
- import { DEFAULT_RESILIENCE_CONFIG } from "./utils/constants";
30
- import { sanitizeOutboundCookieHeader } from "./utils/cookieSanitizer";
31
- import { vtexOperationRouter } from "./utils/operationRouter";
29
+ import { DEFAULT_RESILIENCE_CONFIG } from "./utils/constants.ts";
30
+ import { sanitizeOutboundCookieHeader } from "./utils/cookieSanitizer.ts";
31
+ import { vtexOperationRouter } from "./utils/operationRouter.ts";
32
32
  import type {
33
- Category,
34
- FacetSearchResult,
35
- Fuzzy,
36
- LegacyProduct,
37
- LegacySort,
38
- CrossSellingType,
39
- OrderForm,
40
- OrderFormItemInput,
41
- PageType,
42
- ProductSearchResult,
43
- SelectedFacet,
44
- Session,
45
- SimulationBehavior,
46
- SimulationOrderForm,
47
- Sort,
48
- Suggestion,
49
- } from "./utils/types";
33
+ Category,
34
+ CrossSellingType,
35
+ FacetSearchResult,
36
+ Fuzzy,
37
+ LegacyProduct,
38
+ LegacySort,
39
+ OrderForm,
40
+ OrderFormItemInput,
41
+ PageType,
42
+ ProductSearchResult,
43
+ SelectedFacet,
44
+ Session,
45
+ SimulationBehavior,
46
+ SimulationOrderForm,
47
+ Sort,
48
+ Suggestion,
49
+ } from "./utils/types.ts";
50
50
 
51
51
  export interface VtexClientConfig {
52
- /** The VTEX account name, e.g. `mystore`. */
53
- account: string;
54
- /** Sent as `X-VTEX-API-AppKey` when set together with `appToken`. */
55
- appKey?: string;
56
- /** Sent as `X-VTEX-API-AppToken` when set together with `appKey`. */
57
- appToken?: string;
58
- /** @default "vtexcommercestable" */
59
- environment?: string;
60
- /** @default "com.br" */
61
- domain?: string;
62
- /** Default sales channel (`sc`), overridable per call where VTEX accepts one. */
63
- salesChannel?: string;
64
- /** Default Intelligent Search locale, e.g. `pt-BR`. */
65
- locale?: string;
66
- /** The fetch underneath; for tests. Defaults to `globalThis.fetch`. */
67
- fetch?: typeof fetch;
68
- /** On by default (2 retries, 150ms backoff); idempotent requests only. `false` turns it off. */
69
- retry?: { attempts: number; backoffMs?: number } | false;
70
- /** On by default (opens after 5 failures, for 5s). `false` turns it off. */
71
- circuitBreaker?: { failures: number; cooldownMs: number } | false;
52
+ /** The VTEX account name, e.g. `mystore`. */
53
+ account: string;
54
+ /** Sent as `X-VTEX-API-AppKey` when set together with `appToken`. */
55
+ appKey?: string;
56
+ /** Sent as `X-VTEX-API-AppToken` when set together with `appKey`. */
57
+ appToken?: string;
58
+ /** @default "vtexcommercestable" */
59
+ environment?: string;
60
+ /** @default "com.br" */
61
+ domain?: string;
62
+ /** Default sales channel (`sc`), overridable per call where VTEX accepts one. */
63
+ salesChannel?: string;
64
+ /** Default Intelligent Search locale, e.g. `pt-BR`. */
65
+ locale?: string;
66
+ /** The fetch underneath; for tests. Defaults to `globalThis.fetch`. */
67
+ fetch?: typeof globalThis.fetch;
68
+ /** On by default (2 retries, 150ms backoff); idempotent requests only. `false` turns it off. */
69
+ retry?: { attempts: number; backoffMs?: number } | false;
70
+ /** On by default (opens after 5 failures, for 5s). `false` turns it off. */
71
+ circuitBreaker?: { failures: number; cooldownMs: number } | false;
72
72
  }
73
73
 
74
74
  const VTEX_DEFAULT_RETRY = {
75
- attempts: DEFAULT_RESILIENCE_CONFIG.maxRetries,
76
- backoffMs: DEFAULT_RESILIENCE_CONFIG.backoffBaseMs,
75
+ attempts: DEFAULT_RESILIENCE_CONFIG.maxRetries,
76
+ backoffMs: DEFAULT_RESILIENCE_CONFIG.backoffBaseMs,
77
77
  } as const;
78
78
 
79
79
  const VTEX_DEFAULT_CIRCUIT_BREAKER = {
80
- failures: DEFAULT_RESILIENCE_CONFIG.breakerConsecutiveFailures,
81
- cooldownMs: DEFAULT_RESILIENCE_CONFIG.breakerOpenCooldownMs,
80
+ failures: DEFAULT_RESILIENCE_CONFIG.breakerConsecutiveFailures,
81
+ cooldownMs: DEFAULT_RESILIENCE_CONFIG.breakerOpenCooldownMs,
82
82
  } as const;
83
83
 
84
84
  /** Per-call inputs. Nothing is read from the incoming request. */
85
85
  export interface VtexRequestOptions {
86
- /** The shopper's `Cookie` header to forward (non-ASCII and malformed pairs are dropped). */
87
- cookie?: string;
88
- signal?: AbortSignal;
86
+ /** The shopper's `Cookie` header to forward (non-ASCII and malformed pairs are dropped). */
87
+ cookie?: string;
88
+ signal?: AbortSignal;
89
89
  }
90
90
 
91
91
  /** A shopper-scoped response: the body plus VTEX's `Set-Cookie` values, unmodified, for the caller to forward. */
92
92
  export interface VtexResponse<T> {
93
- data: T;
94
- setCookies: string[];
93
+ data: T;
94
+ setCookies: string[];
95
95
  }
96
96
 
97
97
  /** A failed VTEX call. Carries the operation and the status, never a body, URL, token or cookie. */
98
98
  export class VtexError extends Error {
99
- constructor(
100
- readonly operation: string,
101
- readonly status: number,
102
- ) {
103
- super(`vtex ${operation} failed with HTTP ${status}`);
104
- this.name = "VtexError";
105
- }
99
+ constructor(
100
+ readonly operation: string,
101
+ readonly status: number,
102
+ ) {
103
+ super(`vtex ${operation} failed with HTTP ${status}`);
104
+ this.name = "VtexError";
105
+ }
106
106
  }
107
107
 
108
108
  /** Intelligent Search arguments, mirroring its query parameters. */
109
109
  export interface VtexSearchArgs extends VtexRequestOptions {
110
- query?: string;
111
- /** Selected facets, sent as the path (`category-1/shirts/brand/acme`). */
112
- facets?: SelectedFacet[];
113
- /** Intelligent Search's 1-based page. */
114
- page?: number;
115
- count?: number;
116
- sort?: Sort;
117
- fuzzy?: Fuzzy;
118
- locale?: string;
119
- hideUnavailableItems?: boolean;
120
- simulationBehavior?: SimulationBehavior;
121
- /** The shopper's region (from the `vtex_segment` cookie); part of the cache key. */
122
- regionId?: string;
123
- salesChannel?: string;
110
+ query?: string;
111
+ /** Selected facets, sent as the path (`category-1/shirts/brand/acme`). */
112
+ facets?: SelectedFacet[];
113
+ /** Intelligent Search's 1-based page. */
114
+ page?: number;
115
+ count?: number;
116
+ sort?: Sort;
117
+ fuzzy?: Fuzzy;
118
+ locale?: string;
119
+ hideUnavailableItems?: boolean;
120
+ simulationBehavior?: SimulationBehavior;
121
+ /** The shopper's region (from the `vtex_segment` cookie); part of the cache key. */
122
+ regionId?: string;
123
+ salesChannel?: string;
124
124
  }
125
125
 
126
126
  /** Legacy Catalog search arguments (`/api/catalog_system/pub/products/search`). */
127
127
  export interface VtexCatalogSearchArgs extends VtexRequestOptions {
128
- /** Path term, e.g. a category path. */
129
- term?: string;
130
- /** Full-text query (`ft`). */
131
- ft?: string;
132
- /** Filter queries (`fq`), e.g. `productId:123`. */
133
- fq?: string[];
134
- /** `_from`, 0-based inclusive. */
135
- from?: number;
136
- /** `_to`, 0-based inclusive. */
137
- to?: number;
138
- /** `O`. */
139
- sort?: LegacySort;
140
- salesChannel?: string;
128
+ /** Path term, e.g. a category path. */
129
+ term?: string;
130
+ /** Full-text query (`ft`). */
131
+ ft?: string;
132
+ /** Filter queries (`fq`), e.g. `productId:123`. */
133
+ fq?: string[];
134
+ /** `_from`, 0-based inclusive. */
135
+ from?: number;
136
+ /** `_to`, 0-based inclusive. */
137
+ to?: number;
138
+ /** `O`. */
139
+ sort?: LegacySort;
140
+ salesChannel?: string;
141
141
  }
142
142
 
143
143
  interface CallInit extends VtexRequestOptions {
144
- method?: string;
145
- body?: unknown;
146
- headers?: Record<string, string>;
144
+ method?: string;
145
+ body?: unknown;
146
+ headers?: Record<string, string>;
147
147
  }
148
148
 
149
149
  /**
@@ -152,282 +152,293 @@ interface CallInit extends VtexRequestOptions {
152
152
  * query (`?`, `#`) while the app credentials are attached.
153
153
  */
154
154
  function pathSegments(path: string): string {
155
- return path
156
- .split("/")
157
- .filter((segment) => segment !== "")
158
- .map((segment) => {
159
- if (/^(\.|%2e){1,2}$/i.test(segment)) throw new Error("vtex: invalid path segment");
160
- return encodeURIComponent(segment);
161
- })
162
- .join("/");
155
+ return path
156
+ .split("/")
157
+ .filter((segment) => segment !== "")
158
+ .map((segment) => {
159
+ if (/^(\.|%2e){1,2}$/i.test(segment)) throw new Error("vtex: invalid path segment");
160
+ return encodeURIComponent(segment);
161
+ })
162
+ .join("/");
163
163
  }
164
164
 
165
165
  export function createVtexClient(config: VtexClientConfig) {
166
- const host = `${config.account}.${config.environment ?? "vtexcommercestable"}.${config.domain ?? "com.br"}`;
167
- const base = `https://${host}`;
168
- const request = createInstrumentedFetch({
169
- provider: "vtex",
170
- fetch: config.fetch,
171
- retry: config.retry === false ? undefined : (config.retry ?? VTEX_DEFAULT_RETRY),
172
- circuitBreaker:
173
- config.circuitBreaker === false
174
- ? undefined
175
- : (config.circuitBreaker ?? VTEX_DEFAULT_CIRCUIT_BREAKER),
176
- });
166
+ const host = `${config.account}.${config.environment ?? "vtexcommercestable"}.${config.domain ?? "com.br"}`;
167
+ const base = `https://${host}`;
168
+ const request = createInstrumentedFetch({
169
+ provider: "vtex",
170
+ fetch: config.fetch,
171
+ retry: config.retry === false ? undefined : (config.retry ?? VTEX_DEFAULT_RETRY),
172
+ circuitBreaker:
173
+ config.circuitBreaker === false
174
+ ? undefined
175
+ : (config.circuitBreaker ?? VTEX_DEFAULT_CIRCUIT_BREAKER),
176
+ });
177
177
 
178
- async function call(url: string | URL, init: CallInit = {}): Promise<Response> {
179
- const method = (init.method ?? "GET").toUpperCase();
180
- const operation = vtexOperationRouter(String(url), method) ?? "unknown";
181
- const headers = new Headers({ accept: "application/json", ...init.headers });
182
- if (init.body !== undefined) headers.set("content-type", "application/json");
183
- // Shopper-scoped calls go out as the shopper only: with app credentials
184
- // VTEX would answer an orderForm with the profile data unmasked.
185
- if (config.appKey && config.appToken && !init.cookie) {
186
- headers.set("x-vtex-api-appkey", config.appKey);
187
- headers.set("x-vtex-api-apptoken", config.appToken);
188
- }
189
- if (init.cookie) {
190
- const { cookies } = sanitizeOutboundCookieHeader(init.cookie);
191
- if (cookies) headers.set("cookie", cookies);
192
- }
193
- const response = await request(url, {
194
- method,
195
- headers,
196
- body: init.body === undefined ? undefined : JSON.stringify(init.body),
197
- signal: init.signal,
198
- operation,
199
- });
200
- if (!response.ok) {
201
- void response.body?.cancel().catch(() => {});
202
- throw new VtexError(operation, response.status);
203
- }
204
- return response;
205
- }
178
+ async function call(url: string | URL, init: CallInit = {}): Promise<Response> {
179
+ const method = (init.method ?? "GET").toUpperCase();
180
+ const operation = vtexOperationRouter(String(url), method) ?? "unknown";
181
+ const headers = new Headers({ accept: "application/json", ...init.headers });
182
+ if (init.body !== undefined) headers.set("content-type", "application/json");
183
+ // Shopper-scoped calls go out as the shopper only: with app credentials
184
+ // VTEX would answer an orderForm with the profile data unmasked.
185
+ if (config.appKey && config.appToken && !init.cookie) {
186
+ headers.set("x-vtex-api-appkey", config.appKey);
187
+ headers.set("x-vtex-api-apptoken", config.appToken);
188
+ }
189
+ if (init.cookie) {
190
+ const { cookies } = sanitizeOutboundCookieHeader(init.cookie);
191
+ if (cookies) headers.set("cookie", cookies);
192
+ }
193
+ const response = await request(url, {
194
+ method,
195
+ headers,
196
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
197
+ signal: init.signal,
198
+ operation,
199
+ });
200
+ if (!response.ok) {
201
+ void response.body?.cancel().catch(() => {});
202
+ throw new VtexError(operation, response.status);
203
+ }
204
+ return response;
205
+ }
206
206
 
207
- /** Only the per-call request options, never the operation's other arguments. */
208
- const pick = (options: VtexRequestOptions = {}): VtexRequestOptions => ({
209
- cookie: options.cookie,
210
- signal: options.signal,
211
- });
207
+ /** Only the per-call request options, never the operation's other arguments. */
208
+ const pick = (options: VtexRequestOptions = {}): VtexRequestOptions => ({
209
+ cookie: options.cookie,
210
+ signal: options.signal,
211
+ });
212
212
 
213
- const json = async <T>(url: string | URL, init?: CallInit): Promise<T> =>
214
- (await call(url, init)).json() as Promise<T>;
213
+ const json = async <T>(url: string | URL, init?: CallInit): Promise<T> =>
214
+ (await call(url, init)).json() as Promise<T>;
215
215
 
216
- const withCookies = async <T>(url: string | URL, init?: CallInit): Promise<VtexResponse<T>> => {
217
- const response = await call(url, init);
218
- const setCookies =
219
- typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
220
- return { data: (await response.json()) as T, setCookies };
221
- };
216
+ const withCookies = async <T>(url: string | URL, init?: CallInit): Promise<VtexResponse<T>> => {
217
+ const response = await call(url, init);
218
+ const setCookies =
219
+ typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
220
+ return { data: (await response.json()) as T, setCookies };
221
+ };
222
222
 
223
- const url = (path: string, params: Record<string, string | number | boolean | undefined> = {}) => {
224
- const result = new URL(path, base);
225
- for (const [key, value] of Object.entries(params)) {
226
- if (value !== undefined && value !== "") result.searchParams.set(key, String(value));
227
- }
228
- return result;
229
- };
223
+ const url = (
224
+ path: string,
225
+ params: Record<string, string | number | boolean | undefined> = {},
226
+ ) => {
227
+ const result = new URL(path, base);
228
+ for (const [key, value] of Object.entries(params)) {
229
+ if (value !== undefined && value !== "") result.searchParams.set(key, String(value));
230
+ }
231
+ return result;
232
+ };
230
233
 
231
- const isUrl = (endpoint: string, args: VtexSearchArgs) => {
232
- const facetPath = (args.facets ?? [])
233
- .map(({ key, value }) =>
234
- key ? `${encodeURIComponent(key)}/${encodeURIComponent(value)}` : encodeURIComponent(value),
235
- )
236
- .join("/");
237
- return url(`/api/io/_v/api/intelligent-search/${endpoint}/${facetPath}`, {
238
- query: args.query,
239
- page: args.page,
240
- count: args.count,
241
- sort: args.sort,
242
- fuzzy: args.fuzzy,
243
- locale: args.locale ?? config.locale,
244
- hideUnavailableItems: args.hideUnavailableItems,
245
- simulationBehavior: args.simulationBehavior,
246
- regionId: args.regionId,
247
- sc: args.salesChannel ?? config.salesChannel,
248
- });
249
- };
234
+ const isUrl = (endpoint: string, args: VtexSearchArgs) => {
235
+ const facetPath = (args.facets ?? [])
236
+ .map(({ key, value }) =>
237
+ key ? `${encodeURIComponent(key)}/${encodeURIComponent(value)}` : encodeURIComponent(value),
238
+ )
239
+ .join("/");
240
+ return url(`/api/io/_v/api/intelligent-search/${endpoint}/${facetPath}`, {
241
+ query: args.query,
242
+ page: args.page,
243
+ count: args.count,
244
+ sort: args.sort,
245
+ fuzzy: args.fuzzy,
246
+ locale: args.locale ?? config.locale,
247
+ hideUnavailableItems: args.hideUnavailableItems,
248
+ simulationBehavior: args.simulationBehavior,
249
+ regionId: args.regionId,
250
+ sc: args.salesChannel ?? config.salesChannel,
251
+ });
252
+ };
250
253
 
251
- const orderFormPath = (orderFormId: string, rest = "") =>
252
- `/api/checkout/pub/orderForm/${encodeURIComponent(orderFormId)}${rest}`;
254
+ const orderFormPath = (orderFormId: string, rest = "") =>
255
+ `/api/checkout/pub/orderForm/${encodeURIComponent(orderFormId)}${rest}`;
253
256
 
254
- return {
255
- /** Intelligent Search. */
256
- search: {
257
- products: (args: VtexSearchArgs = {}) =>
258
- json<ProductSearchResult>(isUrl("product_search", args), pick(args)),
259
- facets: (args: VtexSearchArgs = {}) => json<FacetSearchResult>(isUrl("facets", args), pick(args)),
260
- suggestions: (args: VtexSearchArgs) =>
261
- json<Suggestion>(isUrl("search_suggestions", { ...args, facets: [] }), pick(args)),
262
- autocomplete: (args: VtexSearchArgs) =>
263
- json<Suggestion>(isUrl("autocomplete_suggestions", { ...args, facets: [] }), pick(args)),
264
- topSearches: (args: VtexSearchArgs = {}) =>
265
- json<Suggestion>(isUrl("top_searches", { ...args, facets: [] }), pick(args)),
266
- },
257
+ return {
258
+ /** Intelligent Search. */
259
+ search: {
260
+ products: (args: VtexSearchArgs = {}) =>
261
+ json<ProductSearchResult>(isUrl("product_search", args), pick(args)),
262
+ facets: (args: VtexSearchArgs = {}) =>
263
+ json<FacetSearchResult>(isUrl("facets", args), pick(args)),
264
+ suggestions: (args: VtexSearchArgs) =>
265
+ json<Suggestion>(isUrl("search_suggestions", { ...args, facets: [] }), pick(args)),
266
+ autocomplete: (args: VtexSearchArgs) =>
267
+ json<Suggestion>(isUrl("autocomplete_suggestions", { ...args, facets: [] }), pick(args)),
268
+ topSearches: (args: VtexSearchArgs = {}) =>
269
+ json<Suggestion>(isUrl("top_searches", { ...args, facets: [] }), pick(args)),
270
+ },
267
271
 
268
- /** Legacy Catalog API. */
269
- catalog: {
270
- pageType: async (path: string, options?: VtexRequestOptions) =>
271
- json<PageType>(url(`/api/catalog_system/pub/portal/pagetype/${pathSegments(path)}`), options),
272
- products: async (args: VtexCatalogSearchArgs = {}) => {
273
- const target = url(
274
- `/api/catalog_system/pub/products/search/${pathSegments(args.term ?? "")}`,
275
- {
276
- ft: args.ft,
277
- _from: args.from,
278
- _to: args.to,
279
- O: args.sort,
280
- sc: args.salesChannel ?? config.salesChannel,
281
- },
282
- );
283
- for (const fq of args.fq ?? []) target.searchParams.append("fq", fq);
284
- return json<LegacyProduct[]>(target, pick(args));
285
- },
286
- crossSelling: (type: CrossSellingType, productId: string, options?: VtexRequestOptions) =>
287
- json<LegacyProduct[]>(
288
- url(
289
- `/api/catalog_system/pub/products/crossselling/${type}/${encodeURIComponent(productId)}`,
290
- ),
291
- options,
292
- ),
293
- categoryTree: (levels = 3, options?: VtexRequestOptions) =>
294
- json<Category[]>(url(`/api/catalog_system/pub/category/tree/${levels}`), options),
295
- },
272
+ /** Legacy Catalog API. */
273
+ catalog: {
274
+ pageType: async (path: string, options?: VtexRequestOptions) =>
275
+ json<PageType>(
276
+ url(`/api/catalog_system/pub/portal/pagetype/${pathSegments(path)}`),
277
+ options,
278
+ ),
279
+ products: async (args: VtexCatalogSearchArgs = {}) => {
280
+ const target = url(
281
+ `/api/catalog_system/pub/products/search/${pathSegments(args.term ?? "")}`,
282
+ {
283
+ ft: args.ft,
284
+ _from: args.from,
285
+ _to: args.to,
286
+ O: args.sort,
287
+ sc: args.salesChannel ?? config.salesChannel,
288
+ },
289
+ );
290
+ for (const fq of args.fq ?? []) target.searchParams.append("fq", fq);
291
+ return json<LegacyProduct[]>(target, pick(args));
292
+ },
293
+ crossSelling: (type: CrossSellingType, productId: string, options?: VtexRequestOptions) =>
294
+ json<LegacyProduct[]>(
295
+ url(
296
+ `/api/catalog_system/pub/products/crossselling/${type}/${encodeURIComponent(productId)}`,
297
+ ),
298
+ options,
299
+ ),
300
+ categoryTree: (levels = 3, options?: VtexRequestOptions) =>
301
+ json<Category[]>(url(`/api/catalog_system/pub/category/tree/${levels}`), options),
302
+ },
296
303
 
297
- /** Checkout API. Cart calls are shopper-scoped and return VTEX's `Set-Cookie` values. */
298
- checkout: {
299
- /**
300
- * Gets the shopper's cart from their `checkout.vtex.com` cookie, or creates one.
301
- * Every section by default; `sections` narrows it (`expectedOrderFormSections`).
302
- */
303
- orderForm: (options: VtexRequestOptions & { sections?: string[] } = {}) =>
304
- withCookies<OrderForm>(url("/api/checkout/pub/orderForm"), {
305
- ...pick(options),
306
- method: "POST",
307
- body: options.sections ? { expectedOrderFormSections: options.sections } : {},
308
- }),
309
- addItems: (orderFormId: string, orderItems: OrderFormItemInput[], options?: VtexRequestOptions) =>
310
- withCookies<OrderForm>(url(orderFormPath(orderFormId, "/items")), {
311
- ...options,
312
- method: "POST",
313
- body: { orderItems },
314
- }),
315
- updateItems: (
316
- orderFormId: string,
317
- orderItems: { index: number; quantity: number }[],
318
- options?: VtexRequestOptions,
319
- ) =>
320
- withCookies<OrderForm>(url(orderFormPath(orderFormId, "/items/update")), {
321
- ...options,
322
- method: "POST",
323
- body: { orderItems },
324
- }),
325
- addCoupon: (orderFormId: string, text: string, options?: VtexRequestOptions) =>
326
- withCookies<OrderForm>(url(orderFormPath(orderFormId, "/coupons")), {
327
- ...options,
328
- method: "POST",
329
- body: { text },
330
- }),
331
- attachment: (
332
- orderFormId: string,
333
- name: string,
334
- body: Record<string, unknown>,
335
- options?: VtexRequestOptions,
336
- ) =>
337
- withCookies<OrderForm>(
338
- url(orderFormPath(orderFormId, `/attachments/${encodeURIComponent(name)}`)),
339
- { ...options, method: "POST", body },
340
- ),
341
- simulation: (
342
- body: {
343
- items: { id: string; quantity: number; seller: string }[];
344
- postalCode?: string;
345
- country?: string;
346
- },
347
- options: VtexRequestOptions & { salesChannel?: string } = {},
348
- ) =>
349
- json<SimulationOrderForm>(
350
- url("/api/checkout/pub/orderForms/simulation", {
351
- sc: options.salesChannel ?? config.salesChannel,
352
- }),
353
- { ...pick(options), method: "POST", body },
354
- ),
355
- /** The region (and its sellers) serving a postal code; its `id` is the `regionId`. */
356
- regions: (
357
- args: { postalCode: string; country: string; salesChannel?: string },
358
- options?: VtexRequestOptions,
359
- ) =>
360
- json<{ id: string; sellers: { id: string; name: string }[] }[]>(
361
- url("/api/checkout/pub/regions", {
362
- postalCode: args.postalCode,
363
- country: args.country,
364
- sc: args.salesChannel ?? config.salesChannel,
365
- }),
366
- options,
367
- ),
368
- },
304
+ /** Checkout API. Cart calls are shopper-scoped and return VTEX's `Set-Cookie` values. */
305
+ checkout: {
306
+ /**
307
+ * Gets the shopper's cart from their `checkout.vtex.com` cookie, or creates one.
308
+ * Every section by default; `sections` narrows it (`expectedOrderFormSections`).
309
+ */
310
+ orderForm: (options: VtexRequestOptions & { sections?: string[] } = {}) =>
311
+ withCookies<OrderForm>(url("/api/checkout/pub/orderForm"), {
312
+ ...pick(options),
313
+ method: "POST",
314
+ body: options.sections ? { expectedOrderFormSections: options.sections } : {},
315
+ }),
316
+ addItems: (
317
+ orderFormId: string,
318
+ orderItems: OrderFormItemInput[],
319
+ options?: VtexRequestOptions,
320
+ ) =>
321
+ withCookies<OrderForm>(url(orderFormPath(orderFormId, "/items")), {
322
+ ...options,
323
+ method: "POST",
324
+ body: { orderItems },
325
+ }),
326
+ updateItems: (
327
+ orderFormId: string,
328
+ orderItems: { index: number; quantity: number }[],
329
+ options?: VtexRequestOptions,
330
+ ) =>
331
+ withCookies<OrderForm>(url(orderFormPath(orderFormId, "/items/update")), {
332
+ ...options,
333
+ method: "POST",
334
+ body: { orderItems },
335
+ }),
336
+ addCoupon: (orderFormId: string, text: string, options?: VtexRequestOptions) =>
337
+ withCookies<OrderForm>(url(orderFormPath(orderFormId, "/coupons")), {
338
+ ...options,
339
+ method: "POST",
340
+ body: { text },
341
+ }),
342
+ attachment: (
343
+ orderFormId: string,
344
+ name: string,
345
+ body: Record<string, unknown>,
346
+ options?: VtexRequestOptions,
347
+ ) =>
348
+ withCookies<OrderForm>(
349
+ url(orderFormPath(orderFormId, `/attachments/${encodeURIComponent(name)}`)),
350
+ { ...options, method: "POST", body },
351
+ ),
352
+ simulation: (
353
+ body: {
354
+ items: { id: string; quantity: number; seller: string }[];
355
+ postalCode?: string;
356
+ country?: string;
357
+ },
358
+ options: VtexRequestOptions & { salesChannel?: string } = {},
359
+ ) =>
360
+ json<SimulationOrderForm>(
361
+ url("/api/checkout/pub/orderForms/simulation", {
362
+ sc: options.salesChannel ?? config.salesChannel,
363
+ }),
364
+ { ...pick(options), method: "POST", body },
365
+ ),
366
+ /** The region (and its sellers) serving a postal code; its `id` is the `regionId`. */
367
+ regions: (
368
+ args: { postalCode: string; country: string; salesChannel?: string },
369
+ options?: VtexRequestOptions,
370
+ ) =>
371
+ json<{ id: string; sellers: { id: string; name: string }[] }[]>(
372
+ url("/api/checkout/pub/regions", {
373
+ postalCode: args.postalCode,
374
+ country: args.country,
375
+ sc: args.salesChannel ?? config.salesChannel,
376
+ }),
377
+ options,
378
+ ),
379
+ },
369
380
 
370
- /** Session API. Shopper-scoped; returns VTEX's `Set-Cookie` values. */
371
- sessions: {
372
- get: (options: VtexRequestOptions & { items?: string[] } = {}) =>
373
- withCookies<Session>(
374
- url("/api/sessions", { items: options.items?.join(",") }),
375
- pick(options),
376
- ),
377
- update: (publicProps: Record<string, { value: string }>, options?: VtexRequestOptions) =>
378
- withCookies<{ id: string; sessionToken?: string }>(url("/api/sessions"), {
379
- ...options,
380
- method: "POST",
381
- body: { public: publicProps },
382
- }),
383
- },
381
+ /** Session API. Shopper-scoped; returns VTEX's `Set-Cookie` values. */
382
+ sessions: {
383
+ get: (options: VtexRequestOptions & { items?: string[] } = {}) =>
384
+ withCookies<Session>(
385
+ url("/api/sessions", { items: options.items?.join(",") }),
386
+ pick(options),
387
+ ),
388
+ update: (publicProps: Record<string, { value: string }>, options?: VtexRequestOptions) =>
389
+ withCookies<{ id: string; sessionToken?: string }>(url("/api/sessions"), {
390
+ ...options,
391
+ method: "POST",
392
+ body: { public: publicProps },
393
+ }),
394
+ },
384
395
 
385
- /** VTEX IO's private GraphQL endpoint (`{account}.myvtex.com`). */
386
- io: {
387
- graphql: async <T>(
388
- body: { query: string; variables?: Record<string, unknown>; operationName?: string },
389
- options?: VtexRequestOptions,
390
- ): Promise<T> => {
391
- const result = await json<{ data: T; errors?: unknown[] }>(
392
- `https://${config.account}.myvtex.com/_v/private/graphql/v1`,
393
- { ...options, method: "POST", body },
394
- );
395
- // GraphQL errors arrive with HTTP 200; their messages can echo inputs, so they stay out.
396
- if (result.errors?.length) throw new VtexError("io.graphql", 200);
397
- return result.data;
398
- },
399
- },
396
+ /** VTEX IO's private GraphQL endpoint (`{account}.myvtex.com`). */
397
+ io: {
398
+ graphql: async <T>(
399
+ body: { query: string; variables?: Record<string, unknown>; operationName?: string },
400
+ options?: VtexRequestOptions,
401
+ ): Promise<T> => {
402
+ const result = await json<{ data: T; errors?: unknown[] }>(
403
+ `https://${config.account}.myvtex.com/_v/private/graphql/v1`,
404
+ { ...options, method: "POST", body },
405
+ );
406
+ // GraphQL errors arrive with HTTP 200; their messages can echo inputs, so they stay out.
407
+ if (result.errors?.length) throw new VtexError("io.graphql", 200);
408
+ return result.data;
409
+ },
410
+ },
400
411
 
401
- /** Master Data v1. */
402
- masterdata: {
403
- search: <T>(
404
- entity: string,
405
- args: VtexRequestOptions & {
406
- where?: string;
407
- fields?: string[];
408
- sort?: string;
409
- from?: number;
410
- to?: number;
411
- } = {},
412
- ) =>
413
- json<T[]>(
414
- url(`/api/dataentities/${encodeURIComponent(entity)}/search`, {
415
- _where: args.where,
416
- _fields: args.fields?.join(","),
417
- _sort: args.sort,
418
- }),
419
- {
420
- ...pick(args),
421
- headers: { "rest-range": `resources=${args.from ?? 0}-${args.to ?? 10}` },
422
- },
423
- ),
424
- create: (entity: string, document: Record<string, unknown>, options?: VtexRequestOptions) =>
425
- json<{ Id: string; Href: string; DocumentId: string }>(
426
- url(`/api/dataentities/${encodeURIComponent(entity)}/documents`),
427
- { ...options, method: "POST", body: document },
428
- ),
429
- },
430
- };
412
+ /** Master Data v1. */
413
+ masterdata: {
414
+ search: <T>(
415
+ entity: string,
416
+ args: VtexRequestOptions & {
417
+ where?: string;
418
+ fields?: string[];
419
+ sort?: string;
420
+ from?: number;
421
+ to?: number;
422
+ } = {},
423
+ ) =>
424
+ json<T[]>(
425
+ url(`/api/dataentities/${encodeURIComponent(entity)}/search`, {
426
+ _where: args.where,
427
+ _fields: args.fields?.join(","),
428
+ _sort: args.sort,
429
+ }),
430
+ {
431
+ ...pick(args),
432
+ headers: { "rest-range": `resources=${args.from ?? 0}-${args.to ?? 10}` },
433
+ },
434
+ ),
435
+ create: (entity: string, document: Record<string, unknown>, options?: VtexRequestOptions) =>
436
+ json<{ Id: string; Href: string; DocumentId: string }>(
437
+ url(`/api/dataentities/${encodeURIComponent(entity)}/documents`),
438
+ { ...options, method: "POST", body: document },
439
+ ),
440
+ },
441
+ };
431
442
  }
432
443
 
433
444
  export type VtexClient = ReturnType<typeof createVtexClient>;