@proxima-io/storefront-core 0.2.0 → 0.8.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 (174) hide show
  1. package/README.md +23 -17
  2. package/dist/addresses/address-book.d.ts +36 -0
  3. package/dist/addresses/address-book.d.ts.map +1 -0
  4. package/dist/addresses/address-book.js +62 -0
  5. package/dist/addresses/address-book.js.map +1 -0
  6. package/dist/analytics/analytics.d.ts +28 -0
  7. package/dist/analytics/analytics.d.ts.map +1 -0
  8. package/dist/analytics/analytics.js +124 -0
  9. package/dist/analytics/analytics.js.map +1 -0
  10. package/dist/analytics/attribution.d.ts +28 -0
  11. package/dist/analytics/attribution.d.ts.map +1 -0
  12. package/dist/analytics/attribution.js +116 -0
  13. package/dist/analytics/attribution.js.map +1 -0
  14. package/dist/analytics/session.d.ts +12 -0
  15. package/dist/analytics/session.d.ts.map +1 -0
  16. package/dist/analytics/session.js +62 -0
  17. package/dist/analytics/session.js.map +1 -0
  18. package/dist/analytics/trackers.d.ts +29 -0
  19. package/dist/analytics/trackers.d.ts.map +1 -0
  20. package/dist/analytics/trackers.js +30 -0
  21. package/dist/analytics/trackers.js.map +1 -0
  22. package/dist/api/endpoints.d.ts +70 -0
  23. package/dist/api/endpoints.d.ts.map +1 -0
  24. package/dist/api/endpoints.js +70 -0
  25. package/dist/api/endpoints.js.map +1 -0
  26. package/dist/api/index.d.ts +3 -0
  27. package/dist/api/index.d.ts.map +1 -0
  28. package/dist/api/index.js +3 -0
  29. package/dist/api/index.js.map +1 -0
  30. package/dist/api/storefront-client.d.ts +50 -0
  31. package/dist/api/storefront-client.d.ts.map +1 -0
  32. package/dist/api/storefront-client.js +123 -0
  33. package/dist/api/storefront-client.js.map +1 -0
  34. package/dist/buyer/auth.d.ts +105 -0
  35. package/dist/buyer/auth.d.ts.map +1 -0
  36. package/dist/buyer/auth.js +215 -0
  37. package/dist/buyer/auth.js.map +1 -0
  38. package/dist/cache/cache.d.ts +31 -0
  39. package/dist/cache/cache.d.ts.map +1 -0
  40. package/dist/cache/cache.js +71 -0
  41. package/dist/cache/cache.js.map +1 -0
  42. package/dist/campaign/countdown.d.ts +40 -0
  43. package/dist/campaign/countdown.d.ts.map +1 -0
  44. package/dist/campaign/countdown.js +71 -0
  45. package/dist/campaign/countdown.js.map +1 -0
  46. package/dist/cart/cart.d.ts +57 -0
  47. package/dist/cart/cart.d.ts.map +1 -0
  48. package/dist/cart/cart.js +64 -0
  49. package/dist/cart/cart.js.map +1 -0
  50. package/dist/catalog/listings.d.ts +87 -0
  51. package/dist/catalog/listings.d.ts.map +1 -0
  52. package/dist/catalog/listings.js +140 -0
  53. package/dist/catalog/listings.js.map +1 -0
  54. package/dist/cms/payment-methods.d.ts +13 -0
  55. package/dist/cms/payment-methods.d.ts.map +1 -0
  56. package/dist/cms/payment-methods.js +41 -0
  57. package/dist/cms/payment-methods.js.map +1 -0
  58. package/dist/cms/website.d.ts +74 -0
  59. package/dist/cms/website.d.ts.map +1 -0
  60. package/dist/cms/website.js +144 -0
  61. package/dist/cms/website.js.map +1 -0
  62. package/dist/cookie-consent/consent.d.ts +22 -0
  63. package/dist/cookie-consent/consent.d.ts.map +1 -0
  64. package/dist/cookie-consent/consent.js +93 -0
  65. package/dist/cookie-consent/consent.js.map +1 -0
  66. package/dist/fixtures-commerce.d.ts +140 -0
  67. package/dist/fixtures-commerce.d.ts.map +1 -0
  68. package/dist/fixtures-commerce.js +743 -0
  69. package/dist/fixtures-commerce.js.map +1 -0
  70. package/dist/index.d.ts +43 -1309
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +40 -1575
  73. package/dist/index.js.map +1 -1
  74. package/dist/internal/http.d.ts +5 -0
  75. package/dist/internal/http.d.ts.map +1 -0
  76. package/dist/internal/http.js +27 -0
  77. package/dist/internal/http.js.map +1 -0
  78. package/dist/orders/guest.d.ts +9 -0
  79. package/dist/orders/guest.d.ts.map +1 -0
  80. package/dist/orders/guest.js +30 -0
  81. package/dist/orders/guest.js.map +1 -0
  82. package/dist/orders/orders.d.ts +23 -0
  83. package/dist/orders/orders.d.ts.map +1 -0
  84. package/dist/orders/orders.js +33 -0
  85. package/dist/orders/orders.js.map +1 -0
  86. package/dist/seo/indexnow.d.ts +24 -0
  87. package/dist/seo/indexnow.d.ts.map +1 -0
  88. package/dist/seo/indexnow.js +50 -0
  89. package/dist/seo/indexnow.js.map +1 -0
  90. package/dist/seo/json-ld.d.ts +52 -0
  91. package/dist/seo/json-ld.d.ts.map +1 -0
  92. package/dist/seo/json-ld.js +175 -0
  93. package/dist/seo/json-ld.js.map +1 -0
  94. package/dist/seo/page-seo.d.ts +21 -0
  95. package/dist/seo/page-seo.d.ts.map +1 -0
  96. package/dist/seo/page-seo.js +68 -0
  97. package/dist/seo/page-seo.js.map +1 -0
  98. package/dist/seo/robots.d.ts +23 -0
  99. package/dist/seo/robots.d.ts.map +1 -0
  100. package/dist/seo/robots.js +35 -0
  101. package/dist/seo/robots.js.map +1 -0
  102. package/dist/seo/sitemap.d.ts +35 -0
  103. package/dist/seo/sitemap.d.ts.map +1 -0
  104. package/dist/seo/sitemap.js +131 -0
  105. package/dist/seo/sitemap.js.map +1 -0
  106. package/dist/server/process.d.ts +136 -0
  107. package/dist/server/process.d.ts.map +1 -0
  108. package/dist/server/process.js +143 -0
  109. package/dist/server/process.js.map +1 -0
  110. package/dist/types/address.d.ts +40 -0
  111. package/dist/types/address.d.ts.map +1 -0
  112. package/dist/types/address.js +2 -0
  113. package/dist/types/address.js.map +1 -0
  114. package/dist/types/analytics.d.ts +79 -0
  115. package/dist/types/analytics.d.ts.map +1 -0
  116. package/dist/types/analytics.js +2 -0
  117. package/dist/types/analytics.js.map +1 -0
  118. package/dist/types/business.d.ts +95 -0
  119. package/dist/types/business.d.ts.map +1 -0
  120. package/dist/types/business.js +2 -0
  121. package/dist/types/business.js.map +1 -0
  122. package/dist/types/buyer.d.ts +144 -0
  123. package/dist/types/buyer.d.ts.map +1 -0
  124. package/dist/types/buyer.js +45 -0
  125. package/dist/types/buyer.js.map +1 -0
  126. package/dist/types/campaign.d.ts +51 -0
  127. package/dist/types/campaign.d.ts.map +1 -0
  128. package/dist/types/campaign.js +2 -0
  129. package/dist/types/campaign.js.map +1 -0
  130. package/dist/types/cart.d.ts +40 -0
  131. package/dist/types/cart.d.ts.map +1 -0
  132. package/dist/types/cart.js +2 -0
  133. package/dist/types/cart.js.map +1 -0
  134. package/dist/types/catalog.d.ts +164 -0
  135. package/dist/types/catalog.d.ts.map +1 -0
  136. package/dist/types/catalog.js +2 -0
  137. package/dist/types/catalog.js.map +1 -0
  138. package/dist/types/cms.d.ts +196 -0
  139. package/dist/types/cms.d.ts.map +1 -0
  140. package/dist/types/cms.js +2 -0
  141. package/dist/types/cms.js.map +1 -0
  142. package/dist/types/cookie-consent.d.ts +18 -0
  143. package/dist/types/cookie-consent.d.ts.map +1 -0
  144. package/dist/types/cookie-consent.js +7 -0
  145. package/dist/types/cookie-consent.js.map +1 -0
  146. package/dist/types/guest-order.d.ts +16 -0
  147. package/dist/types/guest-order.d.ts.map +1 -0
  148. package/dist/types/guest-order.js +9 -0
  149. package/dist/types/guest-order.js.map +1 -0
  150. package/dist/types/listing.d.ts +14 -0
  151. package/dist/types/listing.d.ts.map +1 -0
  152. package/dist/types/listing.js +2 -0
  153. package/dist/types/listing.js.map +1 -0
  154. package/dist/types/order.d.ts +40 -0
  155. package/dist/types/order.d.ts.map +1 -0
  156. package/dist/types/order.js +2 -0
  157. package/dist/types/order.js.map +1 -0
  158. package/dist/types/seo.d.ts +93 -0
  159. package/dist/types/seo.d.ts.map +1 -0
  160. package/dist/types/seo.js +2 -0
  161. package/dist/types/seo.js.map +1 -0
  162. package/dist/types/server-env.d.ts +19 -0
  163. package/dist/types/server-env.d.ts.map +1 -0
  164. package/dist/types/server-env.js +10 -0
  165. package/dist/types/server-env.js.map +1 -0
  166. package/dist/types/wishlist.d.ts +10 -0
  167. package/dist/types/wishlist.d.ts.map +1 -0
  168. package/dist/types/wishlist.js +2 -0
  169. package/dist/types/wishlist.js.map +1 -0
  170. package/dist/wishlist/wishlist.d.ts +28 -0
  171. package/dist/wishlist/wishlist.d.ts.map +1 -0
  172. package/dist/wishlist/wishlist.js +42 -0
  173. package/dist/wishlist/wishlist.js.map +1 -0
  174. package/package.json +5 -1
