@bacano/sdk 1.0.1 → 1.1.8

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 (72) hide show
  1. package/README.md +439 -18
  2. package/STOREFRONT_CLARITY.md +140 -0
  3. package/STOREFRONT_CONSTRUCTION.md +105 -0
  4. package/STOREFRONT_LEADS.md +130 -0
  5. package/STOREFRONT_LEGAL.md +66 -0
  6. package/STOREFRONT_MARKETING.md +291 -0
  7. package/STOREFRONT_PERFORMANCE.md +304 -0
  8. package/STOREFRONT_PREVIEW.md +499 -0
  9. package/STOREFRONT_PRODUCT_SPECIFICATIONS.md +127 -0
  10. package/STOREFRONT_VOLUME_PRICING.md +131 -0
  11. package/client.d.ts +8 -1
  12. package/errors.d.ts +9 -1
  13. package/graphql.cjs +266 -3
  14. package/graphql.d.ts +2 -2
  15. package/graphql.js +266 -3
  16. package/index.cjs +2035 -255
  17. package/index.d.ts +8 -6
  18. package/index.js +2035 -255
  19. package/marketing/attribution.d.ts +53 -0
  20. package/marketing/clarity.d.ts +3 -0
  21. package/marketing/domain.d.ts +1 -0
  22. package/marketing/ga4.d.ts +2 -0
  23. package/marketing/index.d.ts +5 -0
  24. package/marketing/runtime.d.ts +2 -0
  25. package/marketing/sanitize.d.ts +2 -0
  26. package/marketing/types.d.ts +111 -0
  27. package/media.d.ts +45 -2
  28. package/modules/auth.d.ts +3 -2
  29. package/modules/availability.d.ts +2 -2
  30. package/modules/cart-errors.d.ts +2 -0
  31. package/modules/cart-session.d.ts +2 -2
  32. package/modules/cart.d.ts +4 -3
  33. package/modules/catalog.d.ts +15 -3
  34. package/modules/content.d.ts +2 -2
  35. package/modules/marketing.d.ts +5 -0
  36. package/modules/orders.d.ts +2 -2
  37. package/modules/profile.d.ts +1 -1
  38. package/modules/static-catalog.d.ts +13 -0
  39. package/modules/website.d.ts +7 -1
  40. package/package.json +14 -3
  41. package/react/context-registry.d.ts +9 -0
  42. package/react/context.d.ts +1 -1
  43. package/react/hooks.d.ts +24 -2
  44. package/react/marketing-context.d.ts +3 -0
  45. package/react/marketing-provider.d.ts +10 -0
  46. package/react/preview/cache.d.ts +23 -0
  47. package/react/preview/context.d.ts +5 -0
  48. package/react/preview/navigation.d.ts +9 -0
  49. package/react/preview/provider.d.ts +3 -0
  50. package/react/preview/shell.d.ts +3 -0
  51. package/react/preview/toolbar.d.ts +3 -0
  52. package/react/preview/types.d.ts +60 -0
  53. package/react/preview.cjs +742 -0
  54. package/react/preview.d.ts +14 -0
  55. package/react/preview.js +730 -0
  56. package/react/website-gate.d.ts +22 -0
  57. package/react.cjs +3893 -1795
  58. package/react.d.ts +8 -4
  59. package/react.js +3891 -1789
  60. package/skills/bacano-web/SKILL.md +116 -0
  61. package/skills/bacano-web/agents/openai.yaml +4 -0
  62. package/skills/bacano-web/references/architecture-runtime.md +109 -0
  63. package/skills/bacano-web/references/common.md +92 -0
  64. package/skills/bacano-web/references/informational.md +39 -0
  65. package/skills/bacano-web/references/report.md +70 -0
  66. package/skills/bacano-web/references/storefront.md +116 -0
  67. package/skills/bacano-web/scripts/audit-http.mjs +284 -0
  68. package/skills/bacano-web/scripts/cli.mjs +222 -0
  69. package/skills/bacano-web/scripts/postinstall.mjs +8 -0
  70. package/transport/documents.d.ts +23 -18
  71. package/transport/graphql.d.ts +13 -1
  72. package/types.d.ts +338 -0
package/index.js CHANGED
@@ -1,10 +1,23 @@
1
1
  // libs/sdk/src/errors.ts
2
2
  var WebsiteError = class extends Error {
3
3
  code;
4
- constructor(message, code = "WEBSITE_ERROR") {
4
+ status;
5
+ retryAfterSeconds;
6
+ constructor(message, code = "WEBSITE_ERROR", options) {
5
7
  super(message);
6
8
  this.name = "WebsiteError";
7
9
  this.code = code;
10
+ this.status = options?.status ?? null;
11
+ this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
12
+ }
13
+ };
14
+ var RateLimitError = class extends WebsiteError {
15
+ constructor(message, retryAfterSeconds) {
16
+ super(message, "RATE_LIMITED", {
17
+ status: 429,
18
+ retryAfterSeconds
19
+ });
20
+ this.name = "RateLimitError";
8
21
  }
9
22
  };
10
23
  var NotInitializedError = class extends WebsiteError {
@@ -40,7 +53,109 @@ var GraphQLRequestError = class extends WebsiteError {
40
53
  }
41
54
  };
42
55
 
56
+ // libs/sdk/src/media.ts
57
+ var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
58
+ var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
59
+ var CONTENT_HERO_MAX_WIDTH = 1920;
60
+ var MEDIA_VARIANTS = {
61
+ thumbnail: { width: 240, quality: 75, format: "webp" },
62
+ card: { width: 640, quality: 80, format: "webp" },
63
+ detail: { width: 1400, quality: 85, format: "webp" }
64
+ };
65
+ function toSafeInteger(value) {
66
+ if (!Number.isFinite(value)) return void 0;
67
+ return Math.max(1, Math.floor(value));
68
+ }
69
+ function buildQuery(options) {
70
+ const params = new URLSearchParams();
71
+ const width = toSafeInteger(options.width);
72
+ const height = toSafeInteger(options.height);
73
+ const quality = toSafeInteger(options.quality);
74
+ if (width) params.set("w", String(width));
75
+ if (height) params.set("h", String(height));
76
+ if (quality) params.set("q", String(Math.min(100, quality)));
77
+ if (options.format) params.set("f", options.format);
78
+ const query = params.toString();
79
+ return query ? `?${query}` : "";
80
+ }
81
+ function buildStorageFileUrl(storageUrl, fileId, variant = "original") {
82
+ if (!storageUrl || !fileId) return "";
83
+ const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
84
+ if (variant === "original") return baseUrl;
85
+ return `${baseUrl}${buildQuery(MEDIA_VARIANTS[variant])}`;
86
+ }
87
+ function buildStorageTransformedFileUrl(storageUrl, fileId, options) {
88
+ if (!storageUrl || !fileId) return "";
89
+ const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
90
+ return `${baseUrl}${buildQuery(options)}`;
91
+ }
92
+ function buildStorageResponsiveSources(storageUrl, fileId, options = {}) {
93
+ const widths = options.widths ?? [...DEFAULT_RESPONSIVE_WIDTHS];
94
+ const uniqueWidths = [...new Set(widths.map(toSafeInteger))].filter((width) => Boolean(width)).sort((left, right) => left - right);
95
+ return uniqueWidths.map((width) => ({
96
+ width,
97
+ url: buildStorageTransformedFileUrl(storageUrl, fileId, {
98
+ width,
99
+ quality: options.quality ?? 80,
100
+ format: options.format ?? "webp"
101
+ })
102
+ }));
103
+ }
104
+ function formatMediaSrcSet(sources) {
105
+ return sources.filter((source) => source.url && source.width > 0).map((source) => `${source.url} ${source.width}w`).join(", ");
106
+ }
107
+ function buildStorageMediaUrls(storageUrl, fileId) {
108
+ return {
109
+ originalUrl: buildStorageFileUrl(storageUrl, fileId, "original"),
110
+ thumbnailUrl: buildStorageFileUrl(storageUrl, fileId, "thumbnail"),
111
+ cardUrl: buildStorageFileUrl(storageUrl, fileId, "card"),
112
+ detailUrl: buildStorageFileUrl(storageUrl, fileId, "detail"),
113
+ responsiveSources: buildStorageResponsiveSources(storageUrl, fileId)
114
+ };
115
+ }
116
+ function buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) {
117
+ const legacyUrls = buildStorageMediaUrls(storageUrl, fileId);
118
+ const safeOriginalWidth = toSafeInteger(originalWidth ?? void 0);
119
+ if (!safeOriginalWidth) {
120
+ return {
121
+ ...legacyUrls,
122
+ heroUrl: legacyUrls.originalUrl,
123
+ responsiveSources: []
124
+ };
125
+ }
126
+ const responsiveWidths = [
127
+ ...CONTENT_HERO_WIDTHS.filter((width) => width <= safeOriginalWidth),
128
+ safeOriginalWidth
129
+ ];
130
+ const heroWidth = Math.min(safeOriginalWidth, CONTENT_HERO_MAX_WIDTH);
131
+ return {
132
+ ...legacyUrls,
133
+ heroUrl: buildStorageTransformedFileUrl(storageUrl, fileId, {
134
+ width: heroWidth,
135
+ quality: 88,
136
+ format: "webp"
137
+ }),
138
+ responsiveSources: buildStorageResponsiveSources(storageUrl, fileId, {
139
+ widths: responsiveWidths,
140
+ quality: 85,
141
+ format: "webp"
142
+ })
143
+ };
144
+ }
145
+
43
146
  // libs/sdk/src/modules/auth.ts
147
+ function getWebsiteContextFromPublicConfig(config) {
148
+ if (config.contextSchemaVersion !== 1) return void 0;
149
+ return {
150
+ websiteId: config.websiteId,
151
+ companyId: config.companyId,
152
+ chainId: config.chainId ?? null,
153
+ name: config.name,
154
+ slug: config.slug,
155
+ domain: config.domain,
156
+ config: config.config ?? null
157
+ };
158
+ }
44
159
  function createAuthHeaders(accessToken) {
45
160
  return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
46
161
  }
@@ -62,14 +177,18 @@ function createRequestHeadersProvider(websiteSlug, getAccessToken) {
62
177
  }
63
178
  async function getWebsitePublicConfig(options) {
64
179
  const apiUrl = options.apiUrl.replace(/\/+$/, "");
65
- const headers = options.origin ? { Origin: options.origin } : void 0;
180
+ const headers = {};
181
+ if (options.origin) headers.Origin = options.origin;
182
+ if (options.previewToken) {
183
+ headers["X-Bacano-Preview-Token"] = options.previewToken;
184
+ }
66
185
  const params = new URLSearchParams({ websiteSlug: options.websiteSlug });
67
186
  let response;
68
187
  try {
69
188
  response = await fetch(
70
189
  `${apiUrl}/api/v1/website/public-config?${params.toString()}`,
71
190
  {
72
- headers,
191
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
73
192
  cache: "no-store"
74
193
  }
75
194
  );
@@ -93,12 +212,80 @@ async function getWebsitePublicConfig(options) {
93
212
  require_document: false,
94
213
  document_types: []
95
214
  };
215
+ const marketing = raw.marketing ?? {
216
+ schema_version: 1,
217
+ enabled: false,
218
+ currency_code: "COP",
219
+ time_zone: "America/Bogota",
220
+ attribution_window_days: 90,
221
+ consent_policy: null,
222
+ destinations: { ga4: null, meta: null, clarity: null }
223
+ };
224
+ const branding = raw.branding ?? {
225
+ schema_version: 1,
226
+ favicon: null
227
+ };
228
+ const legal = raw.legal ?? {
229
+ schema_version: 1,
230
+ version: 0,
231
+ content_hash: null,
232
+ documents: {
233
+ terms: null,
234
+ shipping: null,
235
+ returns: null,
236
+ privacy: null,
237
+ cookies: null
238
+ },
239
+ inquiries_channel: null,
240
+ direct_marketing_consent: null,
241
+ cookie_notice: {
242
+ enabled: false,
243
+ message: "Usamos cookies propias y de terceros para mejorar tu experiencia y medir el funcionamiento del sitio.",
244
+ button_label: "Entendido"
245
+ }
246
+ };
247
+ const mapConstructionImage = (image) => {
248
+ if (!image) return null;
249
+ const urls = buildStorageContentMediaUrls(
250
+ `${apiUrl}/storage/v1`,
251
+ image.file_id,
252
+ image.width
253
+ );
254
+ return {
255
+ fileId: image.file_id,
256
+ name: image.name,
257
+ altText: image.alt_text,
258
+ width: image.width,
259
+ height: image.height,
260
+ originalUrl: urls.originalUrl,
261
+ heroUrl: urls.heroUrl,
262
+ responsiveSources: urls.responsiveSources
263
+ };
264
+ };
265
+ const construction = raw.construction ?? {
266
+ schema_version: 1,
267
+ enabled: false,
268
+ enforced: false,
269
+ preview_access_granted: false,
270
+ title: null,
271
+ message: null,
272
+ desktop_image: null,
273
+ mobile_image: null
274
+ };
275
+ const volumePricing = raw.volume_pricing ?? {
276
+ schema_version: 1,
277
+ enabled: false,
278
+ tiers: []
279
+ };
96
280
  return {
97
281
  websiteId: raw.website_id,
98
282
  companyId: raw.company_id,
283
+ contextSchemaVersion: raw.context_schema_version === 1 ? 1 : null,
284
+ chainId: raw.chain_id ?? null,
99
285
  slug: raw.slug,
100
286
  name: raw.name,
101
287
  domain: raw.domain,
288
+ config: raw.config ?? null,
102
289
  buyerAccountsEnabled: raw.buyer_accounts_enabled,
103
290
  clerkPublishableKey: raw.clerk_publishable_key,
104
291
  customerIdentification: {
@@ -114,56 +301,89 @@ async function getWebsitePublicConfig(options) {
114
301
  isDefault: documentType.is_default
115
302
  })
116
303
  )
304
+ },
305
+ branding: {
306
+ schemaVersion: branding.schema_version,
307
+ favicon: branding.favicon ? {
308
+ fileId: branding.favicon.file_id,
309
+ name: branding.favicon.name,
310
+ altText: branding.favicon.alt_text,
311
+ width: branding.favicon.width,
312
+ height: branding.favicon.height,
313
+ originalUrl: buildStorageFileUrl(
314
+ `${apiUrl}/storage/v1`,
315
+ branding.favicon.file_id
316
+ )
317
+ } : null
318
+ },
319
+ marketing: {
320
+ schemaVersion: marketing.schema_version,
321
+ enabled: marketing.enabled,
322
+ currencyCode: marketing.currency_code,
323
+ timeZone: marketing.time_zone,
324
+ attributionWindowDays: marketing.attribution_window_days,
325
+ consentPolicy: marketing.consent_policy ? {
326
+ id: marketing.consent_policy.id,
327
+ version: marketing.consent_policy.version,
328
+ statement: marketing.consent_policy.statement,
329
+ statementHash: marketing.consent_policy.statement_hash
330
+ } : null,
331
+ destinations: {
332
+ ga4: marketing.destinations.ga4 ? {
333
+ measurementId: marketing.destinations.ga4.measurement_id,
334
+ diagnosticMode: marketing.destinations.ga4.diagnostic_mode ?? false
335
+ } : null,
336
+ meta: marketing.destinations.meta ? {
337
+ pixelId: marketing.destinations.meta.pixel_id,
338
+ diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
339
+ } : null,
340
+ clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
341
+ }
342
+ },
343
+ legal: {
344
+ schemaVersion: legal.schema_version,
345
+ version: legal.version,
346
+ contentHash: legal.content_hash,
347
+ documents: {
348
+ terms: legal.documents.terms,
349
+ shipping: legal.documents.shipping ?? null,
350
+ returns: legal.documents.returns ?? null,
351
+ privacy: legal.documents.privacy,
352
+ cookies: legal.documents.cookies
353
+ },
354
+ inquiriesChannel: legal.inquiries_channel,
355
+ directMarketingConsent: legal.direct_marketing_consent ? {
356
+ version: legal.direct_marketing_consent.version,
357
+ statement: legal.direct_marketing_consent.statement,
358
+ statementHash: legal.direct_marketing_consent.statement_hash
359
+ } : null,
360
+ cookieNotice: {
361
+ enabled: legal.cookie_notice.enabled,
362
+ message: legal.cookie_notice.message,
363
+ buttonLabel: legal.cookie_notice.button_label
364
+ }
365
+ },
366
+ construction: {
367
+ schemaVersion: construction.schema_version,
368
+ enabled: construction.enabled,
369
+ enforced: construction.enforced,
370
+ previewAccessGranted: construction.preview_access_granted,
371
+ title: construction.title,
372
+ message: construction.message,
373
+ desktopImage: mapConstructionImage(construction.desktop_image),
374
+ mobileImage: mapConstructionImage(construction.mobile_image)
375
+ },
376
+ volumePricing: {
377
+ schemaVersion: volumePricing.schema_version,
378
+ enabled: volumePricing.enabled,
379
+ tiers: volumePricing.tiers.map((tier) => ({
380
+ priceType: tier.price_type,
381
+ minQuantity: tier.min_quantity
382
+ }))
117
383
  }
118
384
  };
