@decocms/apps 0.20.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 (113) hide show
  1. package/.github/workflows/release.yml +34 -0
  2. package/.releaserc.json +25 -0
  3. package/commerce/components/Image.tsx +209 -0
  4. package/commerce/components/JsonLd.tsx +285 -0
  5. package/commerce/sdk/analytics.ts +24 -0
  6. package/commerce/sdk/formatPrice.ts +23 -0
  7. package/commerce/sdk/url.ts +9 -0
  8. package/commerce/sdk/useOffer.ts +75 -0
  9. package/commerce/sdk/useVariantPossibilities.ts +43 -0
  10. package/commerce/types/commerce.ts +1105 -0
  11. package/commerce/utils/canonical.ts +11 -0
  12. package/commerce/utils/constants.ts +9 -0
  13. package/commerce/utils/filters.ts +10 -0
  14. package/commerce/utils/productToAnalyticsItem.ts +67 -0
  15. package/commerce/utils/stateByZip.ts +50 -0
  16. package/knip.json +19 -0
  17. package/package.json +77 -0
  18. package/shopify/actions/cart/addItems.ts +37 -0
  19. package/shopify/actions/cart/updateCoupons.ts +32 -0
  20. package/shopify/actions/cart/updateItems.ts +32 -0
  21. package/shopify/actions/user/signIn.ts +45 -0
  22. package/shopify/actions/user/signUp.ts +36 -0
  23. package/shopify/client.ts +58 -0
  24. package/shopify/index.ts +32 -0
  25. package/shopify/init.ts +40 -0
  26. package/shopify/loaders/ProductDetailsPage.ts +35 -0
  27. package/shopify/loaders/ProductList.ts +101 -0
  28. package/shopify/loaders/ProductListingPage.ts +180 -0
  29. package/shopify/loaders/RelatedProducts.ts +45 -0
  30. package/shopify/loaders/cart.ts +73 -0
  31. package/shopify/loaders/shop.ts +40 -0
  32. package/shopify/loaders/user.ts +44 -0
  33. package/shopify/utils/admin/admin.ts +57 -0
  34. package/shopify/utils/admin/queries.ts +29 -0
  35. package/shopify/utils/cart.ts +28 -0
  36. package/shopify/utils/cookies.ts +85 -0
  37. package/shopify/utils/enums.ts +438 -0
  38. package/shopify/utils/graphql.ts +69 -0
  39. package/shopify/utils/storefront/queries.ts +530 -0
  40. package/shopify/utils/storefront/storefront.graphql.gen.ts +113 -0
  41. package/shopify/utils/transform.ts +436 -0
  42. package/shopify/utils/types.ts +191 -0
  43. package/shopify/utils/user.ts +23 -0
  44. package/shopify/utils/utils.ts +164 -0
  45. package/tsconfig.json +11 -0
  46. package/vtex/README.md +6 -0
  47. package/vtex/actions/address.ts +211 -0
  48. package/vtex/actions/auth.ts +337 -0
  49. package/vtex/actions/checkout.ts +497 -0
  50. package/vtex/actions/index.ts +11 -0
  51. package/vtex/actions/masterData.ts +170 -0
  52. package/vtex/actions/misc.ts +196 -0
  53. package/vtex/actions/newsletter.ts +108 -0
  54. package/vtex/actions/orders.ts +37 -0
  55. package/vtex/actions/profile.ts +119 -0
  56. package/vtex/actions/session.ts +87 -0
  57. package/vtex/actions/trigger.ts +43 -0
  58. package/vtex/actions/wishlist.ts +116 -0
  59. package/vtex/client.ts +423 -0
  60. package/vtex/hooks/index.ts +4 -0
  61. package/vtex/hooks/useAutocomplete.ts +89 -0
  62. package/vtex/hooks/useCart.ts +219 -0
  63. package/vtex/hooks/useUser.ts +78 -0
  64. package/vtex/hooks/useWishlist.ts +119 -0
  65. package/vtex/index.ts +14 -0
  66. package/vtex/inline-loaders/productDetailsPage.ts +75 -0
  67. package/vtex/inline-loaders/productList.ts +163 -0
  68. package/vtex/inline-loaders/productListingPage.ts +447 -0
  69. package/vtex/inline-loaders/relatedProducts.ts +83 -0
  70. package/vtex/inline-loaders/suggestions.ts +49 -0
  71. package/vtex/inline-loaders/workflowProducts.ts +68 -0
  72. package/vtex/invoke.ts +202 -0
  73. package/vtex/loaders/address.ts +120 -0
  74. package/vtex/loaders/brands.ts +51 -0
  75. package/vtex/loaders/cart.ts +49 -0
  76. package/vtex/loaders/catalog.ts +165 -0
  77. package/vtex/loaders/collections.ts +57 -0
  78. package/vtex/loaders/index.ts +19 -0
  79. package/vtex/loaders/legacy.ts +671 -0
  80. package/vtex/loaders/logistics.ts +115 -0
  81. package/vtex/loaders/navbar.ts +29 -0
  82. package/vtex/loaders/orders.ts +103 -0
  83. package/vtex/loaders/pageType.ts +62 -0
  84. package/vtex/loaders/payment.ts +107 -0
  85. package/vtex/loaders/profile.ts +138 -0
  86. package/vtex/loaders/promotion.ts +33 -0
  87. package/vtex/loaders/search.ts +127 -0
  88. package/vtex/loaders/session.ts +91 -0
  89. package/vtex/loaders/user.ts +89 -0
  90. package/vtex/loaders/wishlist.ts +89 -0
  91. package/vtex/loaders/wishlistProducts.ts +81 -0
  92. package/vtex/loaders/workflow.ts +323 -0
  93. package/vtex/logo.png +0 -0
  94. package/vtex/middleware.ts +229 -0
  95. package/vtex/types.ts +248 -0
  96. package/vtex/utils/batch.ts +21 -0
  97. package/vtex/utils/cookies.ts +76 -0
  98. package/vtex/utils/enrichment.ts +540 -0
  99. package/vtex/utils/fetchCache.ts +150 -0
  100. package/vtex/utils/index.ts +17 -0
  101. package/vtex/utils/intelligentSearch.ts +84 -0
  102. package/vtex/utils/legacy.ts +155 -0
  103. package/vtex/utils/pickAndOmit.ts +30 -0
  104. package/vtex/utils/proxy.ts +196 -0
  105. package/vtex/utils/resourceRange.ts +10 -0
  106. package/vtex/utils/segment.ts +163 -0
  107. package/vtex/utils/similars.ts +38 -0
  108. package/vtex/utils/sitemap.ts +133 -0
  109. package/vtex/utils/slugCache.ts +32 -0
  110. package/vtex/utils/slugify.ts +13 -0
  111. package/vtex/utils/transform.ts +1331 -0
  112. package/vtex/utils/types.ts +1884 -0
  113. package/vtex/utils/vtexId.ts +103 -0