@@ -0,0 +1,743 @@
1
+ export class FixtureGuestOrderError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "FixtureGuestOrderError";
7
+ }
8
+ }
9
+ // ---------------------------------------------------------------------------
10
+ // Constants
11
+ // ---------------------------------------------------------------------------
12
+ const SORT_OPTIONS = [
13
+ { value: "newest", label: "Más recientes" },
14
+ { value: "price_asc", label: "Precio: menor a mayor" },
15
+ { value: "price_desc", label: "Precio: mayor a menor" },
16
+ { value: "name_asc", label: "Nombre A–Z" },
17
+ ];
18
+ const EMPTY_CART = {
19
+ id: "fixture-empty-cart",
20
+ session_id: null,
21
+ customer_id: null,
22
+ items: [],
23
+ totals: { subtotal: 0, formatted_subtotal: "S/ 0.00", currency: "PEN" },
24
+ };
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+ function localizedString(value, locale = "es") {
29
+ if (!value)
30
+ return "";
31
+ if (typeof value === "string")
32
+ return value;
33
+ if (typeof value === "object" && value !== null) {
34
+ const record = value;
35
+ return record[locale] ?? record.es ?? Object.values(record)[0] ?? "";
36
+ }
37
+ return String(value);
38
+ }
39
+ function resolveImage(item) {
40
+ const imageUrl = item.image_url;
41
+ if (imageUrl?.medium ?? imageUrl?.large)
42
+ return imageUrl.medium ?? imageUrl.large ?? "";
43
+ const images = item.images;
44
+ if (Array.isArray(images) && images[0])
45
+ return images[0];
46
+ const image = item.image;
47
+ return image ?? "";
48
+ }
49
+ function catalogItemToSummary(item, currency) {
50
+ const variant = Array.isArray(item.variants) ? item.variants[0] : null;
51
+ const price = Number(item.price ?? variant?.price ?? 0);
52
+ return {
53
+ id: Number(item.id),
54
+ slug: String(item.slug),
55
+ name: localizedString(item.name),
56
+ price,
57
+ price_formatted: null,
58
+ image_url: resolveImage(item),
59
+ brand_name: localizedString(item.brand?.name),
60
+ category_name: localizedString(item.category?.name) || null,
61
+ badge: item.badge ?? null,
62
+ rating: 0,
63
+ currency,
64
+ default_variant_id: Number(item.default_variant_id ?? variant?.id ?? item.id),
65
+ };
66
+ }
67
+ function brandSlugFromCatalog(item) {
68
+ const brand = item.brand;
69
+ return String(brand?.slug ?? "");
70
+ }
71
+ function categorySlugFromCatalog(item) {
72
+ const category = item.category;
73
+ return String(category?.slug ?? "");
74
+ }
75
+ function variantStock(variant, item) {
76
+ if (variant && variant.stock != null)
77
+ return Number(variant.stock);
78
+ if (item.stock != null)
79
+ return Number(item.stock);
80
+ return 999;
81
+ }
82
+ function itemHasStock(item) {
83
+ const variants = Array.isArray(item.variants) ? item.variants : [];
84
+ if (variants.length > 0) {
85
+ return variants.some((v) => variantStock(v, item) > 0);
86
+ }
87
+ return variantStock(null, item) > 0;
88
+ }
89
+ function findCatalogVariant(catalog, variantId) {
90
+ for (const item of catalog) {
91
+ const variants = Array.isArray(item.variants) ? item.variants : [];
92
+ for (const raw of variants) {
93
+ const variant = raw;
94
+ if (Number(variant.id) === variantId)
95
+ return { item, variant };
96
+ }
97
+ if (Number(item.default_variant_id) === variantId && variants[0]) {
98
+ return { item, variant: variants[0] };
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+ function findCategoryNode(slug, nodes) {
104
+ for (const node of nodes) {
105
+ if (node.slug === slug)
106
+ return node;
107
+ const child = findCategoryNode(slug, node.children ?? []);
108
+ if (child)
109
+ return child;
110
+ }
111
+ return null;
112
+ }
113
+ function resolveSort(sort) {
114
+ return sort && SORT_OPTIONS.some((o) => o.value === sort) ? sort : "newest";
115
+ }
116
+ function sortItems(items, sort) {
117
+ const copy = [...items];
118
+ switch (sort) {
119
+ case "price_asc":
120
+ return copy.sort((a, b) => a.price - b.price);
121
+ case "price_desc":
122
+ return copy.sort((a, b) => b.price - a.price);
123
+ case "name_asc":
124
+ return copy.sort((a, b) => a.name.localeCompare(b.name, "es"));
125
+ case "newest":
126
+ default:
127
+ return copy.sort((a, b) => b.id - a.id);
128
+ }
129
+ }
130
+ function paginate(items, page, pageSize) {
131
+ const total = items.length;
132
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
133
+ const start = (page - 1) * pageSize;
134
+ return { items: items.slice(start, start + pageSize), total, totalPages };
135
+ }
136
+ function formatMoney(amount, currency, locale) {
137
+ try {
138
+ return new Intl.NumberFormat(locale, {
139
+ style: "currency",
140
+ currency,
141
+ minimumFractionDigits: 2,
142
+ }).format(amount);
143
+ }
144
+ catch {
145
+ return `${currency} ${amount.toFixed(2)}`;
146
+ }
147
+ }
148
+ function recalcTotals(cart, locale) {
149
+ const subtotal = cart.items.reduce((sum, line) => sum + (line.metadata?.unit_price ?? 0) * line.quantity, 0);
150
+ const currency = cart.totals.currency || "PEN";
151
+ return {
152
+ ...cart,
153
+ totals: {
154
+ subtotal,
155
+ formatted_subtotal: formatMoney(subtotal, currency, locale),
156
+ currency,
157
+ },
158
+ };
159
+ }
160
+ function sessionKey(sessionId) {
161
+ return sessionId?.trim() || "__fixture_default__";
162
+ }
163
+ function cloneCart(cart) {
164
+ return structuredClone(cart);
165
+ }
166
+ function buildLineMetadata(item, variant, currency, locale) {
167
+ const unitPrice = Number(variant.price ?? item.price ?? 0);
168
+ return {
169
+ name: localizedString(item.name),
170
+ slug: String(item.slug),
171
+ brand: localizedString(item.brand?.name) || null,
172
+ image: resolveImage(item) || null,
173
+ unit_price: unitPrice,
174
+ unit_price_string: formatMoney(unitPrice, currency, locale),
175
+ };
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // In-memory cart (fixtures mode — per-process, demo/dev)
179
+ // ---------------------------------------------------------------------------
180
+ function createCartStore(template, locale, currency) {
181
+ const seed = cloneCart(template ?? EMPTY_CART);
182
+ seed.totals.currency = seed.totals.currency || currency;
183
+ const carts = new Map();
184
+ let nextItemId = Math.max(1, ...(seed.items.map((i) => i.id)), 100);
185
+ let sessionCounter = 0;
186
+ function ensureCart(sessionId) {
187
+ const key = sessionKey(sessionId);
188
+ if (!carts.has(key)) {
189
+ const initial = cloneCart(seed);
190
+ if (sessionId) {
191
+ initial.session_id = sessionId;
192
+ }
193
+ else if (!initial.session_id) {
194
+ initial.session_id = `fixture-session-${++sessionCounter}`;
195
+ }
196
+ carts.set(key, recalcTotals(initial, locale));
197
+ }
198
+ return carts.get(key);
199
+ }
200
+ function persist(sessionId, cart) {
201
+ const key = sessionKey(sessionId ?? cart.session_id);
202
+ carts.set(key, recalcTotals(cart, locale));
203
+ return cloneCart(carts.get(key));
204
+ }
205
+ return {
206
+ getCart(sessionId) {
207
+ return cloneCart(ensureCart(sessionId));
208
+ },
209
+ _addToCart(catalog, sessionId, variantId, quantity) {
210
+ const match = findCatalogVariant(catalog, variantId);
211
+ if (!match)
212
+ throw new FixtureGuestOrderError("VARIANT_NOT_FOUND", `Variant ${variantId} not found`);
213
+ const stock = variantStock(match.variant, match.item);
214
+ if (stock < quantity)
215
+ throw new FixtureGuestOrderError("OUT_OF_STOCK", "Insufficient stock");
216
+ const cart = ensureCart(sessionId);
217
+ if (!cart.session_id)
218
+ cart.session_id = sessionId ?? `fixture-session-${++sessionCounter}`;
219
+ const existing = cart.items.find((line) => line.product_variant_id === variantId);
220
+ if (existing) {
221
+ const newQty = existing.quantity + quantity;
222
+ if (stock < newQty)
223
+ throw new FixtureGuestOrderError("OUT_OF_STOCK", "Insufficient stock");
224
+ existing.quantity = newQty;
225
+ }
226
+ else {
227
+ cart.items.push({
228
+ id: nextItemId++,
229
+ product_variant_id: variantId,
230
+ quantity,
231
+ metadata: buildLineMetadata(match.item, match.variant, currency, locale),
232
+ });
233
+ }
234
+ return persist(sessionId, cart);
235
+ },
236
+ _updateCartItem(catalog, sessionId, variantId, quantity) {
237
+ const cart = ensureCart(sessionId);
238
+ const line = cart.items.find((item) => item.product_variant_id === variantId);
239
+ if (!line)
240
+ throw new FixtureGuestOrderError("VARIANT_NOT_FOUND", `Variant ${variantId} not in cart`);
241
+ if (quantity <= 0) {
242
+ cart.items = cart.items.filter((item) => item.product_variant_id !== variantId);
243
+ return persist(sessionId, cart);
244
+ }
245
+ const match = findCatalogVariant(catalog, variantId);
246
+ const stock = match ? variantStock(match.variant, match.item) : 999;
247
+ if (stock < quantity)
248
+ throw new FixtureGuestOrderError("OUT_OF_STOCK", "Insufficient stock");
249
+ line.quantity = quantity;
250
+ return persist(sessionId, cart);
251
+ },
252
+ _removeCartItem(sessionId, variantId) {
253
+ const cart = ensureCart(sessionId);
254
+ cart.items = cart.items.filter((item) => item.product_variant_id !== variantId);
255
+ return persist(sessionId, cart);
256
+ },
257
+ _clearCart(sessionId) {
258
+ const key = sessionKey(sessionId);
259
+ const empty = cloneCart(EMPTY_CART);
260
+ empty.session_id = sessionId ?? null;
261
+ empty.totals.currency = currency;
262
+ carts.set(key, recalcTotals(empty, locale));
263
+ },
264
+ };
265
+ }
266
+ // ---------------------------------------------------------------------------
267
+ // Listing builders
268
+ // ---------------------------------------------------------------------------
269
+ function filterCatalogItems(catalog, filters = {}) {
270
+ return catalog.filter((item) => {
271
+ if (filters.brand && brandSlugFromCatalog(item) !== filters.brand)
272
+ return false;
273
+ if (filters.category && categorySlugFromCatalog(item) !== filters.category)
274
+ return false;
275
+ const price = Number(item.price ?? item.variants?.[0]?.price ?? 0);
276
+ if (filters.price_min != null && price < filters.price_min)
277
+ return false;
278
+ if (filters.price_max != null && price > filters.price_max)
279
+ return false;
280
+ if (filters.in_stock && !itemHasStock(item))
281
+ return false;
282
+ return true;
283
+ });
284
+ }
285
+ function buildFacets(catalog) {
286
+ const brandCounts = new Map();
287
+ const categoryCounts = new Map();
288
+ for (const item of catalog) {
289
+ const brandSlug = brandSlugFromCatalog(item);
290
+ const brandLabel = localizedString(item.brand?.name);
291
+ if (brandSlug) {
292
+ const current = brandCounts.get(brandSlug);
293
+ brandCounts.set(brandSlug, { label: brandLabel, count: (current?.count ?? 0) + 1 });
294
+ }
295
+ const catSlug = categorySlugFromCatalog(item);
296
+ const catLabel = localizedString(item.category?.name);
297
+ if (catSlug) {
298
+ const current = categoryCounts.get(catSlug);
299
+ categoryCounts.set(catSlug, { label: catLabel, count: (current?.count ?? 0) + 1 });
300
+ }
301
+ }
302
+ return {
303
+ brand_facets: [...brandCounts.entries()].map(([value, { label, count }]) => ({
304
+ value,
305
+ label,
306
+ count,
307
+ })),
308
+ category_facets: [...categoryCounts.entries()].map(([value, { label, count }]) => ({
309
+ value,
310
+ label,
311
+ count,
312
+ })),
313
+ };
314
+ }
315
+ function buildProductListing(input, params = {}) {
316
+ const page = Math.max(1, params.page ?? 1);
317
+ const pageSize = Math.min(60, Math.max(12, params.page_size ?? 24));
318
+ const sort = resolveSort(params.sort);
319
+ const currency = params.currency ?? "PEN";
320
+ const filtered = filterCatalogItems(input.catalog, params.filters ?? {});
321
+ let items = filtered.map((item) => catalogItemToSummary(item, currency));
322
+ items = sortItems(items, sort);
323
+ const paged = paginate(items, page, pageSize);
324
+ const facets = buildFacets(input.catalog);
325
+ return {
326
+ items: paged.items,
327
+ pagination: {
328
+ page,
329
+ page_size: pageSize,
330
+ total: paged.total,
331
+ total_pages: paged.totalPages,
332
+ },
333
+ sort_current: sort,
334
+ sort_options: [...SORT_OPTIONS],
335
+ ...facets,
336
+ };
337
+ }
338
+ function buildSearchResults(input, params) {
339
+ const q = params.q.trim().toLowerCase();
340
+ const limit = Math.min(60, Math.max(1, params.limit ?? 24));
341
+ const currency = params.currency ?? "PEN";
342
+ if (!q)
343
+ return { query: params.q, hits: [], total: 0 };
344
+ const hits = input.catalog
345
+ .filter((item) => {
346
+ const name = localizedString(item.name).toLowerCase();
347
+ const slug = String(item.slug).toLowerCase();
348
+ const brand = localizedString(item.brand?.name).toLowerCase();
349
+ return name.includes(q) || slug.includes(q) || brand.includes(q);
350
+ })
351
+ .slice(0, limit)
352
+ .map((item) => catalogItemToSummary(item, currency));
353
+ return { query: params.q, hits, total: hits.length };
354
+ }
355
+ function buildCategoryListing(input, slug, params = {}) {
356
+ const page = Math.max(1, params.page ?? 1);
357
+ const pageSize = Math.min(60, Math.max(12, params.pageSize ?? 24));
358
+ const sort = resolveSort(params.sort);
359
+ const currency = params.currency ?? "PEN";
360
+ const catalogBySlug = new Map(input.catalog.map((item) => [String(item.slug), item]));
361
+ const treeNode = findCategoryNode(slug, input.categoryNavTree.nodes);
362
+ const category = {
363
+ id: treeNode?.id ?? 0,
364
+ name: treeNode?.name ?? slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
365
+ slug,
366
+ image_url: treeNode?.image_url ?? null,
367
+ };
368
+ const productSlugs = input.categoryProductMap?.[slug] ?? input.catalog.map((item) => String(item.slug));
369
+ let items = productSlugs
370
+ .map((productSlug) => catalogBySlug.get(productSlug))
371
+ .filter((item) => !!item)
372
+ .map((item) => catalogItemToSummary(item, currency));
373
+ if (params.brand) {
374
+ items = items.filter((item) => {
375
+ const raw = catalogBySlug.get(item.slug);
376
+ return raw ? brandSlugFromCatalog(raw) === params.brand : false;
377
+ });
378
+ }
379
+ items = sortItems(items, sort);
380
+ const brandCounts = new Map();
381
+ for (const productSlug of productSlugs) {
382
+ const raw = catalogBySlug.get(productSlug);
383
+ if (!raw)
384
+ continue;
385
+ const brandSlug = brandSlugFromCatalog(raw);
386
+ const brandLabel = localizedString(raw.brand?.name);
387
+ if (!brandSlug)
388
+ continue;
389
+ const current = brandCounts.get(brandSlug);
390
+ brandCounts.set(brandSlug, { label: brandLabel, count: (current?.count ?? 0) + 1 });
391
+ }
392
+ const paged = paginate(items, page, pageSize);
393
+ return {
394
+ category,
395
+ items: paged.items,
396
+ pagination: {
397
+ page,
398
+ page_size: pageSize,
399
+ total: paged.total,
400
+ total_pages: paged.totalPages,
401
+ },
402
+ sort_current: sort,
403
+ sort_options: [...SORT_OPTIONS],
404
+ brand_facets: [...brandCounts.entries()].map(([value, { label, count }]) => ({
405
+ value,
406
+ label,
407
+ count,
408
+ })),
409
+ };
410
+ }
411
+ function buildBrandListing(input, slug, params = {}) {
412
+ const page = Math.max(1, params.page ?? 1);
413
+ const pageSize = Math.min(60, Math.max(12, params.pageSize ?? 24));
414
+ const sort = resolveSort(params.sort);
415
+ const currency = params.currency ?? "PEN";
416
+ const brandProducts = input.catalog.filter((item) => brandSlugFromCatalog(item) === slug);
417
+ const brandName = brandProducts.length > 0
418
+ ? localizedString(brandProducts[0].brand?.name)
419
+ : slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
420
+ let items = brandProducts.map((item) => catalogItemToSummary(item, currency));
421
+ items = sortItems(items, sort);
422
+ const paged = paginate(items, page, pageSize);
423
+ return {
424
+ brand: { id: 0, name: brandName, slug, logo_url: null },
425
+ items: paged.items,
426
+ pagination: {
427
+ page,
428
+ page_size: pageSize,
429
+ total: paged.total,
430
+ total_pages: paged.totalPages,
431
+ },
432
+ sort_current: sort,
433
+ sort_options: [...SORT_OPTIONS],
434
+ category_facets: [],
435
+ };
436
+ }
437
+ // ---------------------------------------------------------------------------
438
+ // createFixtureBundle
439
+ // ---------------------------------------------------------------------------
440
+ export function createFixtureBundle(input) {
441
+ const locale = "es";
442
+ const currency = input.cart?.totals.currency ?? "PEN";
443
+ const cartStore = createCartStore(input.cart ?? null, locale, currency);
444
+ const bundle = {
445
+ ...input,
446
+ cart: input.cart ?? null,
447
+ categoryProductMap: input.categoryProductMap ?? {},
448
+ customerAddresses: input.customerAddresses ?? [],
449
+ getCart(sessionId) {
450
+ return cartStore.getCart(sessionId);
451
+ },
452
+ addToCart(sessionId, variantId, quantity) {
453
+ return cartStore._addToCart(input.catalog, sessionId, variantId, quantity);
454
+ },
455
+ updateCartItem(sessionId, variantId, quantity) {
456
+ return cartStore._updateCartItem(input.catalog, sessionId, variantId, quantity);
457
+ },
458
+ removeCartItem(sessionId, variantId) {
459
+ return cartStore._removeCartItem(sessionId, variantId);
460
+ },
461
+ getCategoryNavTree() {
462
+ return input.categoryNavTree;
463
+ },
464
+ getCategoryProducts(slug, params) {
465
+ return buildCategoryListing(bundle, slug, params);
466
+ },
467
+ getBrandProducts(slug, params) {
468
+ return buildBrandListing(bundle, slug, params);
469
+ },
470
+ getProductListing(params) {
471
+ return buildProductListing(bundle, params);
472
+ },
473
+ searchProducts(params) {
474
+ return buildSearchResults(bundle, params);
475
+ },
476
+ getCustomerAddresses() {
477
+ return input.customerAddresses ?? [];
478
+ },
479
+ processGuestCheckout(sessionId, _payload) {
480
+ const cart = cartStore.getCart(sessionId);
481
+ if (!cart.items.length) {
482
+ throw new FixtureGuestOrderError("CART_NOT_FOUND", "Cart is empty");
483
+ }
484
+ for (const line of cart.items) {
485
+ const match = findCatalogVariant(input.catalog, line.product_variant_id);
486
+ if (!match) {
487
+ throw new FixtureGuestOrderError("VARIANT_NOT_FOUND", "Cart item no longer available");
488
+ }
489
+ if (variantStock(match.variant, match.item) < line.quantity) {
490
+ throw new FixtureGuestOrderError("OUT_OF_STOCK", "Some items are out of stock");
491
+ }
492
+ }
493
+ cartStore._clearCart(sessionId);
494
+ return { orderId: `fixture-order-${Date.now()}` };
495
+ },
496
+ };
497
+ return bundle;
498
+ }
499
+ // ---------------------------------------------------------------------------
500
+ // validateFixtureBundle
501
+ // ---------------------------------------------------------------------------
502
+ export function validateFixtureBundle(input, options = {}) {
503
+ const errors = [];
504
+ const catalogBySlug = new Map(input.catalog.map((item) => [String(item.slug), item]));
505
+ const variantIds = new Set();
506
+ for (const item of input.catalog) {
507
+ const variants = Array.isArray(item.variants) ? item.variants : [];
508
+ for (const v of variants) {
509
+ variantIds.add(Number(v.id));
510
+ }
511
+ if (item.default_variant_id != null) {
512
+ variantIds.add(Number(item.default_variant_id));
513
+ }
514
+ }
515
+ for (const slug of options.compositionProductSlugs ?? []) {
516
+ if (!catalogBySlug.has(slug)) {
517
+ errors.push(`Composition references product slug "${slug}" not found in catalog fixture`);
518
+ }
519
+ }
520
+ if (input.cart?.items) {
521
+ for (const line of input.cart.items) {
522
+ if (!variantIds.has(line.product_variant_id)) {
523
+ errors.push(`Cart item variant_id ${line.product_variant_id} not found in catalog variants`);
524
+ }
525
+ }
526
+ }
527
+ for (const [categorySlug, productSlugs] of Object.entries(input.categoryProductMap ?? {})) {
528
+ for (const productSlug of productSlugs) {
529
+ if (!catalogBySlug.has(productSlug)) {
530
+ errors.push(`categoryProductMap["${categorySlug}"] references unknown product slug "${productSlug}"`);
531
+ }
532
+ }
533
+ }
534
+ return errors;
535
+ }
536
+ // ---------------------------------------------------------------------------
537
+ // createStorefrontDataSource
538
+ // ---------------------------------------------------------------------------
539
+ export function createStorefrontDataSource(config) {
540
+ const { mode, fixtures, apiConfig, website } = config;
541
+ async function live(fn) {
542
+ const mod = await import("./index.js");
543
+ return fn(mod);
544
+ }
545
+ return {
546
+ async getCart(params = {}) {
547
+ if (mode === "fixtures")
548
+ return fixtures.getCart(params.sessionId);
549
+ return live((m) => m.fetchCart(apiConfig, website, params));
550
+ },
551
+ async addToCart(params) {
552
+ if (mode === "fixtures") {
553
+ try {
554
+ return fixtures.addToCart(params.sessionId, params.variantId, params.quantity ?? 1);
555
+ }
556
+ catch (err) {
557
+ if (err instanceof FixtureGuestOrderError)
558
+ throw err;
559
+ throw err;
560
+ }
561
+ }
562
+ return live((m) => m.addToCart(apiConfig, website, {
563
+ token: params.token,
564
+ sessionId: params.sessionId,
565
+ variantId: params.variantId,
566
+ quantity: params.quantity ?? 1,
567
+ }));
568
+ },
569
+ async updateCartItem(params) {
570
+ if (mode === "fixtures")
571
+ return fixtures.updateCartItem(params.sessionId, params.variantId, params.quantity ?? 0);
572
+ return live((m) => m.updateCartItem(apiConfig, website, {
573
+ token: params.token,
574
+ sessionId: params.sessionId,
575
+ variantId: params.variantId,
576
+ quantity: params.quantity ?? 0,
577
+ }));
578
+ },
579
+ async removeCartItem(params) {
580
+ if (mode === "fixtures")
581
+ return fixtures.removeCartItem(params.sessionId, params.variantId);
582
+ return live((m) => m.removeCartItem(apiConfig, website, {
583
+ token: params.token,
584
+ sessionId: params.sessionId,
585
+ variantId: params.variantId,
586
+ }));
587
+ },
588
+ async getCategoryNavTree(params = {}) {
589
+ if (mode === "fixtures")
590
+ return fixtures.getCategoryNavTree();
591
+ return live((m) => m.fetchCategoryNavTree(apiConfig, website, params));
592
+ },
593
+ async getCategoryProducts(slug, params = {}) {
594
+ if (mode === "fixtures") {
595
+ return fixtures.getCategoryProducts(slug, {
596
+ page: params.page,
597
+ pageSize: params.pageSize,
598
+ sort: params.sort,
599
+ brand: params.brand,
600
+ currency: website.currency,
601
+ locale: website.locale,
602
+ });
603
+ }
604
+ return live((m) => m.fetchCategoryProducts(apiConfig, website, {
605
+ slug,
606
+ page: params.page,
607
+ pageSize: params.pageSize,
608
+ sort: params.sort,
609
+ brand: params.brand,
610
+ q: params.q,
611
+ }));
612
+ },
613
+ async getBrandProducts(slug, params = {}) {
614
+ if (mode === "fixtures") {
615
+ return fixtures.getBrandProducts(slug, {
616
+ page: params.page,
617
+ pageSize: params.pageSize,
618
+ sort: params.sort,
619
+ category: params.category,
620
+ currency: website.currency,
621
+ locale: website.locale,
622
+ });
623
+ }
624
+ return live((m) => m.fetchBrandProducts(apiConfig, website, {
625
+ slug,
626
+ page: params.page,
627
+ pageSize: params.pageSize,
628
+ sort: params.sort,
629
+ category: params.category,
630
+ q: params.q,
631
+ }));
632
+ },
633
+ async getProductListing(params = {}) {
634
+ if (mode === "fixtures") {
635
+ return fixtures.getProductListing({
636
+ ...params,
637
+ currency: website.currency,
638
+ locale: website.locale,
639
+ });
640
+ }
641
+ return live((m) => m.fetchProductListing(apiConfig, website, {
642
+ filters: params.filters,
643
+ sort: params.sort,
644
+ page: params.page,
645
+ page_size: params.page_size,
646
+ }));
647
+ },
648
+ async searchProducts(params) {
649
+ if (mode === "fixtures") {
650
+ return fixtures.searchProducts({
651
+ ...params,
652
+ currency: website.currency,
653
+ locale: website.locale,
654
+ });
655
+ }
656
+ return live((m) => m.searchStorefront(apiConfig, website, {
657
+ q: params.q,
658
+ limit: params.limit,
659
+ locale: website.locale,
660
+ currency: website.currency,
661
+ }));
662
+ },
663
+ async getCustomerAddresses(params = {}) {
664
+ if (mode === "fixtures")
665
+ return fixtures.getCustomerAddresses();
666
+ if (!params.token)
667
+ return [];
668
+ return live((m) => m.fetchCustomerAddresses(apiConfig, website, { token: params.token }));
669
+ },
670
+ async getBuyerProfile(params) {
671
+ if (mode === "fixtures") {
672
+ throw new Error("Buyer profile requires live API");
673
+ }
674
+ return live((m) => m.fetchBuyerProfile(apiConfig, website, { token: params.token }));
675
+ },
676
+ async getOrders(params) {
677
+ if (mode === "fixtures") {
678
+ return { items: [], total: 0, page: params.page ?? 1, size: params.size ?? 20 };
679
+ }
680
+ return live((m) => m.fetchOrders(apiConfig, website, {
681
+ token: params.token,
682
+ page: params.page,
683
+ size: params.size,
684
+ }));
685
+ },
686
+ async getOrder(params) {
687
+ if (mode === "fixtures") {
688
+ throw Object.assign(new Error("Order not found"), { status: 404 });
689
+ }
690
+ return live((m) => m.fetchOrder(apiConfig, website, {
691
+ token: params.token,
692
+ orderId: params.orderId,
693
+ }));
694
+ },
695
+ async searchUbigeo(params) {
696
+ if (mode === "fixtures")
697
+ return [];
698
+ return live((m) => m.searchUbigeo(apiConfig, { q: params.q }));
699
+ },
700
+ async processGuestCheckout(payload) {
701
+ if (mode === "fixtures") {
702
+ try {
703
+ return fixtures.processGuestCheckout(payload.session_id, payload);
704
+ }
705
+ catch (err) {
706
+ if (err instanceof FixtureGuestOrderError)
707
+ throw err;
708
+ throw err;
709
+ }
710
+ }
711
+ return live((m) => {
712
+ const websiteFull = website;
713
+ return m.initiateGuestOrder(apiConfig, websiteFull, payload).then((result) => ({
714
+ orderId: result.orderId,
715
+ }));
716
+ });
717
+ },
718
+ };
719
+ }
720
+ /**
721
+ * Resolve mode + website then return a StorefrontDataSource.
722
+ * Apps inject env-specific callbacks; all commerce/live vs fixtures routing stays in SDK.
723
+ */
724
+ export async function resolveStorefrontDataSourceForRequest(options) {
725
+ const host = options.getRequestHost(options.request);
726
+ const mode = options.resolveMode(host);
727
+ if (mode === "fixtures") {
728
+ return createStorefrontDataSource({
729
+ mode,
730
+ fixtures: options.fixtures,
731
+ apiConfig: { baseUrl: options.apiConfig.baseUrl },
732
+ website: options.getFixtureWebsite(),
733
+ });
734
+ }
735
+ const website = await options.fetchLiveWebsite();
736
+ return createStorefrontDataSource({
737
+ mode,
738
+ fixtures: options.fixtures,
739
+ apiConfig: { baseUrl: options.apiConfig.baseUrl },
740
+ website,
741
+ });
742
+ }
743
+ //# sourceMappingURL=fixtures-commerce.js.map