119
385
  }
120
386
 
121
- // libs/sdk/src/transport/graphql.ts
122
- async function executeGraphQL(url, query, variables, headers) {
123
- const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
124
- let res;
125
- try {
126
- res = await fetch(url, {
127
- method: "POST",
128
- headers: {
129
- "Content-Type": "application/json",
130
- ...resolvedHeaders
131
- },
132
- body: JSON.stringify({ query, variables })
133
- });
134
- } catch (err) {
135
- throw new WebsiteError(
136
- `Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
137
- "NETWORK_ERROR"
138
- );
139
- }
140
- if (res.status === 429) {
141
- throw new WebsiteError(
142
- "Rate limit exceeded. Please try again later.",
143
- "RATE_LIMITED"
144
- );
145
- }
146
- let body;
147
- try {
148
- body = await res.json();
149
- } catch {
150
- throw new WebsiteError(
151
- `Non-JSON response from ${url} (status ${res.status})`,
152
- "INVALID_RESPONSE"
153
- );
154
- }
155
- if (body.errors && body.errors.length > 0) {
156
- throw new GraphQLRequestError(body.errors);
157
- }
158
- if (!body.data) {
159
- throw new WebsiteError(
160
- "GraphQL response contains no data",
161
- "EMPTY_RESPONSE"
162
- );
163
- }
164
- return body.data;
165
- }
166
-
167
387
  // libs/sdk/src/transport/documents.ts
168
388
  var RESOLVE_WEBSITE_BY_SLUG = (
169
389
  /* GraphQL */
@@ -307,7 +527,6 @@ var GET_PRODUCTS = (
307
527
  ) {
308
528
  id
309
529
  related_product_id
310
- display_label
311
530
  sort_order
312
531
  related_product {
313
532
  id
@@ -329,6 +548,52 @@ var GET_PRODUCTS = (
329
548
  }
330
549
  }
331
550
  }
551
+ product_option_group_members(limit: 1) {
552
+ group {
553
+ id
554
+ display_title
555
+ members(
556
+ where: {
557
+ product: {
558
+ product_website_publications: {
559
+ website_id: { _eq: $websiteId }
560
+ is_visible: { _eq: true }
561
+ }
562
+ chain_products: {
563
+ chain_id: { _eq: $chainId }
564
+ is_enabled: { _eq: true }
565
+ status: { _eq: active }
566
+ }
567
+ }
568
+ }
569
+ order_by: { sort_order: asc }
570
+ ) {
571
+ id
572
+ product_id
573
+ display_label
574
+ sort_order
575
+ product {
576
+ id
577
+ slug
578
+ name
579
+ sku
580
+ product_media(
581
+ where: { file: { mimeType: { _ilike: "image/%" } } }
582
+ order_by: { order: asc }
583
+ limit: 1
584
+ ) {
585
+ id
586
+ file_id
587
+ order
588
+ file {
589
+ mimeType
590
+ name
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ }
332
597
  product_attributes {
333
598
  id
334
599
  value
@@ -413,6 +678,52 @@ var GET_PRODUCT_BY_PK = (
413
678
  limit: 1
414
679
  ) {
415
680
  id
681
+ product_option_group_members(limit: 1) {
682
+ group {
683
+ id
684
+ display_title
685
+ members(
686
+ where: {
687
+ product: {
688
+ product_website_publications: {
689
+ website_id: { _eq: $websiteId }
690
+ is_visible: { _eq: true }
691
+ }
692
+ chain_products: {
693
+ chain_id: { _eq: $chainId }
694
+ is_enabled: { _eq: true }
695
+ status: { _eq: active }
696
+ }
697
+ }
698
+ }
699
+ order_by: { sort_order: asc }
700
+ ) {
701
+ id
702
+ product_id
703
+ display_label
704
+ sort_order
705
+ product {
706
+ id
707
+ slug
708
+ name
709
+ sku
710
+ product_media(
711
+ where: { file: { mimeType: { _ilike: "image/%" } } }
712
+ order_by: { order: asc }
713
+ limit: 1
714
+ ) {
715
+ id
716
+ file_id
717
+ order
718
+ file {
719
+ mimeType
720
+ name
721
+ }
722
+ }
723
+ }
724
+ }
725
+ }
726
+ }
416
727
  name
417
728
  description
418
729
  long_description
@@ -499,7 +810,6 @@ var GET_PRODUCT_BY_PK = (
499
810
  ) {
500
811
  id
501
812
  related_product_id
502
- display_label
503
813
  sort_order
504
814
  related_product {
505
815
  id
@@ -873,6 +1183,24 @@ var WEBSITE_GET_PRODUCT_RECOMMENDATIONS = (
873
1183
  }
874
1184
  `
875
1185
  );
1186
+ var WEBSITE_GET_PRODUCT_SPECIFICATIONS = (
1187
+ /* GraphQL */
1188
+ `
1189
+ mutation WebsiteGetProductSpecifications(
1190
+ $websiteId: uuid!
1191
+ $productIds: [uuid!]!
1192
+ ) {
1193
+ result: websiteGetProductSpecifications(
1194
+ website_id: $websiteId
1195
+ product_ids: $productIds
1196
+ ) {
1197
+ success
1198
+ specifications
1199
+ errors
1200
+ }
1201
+ }
1202
+ `
1203
+ );
876
1204
  var GET_BRANDS = (
877
1205
  /* GraphQL */
878
1206
  `
@@ -981,14 +1309,76 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
981
1309
  var CHECK_AVAILABILITY = (
982
1310
  /* GraphQL */
983
1311
  `
984
- query CheckAvailability($variantIds: [uuid!]!, $chainId: uuid!) {
1312
+ query CheckAvailability(
1313
+ $variantIds: [uuid!]!
1314
+ $chainId: uuid!
1315
+ $websiteId: uuid
1316
+ ) {
1317
+ websiteProductAvailability(
1318
+ product_variant_ids: $variantIds
1319
+ chain_id: $chainId
1320
+ website_id: $websiteId
1321
+ ) {
1322
+ product_variant_id
1323
+ in_stock
1324
+ label
1325
+ max_purchasable_quantity
1326
+ }
1327
+ }
1328
+ `
1329
+ );
1330
+ var GET_PRODUCT_LIVE_STATE = (
1331
+ /* GraphQL */
1332
+ `
1333
+ query GetProductLiveState(
1334
+ $variantIds: [uuid!]!
1335
+ $companyId: uuid!
1336
+ $chainId: uuid!
1337
+ $websiteId: uuid!
1338
+ ) {
1339
+ product_variants(
1340
+ where: {
1341
+ id: { _in: $variantIds }
1342
+ is_disabled: { _eq: false }
1343
+ product: {
1344
+ company_id: { _eq: $companyId }
1345
+ product_website_publications: {
1346
+ website_id: { _eq: $websiteId }
1347
+ is_visible: { _eq: true }
1348
+ }
1349
+ chain_products: {
1350
+ chain_id: { _eq: $chainId }
1351
+ is_enabled: { _eq: true }
1352
+ status: { _eq: active }
1353
+ }
1354
+ }
1355
+ }
1356
+ ) {
1357
+ id
1358
+ product_variant_prices(
1359
+ where: {
1360
+ price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
1361
+ _or: [
1362
+ { chain_id: { _eq: $chainId } }
1363
+ { chain_id: { _is_null: true } }
1364
+ ]
1365
+ }
1366
+ ) {
1367
+ id
1368
+ price
1369
+ price_type
1370
+ chain_id
1371
+ }
1372
+ }
985
1373
  websiteProductAvailability(
986
1374
  product_variant_ids: $variantIds
987
1375
  chain_id: $chainId
1376
+ website_id: $websiteId
988
1377
  ) {
989
1378
  product_variant_id
990
1379
  in_stock
991
1380
  label
1381
+ max_purchasable_quantity
992
1382
  }
993
1383
  }
994
1384
  `
@@ -1021,10 +1411,28 @@ var CREATE_CART = (
1021
1411
  total_with_discount
1022
1412
  created_at
1023
1413
  updated_at
1414
+ volume_pricing {
1415
+ enabled
1416
+ eligibleQuantity
1417
+ appliedPriceType
1418
+ appliedMinQuantity
1419
+ nextPriceType
1420
+ nextMinQuantity
1421
+ remainingQuantity
1422
+ totalSavings
1423
+ }
1024
1424
  items {
1025
1425
  id
1026
1426
  product_variant_id
1027
1427
  quantity
1428
+ unit_price
1429
+ line_total
1430
+ base_unit_price
1431
+ unit_savings
1432
+ line_savings
1433
+ volume_pricing_eligible
1434
+ resolved_price_type
1435
+ volume_pricing_min_quantity
1028
1436
  product_variant {
1029
1437
  id
1030
1438
  sku
@@ -1064,10 +1472,28 @@ var GET_CART = (
1064
1472
  total_with_discount
1065
1473
  created_at
1066
1474
  updated_at
1475
+ volume_pricing {
1476
+ enabled
1477
+ eligibleQuantity
1478
+ appliedPriceType
1479
+ appliedMinQuantity
1480
+ nextPriceType
1481
+ nextMinQuantity
1482
+ remainingQuantity
1483
+ totalSavings
1484
+ }
1067
1485
  items {
1068
1486
  id
1069
1487
  product_variant_id
1070
1488
  quantity
1489
+ unit_price
1490
+ line_total
1491
+ base_unit_price
1492
+ unit_savings
1493
+ line_savings
1494
+ volume_pricing_eligible
1495
+ resolved_price_type
1496
+ volume_pricing_min_quantity
1071
1497
  product_variant {
1072
1498
  id
1073
1499
  sku
@@ -1098,6 +1524,7 @@ var START_GUEST_CHECKOUT = (
1098
1524
  $contact: WebsiteGuestContactInput!
1099
1525
  $items: [WebsiteCheckoutItemInput!]!
1100
1526
  $fulfillmentFamily: String
1527
+ $marketingAttribution: WebsiteMarketingAttributionInput
1101
1528
  ) {
1102
1529
  websiteStartGuestCheckout(
1103
1530
  website_id: $websiteId
@@ -1105,6 +1532,7 @@ var START_GUEST_CHECKOUT = (
1105
1532
  contact: $contact
1106
1533
  items: $items
1107
1534
  fulfillment_family: $fulfillmentFamily
1535
+ marketing_attribution: $marketingAttribution
1108
1536
  ) {
1109
1537
  success
1110
1538
  order_id
@@ -1119,10 +1547,28 @@ var START_GUEST_CHECKOUT = (
1119
1547
  total_with_discount
1120
1548
  created_at
1121
1549
  updated_at
1550
+ volume_pricing {
1551
+ enabled
1552
+ eligibleQuantity
1553
+ appliedPriceType
1554
+ appliedMinQuantity
1555
+ nextPriceType
1556
+ nextMinQuantity
1557
+ remainingQuantity
1558
+ totalSavings
1559
+ }
1122
1560
  items {
1123
1561
  id
1124
1562
  product_variant_id
1125
1563
  quantity
1564
+ unit_price
1565
+ line_total
1566
+ base_unit_price
1567
+ unit_savings
1568
+ line_savings
1569
+ volume_pricing_eligible
1570
+ resolved_price_type
1571
+ volume_pricing_min_quantity
1126
1572
  product_variant {
1127
1573
  id
1128
1574
  sku
@@ -1172,10 +1618,28 @@ var SET_CART_ITEM = (
1172
1618
  total_with_discount
1173
1619
  created_at
1174
1620
  updated_at
1621
+ volume_pricing {
1622
+ enabled
1623
+ eligibleQuantity
1624
+ appliedPriceType
1625
+ appliedMinQuantity
1626
+ nextPriceType
1627
+ nextMinQuantity
1628
+ remainingQuantity
1629
+ totalSavings
1630
+ }
1175
1631
  items {
1176
1632
  id
1177
1633
  product_variant_id
1178
1634
  quantity
1635
+ unit_price
1636
+ line_total
1637
+ base_unit_price
1638
+ unit_savings
1639
+ line_savings
1640
+ volume_pricing_eligible
1641
+ resolved_price_type
1642
+ volume_pricing_min_quantity
1179
1643
  product_variant {
1180
1644
  id
1181
1645
  sku
@@ -1223,10 +1687,28 @@ var REMOVE_CART_ITEM = (
1223
1687
  total_with_discount
1224
1688
  created_at
1225
1689
  updated_at
1690
+ volume_pricing {
1691
+ enabled
1692
+ eligibleQuantity
1693
+ appliedPriceType
1694
+ appliedMinQuantity
1695
+ nextPriceType
1696
+ nextMinQuantity
1697
+ remainingQuantity
1698
+ totalSavings
1699
+ }
1226
1700
  items {
1227
1701
  id
1228
1702
  product_variant_id
1229
1703
  quantity
1704
+ unit_price
1705
+ line_total
1706
+ base_unit_price
1707
+ unit_savings
1708
+ line_savings
1709
+ volume_pricing_eligible
1710
+ resolved_price_type
1711
+ volume_pricing_min_quantity
1230
1712
  product_variant {
1231
1713
  id
1232
1714
  sku
@@ -1248,6 +1730,19 @@ var REMOVE_CART_ITEM = (
1248
1730
  }
1249
1731
  `
1250
1732
  );
1733
+ var DISCARD_CART = (
1734
+ /* GraphQL */
1735
+ `
1736
+ mutation DiscardCart($orderId: uuid!, $cartToken: String!) {
1737
+ websiteDiscardCart(order_id: $orderId, cart_token: $cartToken) {
1738
+ success
1739
+ order_id
1740
+ website_cart_status
1741
+ errors
1742
+ }
1743
+ }
1744
+ `
1745
+ );
1251
1746
  var CHECKOUT = (
1252
1747
  /* GraphQL */
1253
1748
  `
@@ -1262,6 +1757,8 @@ var CHECKOUT = (
1262
1757
  $paymentMethodId: uuid
1263
1758
  $buyerAccountOptIn: Boolean
1264
1759
  $initialPayment: WebsiteInitialPaymentInput
1760
+ $marketingAttribution: WebsiteMarketingAttributionInput
1761
+ $advertisingConsent: WebsiteAdvertisingConsentInput
1265
1762
  ) {
1266
1763
  websiteCheckout(
1267
1764
  order_id: $orderId
@@ -1274,6 +1771,8 @@ var CHECKOUT = (
1274
1771
  payment_method_id: $paymentMethodId
1275
1772
  buyer_account_opt_in: $buyerAccountOptIn
1276
1773
  initial_payment: $initialPayment
1774
+ marketing_attribution: $marketingAttribution
1775
+ advertising_consent: $advertisingConsent
1277
1776
  ) {
1278
1777
  success
1279
1778
  order_id
@@ -2200,45 +2699,157 @@ var GET_SEARCH_SUGGESTIONS = (
2200
2699
  `
2201
2700
  );
2202
2701
 
2203
- // libs/sdk/src/modules/availability.ts
2204
- var MAX_AVAILABILITY_VARIANT_IDS = 100;
2205
- function mapAvailability(raw) {
2206
- return {
2207
- productVariantId: raw.product_variant_id,
2208
- inStock: raw.in_stock,
2209
- label: raw.label
2210
- };
2702
+ // libs/sdk/src/transport/graphql.ts
2703
+ var DEFAULT_MAX_ATTEMPTS = 4;
2704
+ var DEFAULT_MAX_TOTAL_WAIT_MS = 3 * 60 * 1e3;
2705
+ var DEFAULT_JITTER_RATIO = 0.1;
2706
+ function parseRetryAfterSeconds(res, body) {
2707
+ const header = res.headers?.get?.("Retry-After")?.trim();
2708
+ if (header) {
2709
+ const seconds = Number(header);
2710
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
2711
+ const retryAt = Date.parse(header);
2712
+ if (Number.isFinite(retryAt)) {
2713
+ return Math.max(Math.ceil((retryAt - Date.now()) / 1e3), 0);
2714
+ }
2715
+ }
2716
+ if (body && typeof body === "object") {
2717
+ const seconds = Number(
2718
+ body.retry_after_seconds
2719
+ );
2720
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
2721
+ }
2722
+ return 1;
2211
2723
  }
2212
- function chunkArray(items, chunkSize) {
2213
- const chunks = [];
2214
- for (let index = 0; index < items.length; index += chunkSize) {
2215
- chunks.push(items.slice(index, index + chunkSize));
2724
+ async function createRateLimitError(res) {
2725
+ let body;
2726
+ try {
2727
+ body = await res.json();
2728
+ } catch {
2729
+ body = null;
2216
2730
  }
2217
- return chunks;
2731
+ const message = body && typeof body === "object" && typeof body.message === "string" ? body.message : "Rate limit exceeded. Please try again later.";
2732
+ return new RateLimitError(message, parseRetryAfterSeconds(res, body));
2218
2733
  }
2219
- function uniqueValues(values) {
2220
- return Array.from(new Set(values));
2734
+ function getRetryDelayMs(error, options) {
2735
+ const retryAfterMs = (error.retryAfterSeconds ?? 1) * 1e3;
2736
+ const jitterRatio = Math.max(
2737
+ 0,
2738
+ Math.min(options.jitterRatio ?? DEFAULT_JITTER_RATIO, 1)
2739
+ );
2740
+ return Math.ceil(retryAfterMs * (1 + Math.random() * jitterRatio));
2221
2741
  }
2222
- function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
2223
- return {
2224
- async checkAvailability(productVariantIds) {
2225
- const ctx = getContext();
2226
- if (!ctx.chainId) {
2227
- throw new WebsiteError(
2228
- "This website has no chain configured. Stock availability requires a chain_id.",
2229
- "NO_CHAIN"
2230
- );
2742
+ function wait(delayMs) {
2743
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
2744
+ }
2745
+ async function executeGraphQL(url, query, variables, headers, options) {
2746
+ const retry = options?.retry;
2747
+ const maxAttempts = Math.max(
2748
+ 1,
2749
+ Math.floor(retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)
2750
+ );
2751
+ const maxTotalWaitMs = Math.max(
2752
+ 0,
2753
+ retry?.maxTotalWaitMs ?? DEFAULT_MAX_TOTAL_WAIT_MS
2754
+ );
2755
+ let totalWaitMs = 0;
2756
+ let res = null;
2757
+ for (let attempt = 1; attempt <= (retry ? maxAttempts : 1); attempt += 1) {
2758
+ const resolvedHeaders = typeof headers === "function" ? await headers() : headers ?? {};
2759
+ try {
2760
+ res = await fetch(url, {
2761
+ method: "POST",
2762
+ headers: {
2763
+ "Content-Type": "application/json",
2764
+ ...resolvedHeaders
2765
+ },
2766
+ body: JSON.stringify({ query, variables })
2767
+ });
2768
+ } catch (err) {
2769
+ throw new WebsiteError(
2770
+ `Network error connecting to ${url}: ${err instanceof Error ? err.message : String(err)}`,
2771
+ "NETWORK_ERROR"
2772
+ );
2773
+ }
2774
+ if (res.status !== 429) break;
2775
+ const rateLimitError = await createRateLimitError(res);
2776
+ const delayMs = getRetryDelayMs(rateLimitError, retry ?? {});
2777
+ const canRetry = Boolean(retry) && attempt < maxAttempts && totalWaitMs + delayMs <= maxTotalWaitMs;
2778
+ if (!canRetry) throw rateLimitError;
2779
+ totalWaitMs += delayMs;
2780
+ await wait(delayMs);
2781
+ }
2782
+ if (!res) {
2783
+ throw new WebsiteError(
2784
+ "GraphQL request did not produce a response",
2785
+ "EMPTY_RESPONSE"
2786
+ );
2787
+ }
2788
+ let body;
2789
+ try {
2790
+ body = await res.json();
2791
+ } catch {
2792
+ throw new WebsiteError(
2793
+ `Non-JSON response from ${url} (status ${res.status})`,
2794
+ "INVALID_RESPONSE"
2795
+ );
2796
+ }
2797
+ if (body.errors && body.errors.length > 0) {
2798
+ throw new GraphQLRequestError(body.errors);
2799
+ }
2800
+ if (!body.data) {
2801
+ throw new WebsiteError(
2802
+ "GraphQL response contains no data",
2803
+ "EMPTY_RESPONSE"
2804
+ );
2805
+ }
2806
+ return body.data;
2807
+ }
2808
+
2809
+ // libs/sdk/src/modules/availability.ts
2810
+ var MAX_AVAILABILITY_VARIANT_IDS = 100;
2811
+ function mapAvailability(raw) {
2812
+ return {
2813
+ productVariantId: raw.product_variant_id,
2814
+ inStock: raw.in_stock,
2815
+ label: raw.label,
2816
+ maxPurchasableQuantity: raw.max_purchasable_quantity
2817
+ };
2818
+ }
2819
+ function chunkArray(items, chunkSize) {
2820
+ const chunks = [];
2821
+ for (let index = 0; index < items.length; index += chunkSize) {
2822
+ chunks.push(items.slice(index, index + chunkSize));
2823
+ }
2824
+ return chunks;
2825
+ }
2826
+ function uniqueValues(values) {
2827
+ return Array.from(new Set(values));
2828
+ }
2829
+ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
2830
+ return {
2831
+ async checkAvailability(productVariantIds) {
2832
+ const ctx = getContext();
2833
+ if (!ctx.chainId) {
2834
+ throw new WebsiteError(
2835
+ "This website has no chain configured. Stock availability requires a chain_id.",
2836
+ "NO_CHAIN"
2837
+ );
2231
2838
  }
2232
2839
  if (productVariantIds.length === 0) return [];
2233
2840
  const variantIds = uniqueValues(productVariantIds);
2234
2841
  const results = [];
2235
- for (const chunk of chunkArray(variantIds, MAX_AVAILABILITY_VARIANT_IDS)) {
2842
+ for (const chunk of chunkArray(
2843
+ variantIds,
2844
+ MAX_AVAILABILITY_VARIANT_IDS
2845
+ )) {
2236
2846
  const data = await executeGraphQL(
2237
2847
  graphqlUrl,
2238
2848
  CHECK_AVAILABILITY,
2239
2849
  {
2240
2850
  variantIds: chunk,
2241
- chainId: ctx.chainId
2851
+ chainId: ctx.chainId,
2852
+ websiteId: ctx.websiteId
2242
2853
  },
2243
2854
  getHeaders
2244
2855
  );
@@ -2251,12 +2862,297 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
2251
2862
  };
2252
2863
  }
2253
2864
 
2865
+ // libs/sdk/src/marketing/sanitize.ts
2866
+ var SENSITIVE_PARAMETER_PATTERNS = [
2867
+ /token/i,
2868
+ /secret/i,
2869
+ /password/i,
2870
+ /authorization/i,
2871
+ /session/i,
2872
+ /ticket/i,
2873
+ /code_verifier/i,
2874
+ /client_secret/i,
2875
+ /payment/i,
2876
+ /order[_-]?(?:id|token)/i,
2877
+ /public[_-]?order/i,
2878
+ /clerk/i,
2879
+ /oauth/i,
2880
+ /email/i,
2881
+ /phone/i,
2882
+ /document/i
2883
+ ];
2884
+ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
2885
+ "utm_source",
2886
+ "utm_medium",
2887
+ "utm_campaign",
2888
+ "utm_term",
2889
+ "utm_content",
2890
+ "utm_source_platform",
2891
+ "utm_id",
2892
+ "bacano_origin",
2893
+ "gclid",
2894
+ "gbraid",
2895
+ "wbraid",
2896
+ "fbclid",
2897
+ "ttclid",
2898
+ "msclkid"
2899
+ ]);
2900
+ function sanitizeMarketingUrl(input, baseUrl) {
2901
+ try {
2902
+ const url = new URL(input, baseUrl);
2903
+ const safeParameters = new URLSearchParams();
2904
+ for (const [key, value] of url.searchParams.entries()) {
2905
+ if (ALLOWED_MARKETING_PARAMETERS.has(key.toLowerCase()) && !SENSITIVE_PARAMETER_PATTERNS.some((pattern) => pattern.test(key))) {
2906
+ safeParameters.set(key.toLowerCase(), value.slice(0, 500));
2907
+ }
2908
+ }
2909
+ url.search = safeParameters.toString();
2910
+ url.hash = "";
2911
+ return url.toString();
2912
+ } catch {
2913
+ return "";
2914
+ }
2915
+ }
2916
+ function getAllowedMarketingParameters(input, baseUrl) {
2917
+ const sanitized = sanitizeMarketingUrl(input, baseUrl);
2918
+ if (!sanitized) return {};
2919
+ const url = new URL(sanitized);
2920
+ return Object.fromEntries(url.searchParams.entries());
2921
+ }
2922
+
2923
+ // libs/sdk/src/marketing/attribution.ts
2924
+ var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
2925
+ var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
2926
+ var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
2927
+ var SALES_ORIGIN_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2928
+ function storageKey(websiteId) {
2929
+ return `${STORAGE_PREFIX}:${websiteId}`;
2930
+ }
2931
+ function readStored(websiteId) {
2932
+ if (typeof window === "undefined") return null;
2933
+ try {
2934
+ const value = window.localStorage.getItem(storageKey(websiteId));
2935
+ if (!value) return null;
2936
+ const parsed = JSON.parse(value);
2937
+ if (new Date(parsed.expiresAt).getTime() <= Date.now()) {
2938
+ window.localStorage.removeItem(storageKey(websiteId));
2939
+ return null;
2940
+ }
2941
+ return parsed;
2942
+ } catch {
2943
+ return null;
2944
+ }
2945
+ }
2946
+ function writeStored(websiteId, value) {
2947
+ if (typeof window === "undefined") return;
2948
+ try {
2949
+ window.localStorage.setItem(storageKey(websiteId), JSON.stringify(value));
2950
+ } catch {
2951
+ }
2952
+ }
2953
+ function readCookie(name) {
2954
+ if (typeof document === "undefined") return void 0;
2955
+ try {
2956
+ const prefix = `${name}=`;
2957
+ return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(prefix))?.slice(prefix.length);
2958
+ } catch {
2959
+ return void 0;
2960
+ }
2961
+ }
2962
+ function validMetaBrowserId(value) {
2963
+ const normalized = value?.trim();
2964
+ return normalized && normalized.length <= 512 && META_BROWSER_ID_PATTERN.test(normalized) ? normalized : void 0;
2965
+ }
2966
+ function optional(value) {
2967
+ const normalized = value?.trim();
2968
+ return normalized ? normalized.slice(0, 500) : void 0;
2969
+ }
2970
+ function validSalesOriginKey(value) {
2971
+ const normalized = value?.trim().toLowerCase();
2972
+ return normalized && normalized.length <= 100 && SALES_ORIGIN_KEY_PATTERN.test(normalized) ? normalized : void 0;
2973
+ }
2974
+ function buildTouchpoint() {
2975
+ if (typeof window === "undefined") return null;
2976
+ const params = getAllowedMarketingParameters(window.location.href);
2977
+ const referrerHost = (() => {
2978
+ if (!document.referrer) return void 0;
2979
+ try {
2980
+ const hostname = new URL(document.referrer).hostname;
2981
+ return hostname === window.location.hostname ? void 0 : hostname.slice(0, 255);
2982
+ } catch {
2983
+ return void 0;
2984
+ }
2985
+ })();
2986
+ return {
2987
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2988
+ landingUrl: sanitizeMarketingUrl(window.location.href),
2989
+ referrerHost,
2990
+ source: optional(params.utm_source),
2991
+ medium: optional(params.utm_medium),
2992
+ campaign: optional(params.utm_campaign),
2993
+ term: optional(params.utm_term),
2994
+ content: optional(params.utm_content),
2995
+ sourcePlatform: optional(params.utm_source_platform),
2996
+ campaignId: optional(params.utm_id),
2997
+ salesOriginKey: validSalesOriginKey(params.bacano_origin),
2998
+ gclid: optional(params.gclid),
2999
+ gbraid: optional(params.gbraid),
3000
+ wbraid: optional(params.wbraid),
3001
+ fbclid: optional(params.fbclid),
3002
+ ttclid: optional(params.ttclid),
3003
+ msclkid: optional(params.msclkid)
3004
+ };
3005
+ }
3006
+ function isNonDirect(touchpoint) {
3007
+ return Boolean(
3008
+ touchpoint.salesOriginKey || touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
3009
+ );
3010
+ }
3011
+ function toPublic(stored) {
3012
+ return {
3013
+ schemaVersion: 1,
3014
+ firstTouch: stored.firstTouch,
3015
+ lastNonDirectTouch: stored.lastNonDirectTouch,
3016
+ ga4ClientId: stored.ga4ClientId,
3017
+ ga4SessionId: stored.ga4SessionId,
3018
+ metaFbp: stored.metaFbp,
3019
+ metaFbc: stored.metaFbc,
3020
+ clientUserAgent: stored.clientUserAgent,
3021
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
3022
+ expiresAt: stored.expiresAt
3023
+ };
3024
+ }
3025
+ function refreshWebsiteMarketingMetaContext(websiteId) {
3026
+ try {
3027
+ const stored = readStored(websiteId);
3028
+ if (!stored || typeof window === "undefined") return false;
3029
+ const fbp = validMetaBrowserId(readCookie("_fbp"));
3030
+ const clickTouchpoint = stored.lastNonDirectTouch ?? stored.firstTouch;
3031
+ const derivedFbc = clickTouchpoint.fbclid ? `fb.1.${new Date(clickTouchpoint.capturedAt).getTime()}.${clickTouchpoint.fbclid}` : void 0;
3032
+ const fbc = validMetaBrowserId(readCookie("_fbc") ?? derivedFbc);
3033
+ const clientUserAgent = window.navigator?.userAgent.trim().slice(0, 1024);
3034
+ writeStored(websiteId, {
3035
+ ...stored,
3036
+ metaFbp: fbp ?? stored.metaFbp,
3037
+ metaFbc: fbc ?? stored.metaFbc,
3038
+ clientUserAgent: clientUserAgent || stored.clientUserAgent
3039
+ });
3040
+ return Boolean(fbp || fbc || clientUserAgent);
3041
+ } catch {
3042
+ return false;
3043
+ }
3044
+ }
3045
+ function storeWebsiteMarketingGa4ClientId(websiteId, clientId) {
3046
+ if (typeof clientId !== "string") return false;
3047
+ const normalized = clientId.trim();
3048
+ if (!GA4_CLIENT_ID_PATTERN.test(normalized)) return false;
3049
+ const stored = readStored(websiteId);
3050
+ if (!stored) return false;
3051
+ writeStored(websiteId, { ...stored, ga4ClientId: normalized });
3052
+ return true;
3053
+ }
3054
+ function storeWebsiteMarketingGa4SessionId(websiteId, sessionId) {
3055
+ const normalized = typeof sessionId === "number" && Number.isSafeInteger(sessionId) ? String(sessionId) : typeof sessionId === "string" ? sessionId.trim() : "";
3056
+ const numericValue = Number(normalized);
3057
+ if (!/^\d{1,16}$/.test(normalized) || !Number.isSafeInteger(numericValue) || numericValue <= 0) {
3058
+ return false;
3059
+ }
3060
+ const stored = readStored(websiteId);
3061
+ if (!stored) return false;
3062
+ writeStored(websiteId, { ...stored, ga4SessionId: normalized });
3063
+ return true;
3064
+ }
3065
+ function captureWebsiteMarketingAttribution(websiteId, windowDays) {
3066
+ const current = buildTouchpoint();
3067
+ if (!current) return null;
3068
+ const stored = readStored(websiteId);
3069
+ const expiresAt = stored?.expiresAt ?? new Date(Date.now() + Math.max(1, windowDays) * 864e5).toISOString();
3070
+ const next = {
3071
+ firstTouch: stored?.firstTouch ?? current,
3072
+ lastNonDirectTouch: isNonDirect(current) ? current : stored?.lastNonDirectTouch ?? null,
3073
+ ga4ClientId: stored?.ga4ClientId,
3074
+ ga4SessionId: stored?.ga4SessionId,
3075
+ metaFbp: stored?.metaFbp,
3076
+ metaFbc: stored?.metaFbc,
3077
+ clientUserAgent: stored?.clientUserAgent,
3078
+ expiresAt
3079
+ };
3080
+ writeStored(websiteId, next);
3081
+ return toPublic(next);
3082
+ }
3083
+ function getWebsiteMarketingAttribution(websiteId) {
3084
+ const stored = readStored(websiteId);
3085
+ return stored ? toPublic(stored) : null;
3086
+ }
3087
+ function serializeTouchpoint(touchpoint) {
3088
+ return {
3089
+ captured_at: touchpoint.capturedAt,
3090
+ landing_url: touchpoint.landingUrl,
3091
+ referrer_host: touchpoint.referrerHost,
3092
+ source: touchpoint.source,
3093
+ medium: touchpoint.medium,
3094
+ campaign: touchpoint.campaign,
3095
+ term: touchpoint.term,
3096
+ content: touchpoint.content,
3097
+ source_platform: touchpoint.sourcePlatform,
3098
+ campaign_id: touchpoint.campaignId,
3099
+ sales_origin_key: touchpoint.salesOriginKey,
3100
+ gclid: touchpoint.gclid,
3101
+ gbraid: touchpoint.gbraid,
3102
+ wbraid: touchpoint.wbraid,
3103
+ fbclid: touchpoint.fbclid,
3104
+ ttclid: touchpoint.ttclid,
3105
+ msclkid: touchpoint.msclkid
3106
+ };
3107
+ }
3108
+ function serializeWebsiteMarketingAttribution(attribution) {
3109
+ return {
3110
+ schema_version: attribution.schemaVersion,
3111
+ first_touch: serializeTouchpoint(attribution.firstTouch),
3112
+ last_non_direct_touch: attribution.lastNonDirectTouch ? serializeTouchpoint(attribution.lastNonDirectTouch) : null,
3113
+ ga4_client_id: attribution.ga4ClientId,
3114
+ ga4_session_id: attribution.ga4SessionId,
3115
+ meta_fbp: attribution.metaFbp,
3116
+ meta_fbc: attribution.metaFbc,
3117
+ client_user_agent: attribution.clientUserAgent,
3118
+ captured_at: attribution.capturedAt
3119
+ };
3120
+ }
3121
+
3122
+ // libs/sdk/src/modules/cart-errors.ts
3123
+ var RECOVERABLE_CART_SESSION_ERRORS = /* @__PURE__ */ new Set([
3124
+ "INVALID_CART_TOKEN",
3125
+ "CART_NOT_FOUND"
3126
+ ]);
3127
+ function firstStableErrorCode(errors) {
3128
+ const candidate = errors?.[0]?.split(":", 1)[0]?.trim();
3129
+ return candidate && /^[A-Z][A-Z0-9_]+$/.test(candidate) ? candidate : void 0;
3130
+ }
3131
+ function getStableCartActionErrorCode(errors, fallbackCode) {
3132
+ return firstStableErrorCode(errors) ?? fallbackCode;
3133
+ }
3134
+ function hasRecoverableCartSessionError(errors) {
3135
+ return Boolean(
3136
+ errors?.some(
3137
+ (error) => RECOVERABLE_CART_SESSION_ERRORS.has(error.split(":", 1)[0]?.trim())
3138
+ )
3139
+ );
3140
+ }
3141
+
2254
3142
  // libs/sdk/src/modules/cart.ts
2255
3143
  function mapCartItem(raw) {
2256
3144
  return {
2257
3145
  id: raw.id,
2258
3146
  productVariantId: raw.product_variant_id,
2259
3147
  quantity: raw.quantity,
3148
+ unitPrice: raw.unit_price ?? null,
3149
+ lineTotal: raw.line_total ?? null,
3150
+ baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
3151
+ unitSavings: raw.unit_savings ?? 0,
3152
+ lineSavings: raw.line_savings ?? 0,
3153
+ volumePricingEligible: raw.volume_pricing_eligible ?? false,
3154
+ resolvedPriceType: raw.resolved_price_type ?? null,
3155
+ volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
2260
3156
  productVariant: {
2261
3157
  sku: raw.product_variant.sku,
2262
3158
  product: raw.product_variant.product,
@@ -2282,13 +3178,29 @@ function mapCart(raw, cartToken) {
2282
3178
  },
2283
3179
  status: raw.status,
2284
3180
  createdAt: raw.created_at,
2285
- updatedAt: raw.updated_at
3181
+ updatedAt: raw.updated_at,
3182
+ volumePricing: raw.volume_pricing ? {
3183
+ ...raw.volume_pricing,
3184
+ totalSavings: raw.volume_pricing.totalSavings ?? 0
3185
+ } : {
3186
+ enabled: false,
3187
+ eligibleQuantity: 0,
3188
+ appliedPriceType: null,
3189
+ appliedMinQuantity: null,
3190
+ nextPriceType: null,
3191
+ nextMinQuantity: null,
3192
+ remainingQuantity: null,
3193
+ totalSavings: 0
3194
+ }
2286
3195
  };
2287
3196
  }
2288
3197
  function assertActionSuccess(result, fallbackCode) {
2289
3198
  if (!result.success || !result.order_id || !result.cart_token || !result.cart) {
2290
3199
  const message = result.errors?.join("; ") || "Cart action failed";
2291
- throw new WebsiteError(message, fallbackCode);
3200
+ throw new WebsiteError(
3201
+ message,
3202
+ getStableCartActionErrorCode(result.errors, fallbackCode)
3203
+ );
2292
3204
  }
2293
3205
  }
2294
3206
  function requireCartSession(storage) {
@@ -2306,7 +3218,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2306
3218
  const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
2307
3219
  const result = data.websiteGetCart;
2308
3220
  if (!result.success) {
2309
- if (result.errors?.includes("CART_NOT_FOUND")) {
3221
+ if (hasRecoverableCartSessionError(result.errors)) {
2310
3222
  cartSessionStorage.clear();
2311
3223
  return null;
2312
3224
  }
@@ -2359,23 +3271,31 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2359
3271
  "NO_CHAIN"
2360
3272
  );
2361
3273
  }
3274
+ const requestStartCheckout = async (cartToken) => {
3275
+ const data = await executeGraphQL(
3276
+ graphqlUrl,
3277
+ START_GUEST_CHECKOUT,
3278
+ {
3279
+ websiteId: ctx.websiteId,
3280
+ cartToken,
3281
+ contact: input.contact,
3282
+ fulfillmentFamily: input.fulfillmentFamily,
3283
+ items: input.items.map((item) => ({
3284
+ product_variant_id: item.productVariantId,
3285
+ quantity: item.quantity
3286
+ })),
3287
+ marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
3288
+ },
3289
+ getHeaders
3290
+ );
3291
+ return data.websiteStartGuestCheckout;
3292
+ };
2362
3293
  const existing = cartSessionStorage.get();
2363
- const data = await executeGraphQL(
2364
- graphqlUrl,
2365
- START_GUEST_CHECKOUT,
2366
- {
2367
- websiteId: ctx.websiteId,
2368
- cartToken: existing?.cartToken,
2369
- contact: input.contact,
2370
- fulfillmentFamily: input.fulfillmentFamily,
2371
- items: input.items.map((item) => ({
2372
- product_variant_id: item.productVariantId,
2373
- quantity: item.quantity
2374
- }))
2375
- },
2376
- getHeaders
2377
- );
2378
- const result = data.websiteStartGuestCheckout;
3294
+ let result = await requestStartCheckout(existing?.cartToken);
3295
+ if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
3296
+ cartSessionStorage.clear();
3297
+ result = await requestStartCheckout();
3298
+ }
2379
3299
  assertActionSuccess(result, "CHECKOUT_START_ERROR");
2380
3300
  cartSessionStorage.set({
2381
3301
  orderId: result.order_id,
@@ -2398,7 +3318,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2398
3318
  const session = cartSessionStorage.get();
2399
3319
  if (!session) {
2400
3320
  throw new WebsiteError(
2401
- "Start checkout with contact before syncing cart items to Bacano",
3321
+ "Create or recover a Bacano cart session before syncing cart items",
2402
3322
  "CART_SESSION_MISSING"
2403
3323
  );
2404
3324
  }
@@ -2466,11 +3386,37 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2466
3386
  );
2467
3387
  const result = data.websiteRemoveCartItem;
2468
3388
  assertActionSuccess(result, "CART_ITEM_ERROR");
3389
+ const cart = mapCart(result.cart, result.cart_token);
3390
+ if (cart.items.length === 0) {
3391
+ cartSessionStorage.clear();
3392
+ return cart;
3393
+ }
2469
3394
  cartSessionStorage.set({
2470
3395
  orderId: result.order_id,
2471
3396
  cartToken: result.cart_token
2472
3397
  });
2473
- return mapCart(result.cart, result.cart_token);
3398
+ return cart;
3399
+ },
3400
+ async clear() {
3401
+ const session = cartSessionStorage.get();
3402
+ if (!session) return;
3403
+ const data = await executeGraphQL(
3404
+ graphqlUrl,
3405
+ DISCARD_CART,
3406
+ {
3407
+ orderId: session.orderId,
3408
+ cartToken: session.cartToken
3409
+ },
3410
+ getHeaders
3411
+ );
3412
+ const result = data.websiteDiscardCart;
3413
+ if (!result.success && !hasRecoverableCartSessionError(result.errors)) {
3414
+ throw new WebsiteError(
3415
+ result.errors?.join("; ") || "Cart could not be discarded",
3416
+ getStableCartActionErrorCode(result.errors, "CART_DISCARD_ERROR")
3417
+ );
3418
+ }
3419
+ cartSessionStorage.clear();
2474
3420
  }
2475
3421
  };
2476
3422
  }
@@ -2493,7 +3439,9 @@ function createCartSessionStorage(kind, getSlug) {
2493
3439
  const key = readKey();
2494
3440
  const match = document.cookie.split("; ").find((row) => row.startsWith(`${key}=`));
2495
3441
  if (!match) return null;
2496
- return JSON.parse(decodeURIComponent(match.split("=").slice(1).join("=")));
3442
+ return JSON.parse(
3443
+ decodeURIComponent(match.split("=").slice(1).join("="))
3444
+ );
2497
3445
  } catch {
2498
3446
  return null;
2499
3447
  }
@@ -2533,46 +3481,129 @@ function createCartSessionStorage(kind, getSlug) {
2533
3481
  };
2534
3482
  }
2535
3483
 
2536
- // libs/sdk/src/media.ts
2537
- var MEDIA_VARIANTS = {
2538
- thumbnail: { width: 240, quality: 75, format: "webp" },
2539
- card: { width: 640, quality: 80, format: "webp" },
2540
- detail: { width: 1400, quality: 85, format: "webp" }
2541
- };
2542
- function toSafeInteger(value) {
2543
- if (!Number.isFinite(value)) return void 0;
2544
- return Math.max(1, Math.floor(value));
2545
- }
2546
- function buildQuery(options) {
2547
- const params = new URLSearchParams();
2548
- const width = toSafeInteger(options.width);
2549
- const height = toSafeInteger(options.height);
2550
- const quality = toSafeInteger(options.quality);
2551
- if (width) params.set("w", String(width));
2552
- if (height) params.set("h", String(height));
2553
- if (quality) params.set("q", String(Math.min(100, quality)));
2554
- if (options.format) params.set("f", options.format);
2555
- const query = params.toString();
2556
- return query ? `?${query}` : "";
2557
- }
2558
- function buildStorageFileUrl(storageUrl, fileId, variant = "original") {
2559
- if (!storageUrl || !fileId) return "";
2560
- const baseUrl = `${storageUrl.replace(/\/+$/, "")}/files/${fileId}`;
2561
- if (variant === "original") return baseUrl;
2562
- return `${baseUrl}${buildQuery(MEDIA_VARIANTS[variant])}`;
2563
- }
2564
- function buildStorageMediaUrls(storageUrl, fileId) {
2565
- return {
2566
- originalUrl: buildStorageFileUrl(storageUrl, fileId, "original"),
2567
- thumbnailUrl: buildStorageFileUrl(storageUrl, fileId, "thumbnail"),
2568
- cardUrl: buildStorageFileUrl(storageUrl, fileId, "card"),
2569
- detailUrl: buildStorageFileUrl(storageUrl, fileId, "detail")
3484
+ // libs/sdk/src/modules/static-catalog.ts
3485
+ var STATIC_CATALOG_PAGE_SIZE = 100;
3486
+ var STATIC_CATALOG_MAX_PRODUCTS = 1e4;
3487
+ var STATIC_CATALOG_INITIAL_PAGE_SIZE = 24;
3488
+ async function collectStaticCatalog({
3489
+ websiteId,
3490
+ snapshot: options,
3491
+ bootstrap,
3492
+ loadPage
3493
+ }) {
3494
+ const pageSize = options.pageSize ?? STATIC_CATALOG_PAGE_SIZE;
3495
+ const maxProducts = options.maxProducts ?? STATIC_CATALOG_MAX_PRODUCTS;
3496
+ const initialPageSize = bootstrap?.initialPageSize ?? STATIC_CATALOG_INITIAL_PAGE_SIZE;
3497
+ const sortBy = bootstrap?.sortBy ?? "name_asc";
3498
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
3499
+ throw new WebsiteError(
3500
+ "Static catalog pageSize must be an integer between 1 and 100.",
3501
+ "INVALID_STATIC_CATALOG_PAGE_SIZE"
3502
+ );
3503
+ }
3504
+ if (!Number.isInteger(maxProducts) || maxProducts < 1 || maxProducts > STATIC_CATALOG_MAX_PRODUCTS) {
3505
+ throw new WebsiteError(
3506
+ `Static catalog maxProducts must be an integer between 1 and ${STATIC_CATALOG_MAX_PRODUCTS}.`,
3507
+ "INVALID_STATIC_CATALOG_MAX_PRODUCTS"
3508
+ );
3509
+ }
3510
+ if (bootstrap && (!Number.isInteger(initialPageSize) || initialPageSize < 1 || initialPageSize > 100)) {
3511
+ throw new WebsiteError(
3512
+ "Static catalog initialPageSize must be an integer between 1 and 100.",
3513
+ "INVALID_STATIC_CATALOG_INITIAL_PAGE_SIZE"
3514
+ );
3515
+ }
3516
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
3517
+ const products = [];
3518
+ let pageCount = 0;
3519
+ let expectedTotal = null;
3520
+ let firstPage = null;
3521
+ for (let offset = 0; ; ) {
3522
+ const includeBootstrapData = Boolean(bootstrap && offset === 0);
3523
+ const requestLimit = includeBootstrapData ? Math.max(pageSize, initialPageSize) : pageSize;
3524
+ const page = await loadPage({
3525
+ categoryListKey: options.categoryListKey,
3526
+ attributeListKey: options.attributeListKey,
3527
+ sortBy,
3528
+ limit: requestLimit,
3529
+ offset,
3530
+ include: {
3531
+ products: true,
3532
+ detailVariants: true,
3533
+ categories: includeBootstrapData,
3534
+ attributes: true,
3535
+ filters: includeBootstrapData,
3536
+ brands: includeBootstrapData,
3537
+ debug: false
3538
+ }
3539
+ });
3540
+ if (offset === 0) firstPage = page;
3541
+ pageCount += 1;
3542
+ const pageTotal = page.pagination.total;
3543
+ if (expectedTotal === null) {
3544
+ expectedTotal = pageTotal;
3545
+ } else if (pageTotal !== expectedTotal) {
3546
+ throwCatalogChanged();
3547
+ }
3548
+ if (pageTotal > maxProducts) {
3549
+ throw new WebsiteError(
3550
+ `Static catalog contains ${pageTotal} products and exceeds maxProducts ${maxProducts}.`,
3551
+ "STATIC_CATALOG_LIMIT_EXCEEDED"
3552
+ );
3553
+ }
3554
+ products.push(...page.products);
3555
+ if (!page.pagination.hasMore) break;
3556
+ if (page.products.length === 0) {
3557
+ throw new WebsiteError(
3558
+ "Static catalog pagination did not advance.",
3559
+ "STATIC_CATALOG_PAGINATION_STALLED"
3560
+ );
3561
+ }
3562
+ offset += page.products.length;
3563
+ }
3564
+ const total = expectedTotal ?? 0;
3565
+ const uniqueProductIds = new Set(products.map((product) => product.id));
3566
+ if (products.length !== total || uniqueProductIds.size !== total) {
3567
+ throwCatalogChanged();
3568
+ }
3569
+ const snapshot = {
3570
+ schemaVersion: 1,
3571
+ snapshotId: `${websiteId}:${generatedAt}`,
3572
+ generatedAt,
3573
+ websiteId,
3574
+ products,
3575
+ total,
3576
+ pageSize,
3577
+ pageCount
2570
3578
  };
3579
+ const initialPage = bootstrap && firstPage ? {
3580
+ ...firstPage,
3581
+ products: firstPage.products.slice(0, initialPageSize),
3582
+ pagination: {
3583
+ ...firstPage.pagination,
3584
+ limit: initialPageSize,
3585
+ offset: 0,
3586
+ hasMore: total > initialPageSize
3587
+ }
3588
+ } : null;
3589
+ return { snapshot, initialPage };
3590
+ }
3591
+ function throwCatalogChanged() {
3592
+ throw new WebsiteError(
3593
+ "Static catalog changed while it was being collected. Retry the build to get a consistent snapshot.",
3594
+ "STATIC_CATALOG_CHANGED_DURING_BUILD"
3595
+ );
2571
3596
  }
2572
3597
 
2573
3598
  // libs/sdk/src/modules/catalog.ts
2574
3599
  var MAX_AVAILABILITY_VARIANT_IDS2 = 100;
2575
3600
  var DEFAULT_FILTER_ATTRIBUTE_LIST_KEY = "atributos";
3601
+ var MAX_LIVE_STATE_VARIANT_IDS = 100;
3602
+ var STATIC_CATALOG_RETRY = {
3603
+ maxAttempts: 4,
3604
+ maxTotalWaitMs: 3 * 60 * 1e3,
3605
+ jitterRatio: 0.1
3606
+ };
2576
3607
  function mapPrice(raw) {
2577
3608
  return {
2578
3609
  id: raw.id,
@@ -2605,6 +3636,9 @@ function getProductImages(media) {
2605
3636
  function getProductVideos(media) {
2606
3637
  return getProductMediaByKind(media, "video");
2607
3638
  }
3639
+ function getProductDetailVariants(product) {
3640
+ return product.detailVariants ?? product.variants;
3641
+ }
2608
3642
  function getPrimaryProductImage(media) {
2609
3643
  return getProductImages(media)[0] ?? null;
2610
3644
  }
@@ -2754,7 +3788,6 @@ function mapRelatedProduct(raw, storageUrl) {
2754
3788
  slug: raw.related_product.slug,
2755
3789
  name: raw.related_product.name,
2756
3790
  sku: raw.related_product.sku,
2757
- displayLabel: raw.display_label,
2758
3791
  sortOrder: raw.sort_order ?? 0,
2759
3792
  media: raw.related_product.product_media.map(
2760
3793
  (m) => mapMedia(m, storageUrl)
@@ -2762,6 +3795,26 @@ function mapRelatedProduct(raw, storageUrl) {
2762
3795
  };
2763
3796
  }
2764
3797
  function mapProduct(raw, storageUrl, labels, chainId) {
3798
+ const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
3799
+ const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
3800
+ const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
3801
+ const rawOptionGroup = raw.product_option_group_members?.[0]?.group;
3802
+ const optionGroup = rawOptionGroup && rawOptionGroup.members.length >= 2 ? {
3803
+ id: rawOptionGroup.id,
3804
+ title: rawOptionGroup.display_title,
3805
+ members: rawOptionGroup.members.map((member) => ({
3806
+ id: member.id,
3807
+ productId: member.product_id,
3808
+ slug: member.product.slug,
3809
+ name: member.product.name,
3810
+ sku: member.product.sku,
3811
+ displayLabel: member.display_label,
3812
+ sortOrder: member.sort_order ?? 0,
3813
+ media: member.product.product_media.map(
3814
+ (media) => mapMedia(media, storageUrl)
3815
+ )
3816
+ }))
3817
+ } : null;
2765
3818
  return {
2766
3819
  id: raw.id,
2767
3820
  name: raw.name,
@@ -2778,16 +3831,17 @@ function mapProduct(raw, storageUrl, labels, chainId) {
2778
3831
  measurementUnit: raw.measurement_unit,
2779
3832
  createdAt: raw.created_at,
2780
3833
  updatedAt: raw.updated_at ?? raw.created_at,
2781
- variants: raw.product_variants.map(
2782
- (variant) => mapVariant(variant, labels, chainId)
2783
- ),
3834
+ variants,
3835
+ detailVariants,
2784
3836
  media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
2785
3837
  attributes: raw.product_attributes.map(
2786
3838
  (attribute) => mapProductAttribute(attribute, labels)
2787
3839
  ),
2788
3840
  relatedProducts: (raw.product_related_products ?? []).map(
2789
3841
  (relation) => mapRelatedProduct(relation, storageUrl)
2790
- )
3842
+ ),
3843
+ optionGroup,
3844
+ specification: raw.product_specification ?? null
2791
3845
  };
2792
3846
  }
2793
3847
  function mapCategory(raw) {
@@ -3118,7 +4172,7 @@ function hasMeaningfulCategoryListFilters(filters) {
3118
4172
  )
3119
4173
  );
3120
4174
  }
3121
- async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId) {
4175
+ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId, websiteId) {
3122
4176
  const uniqueVariantIds = uniqueValues2(variantIds);
3123
4177
  const availabilityByVariantId = /* @__PURE__ */ new Map();
3124
4178
  for (const chunk of chunkArray2(
@@ -3128,7 +4182,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3128
4182
  const data = await executeGraphQL(
3129
4183
  graphqlUrl,
3130
4184
  CHECK_AVAILABILITY,
3131
- { variantIds: chunk, chainId },
4185
+ { variantIds: chunk, chainId, websiteId },
3132
4186
  getHeaders
3133
4187
  );
3134
4188
  for (const item of data.websiteProductAvailability ?? []) {
@@ -3139,7 +4193,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3139
4193
  }
3140
4194
  return availabilityByVariantId;
3141
4195
  }
3142
- async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4196
+ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3143
4197
  const variantIds = products.flatMap(
3144
4198
  (product) => product.variants.map((variant) => variant.id)
3145
4199
  );
@@ -3150,7 +4204,8 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3150
4204
  graphqlUrl,
3151
4205
  getHeaders,
3152
4206
  variantIds,
3153
- chainId
4207
+ chainId,
4208
+ websiteId
3154
4209
  )).entries()
3155
4210
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3156
4211
  );
@@ -3168,7 +4223,7 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3168
4223
  )
3169
4224
  })).filter((product) => product.variants.length > 0);
3170
4225
  }
3171
- async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4226
+ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3172
4227
  const variantIds = products.flatMap(
3173
4228
  (product) => product.product_variants.map((variant) => variant.id)
3174
4229
  );
@@ -3179,7 +4234,8 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
3179
4234
  graphqlUrl,
3180
4235
  getHeaders,
3181
4236
  variantIds,
3182
- chainId
4237
+ chainId,
4238
+ websiteId
3183
4239
  )).entries()
3184
4240
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3185
4241
  );
@@ -3210,6 +4266,32 @@ function mapProductSeoEntry(raw, storageUrl) {
3210
4266
  };
3211
4267
  }
