@bacano/sdk 1.1.0 → 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.
package/index.js CHANGED
@@ -221,6 +221,10 @@ async function getWebsitePublicConfig(options) {
221
221
  consent_policy: null,
222
222
  destinations: { ga4: null, meta: null, clarity: null }
223
223
  };
224
+ const branding = raw.branding ?? {
225
+ schema_version: 1,
226
+ favicon: null
227
+ };
224
228
  const legal = raw.legal ?? {
225
229
  schema_version: 1,
226
230
  version: 0,
@@ -268,6 +272,11 @@ async function getWebsitePublicConfig(options) {
268
272
  desktop_image: null,
269
273
  mobile_image: null
270
274
  };
275
+ const volumePricing = raw.volume_pricing ?? {
276
+ schema_version: 1,
277
+ enabled: false,
278
+ tiers: []
279
+ };
271
280
  return {
272
281
  websiteId: raw.website_id,
273
282
  companyId: raw.company_id,
@@ -293,6 +302,20 @@ async function getWebsitePublicConfig(options) {
293
302
  })
294
303
  )
295
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
+ },
296
319
  marketing: {
297
320
  schemaVersion: marketing.schema_version,
298
321
  enabled: marketing.enabled,
@@ -349,6 +372,14 @@ async function getWebsitePublicConfig(options) {
349
372
  message: construction.message,
350
373
  desktopImage: mapConstructionImage(construction.desktop_image),
351
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
+ }))
352
383
  }
353
384
  };
354
385
  }