package/vtex/invoke.ts ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Typed invoke object for VTEX actions — same DX as deco-cx/deco.
3
+ *
4
+ * Client-side components call `invoke.vtex.actions.*({ data: ... })`.
5
+ * Under the hood, each call is a `createServerFn` that executes
6
+ * on the server with full VTEX credentials (appKey/appToken).
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { invoke } from "@decocms/apps/vtex/invoke";
11
+ *
12
+ * // Add to cart
13
+ * const cart = await invoke.vtex.actions.addItemsToCart({
14
+ * data: { orderFormId: "abc", orderItems: [{ id: "1", seller: "1", quantity: 1 }] }
15
+ * });
16
+ *
17
+ * // Subscribe to newsletter
18
+ * await invoke.vtex.actions.subscribe({ data: { email: "user@example.com" } });
19
+ *
20
+ * // Notify me when back in stock
21
+ * await invoke.vtex.actions.notifyMe({ data: { email: "u@e.com", skuId: "123" } });
22
+ * ```
23
+ */
24
+ import { createInvokeFn } from "@decocms/start/sdk/createInvoke";
25
+ import {
26
+ getOrCreateCart,
27
+ addItemsToCart,
28
+ updateCartItems,
29
+ addCouponToCart,
30
+ simulateCart,
31
+ getSellersByRegion,
32
+ setShippingPostalCode,
33
+ type SimulationItem,
34
+ } from "./actions/checkout";
35
+ import {
36
+ createDocument,
37
+ getDocument,
38
+ patchDocument,
39
+ searchDocuments,
40
+ uploadAttachment,
41
+ type UploadAttachmentOpts,
42
+ type CreateDocumentResult,
43
+ } from "./actions/masterData";
44
+ import { createSession } from "./actions/session";
45
+ import { subscribe, type SubscribeProps } from "./actions/newsletter";
46
+ import { notifyMe, type NotifyMeProps } from "./actions/misc";
47
+ import type { OrderForm } from "./types";
48
+ import type { RegionResult } from "./actions/checkout";
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // invoke.vtex.actions — typed server functions callable from client
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export const invoke = {
55
+ vtex: {
56
+ actions: {
57
+ // -- Cart (unwrap VtexFetchResult -> OrderForm) -----------------------
58
+
59
+ getOrCreateCart: createInvokeFn(
60
+ (input: { orderFormId?: string }) =>
61
+ getOrCreateCart(input.orderFormId),
62
+ { unwrap: true },
63
+ ) as unknown as (ctx: { data: { orderFormId?: string } }) => Promise<OrderForm>,
64
+
65
+ addItemsToCart: createInvokeFn(
66
+ (input: {
67
+ orderFormId: string;
68
+ orderItems: Array<{
69
+ id: string;
70
+ seller: string;
71
+ quantity: number;
72
+ }>;
73
+ }) => addItemsToCart(input.orderFormId, input.orderItems),
74
+ { unwrap: true },
75
+ ) as unknown as (ctx: {
76
+ data: {
77
+ orderFormId: string;
78
+ orderItems: Array<{
79
+ id: string;
80
+ seller: string;
81
+ quantity: number;
82
+ }>;
83
+ };
84
+ }) => Promise<OrderForm>,
85
+
86
+ updateCartItems: createInvokeFn(
87
+ (input: {
88
+ orderFormId: string;
89
+ orderItems: Array<{ index: number; quantity: number }>;
90
+ }) => updateCartItems(input.orderFormId, input.orderItems),
91
+ { unwrap: true },
92
+ ) as unknown as (ctx: {
93
+ data: {
94
+ orderFormId: string;
95
+ orderItems: Array<{ index: number; quantity: number }>;
96
+ };
97
+ }) => Promise<OrderForm>,
98
+
99
+ addCouponToCart: createInvokeFn(
100
+ (input: { orderFormId: string; text: string }) =>
101
+ addCouponToCart(input.orderFormId, input.text),
102
+ { unwrap: true },
103
+ ) as unknown as (ctx: {
104
+ data: { orderFormId: string; text: string };
105
+ }) => Promise<OrderForm>,
106
+
107
+ simulateCart: createInvokeFn(
108
+ (input: {
109
+ items: SimulationItem[];
110
+ postalCode: string;
111
+ country?: string;
112
+ }) => simulateCart(input.items, input.postalCode, input.country),
113
+ ),
114
+
115
+ // -- Shipping / Region ------------------------------------------------
116
+
117
+ getSellersByRegion: createInvokeFn(
118
+ (input: { postalCode: string; salesChannel?: string }) =>
119
+ getSellersByRegion(input.postalCode, input.salesChannel),
120
+ ) as (ctx: {
121
+ data: { postalCode: string; salesChannel?: string };
122
+ }) => Promise<RegionResult | null>,
123
+
124
+ setShippingPostalCode: createInvokeFn(
125
+ (input: {
126
+ orderFormId: string;
127
+ postalCode: string;
128
+ country?: string;
129
+ }) =>
130
+ setShippingPostalCode(
131
+ input.orderFormId,
132
+ input.postalCode,
133
+ input.country,
134
+ ),
135
+ ) as (ctx: {
136
+ data: {
137
+ orderFormId: string;
138
+ postalCode: string;
139
+ country?: string;
140
+ };
141
+ }) => Promise<boolean>,
142
+
143
+ // -- Session ----------------------------------------------------------
144
+
145
+ createSession: createInvokeFn(
146
+ (input: Record<string, any>) => createSession(input),
147
+ { unwrap: true },
148
+ ),
149
+
150
+ // -- MasterData -------------------------------------------------------
151
+
152
+ createDocument: createInvokeFn(
153
+ (input: { entity: string; data: Record<string, any> }) =>
154
+ createDocument(input.entity, input.data),
155
+ ) as (ctx: {
156
+ data: { entity: string; data: Record<string, any> };
157
+ }) => Promise<CreateDocumentResult>,
158
+
159
+ getDocument: createInvokeFn(
160
+ (input: { entity: string; documentId: string }) =>
161
+ getDocument(input.entity, input.documentId),
162
+ ),
163
+
164
+ patchDocument: createInvokeFn(
165
+ (input: {
166
+ entity: string;
167
+ documentId: string;
168
+ data: Record<string, any>;
169
+ }) => patchDocument(input.entity, input.documentId, input.data),
170
+ ) as (ctx: {
171
+ data: {
172
+ entity: string;
173
+ documentId: string;
174
+ data: Record<string, any>;
175
+ };
176
+ }) => Promise<void>,
177
+
178
+ searchDocuments: createInvokeFn(
179
+ (input: { entity: string; filter: string }) =>
180
+ searchDocuments(input.entity, input.filter),
181
+ ),
182
+
183
+ uploadAttachment: createInvokeFn(
184
+ (input: UploadAttachmentOpts) => uploadAttachment(input),
185
+ ) as (ctx: {
186
+ data: UploadAttachmentOpts;
187
+ }) => Promise<{ ok: true }>,
188
+
189
+ // -- Newsletter -------------------------------------------------------
190
+
191
+ subscribe: createInvokeFn(
192
+ (input: SubscribeProps) => subscribe(input),
193
+ ) as (ctx: { data: SubscribeProps }) => Promise<void>,
194
+
195
+ // -- Misc -------------------------------------------------------------
196
+
197
+ notifyMe: createInvokeFn(
198
+ (input: NotifyMeProps) => notifyMe(input),
199
+ ) as (ctx: { data: NotifyMeProps }) => Promise<void>,
200
+ },
201
+ },
202
+ } as const;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * VTEX Address API loaders.
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/address/getAddressByPostalCode.ts
7
+ * vtex/loaders/address/getUserAddresses.ts
8
+ *
9
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api
10
+ */
11
+ import { vtexFetch, vtexIOGraphQL } from "../client";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export interface PostalAddress {
18
+ "@type": "PostalAddress";
19
+ postalCode?: string;
20
+ addressLocality?: string;
21
+ addressRegion?: string;
22
+ addressCountry?: string;
23
+ streetAddress?: string;
24
+ identifier?: string;
25
+ areaServed?: string;
26
+ description?: string;
27
+ disambiguatingDescription?: string;
28
+ latitude?: number;
29
+ longitude?: number;
30
+ }
31
+
32
+ export interface VtexAddress {
33
+ addressId: string;
34
+ addressType: string;
35
+ addressName: string;
36
+ city: string;
37
+ complement: string;
38
+ country: string;
39
+ neighborhood: string;
40
+ number: string;
41
+ postalCode: string;
42
+ geoCoordinates: number[];
43
+ receiverName: string;
44
+ state: string;
45
+ street: string;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // getAddressByPostalCode
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Look up a postal address by country + postal code (public API).
54
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/postal-code/-countryCode-/-postalCode-
55
+ */
56
+ export async function getAddressByPostalCode(
57
+ countryCode: string,
58
+ postalCode: string,
59
+ ): Promise<PostalAddress> {
60
+ const data = await vtexFetch<Record<string, any>>(
61
+ `/api/checkout/pub/postal-code/${countryCode}/${postalCode}`,
62
+ );
63
+
64
+ return {
65
+ "@type": "PostalAddress",
66
+ postalCode: data.postalCode,
67
+ addressLocality: data.city,
68
+ addressRegion: data.state,
69
+ addressCountry: data.country,
70
+ streetAddress: data.street || undefined,
71
+ identifier: data.number || undefined,
72
+ areaServed: data.neighborhood || undefined,
73
+ description: data.complement || undefined,
74
+ disambiguatingDescription: data.reference || undefined,
75
+ latitude: data.geoCoordinates?.[0] || undefined,
76
+ longitude: data.geoCoordinates?.[1] || undefined,
77
+ };
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // getUserAddresses (authenticated — VTEX IO GraphQL)
82
+ // ---------------------------------------------------------------------------
83
+
84
+ const USER_ADDRESSES_QUERY = `query Addresses @context(scope: "private") {
85
+ profile {
86
+ cacheId
87
+ addresses {
88
+ addressId: id
89
+ addressType
90
+ addressName
91
+ city
92
+ complement
93
+ country
94
+ neighborhood
95
+ number
96
+ postalCode
97
+ geoCoordinates
98
+ receiverName
99
+ state
100
+ street
101
+ }
102
+ }
103
+ }`;
104
+
105
+ /**
106
+ * Fetch addresses for the currently authenticated user.
107
+ * Requires a valid VTEX auth cookie.
108
+ */
109
+ export async function getUserAddresses(
110
+ authCookie: string,
111
+ ): Promise<VtexAddress[]> {
112
+ const { profile } = await vtexIOGraphQL<{
113
+ profile: { addresses: VtexAddress[] };
114
+ }>(
115
+ { query: USER_ADDRESSES_QUERY },
116
+ { cookie: authCookie },
117
+ );
118
+
119
+ return profile?.addresses ?? [];
120
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * VTEX brand loaders.
3
+ * Returns schema.org Brand[] matching the original deco-cx/apps format.
4
+ *
5
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/brand/list
6
+ */
7
+ import { vtexFetch, getVtexConfig } from "../client";
8
+ import { toBrand } from "../utils/transform";
9
+ import type { Brand } from "../../commerce/types/commerce";
10
+ import type { Brand as BrandVTEX } from "../utils/types";
11
+
12
+ export interface ListBrandsOpts {
13
+ /** When true, only returns active brands. @default false */
14
+ filterInactive?: boolean;
15
+ }
16
+
17
+ /**
18
+ * List brands from the VTEX catalog, transformed to schema.org Brand format.
19
+ */
20
+ export async function listBrands(
21
+ opts?: ListBrandsOpts,
22
+ ): Promise<Brand[]> {
23
+ const config = getVtexConfig();
24
+ const baseUrl = `https://${config.account}.vteximg.com.br/arquivos/ids`;
25
+
26
+ const brands = await vtexFetch<BrandVTEX[]>(
27
+ "/api/catalog_system/pub/brand/list",
28
+ );
29
+
30
+ const filtered = opts?.filterInactive
31
+ ? brands.filter((b) => b.isActive)
32
+ : brands;
33
+
34
+ return filtered.map((b) => toBrand(b, baseUrl));
35
+ }
36
+
37
+ /**
38
+ * Get a single brand by ID, as schema.org Brand.
39
+ */
40
+ export async function getBrandById(brandId: number): Promise<Brand | null> {
41
+ try {
42
+ const config = getVtexConfig();
43
+ const baseUrl = `https://${config.account}.vteximg.com.br/arquivos/ids`;
44
+ const brand = await vtexFetch<BrandVTEX>(
45
+ `/api/catalog_system/pub/brand/${brandId}`,
46
+ );
47
+ return toBrand(brand, baseUrl);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * VTEX Cart (OrderForm) loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/cart.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/orderForm
9
+ */
10
+ import { vtexFetch, getVtexConfig } from "../client";
11
+ import type { OrderForm } from "../utils/types";
12
+ import { forceHttpsOnAssets } from "../utils/transform";
13
+ import { DEFAULT_EXPECTED_SECTIONS } from "../actions/checkout";
14
+
15
+ /**
16
+ * Fetch the current cart (OrderForm).
17
+ *
18
+ * When `orderFormId` is provided the existing cart is retrieved;
19
+ * otherwise a fresh OrderForm is created via POST.
20
+ *
21
+ * @param orderFormId - Optional existing orderForm ID (from checkout cookie)
22
+ * @param salesChannel - Optional sales channel override
23
+ * @param authCookie - Optional cookie string for authenticated requests
24
+ */
25
+ export async function getCart(
26
+ orderFormId?: string,
27
+ opts?: { salesChannel?: string; authCookie?: string },
28
+ ): Promise<OrderForm> {
29
+ const { salesChannel } = getVtexConfig();
30
+ const sc = opts?.salesChannel ?? salesChannel;
31
+ const headers: Record<string, string> = {};
32
+ if (opts?.authCookie) headers.cookie = opts.authCookie;
33
+
34
+ const scParam = sc ? `?sc=${sc}` : "";
35
+
36
+ const body = JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS });
37
+
38
+ const cart = orderFormId
39
+ ? await vtexFetch<OrderForm>(
40
+ `/api/checkout/pub/orderForm/${orderFormId}${scParam}`,
41
+ { method: "POST", headers, body },
42
+ )
43
+ : await vtexFetch<OrderForm>(
44
+ `/api/checkout/pub/orderForm${scParam}`,
45
+ { method: "POST", headers, body },
46
+ );
47
+
48
+ return forceHttpsOnAssets(cart);
49
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * VTEX Catalog System API loaders.
3
+ * Pure async functions using vtexFetch. Require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps vtex/loaders/legacy/*.ts and vtex/utils/client.ts
6
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api
7
+ */
8
+ import { vtexFetch, getVtexConfig } from "../client";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Product search (public)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export type LegacySort =
15
+ | ""
16
+ | "OrderByTopSaleDESC"
17
+ | "OrderByReleaseDateDESC"
18
+ | "OrderByBestDiscountDESC"
19
+ | "OrderByPriceDESC"
20
+ | "OrderByPriceASC"
21
+ | "OrderByNameASC"
22
+ | "OrderByNameDESC"
23
+ | "OrderByScoreDESC";
24
+
25
+ export interface SearchProductsOpts {
26
+ fq?: string | string[];
27
+ ft?: string;
28
+ sort?: LegacySort;
29
+ from?: number;
30
+ to?: number;
31
+ }
32
+
33
+ /**
34
+ * Search products using the VTEX Catalog API.
35
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
36
+ */
37
+ export async function searchProducts<T = any>(opts: SearchProductsOpts = {}): Promise<T[]> {
38
+ const params = new URLSearchParams();
39
+ const fqs = Array.isArray(opts.fq) ? opts.fq : opts.fq ? [opts.fq] : [];
40
+ for (const f of fqs) params.append("fq", f);
41
+ if (opts.ft) params.set("ft", opts.ft);
42
+ if (opts.sort) params.set("O", opts.sort);
43
+ if (opts.from != null) params.set("_from", String(opts.from));
44
+ if (opts.to != null) params.set("_to", String(opts.to));
45
+
46
+ const { salesChannel } = getVtexConfig();
47
+ if (salesChannel) params.set("sc", salesChannel);
48
+
49
+ return vtexFetch<T[]>(`/api/catalog_system/pub/products/search/?${params}`);
50
+ }
51
+
52
+ /**
53
+ * Get a product by productId or skuId.
54
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
55
+ */
56
+ export async function getProductByIdOrSku<T = any>(opts: {
57
+ productId?: string;
58
+ skuId?: string;
59
+ }): Promise<T | null> {
60
+ let fq: string;
61
+ if (opts.productId) fq = `productId:${opts.productId}`;
62
+ else if (opts.skuId) fq = `skuId:${opts.skuId}`;
63
+ else throw new Error("Neither productId nor skuId provided.");
64
+
65
+ const results = await searchProducts<T>({ fq });
66
+ return results[0] ?? null;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Product variations (public)
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Get product SKU variations.
75
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/variations/-productId-
76
+ */
77
+ export async function getProductVariations<T = any>(productId: string): Promise<T> {
78
+ return vtexFetch<T>(`/api/catalog_system/pub/products/variations/${productId}`);
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Product specification (private — requires appKey/appToken)
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Get product specifications (private API).
87
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/products/-productId-/specification
88
+ */
89
+ export async function getProductSpecification<T = any>(productId: string): Promise<T> {
90
+ return vtexFetch<T>(`/api/catalog_system/pvt/products/${productId}/Specification`);
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Cross-selling
95
+ // ---------------------------------------------------------------------------
96
+
97
+ export type CrossSellingType =
98
+ | "similars"
99
+ | "suggestions"
100
+ | "accessories"
101
+ | "whosawalsosaw"
102
+ | "whosawalsobought"
103
+ | "whoboughtalsobought"
104
+ | "showtogether";
105
+
106
+ /**
107
+ * Get cross-selling products.
108
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/crossselling/-type-/-productId-
109
+ */
110
+ export async function getCrossSelling<T = any>(
111
+ type: CrossSellingType,
112
+ productId: string,
113
+ ): Promise<T[]> {
114
+ return vtexFetch<T[]>(
115
+ `/api/catalog_system/pub/products/crossselling/${type}/${productId}`,
116
+ );
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Categories
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /**
124
+ * Get the category tree up to a given depth level.
125
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/category/tree/-categoryLevels-
126
+ */
127
+ export async function getCategoryTree<T = any>(levels = 3): Promise<T[]> {
128
+ return vtexFetch<T[]>(`/api/catalog_system/pub/category/tree/${levels}`);
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Brands
133
+ // ---------------------------------------------------------------------------
134
+
135
+ /**
136
+ * Get all brands.
137
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/brand/list
138
+ */
139
+ export async function getBrands<T = any>(): Promise<T[]> {
140
+ return vtexFetch<T[]>("/api/catalog_system/pub/brand/list");
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Page type
145
+ // ---------------------------------------------------------------------------
146
+
147
+ /**
148
+ * Get the page type for a given term/path.
149
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/portal/pagetype/-term-
150
+ */
151
+ export async function getPageType<T = any>(term: string): Promise<T> {
152
+ return vtexFetch<T>(`/api/catalog_system/pub/portal/pagetype/${term}`);
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Facets (legacy)
157
+ // ---------------------------------------------------------------------------
158
+
159
+ /**
160
+ * Get facets/filters for a search term.
161
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/facets/search/-term-
162
+ */
163
+ export async function getFacets<T = any>(term: string): Promise<T> {
164
+ return vtexFetch<T>(`/api/catalog_system/pub/facets/search/${term}`);
165
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * VTEX Collections loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/collections/list.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/collection/search
9
+ */
10
+ import { vtexFetch } from "../client";
11
+ import type { CollectionList } from "../utils/types";
12
+
13
+ export interface CollectionOption {
14
+ value: string;
15
+ label: string;
16
+ }
17
+
18
+ /**
19
+ * Fetch collections, optionally filtering by search term.
20
+ *
21
+ * Without a term returns up to 3 000 collections; with a term
22
+ * returns the first 15 matches. Results are mapped to a simple
23
+ * `{ value, label }` list suitable for dropdowns / selectors.
24
+ *
25
+ * Note: uses the **pvt** (private) endpoint — requires appKey/appToken.
26
+ */
27
+ export async function getCollections(
28
+ term?: string,
29
+ ): Promise<CollectionOption[]> {
30
+ const params = new URLSearchParams();
31
+
32
+ if (term) {
33
+ params.set("page", "1");
34
+ params.set("pageSize", "15");
35
+ const list = await vtexFetch<CollectionList>(
36
+ `/api/catalog_system/pvt/collection/search/${encodeURIComponent(term)}?${params}`,
37
+ );
38
+ return mapToOptions(list);
39
+ }
40
+
41
+ params.set("page", "1");
42
+ params.set("pageSize", "3000");
43
+ params.set("orderByAsc", "false");
44
+ const list = await vtexFetch<CollectionList>(
45
+ `/api/catalog_system/pvt/collection/search?${params}`,
46
+ );
47
+ return mapToOptions(list);
48
+ }
49
+
50
+ function mapToOptions(list: CollectionList): CollectionOption[] {
51
+ return (
52
+ list.items?.map((c) => ({
53
+ value: `${c.id}`,
54
+ label: `${c.id} - ${c.name}`,
55
+ })) ?? []
56
+ );
57
+ }
@@ -0,0 +1,19 @@
1
+ export * from "./address";
2
+ export * from "./brands";
3
+ export * from "./cart";
4
+ export * from "./catalog";
5
+ export * from "./collections";
6
+ export * from "./legacy";
7
+ export * from "./logistics";
8
+ export * from "./navbar";
9
+ export * from "./orders";
10
+ export * from "./pageType";
11
+ export * from "./payment";
12
+ export * from "./profile";
13
+ export * from "./promotion";
14
+ export * from "./search";
15
+ export * from "./session";
16
+ export * from "./user";
17
+ export * from "./wishlist";
18
+ export * from "./wishlistProducts";
19
+ export * from "./workflow";