@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
@@ -0,0 +1,444 @@
1
+ /**
2
+ * The VTEX upstream client: typed request functions over the framework's
3
+ * instrumented fetch (`@decocms/blocks/fetch`). See /next/upstream-clients.
4
+ *
5
+ * ```ts
6
+ * import { createVtexClient } from "@decocms/apps-vtex";
7
+ *
8
+ * export const vtex = createVtexClient({
9
+ * account: process.env.VTEX_ACCOUNT!,
10
+ * appKey: process.env.VTEX_APP_KEY!,
11
+ * appToken: process.env.VTEX_APP_TOKEN!,
12
+ * });
13
+ *
14
+ * const result = await vtex.search.products({ query: "linen shirt", count: 12 });
15
+ * ```
16
+ *
17
+ * Thin by design: it returns VTEX's own types, takes every setting and every
18
+ * per-shopper input (cookies, region) as arguments, and never reads the
19
+ * incoming request, caches, or converts. Converters, hooks, cart/session/sign-in
20
+ * flows and loaders live in the VTEX platform template and site code; upstream
21
+ * caching lives in the site (/next/caching#upstream-data), keyed
22
+ * by everything a response depends on, such as `regionId`.
23
+ *
24
+ * Retries and a circuit breaker are ON by default for VTEX (and only for
25
+ * VTEX). Retries apply to idempotent requests only, so a checkout POST is
26
+ * never repeated. Pass `retry: false` / `circuitBreaker: false` to turn them off.
27
+ */
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";
32
+ import type {
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";
50
+
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 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
+ }
73
+
74
+ const VTEX_DEFAULT_RETRY = {
75
+ attempts: DEFAULT_RESILIENCE_CONFIG.maxRetries,
76
+ backoffMs: DEFAULT_RESILIENCE_CONFIG.backoffBaseMs,
77
+ } as const;
78
+
79
+ const VTEX_DEFAULT_CIRCUIT_BREAKER = {
80
+ failures: DEFAULT_RESILIENCE_CONFIG.breakerConsecutiveFailures,
81
+ cooldownMs: DEFAULT_RESILIENCE_CONFIG.breakerOpenCooldownMs,
82
+ } as const;
83
+
84
+ /** Per-call inputs. Nothing is read from the incoming request. */
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;
89
+ }
90
+
91
+ /** A shopper-scoped response: the body plus VTEX's `Set-Cookie` values, unmodified, for the caller to forward. */
92
+ export interface VtexResponse<T> {
93
+ data: T;
94
+ setCookies: string[];
95
+ }
96
+
97
+ /** A failed VTEX call. Carries the operation and the status, never a body, URL, token or cookie. */
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
+ }
106
+ }
107
+
108
+ /** Intelligent Search arguments, mirroring its query parameters. */
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;
124
+ }
125
+
126
+ /** Legacy Catalog search arguments (`/api/catalog_system/pub/products/search`). */
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;
141
+ }
142
+
143
+ interface CallInit extends VtexRequestOptions {
144
+ method?: string;
145
+ body?: unknown;
146
+ headers?: Record<string, string>;
147
+ }
148
+
149
+ /**
150
+ * Encodes a caller-supplied path (e.g. the shopper's URL path) segment by
151
+ * segment, so it can't climb out of its endpoint (`..`, `%2e%2e`) or inject a
152
+ * query (`?`, `#`) while the app credentials are attached.
153
+ */
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("/");
163
+ }
164
+
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
+ });
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
+ }
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
+ });
212
+
213
+ const json = async <T>(url: string | URL, init?: CallInit): Promise<T> =>
214
+ (await call(url, init)).json() as Promise<T>;
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
+ };
222
+
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
+ };
233
+
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
+ };
253
+
254
+ const orderFormPath = (orderFormId: string, rest = "") =>
255
+ `/api/checkout/pub/orderForm/${encodeURIComponent(orderFormId)}${rest}`;
256
+
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
+ },
271
+
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
+ },
303
+
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
+ },
380
+
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
+ },
395
+
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
+ },
411
+
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
+ };
442
+ }
443
+
444
+ export type VtexClient = ReturnType<typeof createVtexClient>;
package/src/README.md DELETED
@@ -1,6 +0,0 @@
1
- VTEX is a cloud-based e-commerce platform and digital commerce company that provides a comprehensive set of tools and services for businesses looking to establish and manage their online retail operations.
2
-
3
- This app wrapps VTEX API into a comprehensive set of loaders/actions/workflows
4
- empowering non technical users to interact and act upon their headless commerce.
5
-
6
- If you want to use a custom search engine (Algolia, Typesense etc), you will need to fill the App Key & App Token properties. For these, follow this guide