@@ -496,7 +527,6 @@ var GET_PRODUCTS = (
496
527
  ) {
497
528
  id
498
529
  related_product_id
499
- display_label
500
530
  sort_order
501
531
  related_product {
502
532
  id
@@ -518,6 +548,52 @@ var GET_PRODUCTS = (
518
548
  }
519
549
  }
520
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
+ }
521
597
  product_attributes {
522
598
  id
523
599
  value
@@ -602,6 +678,52 @@ var GET_PRODUCT_BY_PK = (
602
678
  limit: 1
603
679
  ) {
604
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
+ }
605
727
  name
606
728
  description
607
729
  long_description
@@ -688,7 +810,6 @@ var GET_PRODUCT_BY_PK = (
688
810
  ) {
689
811
  id
690
812
  related_product_id
691
- display_label
692
813
  sort_order
693
814
  related_product {
694
815
  id
@@ -1062,6 +1183,24 @@ var WEBSITE_GET_PRODUCT_RECOMMENDATIONS = (
1062
1183
  }
1063
1184
  `
1064
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
+ );
1065
1204
  var GET_BRANDS = (
1066
1205
  /* GraphQL */
1067
1206
  `
@@ -1170,10 +1309,15 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1170
1309
  var CHECK_AVAILABILITY = (
1171
1310
  /* GraphQL */
1172
1311
  `
1173
- query CheckAvailability($variantIds: [uuid!]!, $chainId: uuid!) {
1312
+ query CheckAvailability(
1313
+ $variantIds: [uuid!]!
1314
+ $chainId: uuid!
1315
+ $websiteId: uuid
1316
+ ) {
1174
1317
  websiteProductAvailability(
1175
1318
  product_variant_ids: $variantIds
1176
1319
  chain_id: $chainId
1320
+ website_id: $websiteId
1177
1321
  ) {
1178
1322
  product_variant_id
1179
1323
  in_stock
@@ -1229,6 +1373,7 @@ var GET_PRODUCT_LIVE_STATE = (
1229
1373
  websiteProductAvailability(
1230
1374
  product_variant_ids: $variantIds
1231
1375
  chain_id: $chainId
1376
+ website_id: $websiteId
1232
1377
  ) {
1233
1378
  product_variant_id
1234
1379
  in_stock
@@ -1266,10 +1411,28 @@ var CREATE_CART = (
1266
1411
  total_with_discount
1267
1412
  created_at
1268
1413
  updated_at
1414
+ volume_pricing {
1415
+ enabled
1416
+ eligibleQuantity
1417
+ appliedPriceType
1418
+ appliedMinQuantity
1419
+ nextPriceType
1420
+ nextMinQuantity
1421
+ remainingQuantity
1422
+ totalSavings
1423
+ }
1269
1424
  items {
1270
1425
  id
1271
1426
  product_variant_id
1272
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
1273
1436
  product_variant {
1274
1437
  id
1275
1438
  sku
@@ -1309,10 +1472,28 @@ var GET_CART = (
1309
1472
  total_with_discount
1310
1473
  created_at
1311
1474
  updated_at
1475
+ volume_pricing {
1476
+ enabled
1477
+ eligibleQuantity
1478
+ appliedPriceType
1479
+ appliedMinQuantity
1480
+ nextPriceType
1481
+ nextMinQuantity
1482
+ remainingQuantity
1483
+ totalSavings
1484
+ }
1312
1485
  items {
1313
1486
  id
1314
1487
  product_variant_id
1315
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
1316
1497
  product_variant {
1317
1498
  id
1318
1499
  sku
@@ -1366,10 +1547,28 @@ var START_GUEST_CHECKOUT = (
1366
1547
  total_with_discount
1367
1548
  created_at
1368
1549
  updated_at
1550
+ volume_pricing {
1551
+ enabled
1552
+ eligibleQuantity
1553
+ appliedPriceType
1554
+ appliedMinQuantity
1555
+ nextPriceType
1556
+ nextMinQuantity
1557
+ remainingQuantity
1558
+ totalSavings
1559
+ }
1369
1560
  items {
1370
1561
  id
1371
1562
  product_variant_id
1372
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
1373
1572
  product_variant {
1374
1573
  id
1375
1574
  sku
@@ -1419,10 +1618,28 @@ var SET_CART_ITEM = (
1419
1618
  total_with_discount
1420
1619
  created_at
1421
1620
  updated_at
1621
+ volume_pricing {
1622
+ enabled
1623
+ eligibleQuantity
1624
+ appliedPriceType
1625
+ appliedMinQuantity
1626
+ nextPriceType
1627
+ nextMinQuantity
1628
+ remainingQuantity
1629
+ totalSavings
1630
+ }
1422
1631
  items {
1423
1632
  id
1424
1633
  product_variant_id
1425
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
1426
1643
  product_variant {
1427
1644
  id
1428
1645
  sku
@@ -1470,10 +1687,28 @@ var REMOVE_CART_ITEM = (
1470
1687
  total_with_discount
1471
1688
  created_at
1472
1689
  updated_at
1690
+ volume_pricing {
1691
+ enabled
1692
+ eligibleQuantity
1693
+ appliedPriceType
1694
+ appliedMinQuantity
1695
+ nextPriceType
1696
+ nextMinQuantity
1697
+ remainingQuantity
1698
+ totalSavings
1699
+ }
1473
1700
  items {
1474
1701
  id
1475
1702
  product_variant_id
1476
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
1477
1712
  product_variant {
1478
1713
  id
1479
1714
  sku
@@ -1495,6 +1730,19 @@ var REMOVE_CART_ITEM = (
1495
1730
  }
1496
1731
  `
1497
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
+ );
1498
1746
  var CHECKOUT = (
1499
1747
  /* GraphQL */
1500
1748
  `
@@ -2600,7 +2848,8 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
2600
2848
  CHECK_AVAILABILITY,
2601
2849
  {
2602
2850
  variantIds: chunk,
2603
- chainId: ctx.chainId
2851
+ chainId: ctx.chainId,
2852
+ websiteId: ctx.websiteId
2604
2853
  },
2605
2854
  getHeaders
2606
2855
  );
@@ -2640,6 +2889,7 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
2640
2889
  "utm_content",
2641
2890
  "utm_source_platform",
2642
2891
  "utm_id",
2892
+ "bacano_origin",
2643
2893
  "gclid",
2644
2894
  "gbraid",
2645
2895
  "wbraid",
@@ -2674,6 +2924,7 @@ function getAllowedMarketingParameters(input, baseUrl) {
2674
2924
  var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
2675
2925
  var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
2676
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]+)*$/;
2677
2928
  function storageKey(websiteId) {
2678
2929
  return `${STORAGE_PREFIX}:${websiteId}`;
2679
2930
  }
@@ -2716,6 +2967,10 @@ function optional(value) {
2716
2967
  const normalized = value?.trim();
2717
2968
  return normalized ? normalized.slice(0, 500) : void 0;
2718
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
+ }
2719
2974
  function buildTouchpoint() {
2720
2975
  if (typeof window === "undefined") return null;
2721
2976
  const params = getAllowedMarketingParameters(window.location.href);
@@ -2739,6 +2994,7 @@ function buildTouchpoint() {
2739
2994
  content: optional(params.utm_content),
2740
2995
  sourcePlatform: optional(params.utm_source_platform),
2741
2996
  campaignId: optional(params.utm_id),
2997
+ salesOriginKey: validSalesOriginKey(params.bacano_origin),
2742
2998
  gclid: optional(params.gclid),
2743
2999
  gbraid: optional(params.gbraid),
2744
3000
  wbraid: optional(params.wbraid),
@@ -2749,7 +3005,7 @@ function buildTouchpoint() {
2749
3005
  }
2750
3006
  function isNonDirect(touchpoint) {
2751
3007
  return Boolean(
2752
- touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
3008
+ touchpoint.salesOriginKey || touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
2753
3009
  );
2754
3010
  }
2755
3011
  function toPublic(stored) {
@@ -2840,6 +3096,7 @@ function serializeTouchpoint(touchpoint) {
2840
3096
  content: touchpoint.content,
2841
3097
  source_platform: touchpoint.sourcePlatform,
2842
3098
  campaign_id: touchpoint.campaignId,
3099
+ sales_origin_key: touchpoint.salesOriginKey,
2843
3100
  gclid: touchpoint.gclid,
2844
3101
  gbraid: touchpoint.gbraid,
2845
3102
  wbraid: touchpoint.wbraid,
@@ -2862,12 +3119,40 @@ function serializeWebsiteMarketingAttribution(attribution) {
2862
3119
  };
2863
3120
  }
2864
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
+
2865
3142
  // libs/sdk/src/modules/cart.ts
2866
3143
  function mapCartItem(raw) {
2867
3144
  return {
2868
3145
  id: raw.id,
2869
3146
  productVariantId: raw.product_variant_id,
2870
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,
2871
3156
  productVariant: {
2872
3157
  sku: raw.product_variant.sku,
2873
3158
  product: raw.product_variant.product,
@@ -2893,13 +3178,29 @@ function mapCart(raw, cartToken) {
2893
3178
  },
2894
3179
  status: raw.status,
2895
3180
  createdAt: raw.created_at,
2896
- 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
+ }
2897
3195
  };
2898
3196
  }
2899
3197
  function assertActionSuccess(result, fallbackCode) {
2900
3198
  if (!result.success || !result.order_id || !result.cart_token || !result.cart) {
2901
3199
  const message = result.errors?.join("; ") || "Cart action failed";
2902
- throw new WebsiteError(message, fallbackCode);
3200
+ throw new WebsiteError(
3201
+ message,
3202
+ getStableCartActionErrorCode(result.errors, fallbackCode)
3203
+ );
2903
3204
  }
2904
3205
  }
2905
3206
  function requireCartSession(storage) {
@@ -2917,7 +3218,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2917
3218
  const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
2918
3219
  const result = data.websiteGetCart;
2919
3220
  if (!result.success) {
2920
- if (result.errors?.includes("CART_NOT_FOUND")) {
3221
+ if (hasRecoverableCartSessionError(result.errors)) {
2921
3222
  cartSessionStorage.clear();
2922
3223
  return null;
2923
3224
  }
@@ -2970,24 +3271,31 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2970
3271
  "NO_CHAIN"
2971
3272
  );
2972
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
+ };
2973
3293
  const existing = cartSessionStorage.get();
2974
- const data = await executeGraphQL(
2975
- graphqlUrl,
2976
- START_GUEST_CHECKOUT,
2977
- {
2978
- websiteId: ctx.websiteId,
2979
- cartToken: existing?.cartToken,
2980
- contact: input.contact,
2981
- fulfillmentFamily: input.fulfillmentFamily,
2982
- items: input.items.map((item) => ({
2983
- product_variant_id: item.productVariantId,
2984
- quantity: item.quantity
2985
- })),
2986
- marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
2987
- },
2988
- getHeaders
2989
- );
2990
- 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
+ }
2991
3299
  assertActionSuccess(result, "CHECKOUT_START_ERROR");
2992
3300
  cartSessionStorage.set({
2993
3301
  orderId: result.order_id,
@@ -3010,7 +3318,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3010
3318
  const session = cartSessionStorage.get();
3011
3319
  if (!session) {
3012
3320
  throw new WebsiteError(
3013
- "Start checkout with contact before syncing cart items to Bacano",
3321
+ "Create or recover a Bacano cart session before syncing cart items",
3014
3322
  "CART_SESSION_MISSING"
3015
3323
  );
3016
3324
  }
@@ -3078,11 +3386,37 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3078
3386
  );
3079
3387
  const result = data.websiteRemoveCartItem;
3080
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
+ }
3081
3394
  cartSessionStorage.set({
3082
3395
  orderId: result.order_id,
3083
3396
  cartToken: result.cart_token
3084
3397
  });
3085
- 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();
3086
3420
  }
3087
3421
  };
3088
3422
  }
@@ -3454,7 +3788,6 @@ function mapRelatedProduct(raw, storageUrl) {
3454
3788
  slug: raw.related_product.slug,
3455
3789
  name: raw.related_product.name,
3456
3790
  sku: raw.related_product.sku,
3457
- displayLabel: raw.display_label,
3458
3791
  sortOrder: raw.sort_order ?? 0,
3459
3792
  media: raw.related_product.product_media.map(
3460
3793
  (m) => mapMedia(m, storageUrl)
@@ -3465,6 +3798,23 @@ function mapProduct(raw, storageUrl, labels, chainId) {
3465
3798
  const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
3466
3799
  const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
3467
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;
3468
3818
  return {
3469
3819
  id: raw.id,
3470
3820
  name: raw.name,
@@ -3489,7 +3839,9 @@ function mapProduct(raw, storageUrl, labels, chainId) {
3489
3839
  ),
3490
3840
  relatedProducts: (raw.product_related_products ?? []).map(
3491
3841
  (relation) => mapRelatedProduct(relation, storageUrl)
3492
- )
3842
+ ),
3843
+ optionGroup,
3844
+ specification: raw.product_specification ?? null
3493
3845
  };
3494
3846
  }
3495
3847
  function mapCategory(raw) {
@@ -3820,7 +4172,7 @@ function hasMeaningfulCategoryListFilters(filters) {
3820
4172
  )
3821
4173
  );
3822
4174
  }
3823
- async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId) {
4175
+ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId, websiteId) {
3824
4176
  const uniqueVariantIds = uniqueValues2(variantIds);
3825
4177
  const availabilityByVariantId = /* @__PURE__ */ new Map();
3826
4178
  for (const chunk of chunkArray2(
@@ -3830,7 +4182,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3830
4182
  const data = await executeGraphQL(
3831
4183
  graphqlUrl,
3832
4184
  CHECK_AVAILABILITY,
3833
- { variantIds: chunk, chainId },
4185
+ { variantIds: chunk, chainId, websiteId },
3834
4186
  getHeaders
3835
4187
  );
3836
4188
  for (const item of data.websiteProductAvailability ?? []) {
@@ -3841,7 +4193,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3841
4193
  }
3842
4194
  return availabilityByVariantId;
3843
4195
  }
3844
- async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4196
+ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3845
4197
  const variantIds = products.flatMap(
3846
4198
  (product) => product.variants.map((variant) => variant.id)
3847
4199
  );
@@ -3852,7 +4204,8 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3852
4204
  graphqlUrl,
3853
4205
  getHeaders,
3854
4206
  variantIds,
3855
- chainId
4207
+ chainId,
4208
+ websiteId
3856
4209
  )).entries()
3857
4210
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3858
4211
  );
@@ -3870,7 +4223,7 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3870
4223
  )
3871
4224
  })).filter((product) => product.variants.length > 0);
3872
4225
  }
3873
- async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4226
+ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3874
4227
  const variantIds = products.flatMap(
3875
4228
  (product) => product.product_variants.map((variant) => variant.id)
3876
4229
  );
@@ -3881,7 +4234,8 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
3881
4234
  graphqlUrl,
3882
4235
  getHeaders,
3883
4236
  variantIds,
3884
- chainId
4237
+ chainId,
4238
+ websiteId
3885
4239
  )).entries()
3886
4240
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3887
4241
  );
@@ -3912,6 +4266,32 @@ function mapProductSeoEntry(raw, storageUrl) {
3912
4266
  };
3913
4267
  }
3914
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
+ };
3915
4295
  const mapSingleProductResult = async (data) => {
3916
4296
  const ctx = getContext();
3917
4297
  if (!ctx.chainId) {
@@ -3935,12 +4315,15 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3935
4315
  graphqlUrl,
3936
4316
  getHeaders,
3937
4317
  [mappedProduct],
3938
- ctx.chainId
4318
+ ctx.chainId,
4319
+ ctx.websiteId
3939
4320
  );
3940
4321
  if (!filteredProducts[0]) return null;
4322
+ const specifications = await loadProductSpecifications([product.id]);
3941
4323
  return {
3942
4324
  ...mappedProduct,
3943
- variants: getProductDetailVariants(mappedProduct)
4325
+ variants: getProductDetailVariants(mappedProduct),
4326
+ specification: specifications[product.id] ?? null
3944
4327
  };
3945
4328
  };
3946
4329
  const loadCatalogPage = async (opts, retryRateLimits = false) => {
@@ -3961,6 +4344,19 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3961
4344
  min_price: opts.minPrice,
3962
4345
  max_price: opts.maxPrice,
3963
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,
3964
4360
  sort_by: opts.sortBy,
3965
4361
  limit: opts.limit,
3966
4362
  offset: opts.offset
@@ -4152,7 +4548,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4152
4548
  graphqlUrl,
4153
4549
  getHeaders,
4154
4550
  data.products ?? [],
4155
- ctx.chainId
4551
+ ctx.chainId,
4552
+ ctx.websiteId
4156
4553
  );
4157
4554
  const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
4158
4555
  return {
@@ -4228,8 +4625,16 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4228
4625
  (data.products ?? []).map(
4229
4626
  (p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
4230
4627
  ),
4231
- chainId
4628
+ chainId,
4629
+ ctx.websiteId
4630
+ );
4631
+ const specifications = await loadProductSpecifications(
4632
+ products.map((product) => product.id)
4232
4633
  );
4634
+ products = products.map((product) => ({
4635
+ ...product,
4636
+ specification: specifications[product.id] ?? null
4637
+ }));
4233
4638
  if (opts?.minPrice !== void 0 || opts?.maxPrice !== void 0) {
4234
4639
  products = products.filter(
4235
4640
  (product) => product.variants.some((variant) => {
@@ -4340,7 +4745,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4340
4745
  graphqlUrl,
4341
4746
  getHeaders,
4342
4747
  variantIds,
4343
- chainId
4748
+ chainId,
4749
+ ctx.websiteId
4344
4750
  );
4345
4751
  const applicableCategories = /* @__PURE__ */ new Set();
4346
4752
  const applicableSubcategoryIds = /* @__PURE__ */ new Set();
@@ -4458,7 +4864,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4458
4864
  graphqlUrl,
4459
4865
  getHeaders,
4460
4866
  variantIds,
4461
- chainId
4867
+ chainId,
4868
+ ctx.websiteId
4462
4869
  );
4463
4870
  const publicAttributeLabels = buildPublicAttributeLabels(
4464
4871
  data.catalog_attribute_lists
@@ -4563,7 +4970,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4563
4970
  min: minPrice,
4564
4971
  max: maxPrice
4565
4972
  },
4566
- attributes
4973
+ attributes,
4974
+ technical: []
4567
4975
  };
4568
4976
  },
4569
4977
  async getSearchSuggestions(opts) {
@@ -4617,6 +5025,14 @@ function mapCartItem2(raw) {
4617
5025
  id: raw.id,
4618
5026
  productVariantId: raw.product_variant_id,
4619
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,
4620
5036
  productVariant: {
4621
5037
  sku: raw.product_variant.sku,
4622
5038
  product: raw.product_variant.product,
@@ -4641,7 +5057,20 @@ function mapCart2(raw) {
4641
5057
  },
4642
5058
  status: raw.status,
4643
5059
  createdAt: raw.created_at,
4644
- 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
+ }
4645
5074
  };
4646
5075
  }
4647
5076
  function mapCouponResult(raw) {
@@ -6217,7 +6646,8 @@ function createBacanoClient(config) {
6217
6646
  setItem: cartModule.setItem.bind(cartModule),
6218
6647
  addItem: cartModule.addItem.bind(cartModule),
6219
6648
  updateItem: cartModule.updateItem.bind(cartModule),
6220
- removeItem: cartModule.removeItem.bind(cartModule)
6649
+ removeItem: cartModule.removeItem.bind(cartModule),
6650
+ clear: cartModule.clear.bind(cartModule)
6221
6651
  },
6222
6652
  checkout: {
6223
6653
  getLocationOptions: checkoutModule.getLocationOptions.bind(checkoutModule),