3212
4268
  function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4269
+ const loadProductSpecifications = async (productIds) => {
4270
+ const ctx = getContext();
4271
+ const uniqueProductIds = [...new Set(productIds)];
4272
+ const specifications = {};
4273
+ for (let offset = 0; offset < uniqueProductIds.length; offset += 100) {
4274
+ const batch = uniqueProductIds.slice(offset, offset + 100);
4275
+ try {
4276
+ const data = await executeGraphQL(
4277
+ graphqlUrl,
4278
+ WEBSITE_GET_PRODUCT_SPECIFICATIONS,
4279
+ { websiteId: ctx.websiteId, productIds: batch },
4280
+ getHeaders
4281
+ );
4282
+ if (!data.result?.success) continue;
4283
+ Object.assign(
4284
+ specifications,
4285
+ parseActionJson(
4286
+ data.result.specifications,
4287
+ {}
4288
+ )
4289
+ );
4290
+ } catch {
4291
+ }
4292
+ }
4293
+ return specifications;
4294
+ };
3213
4295
  const mapSingleProductResult = async (data) => {
3214
4296
  const ctx = getContext();
3215
4297
  if (!ctx.chainId) {
@@ -3223,15 +4305,187 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3223
4305
  const publicAttributeLabels = buildPublicAttributeLabels(
3224
4306
  data.catalog_attribute_lists
3225
4307
  );
4308
+ const mappedProduct = mapProduct(
4309
+ product,
4310
+ storageUrl,
4311
+ publicAttributeLabels,
4312
+ ctx.chainId
4313
+ );
3226
4314
  const filteredProducts = await filterProductsForWebsiteStock(
3227
4315
  graphqlUrl,
3228
4316
  getHeaders,
3229
- [mapProduct(product, storageUrl, publicAttributeLabels, ctx.chainId)],
3230
- ctx.chainId
4317
+ [mappedProduct],
4318
+ ctx.chainId,
4319
+ ctx.websiteId
3231
4320
  );
3232
- return filteredProducts[0] ?? null;
4321
+ if (!filteredProducts[0]) return null;
4322
+ const specifications = await loadProductSpecifications([product.id]);
4323
+ return {
4324
+ ...mappedProduct,
4325
+ variants: getProductDetailVariants(mappedProduct),
4326
+ specification: specifications[product.id] ?? null
4327
+ };
4328
+ };
4329
+ const loadCatalogPage = async (opts, retryRateLimits = false) => {
4330
+ const ctx = getContext();
4331
+ if (!ctx.chainId) {
4332
+ throw new WebsiteError(
4333
+ "This website has no chain configured. Catalog requires a chain_id.",
4334
+ "NO_CHAIN"
4335
+ );
4336
+ }
4337
+ const chainId = ctx.chainId;
4338
+ const filters = {
4339
+ category_slug: opts.categorySlug,
4340
+ subcategory_slug: opts.subcategorySlug,
4341
+ tags: opts.tags,
4342
+ search: opts.search,
4343
+ brand_id: opts.brandId,
4344
+ min_price: opts.minPrice,
4345
+ max_price: opts.maxPrice,
4346
+ attributes_by_slug: opts.attributesBySlug,
4347
+ technical_by_field: opts.technicalByField ? Object.fromEntries(
4348
+ Object.entries(opts.technicalByField).map(([fieldId, filter]) => [
4349
+ fieldId,
4350
+ {
4351
+ option_ids: filter.optionIds,
4352
+ boolean_values: filter.booleanValues,
4353
+ min: filter.min,
4354
+ max: filter.max,
4355
+ date_from: filter.dateFrom,
4356
+ date_to: filter.dateTo
4357
+ }
4358
+ ])
4359
+ ) : void 0,
4360
+ sort_by: opts.sortBy,
4361
+ limit: opts.limit,
4362
+ offset: opts.offset
4363
+ };
4364
+ const data = await executeGraphQL(
4365
+ graphqlUrl,
4366
+ WEBSITE_GET_CATALOG_PAGE,
4367
+ {
4368
+ websiteId: ctx.websiteId,
4369
+ categoryListKey: opts.categoryListKey,
4370
+ attributeListKey: opts.attributeListKey,
4371
+ filters,
4372
+ include: opts.include
4373
+ },
4374
+ getHeaders,
4375
+ retryRateLimits ? { retry: STATIC_CATALOG_RETRY } : void 0
4376
+ );
4377
+ if (!data.result.success) {
4378
+ throw new WebsiteError(
4379
+ data.result.errors.join("; ") || "Unable to resolve catalog page",
4380
+ "CATALOG_PAGE_ERROR"
4381
+ );
4382
+ }
4383
+ const products = parseActionJson(data.result.products, []);
4384
+ const categoryList = parseActionJson(
4385
+ data.result.category_list,
4386
+ null
4387
+ );
4388
+ const attributeList = parseActionJson(
4389
+ data.result.attribute_list,
4390
+ null
4391
+ );
4392
+ const filtersResult = parseActionJson(
4393
+ data.result.filters,
4394
+ null
4395
+ );
4396
+ const brands = parseActionJson(data.result.brands, []);
4397
+ const pagination = parseActionJson(
4398
+ data.result.pagination,
4399
+ {
4400
+ limit: opts.limit ?? 24,
4401
+ offset: opts.offset ?? 0,
4402
+ total: 0,
4403
+ hasMore: false
4404
+ }
4405
+ );
4406
+ const appliedFilters = parseActionJson(
4407
+ data.result.applied_filters,
4408
+ {}
4409
+ );
4410
+ const debug = parseActionJson(
4411
+ data.result.debug,
4412
+ null
4413
+ );
4414
+ const labels = buildPublicAttributeLabels(
4415
+ attributeList ? [attributeList] : []
4416
+ );
4417
+ return {
4418
+ products: products.map(
4419
+ (product) => mapProduct(product, storageUrl, labels, chainId)
4420
+ ),
4421
+ categories: categoryList ? mapCategoryList(categoryList) : null,
4422
+ attributes: attributeList ? mapAttributeList(attributeList) : null,
4423
+ filters: filtersResult,
4424
+ brands,
4425
+ pagination,
4426
+ appliedFilters,
4427
+ debug
4428
+ };
3233
4429
  };
3234
4430
  return {
4431
+ async getProductLiveState(productVariantIds) {
4432
+ const ctx = getContext();
4433
+ if (!ctx.chainId) {
4434
+ throw new WebsiteError(
4435
+ "This website has no chain configured. Product live state requires a chain_id.",
4436
+ "NO_CHAIN"
4437
+ );
4438
+ }
4439
+ const chainId = ctx.chainId;
4440
+ const variantIds = [...new Set(productVariantIds)];
4441
+ if (variantIds.length === 0) return [];
4442
+ if (variantIds.length > MAX_LIVE_STATE_VARIANT_IDS) {
4443
+ throw new WebsiteError(
4444
+ `Product live state accepts at most ${MAX_LIVE_STATE_VARIANT_IDS} variants per request.`,
4445
+ "PRODUCT_LIVE_STATE_LIMIT_EXCEEDED"
4446
+ );
4447
+ }
4448
+ const data = await executeGraphQL(
4449
+ graphqlUrl,
4450
+ GET_PRODUCT_LIVE_STATE,
4451
+ {
4452
+ variantIds,
4453
+ companyId: ctx.companyId,
4454
+ chainId,
4455
+ websiteId: ctx.websiteId
4456
+ },
4457
+ getHeaders
4458
+ );
4459
+ const variantsById = new Map(
4460
+ (data.product_variants ?? []).map((variant) => [variant.id, variant])
4461
+ );
4462
+ const availabilityById = new Map(
4463
+ (data.websiteProductAvailability ?? []).map((availability) => [
4464
+ availability.product_variant_id,
4465
+ availability
4466
+ ])
4467
+ );
4468
+ return variantIds.flatMap((variantId) => {
4469
+ const variant = variantsById.get(variantId);
4470
+ if (!variant) return [];
4471
+ const availability = availabilityById.get(variantId);
4472
+ const pricing = resolveProductPricing(
4473
+ selectPublicPrices(
4474
+ variant.product_variant_prices.map(mapPrice),
4475
+ chainId
4476
+ )
4477
+ );
4478
+ return [
4479
+ {
4480
+ productVariantId: variantId,
4481
+ inStock: availability?.in_stock ?? false,
4482
+ availabilityLabel: availability?.label ?? "Agotado",
4483
+ maxPurchasableQuantity: availability?.max_purchasable_quantity ?? 0,
4484
+ pricing
4485
+ }
4486
+ ];
4487
+ });
4488
+ },
3235
4489
  async getProductRecommendations(opts) {
3236
4490
  const ctx = getContext();
3237
4491
  const limit = opts.limit ?? 16;
@@ -3294,7 +4548,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3294
4548
  graphqlUrl,
3295
4549
  getHeaders,
3296
4550
  data.products ?? [],
3297
- ctx.chainId
4551
+ ctx.chainId,
4552
+ ctx.websiteId
3298
4553
  );
3299
4554
  const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
3300
4555
  return {
@@ -3306,86 +4561,36 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3306
4561
  }
3307
4562
  };
3308
4563
  },
