@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
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
@@ -30,120 +30,120 @@ import { DEFAULT_RESILIENCE_CONFIG } from "./utils/constants";
30
30
  import { sanitizeOutboundCookieHeader } from "./utils/cookieSanitizer";
31
31
  import { vtexOperationRouter } from "./utils/operationRouter";
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,
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
49
  } from "./utils/types";
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>;