3309
- async getCatalogPage(opts) {
4564
+ getCatalogPage: loadCatalogPage,
4565
+ async getStaticCatalogSnapshot(opts) {
3310
4566
  const ctx = getContext();
3311
- if (!ctx.chainId) {
3312
- throw new WebsiteError(
3313
- "This website has no chain configured. Catalog requires a chain_id.",
3314
- "NO_CHAIN"
3315
- );
3316
- }
3317
- const chainId = ctx.chainId;
3318
- const filters = {
3319
- category_slug: opts.categorySlug,
3320
- subcategory_slug: opts.subcategorySlug,
3321
- tags: opts.tags,
3322
- search: opts.search,
3323
- brand_id: opts.brandId,
3324
- min_price: opts.minPrice,
3325
- max_price: opts.maxPrice,
3326
- attributes_by_slug: opts.attributesBySlug,
3327
- sort_by: opts.sortBy,
3328
- limit: opts.limit,
3329
- offset: opts.offset
3330
- };
3331
- const data = await executeGraphQL(
3332
- graphqlUrl,
3333
- WEBSITE_GET_CATALOG_PAGE,
3334
- {
3335
- websiteId: ctx.websiteId,
3336
- categoryListKey: opts.categoryListKey,
3337
- attributeListKey: opts.attributeListKey,
3338
- filters,
3339
- include: opts.include
4567
+ return (await collectStaticCatalog({
4568
+ websiteId: ctx.websiteId,
4569
+ snapshot: opts,
4570
+ loadPage: (options) => loadCatalogPage(options, true)
4571
+ })).snapshot;
4572
+ },
4573
+ async getStaticCatalogBootstrap(opts) {
4574
+ const ctx = getContext();
4575
+ const { snapshot, initialPage } = await collectStaticCatalog({
4576
+ websiteId: ctx.websiteId,
4577
+ snapshot: opts,
4578
+ bootstrap: {
4579
+ initialPageSize: opts.initialPageSize,
4580
+ sortBy: opts.sortBy
3340
4581
  },
3341
- getHeaders
3342
- );
3343
- if (!data.result.success) {
4582
+ loadPage: (options) => loadCatalogPage(options, true)
4583
+ });
4584
+ if (!initialPage) {
3344
4585
  throw new WebsiteError(
3345
- data.result.errors.join("; ") || "Unable to resolve catalog page",
3346
- "CATALOG_PAGE_ERROR"
4586
+ "Static catalog bootstrap could not resolve its initial page.",
4587
+ "STATIC_CATALOG_BOOTSTRAP_MISSING_INITIAL_PAGE"
3347
4588
  );
3348
4589
  }
3349
- const products = parseActionJson(data.result.products, []);
3350
- const categoryList = parseActionJson(
3351
- data.result.category_list,
3352
- null
3353
- );
3354
- const attributeList = parseActionJson(
3355
- data.result.attribute_list,
3356
- null
3357
- );
3358
- const filtersResult = parseActionJson(
3359
- data.result.filters,
3360
- null
3361
- );
3362
- const brands = parseActionJson(data.result.brands, []);
3363
- const pagination = parseActionJson(
3364
- data.result.pagination,
3365
- { limit: opts.limit ?? 24, offset: opts.offset ?? 0, total: 0, hasMore: false }
3366
- );
3367
- const appliedFilters = parseActionJson(
3368
- data.result.applied_filters,
3369
- {}
3370
- );
3371
- const debug = parseActionJson(
3372
- data.result.debug,
3373
- null
3374
- );
3375
- const labels = buildPublicAttributeLabels(
3376
- attributeList ? [attributeList] : []
3377
- );
3378
4590
  return {
3379
- products: products.map(
3380
- (product) => mapProduct(product, storageUrl, labels, chainId)
3381
- ),
3382
- categories: categoryList ? mapCategoryList(categoryList) : null,
3383
- attributes: attributeList ? mapAttributeList(attributeList) : null,
3384
- filters: filtersResult,
3385
- brands,
3386
- pagination,
3387
- appliedFilters,
3388
- debug
4591
+ ...snapshot,
4592
+ bootstrapSchemaVersion: 1,
4593
+ initialPage
3389
4594
  };
3390
4595
  },
3391
4596
  async getProducts(opts) {
@@ -3420,8 +4625,16 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3420
4625
  (data.products ?? []).map(
3421
4626
  (p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
3422
4627
  ),
3423
- chainId
4628
+ chainId,
4629
+ ctx.websiteId
3424
4630
  );
4631
+ const specifications = await loadProductSpecifications(
4632
+ products.map((product) => product.id)
4633
+ );
4634
+ products = products.map((product) => ({
4635
+ ...product,
4636
+ specification: specifications[product.id] ?? null
4637
+ }));
3425
4638
  if (opts?.minPrice !== void 0 || opts?.maxPrice !== void 0) {
3426
4639
  products = products.filter(
3427
4640
  (product) => product.variants.some((variant) => {
@@ -3532,7 +4745,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3532
4745
  graphqlUrl,
3533
4746
  getHeaders,
3534
4747
  variantIds,
3535
- chainId
4748
+ chainId,
4749
+ ctx.websiteId
3536
4750
  );
3537
4751
  const applicableCategories = /* @__PURE__ */ new Set();
3538
4752
  const applicableSubcategoryIds = /* @__PURE__ */ new Set();
@@ -3650,7 +4864,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3650
4864
  graphqlUrl,
3651
4865
  getHeaders,
3652
4866
  variantIds,
3653
- chainId
4867
+ chainId,
4868
+ ctx.websiteId
3654
4869
  );
3655
4870
  const publicAttributeLabels = buildPublicAttributeLabels(
3656
4871
  data.catalog_attribute_lists
@@ -3755,7 +4970,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3755
4970
  min: minPrice,
3756
4971
  max: maxPrice
3757
4972
  },
3758
- attributes
4973
+ attributes,
4974
+ technical: []
3759
4975
  };
3760
4976
  },
3761
4977
  async getSearchSuggestions(opts) {
@@ -3809,6 +5025,14 @@ function mapCartItem2(raw) {
3809
5025
  id: raw.id,
3810
5026
  productVariantId: raw.product_variant_id,
3811
5027
  quantity: raw.quantity,
5028
+ unitPrice: raw.unit_price ?? null,
5029
+ lineTotal: raw.line_total ?? null,
5030
+ baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
5031
+ unitSavings: raw.unit_savings ?? 0,
5032
+ lineSavings: raw.line_savings ?? 0,
5033
+ volumePricingEligible: raw.volume_pricing_eligible ?? false,
5034
+ resolvedPriceType: raw.resolved_price_type ?? null,
5035
+ volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
3812
5036
  productVariant: {
3813
5037
  sku: raw.product_variant.sku,
3814
5038
  product: raw.product_variant.product,
@@ -3833,7 +5057,20 @@ function mapCart2(raw) {
3833
5057
  },
3834
5058
  status: raw.status,
3835
5059
  createdAt: raw.created_at,
3836
- updatedAt: raw.updated_at
5060
+ updatedAt: raw.updated_at,
5061
+ volumePricing: raw.volume_pricing ? {
5062
+ ...raw.volume_pricing,
5063
+ totalSavings: raw.volume_pricing.totalSavings ?? 0
5064
+ } : {
5065
+ enabled: false,
5066
+ eligibleQuantity: 0,
5067
+ appliedPriceType: null,
5068
+ appliedMinQuantity: null,
5069
+ nextPriceType: null,
5070
+ nextMinQuantity: null,
5071
+ remainingQuantity: null,
5072
+ totalSavings: 0
5073
+ }
3837
5074
  };
3838
5075
  }
3839
5076
  function mapCouponResult(raw) {
@@ -3852,6 +5089,7 @@ function mapCouponResult(raw) {
3852
5089
  };
3853
5090
  }
3854
5091
  function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
5092
+ let shippingPolicyCache = null;
3855
5093
  return {
3856
5094
  async getLocationOptions() {
3857
5095
  const context = getContext();
@@ -3949,22 +5187,39 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
3949
5187
  }));
3950
5188
  },
3951
5189
  async getShippingPolicy() {
5190
+ const now = Date.now();
5191
+ if (shippingPolicyCache && shippingPolicyCache.expiresAt > now) {
5192
+ return shippingPolicyCache.request;
5193
+ }
3952
5194
  const context = getContext();
3953
- const data = await executeGraphQL(
5195
+ const request = executeGraphQL(
3954
5196
  graphqlUrl,
3955
5197
  GET_SHIPPING_POLICY,
3956
5198
  { websiteId: context.websiteId },
3957
5199
  getHeaders
3958
- );
3959
- const result = data.websiteGetShippingPolicy;
3960
- if (!result.success || !result.policy) {
3961
- throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
3962
- }
3963
- return {
3964
- freeShippingEnabled: result.policy.free_shipping_enabled,
3965
- freeShippingThreshold: result.policy.free_shipping_threshold,
3966
- currency: result.policy.currency
5200
+ ).then((data) => {
5201
+ const result = data.websiteGetShippingPolicy;
5202
+ if (!result.success || !result.policy) {
5203
+ throw new CheckoutError(result.errors ?? ["SHIPPING_POLICY_FAILED"]);
5204
+ }
5205
+ return {
5206
+ freeShippingEnabled: result.policy.free_shipping_enabled,
5207
+ freeShippingThreshold: result.policy.free_shipping_threshold,
5208
+ currency: result.policy.currency
5209
+ };
5210
+ });
5211
+ shippingPolicyCache = {
5212
+ expiresAt: now + 5 * 60 * 1e3,
5213
+ request
3967
5214
  };
5215
+ try {
5216
+ return await request;
5217
+ } catch (error) {
5218
+ if (shippingPolicyCache?.request === request) {
5219
+ shippingPolicyCache = null;
5220
+ }
5221
+ throw error;
5222
+ }
3968
5223
  },
3969
5224
  async getFreeShippingProgress(input) {
3970
5225
  if (input.items.length === 0) {
@@ -4130,6 +5385,13 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
4130
5385
  initialPayment: input.initialPayment ? {
4131
5386
  amount: input.initialPayment.amount,
4132
5387
  payment_method_id: input.initialPayment.paymentMethodId
5388
+ } : void 0,
5389
+ marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0,
5390
+ advertisingConsent: input.advertisingConsent ? {
5391
+ policy_id: input.advertisingConsent.policyId,
5392
+ policy_version: input.advertisingConsent.policyVersion,
5393
+ statement_hash: input.advertisingConsent.statementHash,
5394
+ granted: input.advertisingConsent.granted
4133
5395
  } : void 0
4134
5396
  },
4135
5397
  getHeaders
@@ -4265,18 +5527,23 @@ function readOptionalText(value) {
4265
5527
  function enrichMediaValue(value, storageUrl) {
4266
5528
  const fileId = typeof value.fileId === "string" && value.fileId.trim() ? value.fileId.trim() : "";
4267
5529
  if (!fileId) return value;
4268
- const urls = buildStorageMediaUrls(storageUrl, fileId);
5530
+ const isProductMedia = typeof value.id === "string" && typeof value.order === "number";
5531
+ const isEditorialImage = value.mediaKind === "image" && !isProductMedia;
5532
+ const originalWidth = typeof value.originalWidth === "number" ? value.originalWidth : null;
5533
+ const urls = isEditorialImage ? buildStorageContentMediaUrls(storageUrl, fileId, originalWidth) : buildStorageMediaUrls(storageUrl, fileId);
4269
5534
  return {
4270
5535
  ...value,
4271
5536
  ...urls,
4272
- url: value.url ?? urls.originalUrl
5537
+ url: value.url ?? urls.originalUrl,
5538
+ src: value.src ?? value.url ?? urls.originalUrl
4273
5539
  };
4274
5540
  }
4275
5541
  function normalizeRichTextNode(value, storageUrl) {
4276
5542
  const type = value.type;
4277
5543
  const attrs = isRecord(value.attrs) ? value.attrs : null;
4278
5544
  if (attrs && (type === "bacanoImage" || type === "bacanoVideo") && typeof attrs.fileId === "string") {
4279
- const urls = buildStorageMediaUrls(storageUrl, attrs.fileId);
5545
+ const originalWidth = typeof attrs.originalWidth === "number" ? attrs.originalWidth : null;
5546
+ const urls = type === "bacanoImage" ? buildStorageContentMediaUrls(storageUrl, attrs.fileId, originalWidth) : buildStorageMediaUrls(storageUrl, attrs.fileId);
4280
5547
  const altText = readOptionalText(attrs.altText) ?? readOptionalText(attrs.alt);
4281
5548
  const caption = readOptionalText(attrs.caption);
4282
5549
  const showCaption = Boolean(caption) && attrs.showCaption !== false;
@@ -4408,6 +5675,452 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4408
5675
  };
4409
5676
  }
4410
5677
 
5678
+ // libs/sdk/src/marketing/clarity.ts
5679
+ var initializedDestinations = /* @__PURE__ */ new Set();
5680
+ function clarityWindow() {
5681
+ return typeof window === "undefined" ? null : window;
5682
+ }
5683
+ function prepareClarity(projectId) {
5684
+ const target = clarityWindow();
5685
+ if (!target || initializedDestinations.has(projectId)) return;
5686
+ if (!target.clarity) {
5687
+ const clarity = function() {
5688
+ clarity.q ??= [];
5689
+ clarity.q.push(arguments);
5690
+ };
5691
+ target.clarity = clarity;
5692
+ }
5693
+ initializedDestinations.add(projectId);
5694
+ }
5695
+ function loadClarityScript(projectId) {
5696
+ if (typeof document === "undefined") return;
5697
+ const id = `bacano-clarity-${projectId}`;
5698
+ if (document.getElementById(id)) return;
5699
+ const script = document.createElement("script");
5700
+ script.id = id;
5701
+ script.async = true;
5702
+ script.src = `https://www.clarity.ms/tag/${encodeURIComponent(projectId)}`;
5703
+ script.referrerPolicy = "strict-origin-when-cross-origin";
5704
+ document.head.appendChild(script);
5705
+ }
5706
+ function sendClarityEvent(eventName) {
5707
+ clarityWindow()?.clarity?.("event", eventName);
5708
+ }
5709
+
5710
+ // libs/sdk/src/marketing/domain.ts
5711
+ function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
5712
+ if (canonicalDomain === void 0) return true;
5713
+ if (!canonicalDomain?.trim()) return false;
5714
+ try {
5715
+ const configuredHost = new URL(
5716
+ canonicalDomain.includes("://") ? canonicalDomain : `https://${canonicalDomain}`
5717
+ ).hostname.toLowerCase();
5718
+ return currentHostname.toLowerCase() === configuredHost;
5719
+ } catch {
5720
+ return false;
5721
+ }
5722
+ }
5723
+
5724
+ // libs/sdk/src/marketing/ga4.ts
5725
+ function mapGa4Items(items) {
5726
+ return items?.map((item) => ({
5727
+ item_id: item.itemId,
5728
+ item_name: item.itemName,
5729
+ item_variant: item.itemVariant,
5730
+ item_brand: item.itemBrand,
5731
+ item_category: item.itemCategory,
5732
+ price: item.price,
5733
+ quantity: item.quantity,
5734
+ index: item.index,
5735
+ sku: item.sku
5736
+ }));
5737
+ }
5738
+
5739
+ // libs/sdk/src/marketing/types.ts
5740
+ var MARKETING_SCHEMA_VERSION = 1;
5741
+ var WEBSITE_MARKETING_CONTACT_PLACEMENTS = [
5742
+ "product_detail",
5743
+ "floating",
5744
+ "header",
5745
+ "footer",
5746
+ "contact_page"
5747
+ ];
5748
+
5749
+ // libs/sdk/src/marketing/runtime.ts
5750
+ var initializedGa4Destinations = /* @__PURE__ */ new Set();
5751
+ var initializedMetaDestinations = /* @__PURE__ */ new Set();
5752
+ function marketingWindow() {
5753
+ return typeof window === "undefined" ? null : window;
5754
+ }
5755
+ function loadScriptOnce(id, source) {
5756
+ if (typeof document === "undefined" || document.getElementById(id)) return;
5757
+ const script = document.createElement("script");
5758
+ script.id = id;
5759
+ script.async = true;
5760
+ script.src = source;
5761
+ script.referrerPolicy = "strict-origin-when-cross-origin";
5762
+ document.head.appendChild(script);
5763
+ }
5764
+ function prepareGa4(measurementId) {
5765
+ const target = marketingWindow();
5766
+ if (!target) return;
5767
+ if (initializedGa4Destinations.has(measurementId)) return;
5768
+ target.dataLayer ??= [];
5769
+ target.gtag ??= function() {
5770
+ target.dataLayer?.push(arguments);
5771
+ };
5772
+ target.gtag("js", /* @__PURE__ */ new Date());
5773
+ target.gtag("config", measurementId, { send_page_view: false });
5774
+ initializedGa4Destinations.add(measurementId);
5775
+ }
5776
+ function loadGa4Script(measurementId) {
5777
+ loadScriptOnce(
5778
+ `bacano-ga4-${measurementId}`,
5779
+ `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`
5780
+ );
5781
+ }
5782
+ function captureGa4ClientId(measurementId, websiteId, diagnostic) {
5783
+ const target = marketingWindow();
5784
+ target?.gtag?.("get", measurementId, "client_id", (clientId) => {
5785
+ if (storeWebsiteMarketingGa4ClientId(websiteId, clientId)) return;
5786
+ diagnostic?.({
5787
+ level: "warning",
5788
+ code: "GA4_CLIENT_ID_UNAVAILABLE",
5789
+ message: "GA4 did not provide a valid browser client identifier"
5790
+ });
5791
+ });
5792
+ }
5793
+ function captureGa4SessionId(measurementId, websiteId, diagnostic) {
5794
+ const target = marketingWindow();
5795
+ target?.gtag?.("get", measurementId, "session_id", (sessionId) => {
5796
+ if (storeWebsiteMarketingGa4SessionId(websiteId, sessionId)) return;
5797
+ diagnostic?.({
5798
+ level: "warning",
5799
+ code: "GA4_SESSION_ID_UNAVAILABLE",
5800
+ message: "GA4 did not provide a valid browser session identifier"
5801
+ });
5802
+ });
5803
+ }
5804
+ function prepareMeta(pixelId) {
5805
+ const target = marketingWindow();
5806
+ if (!target) return;
5807
+ if (initializedMetaDestinations.has(pixelId)) return;
5808
+ if (!target.fbq) {
5809
+ const queue = [];
5810
+ const fbq = ((...args) => {
5811
+ if (fbq.callMethod) {
5812
+ fbq.callMethod(...args);
5813
+ return;
5814
+ }
5815
+ queue.push(args);
5816
+ });
5817
+ fbq.push = fbq;
5818
+ fbq.loaded = true;
5819
+ fbq.version = "2.0";
5820
+ fbq.queue = queue;
5821
+ target.fbq = fbq;
5822
+ target._fbq = fbq;
5823
+ }
5824
+ target.fbq("init", pixelId);
5825
+ initializedMetaDestinations.add(pixelId);
5826
+ }
5827
+ function loadMetaScript() {
5828
+ loadScriptOnce(
5829
+ "bacano-meta-pixel",
5830
+ "https://connect.facebook.net/en_US/fbevents.js"
5831
+ );
5832
+ }
5833
+ function scheduleScriptLoad(load, strategy) {
5834
+ const target = marketingWindow();
5835
+ if (!target || strategy !== "idle") {
5836
+ load();
5837
+ return () => void 0;
5838
+ }
5839
+ let completed = false;
5840
+ let idleHandle = null;
5841
+ let timeoutHandle = null;
5842
+ const interactionEvents = ["pointerdown", "keydown", "touchstart"];
5843
+ const cleanup = () => {
5844
+ for (const eventName of interactionEvents) {
5845
+ target.removeEventListener(eventName, finish, true);
5846
+ }
5847
+ if (idleHandle !== null) target.cancelIdleCallback?.(idleHandle);
5848
+ if (timeoutHandle !== null) target.clearTimeout(timeoutHandle);
5849
+ };
5850
+ const finish = () => {
5851
+ if (completed) return;
5852
+ completed = true;
5853
+ cleanup();
5854
+ load();
5855
+ };
5856
+ for (const eventName of interactionEvents) {
5857
+ target.addEventListener(eventName, finish, {
5858
+ capture: true,
5859
+ once: true,
5860
+ passive: true
5861
+ });
5862
+ }
5863
+ if (target.requestIdleCallback) {
5864
+ idleHandle = target.requestIdleCallback(finish, { timeout: 2e3 });
5865
+ } else {
5866
+ timeoutHandle = target.setTimeout(finish, 1500);
5867
+ }
5868
+ return cleanup;
5869
+ }
5870
+ function metaEventName(eventName) {
5871
+ const standard = {
5872
+ page_view: "PageView",
5873
+ search: "Search",
5874
+ view_item: "ViewContent",
5875
+ add_to_cart: "AddToCart",
5876
+ begin_checkout: "InitiateCheckout",
5877
+ add_payment_info: "AddPaymentInfo",
5878
+ contact: "Contact"
5879
+ };
5880
+ return standard[eventName] ?? eventName;
5881
+ }
5882
+ function isWebsiteMarketingContactPlacement(value) {
5883
+ return WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
5884
+ (candidate) => candidate === value
5885
+ );
5886
+ }
5887
+ function contactPlacementFor(event) {
5888
+ const placement = event.contactPlacement;
5889
+ if (event.eventName !== "contact" || !isWebsiteMarketingContactPlacement(placement)) {
5890
+ return void 0;
5891
+ }
5892
+ return placement;
5893
+ }
5894
+ function sendGa4(event, diagnosticMode) {
5895
+ const target = marketingWindow();
5896
+ const contactPlacement = contactPlacementFor(event);
5897
+ target?.gtag?.("event", event.eventName, {
5898
+ currency: event.currency,
5899
+ value: event.value,
5900
+ search_term: event.searchTerm,
5901
+ item_list_id: event.itemListId,
5902
+ item_list_name: event.itemListName,
5903
+ shipping_tier: event.shippingTier,
5904
+ payment_type: event.paymentType,
5905
+ method: event.contactMethod,
5906
+ ...contactPlacement ? { contact_placement: contactPlacement } : void 0,
5907
+ page_location: event.pageLocation,
5908
+ page_title: event.pageTitle,
5909
+ items: mapGa4Items(event.items),
5910
+ event_id: event.eventId,
5911
+ debug_mode: diagnosticMode ? true : void 0
5912
+ });
5913
+ }
5914
+ function sendMeta(event) {
5915
+ const target = marketingWindow();
5916
+ const eventName = metaEventName(event.eventName);
5917
+ const isStandard = eventName !== event.eventName;
5918
+ const contactPlacement = contactPlacementFor(event);
5919
+ target?.fbq?.(
5920
+ isStandard ? "track" : "trackCustom",
5921
+ eventName,
5922
+ {
5923
+ currency: event.currency,
5924
+ value: event.value,
5925
+ search_string: event.searchTerm,
5926
+ content_ids: event.items?.map((item) => item.itemId),
5927
+ contents: event.items?.map((item) => ({
5928
+ id: item.itemId,
5929
+ quantity: item.quantity ?? 1,
5930
+ item_price: item.price
5931
+ })),
5932
+ content_type: event.items?.length ? "product" : void 0,
5933
+ contact_method: event.contactMethod,
5934
+ ...contactPlacement ? { contact_placement: contactPlacement } : void 0
5935
+ },
5936
+ { eventID: event.eventId }
5937
+ );
5938
+ }
5939
+ function newEventId() {
5940
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
5941
+ }
5942
+ function createWebsiteMarketingRuntime(options) {
5943
+ let destroyed = false;
5944
+ let cancelScheduledLoad = () => void 0;
5945
+ const target = marketingWindow();
5946
+ const enabled = options.config.enabled && Boolean(
5947
+ target && matchesCanonicalMarketingHost(
5948
+ options.canonicalDomain,
5949
+ target.location.hostname
5950
+ )
5951
+ ) && Boolean(
5952
+ options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
5953
+ );
5954
+ if (enabled) {
5955
+ try {
5956
+ if (options.config.destinations.ga4) {
5957
+ prepareGa4(options.config.destinations.ga4.measurementId);
5958
+ }
5959
+ if (options.config.destinations.meta) {
5960
+ prepareMeta(options.config.destinations.meta.pixelId);
5961
+ }
5962
+ if (options.config.destinations.clarity) {
5963
+ prepareClarity(options.config.destinations.clarity.projectId);
5964
+ }
5965
+ cancelScheduledLoad = scheduleScriptLoad(() => {
5966
+ if (destroyed) return;
5967
+ if (options.config.destinations.ga4) {
5968
+ loadGa4Script(options.config.destinations.ga4.measurementId);
5969
+ }
5970
+ if (options.config.destinations.meta) loadMetaScript();
5971
+ if (options.config.destinations.clarity) {
5972
+ loadClarityScript(options.config.destinations.clarity.projectId);
5973
+ }
5974
+ }, options.scriptLoadStrategy);
5975
+ captureWebsiteMarketingAttribution(
5976
+ options.websiteId,
5977
+ options.config.attributionWindowDays
5978
+ );
5979
+ if (options.config.destinations.ga4) {
5980
+ captureGa4ClientId(
5981
+ options.config.destinations.ga4.measurementId,
5982
+ options.websiteId,
5983
+ options.diagnostic
5984
+ );
5985
+ captureGa4SessionId(
5986
+ options.config.destinations.ga4.measurementId,
5987
+ options.websiteId,
5988
+ options.diagnostic
5989
+ );
5990
+ }
5991
+ if (options.config.destinations.meta) {
5992
+ refreshWebsiteMarketingMetaContext(options.websiteId);
5993
+ }
5994
+ } catch (error) {
5995
+ options.diagnostic?.({
5996
+ level: "warning",
5997
+ code: "RUNTIME_INITIALIZATION_FAILED",
5998
+ message: error instanceof Error ? error.message : "Initialization failed"
5999
+ });
6000
+ }
6001
+ }
6002
+ return {
6003
+ enabled,
6004
+ track(input) {
6005
+ if (!enabled || destroyed) return;
6006
+ const event = {
6007
+ ...input,
6008
+ schemaVersion: 1,
6009
+ eventId: input.eventId ?? newEventId(),
6010
+ occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6011
+ currency: input.currency ?? options.config.currencyCode,
6012
+ pageLocation: sanitizeMarketingUrl(
6013
+ input.pageLocation ?? (typeof window === "undefined" ? "" : window.location.href)
6014
+ )
6015
+ };
6016
+ try {
6017
+ if (options.config.destinations.ga4) {
6018
+ sendGa4(event, options.config.destinations.ga4.diagnosticMode);
6019
+ }
6020
+ if (options.config.destinations.meta) sendMeta(event);
6021
+ if (options.config.destinations.clarity) {
6022
+ sendClarityEvent(event.eventName);
6023
+ }
6024
+ options.diagnostic?.({
6025
+ level: "debug",
6026
+ code: "EVENT_DISPATCHED",
6027
+ message: "Marketing event dispatched",
6028
+ eventName: event.eventName
6029
+ });
6030
+ } catch (error) {
6031
+ options.diagnostic?.({
6032
+ level: "warning",
6033
+ code: "EVENT_DISPATCH_FAILED",
6034
+ message: error instanceof Error ? error.message : "Dispatch failed",
6035
+ eventName: event.eventName
6036
+ });
6037
+ }
6038
+ },
6039
+ captureAttribution() {
6040
+ if (!enabled) return null;
6041
+ captureWebsiteMarketingAttribution(
6042
+ options.websiteId,
6043
+ options.config.attributionWindowDays
6044
+ );
6045
+ if (options.config.destinations.meta) {
6046
+ refreshWebsiteMarketingMetaContext(options.websiteId);
6047
+ }
6048
+ return getWebsiteMarketingAttribution(options.websiteId);
6049
+ },
6050
+ getAttribution() {
6051
+ if (!enabled) return null;
6052
+ if (options.config.destinations.meta) {
6053
+ refreshWebsiteMarketingMetaContext(options.websiteId);
6054
+ }
6055
+ return getWebsiteMarketingAttribution(options.websiteId);
6056
+ },
6057
+ destroy() {
6058
+ destroyed = true;
6059
+ cancelScheduledLoad();
6060
+ }
6061
+ };
6062
+ }
6063
+
6064
+ // libs/sdk/src/modules/marketing.ts
6065
+ function readRetryAfter(response) {
6066
+ const value = Number(response.headers.get("Retry-After") ?? "1");
6067
+ return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
6068
+ }
6069
+ function createMarketingModule(apiUrl, websiteSlug, getContext) {
6070
+ return {
6071
+ async captureLead(input) {
6072
+ const context = getContext();
6073
+ const attribution = getWebsiteMarketingAttribution(context.websiteId);
6074
+ let response;
6075
+ try {
6076
+ response = await fetch(
6077
+ `${apiUrl.replace(/\/+$/, "")}/api/v1/website/campaign-captures`,
6078
+ {
6079
+ method: "POST",
6080
+ headers: { "Content-Type": "application/json" },
6081
+ body: JSON.stringify({
6082
+ websiteSlug,
6083
+ campaignKey: input.campaignKey,
6084
+ idempotencyKey: input.idempotencyKey,
6085
+ source: input.source ?? "WEBSITE",
6086
+ commercialConsent: input.commercialConsent,
6087
+ contact: input.contact,
6088
+ answers: input.answers ?? {},
6089
+ attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null
6090
+ })
6091
+ }
6092
+ );
6093
+ } catch (error) {
6094
+ throw new WebsiteError(
6095
+ `Could not capture the campaign lead: ${error instanceof Error ? error.message : String(error)}`,
6096
+ "NETWORK_ERROR"
6097
+ );
6098
+ }
6099
+ if (response.status === 429) {
6100
+ throw new RateLimitError(
6101
+ "Rate limit exceeded. Please try again later.",
6102
+ readRetryAfter(response)
6103
+ );
6104
+ }
6105
+ if (!response.ok) {
6106
+ throw new WebsiteError(
6107
+ `Could not capture the campaign lead (status ${response.status})`,
6108
+ response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "INVALID_RESPONSE",
6109
+ { status: response.status }
6110
+ );
6111
+ }
6112
+ const result = await response.json();
6113
+ if (typeof result.accepted !== "boolean" || typeof result.message !== "string") {
6114
+ throw new WebsiteError(
6115
+ "Campaign capture returned an invalid response",
6116
+ "INVALID_RESPONSE"
6117
+ );
6118
+ }
6119
+ return { accepted: result.accepted, message: result.message };
6120
+ }
6121
+ };
6122
+ }
6123
+
4411
6124
  // libs/sdk/src/modules/orders.ts
4412
6125
  function mapOrderItem(raw) {
4413
6126
  return {
@@ -4582,13 +6295,13 @@ function createOrdersModule(graphqlUrl, getHeaders, getContext) {
4582
6295
  )) {
4583
6296
  return order;
4584
6297
  }
4585
- await wait(intervalMs);
6298
+ await wait2(intervalMs);
4586
6299
  }
4587
6300
  throw new CheckoutError(["PAYMENT_STATUS_TIMEOUT"]);
4588
6301
  }
4589
6302
  };
4590
6303
  }
4591
- function wait(ms) {
6304
+ function wait2(ms) {
4592
6305
  return new Promise((resolve) => {
4593
6306
  setTimeout(resolve, ms);
4594
6307
  });
@@ -4747,6 +6460,9 @@ function createProfileModule(graphqlUrl, getHeaders) {
4747
6460
  }
4748
6461
 
4749
6462
  // libs/sdk/src/modules/website.ts
6463
+ var WEBSITE_CONTEXT_CACHE_TTL_MS = 5 * 60 * 1e3;
6464
+ var WEBSITE_CONTEXT_CACHE_MAX_ENTRIES = 100;
6465
+ var websiteContextCache = /* @__PURE__ */ new Map();
4750
6466
  function mapWebsite(raw) {
4751
6467
  return {
4752
6468
  websiteId: raw.id,
@@ -4760,10 +6476,7 @@ function mapWebsite(raw) {
4760
6476
  }
4761
6477
  async function resolveWebsite(graphqlUrl, slug, headers) {
4762
6478
  if (!slug.trim()) {
4763
- throw new WebsiteError(
4764
- "websiteSlug is required",
4765
- "INVALID_CONFIG"
4766
- );
6479
+ throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
4767
6480
  }
4768
6481
  const data = await executeGraphQL(
4769
6482
  graphqlUrl,
@@ -4779,6 +6492,31 @@ async function resolveWebsite(graphqlUrl, slug, headers) {
4779
6492
  }
4780
6493
  return mapWebsite(data.websites[0]);
4781
6494
  }
6495
+ function resolveWebsiteCached(graphqlUrl, slug, headers) {
6496
+ const normalizedSlug = slug.trim();
6497
+ const cacheKey = `${graphqlUrl}::${normalizedSlug}`;
6498
+ const now = Date.now();
6499
+ const cached = websiteContextCache.get(cacheKey);
6500
+ if (cached && cached.expiresAt > now) return cached.request;
6501
+ if (cached) websiteContextCache.delete(cacheKey);
6502
+ const request = resolveWebsite(graphqlUrl, normalizedSlug, headers).catch(
6503
+ (error) => {
6504
+ if (websiteContextCache.get(cacheKey)?.request === request) {
6505
+ websiteContextCache.delete(cacheKey);
6506
+ }
6507
+ throw error;
6508
+ }
6509
+ );
6510
+ if (websiteContextCache.size >= WEBSITE_CONTEXT_CACHE_MAX_ENTRIES) {
6511
+ const oldestKey = websiteContextCache.keys().next().value;
6512
+ if (oldestKey) websiteContextCache.delete(oldestKey);
6513
+ }
6514
+ websiteContextCache.set(cacheKey, {
6515
+ expiresAt: now + WEBSITE_CONTEXT_CACHE_TTL_MS,
6516
+ request
6517
+ });
6518
+ return request;
6519
+ }
4782
6520
 
4783
6521
  // libs/sdk/src/client.ts
4784
6522
  function normalizeApiUrl(apiUrl) {
@@ -4789,10 +6527,17 @@ function createBacanoClient(config) {
4789
6527
  throw new WebsiteError("apiUrl is required", "INVALID_CONFIG");
4790
6528
  if (!config.websiteSlug?.trim())
4791
6529
  throw new WebsiteError("websiteSlug is required", "INVALID_CONFIG");
6530
+ if (config.initialContext && config.initialContext.slug.trim() !== config.websiteSlug.trim()) {
6531
+ throw new WebsiteError(
6532
+ "initialContext slug must match websiteSlug",
6533
+ "INVALID_CONFIG"
6534
+ );
6535
+ }
4792
6536
  const apiUrl = normalizeApiUrl(config.apiUrl);
4793
6537
  const graphqlUrl = `${apiUrl}/api/v1/website/graphql`;
4794
6538
  const storageUrl = `${apiUrl}/storage/v1`;
4795
- let context = null;
6539
+ let context = config.initialContext ? { ...config.initialContext } : null;
6540
+ let initialization = null;
4796
6541
  function getContext() {
4797
6542
  if (!context) throw new NotInitializedError();
4798
6543
  return context;
@@ -4837,13 +6582,27 @@ function createBacanoClient(config) {
4837
6582
  getContext,
4838
6583
  storageUrl
4839
6584
  );
6585
+ const marketingModule = createMarketingModule(
6586
+ apiUrl,
6587
+ config.websiteSlug,
6588
+ getContext
6589
+ );
4840
6590
  return {
4841
6591
  async init() {
4842
- context = await resolveWebsite(
4843
- graphqlUrl,
4844
- config.websiteSlug,
4845
- getWebsiteHeaders
4846
- );
6592
+ if (context) return;
6593
+ if (!initialization) {
6594
+ initialization = resolveWebsiteCached(
6595
+ graphqlUrl,
6596
+ config.websiteSlug,
6597
+ getWebsiteHeaders
6598
+ ).then((resolvedContext) => {
6599
+ context = resolvedContext;
6600
+ }).catch((error) => {
6601
+ initialization = null;
6602
+ throw error;
6603
+ });
6604
+ }
6605
+ await initialization;
4847
6606
  },
4848
6607
  getContext,
4849
6608
  auth: {
@@ -4858,8 +6617,11 @@ function createBacanoClient(config) {
4858
6617
  },
4859
6618
  catalog: {
4860
6619
  getCatalogPage: catalogModule.getCatalogPage.bind(catalogModule),
6620
+ getStaticCatalogSnapshot: catalogModule.getStaticCatalogSnapshot.bind(catalogModule),
6621
+ getStaticCatalogBootstrap: catalogModule.getStaticCatalogBootstrap.bind(catalogModule),
4861
6622
  getProductSeoEntries: catalogModule.getProductSeoEntries.bind(catalogModule),
4862
6623
  getProductRecommendations: catalogModule.getProductRecommendations.bind(catalogModule),
6624
+ getProductLiveState: catalogModule.getProductLiveState.bind(catalogModule),
4863
6625
  getProducts: catalogModule.getProducts.bind(catalogModule),
4864
6626
  getProduct: catalogModule.getProduct.bind(catalogModule),
4865
6627
  getProductBySlug: catalogModule.getProductBySlug.bind(catalogModule),
@@ -4884,7 +6646,8 @@ function createBacanoClient(config) {
4884
6646
  setItem: cartModule.setItem.bind(cartModule),
4885
6647
  addItem: cartModule.addItem.bind(cartModule),
4886
6648
  updateItem: cartModule.updateItem.bind(cartModule),
4887
- removeItem: cartModule.removeItem.bind(cartModule)
6649
+ removeItem: cartModule.removeItem.bind(cartModule),
6650
+ clear: cartModule.clear.bind(cartModule)
4888
6651
  },
4889
6652
  checkout: {
4890
6653
  getLocationOptions: checkoutModule.getLocationOptions.bind(checkoutModule),
@@ -4911,6 +6674,9 @@ function createBacanoClient(config) {
4911
6674
  get: profileModule.get.bind(profileModule),
4912
6675
  getAddresses: profileModule.getAddresses.bind(profileModule),
4913
6676
  update: profileModule.update.bind(profileModule)
6677
+ },
6678
+ marketing: {
6679
+ captureLead: marketingModule.captureLead.bind(marketingModule)
4914
6680
  }
4915
6681
  };
4916
6682
  }
@@ -4918,16 +6684,30 @@ export {
4918
6684
  AuthError,
4919
6685
  CheckoutError,
4920
6686
  GraphQLRequestError,
6687
+ MARKETING_SCHEMA_VERSION,
4921
6688
  NotInitializedError,
6689
+ RateLimitError,
6690
+ WEBSITE_MARKETING_CONTACT_PLACEMENTS,
4922
6691
  WebsiteError,
6692
+ buildStorageContentMediaUrls,
4923
6693
  buildStorageFileUrl,
4924
6694
  buildStorageMediaUrls,
6695
+ buildStorageResponsiveSources,
6696
+ buildStorageTransformedFileUrl,
6697
+ captureWebsiteMarketingAttribution,
4925
6698
  createBacanoClient,
6699
+ createWebsiteMarketingRuntime,
6700
+ formatMediaSrcSet,
6701
+ getAllowedMarketingParameters,
4926
6702
  getPrimaryProductImage,
6703
+ getProductDetailVariants,
4927
6704
  getProductImages,
4928
6705
  getProductMediaByKind,
4929
6706
  getProductMediaKind,
4930
6707
  getProductVideos,
6708
+ getWebsiteContextFromPublicConfig,
6709
+ getWebsiteMarketingAttribution,
4931
6710
  getWebsitePublicConfig,
4932
- resolveProductPricing
6711
+ resolveProductPricing,
6712
+ sanitizeMarketingUrl
4933
6713
  };