@bacano/sdk 1.1.0 → 1.1.9

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.cjs CHANGED
@@ -275,6 +275,10 @@ async function getWebsitePublicConfig(options) {
275
275
  consent_policy: null,
276
276
  destinations: { ga4: null, meta: null, clarity: null }
277
277
  };
278
+ const branding = raw.branding ?? {
279
+ schema_version: 1,
280
+ favicon: null
281
+ };
278
282
  const legal = raw.legal ?? {
279
283
  schema_version: 1,
280
284
  version: 0,
@@ -322,6 +326,11 @@ async function getWebsitePublicConfig(options) {
322
326
  desktop_image: null,
323
327
  mobile_image: null
324
328
  };
329
+ const volumePricing = raw.volume_pricing ?? {
330
+ schema_version: 1,
331
+ enabled: false,
332
+ tiers: []
333
+ };
325
334
  return {
326
335
  websiteId: raw.website_id,
327
336
  companyId: raw.company_id,
@@ -347,6 +356,20 @@ async function getWebsitePublicConfig(options) {
347
356
  })
348
357
  )
349
358
  },
359
+ branding: {
360
+ schemaVersion: branding.schema_version,
361
+ favicon: branding.favicon ? {
362
+ fileId: branding.favicon.file_id,
363
+ name: branding.favicon.name,
364
+ altText: branding.favicon.alt_text,
365
+ width: branding.favicon.width,
366
+ height: branding.favicon.height,
367
+ originalUrl: buildStorageFileUrl(
368
+ `${apiUrl}/storage/v1`,
369
+ branding.favicon.file_id
370
+ )
371
+ } : null
372
+ },
350
373
  marketing: {
351
374
  schemaVersion: marketing.schema_version,
352
375
  enabled: marketing.enabled,
@@ -403,6 +426,14 @@ async function getWebsitePublicConfig(options) {
403
426
  message: construction.message,
404
427
  desktopImage: mapConstructionImage(construction.desktop_image),
405
428
  mobileImage: mapConstructionImage(construction.mobile_image)
429
+ },
430
+ volumePricing: {
431
+ schemaVersion: volumePricing.schema_version,
432
+ enabled: volumePricing.enabled,
433
+ tiers: volumePricing.tiers.map((tier) => ({
434
+ priceType: tier.price_type,
435
+ minQuantity: tier.min_quantity
436
+ }))
406
437
  }
407
438
  };
408
439
  }
@@ -550,7 +581,6 @@ var GET_PRODUCTS = (
550
581
  ) {
551
582
  id
552
583
  related_product_id
553
- display_label
554
584
  sort_order
555
585
  related_product {
556
586
  id
@@ -572,6 +602,52 @@ var GET_PRODUCTS = (
572
602
  }
573
603
  }
574
604
  }
605
+ product_option_group_members(limit: 1) {
606
+ group {
607
+ id
608
+ display_title
609
+ members(
610
+ where: {
611
+ product: {
612
+ product_website_publications: {
613
+ website_id: { _eq: $websiteId }
614
+ is_visible: { _eq: true }
615
+ }
616
+ chain_products: {
617
+ chain_id: { _eq: $chainId }
618
+ is_enabled: { _eq: true }
619
+ status: { _eq: active }
620
+ }
621
+ }
622
+ }
623
+ order_by: { sort_order: asc }
624
+ ) {
625
+ id
626
+ product_id
627
+ display_label
628
+ sort_order
629
+ product {
630
+ id
631
+ slug
632
+ name
633
+ sku
634
+ product_media(
635
+ where: { file: { mimeType: { _ilike: "image/%" } } }
636
+ order_by: { order: asc }
637
+ limit: 1
638
+ ) {
639
+ id
640
+ file_id
641
+ order
642
+ file {
643
+ mimeType
644
+ name
645
+ }
646
+ }
647
+ }
648
+ }
649
+ }
650
+ }
575
651
  product_attributes {
576
652
  id
577
653
  value
@@ -656,6 +732,52 @@ var GET_PRODUCT_BY_PK = (
656
732
  limit: 1
657
733
  ) {
658
734
  id
735
+ product_option_group_members(limit: 1) {
736
+ group {
737
+ id
738
+ display_title
739
+ members(
740
+ where: {
741
+ product: {
742
+ product_website_publications: {
743
+ website_id: { _eq: $websiteId }
744
+ is_visible: { _eq: true }
745
+ }
746
+ chain_products: {
747
+ chain_id: { _eq: $chainId }
748
+ is_enabled: { _eq: true }
749
+ status: { _eq: active }
750
+ }
751
+ }
752
+ }
753
+ order_by: { sort_order: asc }
754
+ ) {
755
+ id
756
+ product_id
757
+ display_label
758
+ sort_order
759
+ product {
760
+ id
761
+ slug
762
+ name
763
+ sku
764
+ product_media(
765
+ where: { file: { mimeType: { _ilike: "image/%" } } }
766
+ order_by: { order: asc }
767
+ limit: 1
768
+ ) {
769
+ id
770
+ file_id
771
+ order
772
+ file {
773
+ mimeType
774
+ name
775
+ }
776
+ }
777
+ }
778
+ }
779
+ }
780
+ }
659
781
  name
660
782
  description
661
783
  long_description
@@ -742,7 +864,6 @@ var GET_PRODUCT_BY_PK = (
742
864
  ) {
743
865
  id
744
866
  related_product_id
745
- display_label
746
867
  sort_order
747
868
  related_product {
748
869
  id
@@ -1116,6 +1237,24 @@ var WEBSITE_GET_PRODUCT_RECOMMENDATIONS = (
1116
1237
  }
1117
1238
  `
1118
1239
  );
1240
+ var WEBSITE_GET_PRODUCT_SPECIFICATIONS = (
1241
+ /* GraphQL */
1242
+ `
1243
+ mutation WebsiteGetProductSpecifications(
1244
+ $websiteId: uuid!
1245
+ $productIds: [uuid!]!
1246
+ ) {
1247
+ result: websiteGetProductSpecifications(
1248
+ website_id: $websiteId
1249
+ product_ids: $productIds
1250
+ ) {
1251
+ success
1252
+ specifications
1253
+ errors
1254
+ }
1255
+ }
1256
+ `
1257
+ );
1119
1258
  var GET_BRANDS = (
1120
1259
  /* GraphQL */
1121
1260
  `
@@ -1224,10 +1363,15 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1224
1363
  var CHECK_AVAILABILITY = (
1225
1364
  /* GraphQL */
1226
1365
  `
1227
- query CheckAvailability($variantIds: [uuid!]!, $chainId: uuid!) {
1366
+ query CheckAvailability(
1367
+ $variantIds: [uuid!]!
1368
+ $chainId: uuid!
1369
+ $websiteId: uuid
1370
+ ) {
1228
1371
  websiteProductAvailability(
1229
1372
  product_variant_ids: $variantIds
1230
1373
  chain_id: $chainId
1374
+ website_id: $websiteId
1231
1375
  ) {
1232
1376
  product_variant_id
1233
1377
  in_stock
@@ -1283,6 +1427,7 @@ var GET_PRODUCT_LIVE_STATE = (
1283
1427
  websiteProductAvailability(
1284
1428
  product_variant_ids: $variantIds
1285
1429
  chain_id: $chainId
1430
+ website_id: $websiteId
1286
1431
  ) {
1287
1432
  product_variant_id
1288
1433
  in_stock
@@ -1320,10 +1465,28 @@ var CREATE_CART = (
1320
1465
  total_with_discount
1321
1466
  created_at
1322
1467
  updated_at
1468
+ volume_pricing {
1469
+ enabled
1470
+ eligibleQuantity
1471
+ appliedPriceType
1472
+ appliedMinQuantity
1473
+ nextPriceType
1474
+ nextMinQuantity
1475
+ remainingQuantity
1476
+ totalSavings
1477
+ }
1323
1478
  items {
1324
1479
  id
1325
1480
  product_variant_id
1326
1481
  quantity
1482
+ unit_price
1483
+ line_total
1484
+ base_unit_price
1485
+ unit_savings
1486
+ line_savings
1487
+ volume_pricing_eligible
1488
+ resolved_price_type
1489
+ volume_pricing_min_quantity
1327
1490
  product_variant {
1328
1491
  id
1329
1492
  sku
@@ -1363,10 +1526,28 @@ var GET_CART = (
1363
1526
  total_with_discount
1364
1527
  created_at
1365
1528
  updated_at
1529
+ volume_pricing {
1530
+ enabled
1531
+ eligibleQuantity
1532
+ appliedPriceType
1533
+ appliedMinQuantity
1534
+ nextPriceType
1535
+ nextMinQuantity
1536
+ remainingQuantity
1537
+ totalSavings
1538
+ }
1366
1539
  items {
1367
1540
  id
1368
1541
  product_variant_id
1369
1542
  quantity
1543
+ unit_price
1544
+ line_total
1545
+ base_unit_price
1546
+ unit_savings
1547
+ line_savings
1548
+ volume_pricing_eligible
1549
+ resolved_price_type
1550
+ volume_pricing_min_quantity
1370
1551
  product_variant {
1371
1552
  id
1372
1553
  sku
@@ -1420,10 +1601,28 @@ var START_GUEST_CHECKOUT = (
1420
1601
  total_with_discount
1421
1602
  created_at
1422
1603
  updated_at
1604
+ volume_pricing {
1605
+ enabled
1606
+ eligibleQuantity
1607
+ appliedPriceType
1608
+ appliedMinQuantity
1609
+ nextPriceType
1610
+ nextMinQuantity
1611
+ remainingQuantity
1612
+ totalSavings
1613
+ }
1423
1614
  items {
1424
1615
  id
1425
1616
  product_variant_id
1426
1617
  quantity
1618
+ unit_price
1619
+ line_total
1620
+ base_unit_price
1621
+ unit_savings
1622
+ line_savings
1623
+ volume_pricing_eligible
1624
+ resolved_price_type
1625
+ volume_pricing_min_quantity
1427
1626
  product_variant {
1428
1627
  id
1429
1628
  sku
@@ -1473,10 +1672,28 @@ var SET_CART_ITEM = (
1473
1672
  total_with_discount
1474
1673
  created_at
1475
1674
  updated_at
1675
+ volume_pricing {
1676
+ enabled
1677
+ eligibleQuantity
1678
+ appliedPriceType
1679
+ appliedMinQuantity
1680
+ nextPriceType
1681
+ nextMinQuantity
1682
+ remainingQuantity
1683
+ totalSavings
1684
+ }
1476
1685
  items {
1477
1686
  id
1478
1687
  product_variant_id
1479
1688
  quantity
1689
+ unit_price
1690
+ line_total
1691
+ base_unit_price
1692
+ unit_savings
1693
+ line_savings
1694
+ volume_pricing_eligible
1695
+ resolved_price_type
1696
+ volume_pricing_min_quantity
1480
1697
  product_variant {
1481
1698
  id
1482
1699
  sku
@@ -1524,10 +1741,28 @@ var REMOVE_CART_ITEM = (
1524
1741
  total_with_discount
1525
1742
  created_at
1526
1743
  updated_at
1744
+ volume_pricing {
1745
+ enabled
1746
+ eligibleQuantity
1747
+ appliedPriceType
1748
+ appliedMinQuantity
1749
+ nextPriceType
1750
+ nextMinQuantity
1751
+ remainingQuantity
1752
+ totalSavings
1753
+ }
1527
1754
  items {
1528
1755
  id
1529
1756
  product_variant_id
1530
1757
  quantity
1758
+ unit_price
1759
+ line_total
1760
+ base_unit_price
1761
+ unit_savings
1762
+ line_savings
1763
+ volume_pricing_eligible
1764
+ resolved_price_type
1765
+ volume_pricing_min_quantity
1531
1766
  product_variant {
1532
1767
  id
1533
1768
  sku
@@ -1549,6 +1784,19 @@ var REMOVE_CART_ITEM = (
1549
1784
  }
1550
1785
  `
1551
1786
  );
1787
+ var DISCARD_CART = (
1788
+ /* GraphQL */
1789
+ `
1790
+ mutation DiscardCart($orderId: uuid!, $cartToken: String!) {
1791
+ websiteDiscardCart(order_id: $orderId, cart_token: $cartToken) {
1792
+ success
1793
+ order_id
1794
+ website_cart_status
1795
+ errors
1796
+ }
1797
+ }
1798
+ `
1799
+ );
1552
1800
  var CHECKOUT = (
1553
1801
  /* GraphQL */
1554
1802
  `
@@ -2654,7 +2902,8 @@ function createAvailabilityModule(graphqlUrl, getHeaders, getContext) {
2654
2902
  CHECK_AVAILABILITY,
2655
2903
  {
2656
2904
  variantIds: chunk,
2657
- chainId: ctx.chainId
2905
+ chainId: ctx.chainId,
2906
+ websiteId: ctx.websiteId
2658
2907
  },
2659
2908
  getHeaders
2660
2909
  );
@@ -2694,6 +2943,7 @@ var ALLOWED_MARKETING_PARAMETERS = /* @__PURE__ */ new Set([
2694
2943
  "utm_content",
2695
2944
  "utm_source_platform",
2696
2945
  "utm_id",
2946
+ "bacano_origin",
2697
2947
  "gclid",
2698
2948
  "gbraid",
2699
2949
  "wbraid",
@@ -2728,6 +2978,7 @@ function getAllowedMarketingParameters(input, baseUrl) {
2728
2978
  var STORAGE_PREFIX = "bacano:marketing:attribution:v1";
2729
2979
  var GA4_CLIENT_ID_PATTERN = /^\d+\.\d+$/;
2730
2980
  var META_BROWSER_ID_PATTERN = /^fb\.\d+\.\d+\.[A-Za-z0-9_-]+$/;
2981
+ var SALES_ORIGIN_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2731
2982
  function storageKey(websiteId) {
2732
2983
  return `${STORAGE_PREFIX}:${websiteId}`;
2733
2984
  }
@@ -2770,6 +3021,10 @@ function optional(value) {
2770
3021
  const normalized = value?.trim();
2771
3022
  return normalized ? normalized.slice(0, 500) : void 0;
2772
3023
  }
3024
+ function validSalesOriginKey(value) {
3025
+ const normalized = value?.trim().toLowerCase();
3026
+ return normalized && normalized.length <= 100 && SALES_ORIGIN_KEY_PATTERN.test(normalized) ? normalized : void 0;
3027
+ }
2773
3028
  function buildTouchpoint() {
2774
3029
  if (typeof window === "undefined") return null;
2775
3030
  const params = getAllowedMarketingParameters(window.location.href);
@@ -2793,6 +3048,7 @@ function buildTouchpoint() {
2793
3048
  content: optional(params.utm_content),
2794
3049
  sourcePlatform: optional(params.utm_source_platform),
2795
3050
  campaignId: optional(params.utm_id),
3051
+ salesOriginKey: validSalesOriginKey(params.bacano_origin),
2796
3052
  gclid: optional(params.gclid),
2797
3053
  gbraid: optional(params.gbraid),
2798
3054
  wbraid: optional(params.wbraid),
@@ -2803,7 +3059,7 @@ function buildTouchpoint() {
2803
3059
  }
2804
3060
  function isNonDirect(touchpoint) {
2805
3061
  return Boolean(
2806
- touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
3062
+ touchpoint.salesOriginKey || touchpoint.source || touchpoint.medium || touchpoint.campaign || touchpoint.gclid || touchpoint.gbraid || touchpoint.wbraid || touchpoint.fbclid || touchpoint.ttclid || touchpoint.msclkid || touchpoint.referrerHost
2807
3063
  );
2808
3064
  }
2809
3065
  function toPublic(stored) {
@@ -2894,6 +3150,7 @@ function serializeTouchpoint(touchpoint) {
2894
3150
  content: touchpoint.content,
2895
3151
  source_platform: touchpoint.sourcePlatform,
2896
3152
  campaign_id: touchpoint.campaignId,
3153
+ sales_origin_key: touchpoint.salesOriginKey,
2897
3154
  gclid: touchpoint.gclid,
2898
3155
  gbraid: touchpoint.gbraid,
2899
3156
  wbraid: touchpoint.wbraid,
@@ -2916,12 +3173,40 @@ function serializeWebsiteMarketingAttribution(attribution) {
2916
3173
  };
2917
3174
  }
2918
3175
 
3176
+ // libs/sdk/src/modules/cart-errors.ts
3177
+ var RECOVERABLE_CART_SESSION_ERRORS = /* @__PURE__ */ new Set([
3178
+ "INVALID_CART_TOKEN",
3179
+ "CART_NOT_FOUND"
3180
+ ]);
3181
+ function firstStableErrorCode(errors) {
3182
+ const candidate = errors?.[0]?.split(":", 1)[0]?.trim();
3183
+ return candidate && /^[A-Z][A-Z0-9_]+$/.test(candidate) ? candidate : void 0;
3184
+ }
3185
+ function getStableCartActionErrorCode(errors, fallbackCode) {
3186
+ return firstStableErrorCode(errors) ?? fallbackCode;
3187
+ }
3188
+ function hasRecoverableCartSessionError(errors) {
3189
+ return Boolean(
3190
+ errors?.some(
3191
+ (error) => RECOVERABLE_CART_SESSION_ERRORS.has(error.split(":", 1)[0]?.trim())
3192
+ )
3193
+ );
3194
+ }
3195
+
2919
3196
  // libs/sdk/src/modules/cart.ts
2920
3197
  function mapCartItem(raw) {
2921
3198
  return {
2922
3199
  id: raw.id,
2923
3200
  productVariantId: raw.product_variant_id,
2924
3201
  quantity: raw.quantity,
3202
+ unitPrice: raw.unit_price ?? null,
3203
+ lineTotal: raw.line_total ?? null,
3204
+ baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
3205
+ unitSavings: raw.unit_savings ?? 0,
3206
+ lineSavings: raw.line_savings ?? 0,
3207
+ volumePricingEligible: raw.volume_pricing_eligible ?? false,
3208
+ resolvedPriceType: raw.resolved_price_type ?? null,
3209
+ volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
2925
3210
  productVariant: {
2926
3211
  sku: raw.product_variant.sku,
2927
3212
  product: raw.product_variant.product,
@@ -2947,13 +3232,29 @@ function mapCart(raw, cartToken) {
2947
3232
  },
2948
3233
  status: raw.status,
2949
3234
  createdAt: raw.created_at,
2950
- updatedAt: raw.updated_at
3235
+ updatedAt: raw.updated_at,
3236
+ volumePricing: raw.volume_pricing ? {
3237
+ ...raw.volume_pricing,
3238
+ totalSavings: raw.volume_pricing.totalSavings ?? 0
3239
+ } : {
3240
+ enabled: false,
3241
+ eligibleQuantity: 0,
3242
+ appliedPriceType: null,
3243
+ appliedMinQuantity: null,
3244
+ nextPriceType: null,
3245
+ nextMinQuantity: null,
3246
+ remainingQuantity: null,
3247
+ totalSavings: 0
3248
+ }
2951
3249
  };
2952
3250
  }
2953
3251
  function assertActionSuccess(result, fallbackCode) {
2954
3252
  if (!result.success || !result.order_id || !result.cart_token || !result.cart) {
2955
3253
  const message = result.errors?.join("; ") || "Cart action failed";
2956
- throw new WebsiteError(message, fallbackCode);
3254
+ throw new WebsiteError(
3255
+ message,
3256
+ getStableCartActionErrorCode(result.errors, fallbackCode)
3257
+ );
2957
3258
  }
2958
3259
  }
2959
3260
  function requireCartSession(storage) {
@@ -2971,7 +3272,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
2971
3272
  const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
2972
3273
  const result = data.websiteGetCart;
2973
3274
  if (!result.success) {
2974
- if (result.errors?.includes("CART_NOT_FOUND")) {
3275
+ if (hasRecoverableCartSessionError(result.errors)) {
2975
3276
  cartSessionStorage.clear();
2976
3277
  return null;
2977
3278
  }
@@ -3024,24 +3325,31 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3024
3325
  "NO_CHAIN"
3025
3326
  );
3026
3327
  }
3328
+ const requestStartCheckout = async (cartToken) => {
3329
+ const data = await executeGraphQL(
3330
+ graphqlUrl,
3331
+ START_GUEST_CHECKOUT,
3332
+ {
3333
+ websiteId: ctx.websiteId,
3334
+ cartToken,
3335
+ contact: input.contact,
3336
+ fulfillmentFamily: input.fulfillmentFamily,
3337
+ items: input.items.map((item) => ({
3338
+ product_variant_id: item.productVariantId,
3339
+ quantity: item.quantity
3340
+ })),
3341
+ marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
3342
+ },
3343
+ getHeaders
3344
+ );
3345
+ return data.websiteStartGuestCheckout;
3346
+ };
3027
3347
  const existing = cartSessionStorage.get();
3028
- const data = await executeGraphQL(
3029
- graphqlUrl,
3030
- START_GUEST_CHECKOUT,
3031
- {
3032
- websiteId: ctx.websiteId,
3033
- cartToken: existing?.cartToken,
3034
- contact: input.contact,
3035
- fulfillmentFamily: input.fulfillmentFamily,
3036
- items: input.items.map((item) => ({
3037
- product_variant_id: item.productVariantId,
3038
- quantity: item.quantity
3039
- })),
3040
- marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
3041
- },
3042
- getHeaders
3043
- );
3044
- const result = data.websiteStartGuestCheckout;
3348
+ let result = await requestStartCheckout(existing?.cartToken);
3349
+ if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
3350
+ cartSessionStorage.clear();
3351
+ result = await requestStartCheckout();
3352
+ }
3045
3353
  assertActionSuccess(result, "CHECKOUT_START_ERROR");
3046
3354
  cartSessionStorage.set({
3047
3355
  orderId: result.order_id,
@@ -3064,7 +3372,7 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3064
3372
  const session = cartSessionStorage.get();
3065
3373
  if (!session) {
3066
3374
  throw new WebsiteError(
3067
- "Start checkout with contact before syncing cart items to Bacano",
3375
+ "Create or recover a Bacano cart session before syncing cart items",
3068
3376
  "CART_SESSION_MISSING"
3069
3377
  );
3070
3378
  }
@@ -3132,11 +3440,37 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3132
3440
  );
3133
3441
  const result = data.websiteRemoveCartItem;
3134
3442
  assertActionSuccess(result, "CART_ITEM_ERROR");
3443
+ const cart = mapCart(result.cart, result.cart_token);
3444
+ if (cart.items.length === 0) {
3445
+ cartSessionStorage.clear();
3446
+ return cart;
3447
+ }
3135
3448
  cartSessionStorage.set({
3136
3449
  orderId: result.order_id,
3137
3450
  cartToken: result.cart_token
3138
3451
  });
3139
- return mapCart(result.cart, result.cart_token);
3452
+ return cart;
3453
+ },
3454
+ async clear() {
3455
+ const session = cartSessionStorage.get();
3456
+ if (!session) return;
3457
+ const data = await executeGraphQL(
3458
+ graphqlUrl,
3459
+ DISCARD_CART,
3460
+ {
3461
+ orderId: session.orderId,
3462
+ cartToken: session.cartToken
3463
+ },
3464
+ getHeaders
3465
+ );
3466
+ const result = data.websiteDiscardCart;
3467
+ if (!result.success && !hasRecoverableCartSessionError(result.errors)) {
3468
+ throw new WebsiteError(
3469
+ result.errors?.join("; ") || "Cart could not be discarded",
3470
+ getStableCartActionErrorCode(result.errors, "CART_DISCARD_ERROR")
3471
+ );
3472
+ }
3473
+ cartSessionStorage.clear();
3140
3474
  }
3141
3475
  };
3142
3476
  }
@@ -3508,7 +3842,6 @@ function mapRelatedProduct(raw, storageUrl) {
3508
3842
  slug: raw.related_product.slug,
3509
3843
  name: raw.related_product.name,
3510
3844
  sku: raw.related_product.sku,
3511
- displayLabel: raw.display_label,
3512
3845
  sortOrder: raw.sort_order ?? 0,
3513
3846
  media: raw.related_product.product_media.map(
3514
3847
  (m) => mapMedia(m, storageUrl)
@@ -3519,6 +3852,23 @@ function mapProduct(raw, storageUrl, labels, chainId) {
3519
3852
  const detailVariants = raw.product_variants.map((variant) => mapVariant(variant, labels, chainId)).filter((variant) => variant.pricing.currentPrice != null);
3520
3853
  const listingVariantIds = raw.listing_variant_ids ? new Set(raw.listing_variant_ids) : null;
3521
3854
  const variants = listingVariantIds ? detailVariants.filter((variant) => listingVariantIds.has(variant.id)) : detailVariants;
3855
+ const rawOptionGroup = raw.product_option_group_members?.[0]?.group;
3856
+ const optionGroup = rawOptionGroup && rawOptionGroup.members.length >= 2 ? {
3857
+ id: rawOptionGroup.id,
3858
+ title: rawOptionGroup.display_title,
3859
+ members: rawOptionGroup.members.map((member) => ({
3860
+ id: member.id,
3861
+ productId: member.product_id,
3862
+ slug: member.product.slug,
3863
+ name: member.product.name,
3864
+ sku: member.product.sku,
3865
+ displayLabel: member.display_label,
3866
+ sortOrder: member.sort_order ?? 0,
3867
+ media: member.product.product_media.map(
3868
+ (media) => mapMedia(media, storageUrl)
3869
+ )
3870
+ }))
3871
+ } : null;
3522
3872
  return {
3523
3873
  id: raw.id,
3524
3874
  name: raw.name,
@@ -3543,7 +3893,9 @@ function mapProduct(raw, storageUrl, labels, chainId) {
3543
3893
  ),
3544
3894
  relatedProducts: (raw.product_related_products ?? []).map(
3545
3895
  (relation) => mapRelatedProduct(relation, storageUrl)
3546
- )
3896
+ ),
3897
+ optionGroup,
3898
+ specification: raw.product_specification ?? null
3547
3899
  };
3548
3900
  }
3549
3901
  function mapCategory(raw) {
@@ -3874,7 +4226,7 @@ function hasMeaningfulCategoryListFilters(filters) {
3874
4226
  )
3875
4227
  );
3876
4228
  }
3877
- async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId) {
4229
+ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variantIds, chainId, websiteId) {
3878
4230
  const uniqueVariantIds = uniqueValues2(variantIds);
3879
4231
  const availabilityByVariantId = /* @__PURE__ */ new Map();
3880
4232
  for (const chunk of chunkArray2(
@@ -3884,7 +4236,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3884
4236
  const data = await executeGraphQL(
3885
4237
  graphqlUrl,
3886
4238
  CHECK_AVAILABILITY,
3887
- { variantIds: chunk, chainId },
4239
+ { variantIds: chunk, chainId, websiteId },
3888
4240
  getHeaders
3889
4241
  );
3890
4242
  for (const item of data.websiteProductAvailability ?? []) {
@@ -3895,7 +4247,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3895
4247
  }
3896
4248
  return availabilityByVariantId;
3897
4249
  }
3898
- async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4250
+ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3899
4251
  const variantIds = products.flatMap(
3900
4252
  (product) => product.variants.map((variant) => variant.id)
3901
4253
  );
@@ -3906,7 +4258,8 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3906
4258
  graphqlUrl,
3907
4259
  getHeaders,
3908
4260
  variantIds,
3909
- chainId
4261
+ chainId,
4262
+ websiteId
3910
4263
  )).entries()
3911
4264
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3912
4265
  );
@@ -3924,7 +4277,7 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3924
4277
  )
3925
4278
  })).filter((product) => product.variants.length > 0);
3926
4279
  }
3927
- async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId) {
4280
+ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3928
4281
  const variantIds = products.flatMap(
3929
4282
  (product) => product.product_variants.map((variant) => variant.id)
3930
4283
  );
@@ -3935,7 +4288,8 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
3935
4288
  graphqlUrl,
3936
4289
  getHeaders,
3937
4290
  variantIds,
3938
- chainId
4291
+ chainId,
4292
+ websiteId
3939
4293
  )).entries()
3940
4294
  ).filter(([, item]) => item.inStock).map(([variantId]) => variantId)
3941
4295
  );
@@ -3966,6 +4320,32 @@ function mapProductSeoEntry(raw, storageUrl) {
3966
4320
  };
3967
4321
  }
3968
4322
  function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4323
+ const loadProductSpecifications = async (productIds) => {
4324
+ const ctx = getContext();
4325
+ const uniqueProductIds = [...new Set(productIds)];
4326
+ const specifications = {};
4327
+ for (let offset = 0; offset < uniqueProductIds.length; offset += 100) {
4328
+ const batch = uniqueProductIds.slice(offset, offset + 100);
4329
+ try {
4330
+ const data = await executeGraphQL(
4331
+ graphqlUrl,
4332
+ WEBSITE_GET_PRODUCT_SPECIFICATIONS,
4333
+ { websiteId: ctx.websiteId, productIds: batch },
4334
+ getHeaders
4335
+ );
4336
+ if (!data.result?.success) continue;
4337
+ Object.assign(
4338
+ specifications,
4339
+ parseActionJson(
4340
+ data.result.specifications,
4341
+ {}
4342
+ )
4343
+ );
4344
+ } catch {
4345
+ }
4346
+ }
4347
+ return specifications;
4348
+ };
3969
4349
  const mapSingleProductResult = async (data) => {
3970
4350
  const ctx = getContext();
3971
4351
  if (!ctx.chainId) {
@@ -3989,12 +4369,15 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3989
4369
  graphqlUrl,
3990
4370
  getHeaders,
3991
4371
  [mappedProduct],
3992
- ctx.chainId
4372
+ ctx.chainId,
4373
+ ctx.websiteId
3993
4374
  );
3994
4375
  if (!filteredProducts[0]) return null;
4376
+ const specifications = await loadProductSpecifications([product.id]);
3995
4377
  return {
3996
4378
  ...mappedProduct,
3997
- variants: getProductDetailVariants(mappedProduct)
4379
+ variants: getProductDetailVariants(mappedProduct),
4380
+ specification: specifications[product.id] ?? null
3998
4381
  };
3999
4382
  };
4000
4383
  const loadCatalogPage = async (opts, retryRateLimits = false) => {
@@ -4015,6 +4398,19 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4015
4398
  min_price: opts.minPrice,
4016
4399
  max_price: opts.maxPrice,
4017
4400
  attributes_by_slug: opts.attributesBySlug,
4401
+ technical_by_field: opts.technicalByField ? Object.fromEntries(
4402
+ Object.entries(opts.technicalByField).map(([fieldId, filter]) => [
4403
+ fieldId,
4404
+ {
4405
+ option_ids: filter.optionIds,
4406
+ boolean_values: filter.booleanValues,
4407
+ min: filter.min,
4408
+ max: filter.max,
4409
+ date_from: filter.dateFrom,
4410
+ date_to: filter.dateTo
4411
+ }
4412
+ ])
4413
+ ) : void 0,
4018
4414
  sort_by: opts.sortBy,
4019
4415
  limit: opts.limit,
4020
4416
  offset: opts.offset
@@ -4206,7 +4602,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4206
4602
  graphqlUrl,
4207
4603
  getHeaders,
4208
4604
  data.products ?? [],
4209
- ctx.chainId
4605
+ ctx.chainId,
4606
+ ctx.websiteId
4210
4607
  );
4211
4608
  const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
4212
4609
  return {
@@ -4282,8 +4679,16 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4282
4679
  (data.products ?? []).map(
4283
4680
  (p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
4284
4681
  ),
4285
- chainId
4682
+ chainId,
4683
+ ctx.websiteId
4684
+ );
4685
+ const specifications = await loadProductSpecifications(
4686
+ products.map((product) => product.id)
4286
4687
  );
4688
+ products = products.map((product) => ({
4689
+ ...product,
4690
+ specification: specifications[product.id] ?? null
4691
+ }));
4287
4692
  if (opts?.minPrice !== void 0 || opts?.maxPrice !== void 0) {
4288
4693
  products = products.filter(
4289
4694
  (product) => product.variants.some((variant) => {
@@ -4394,7 +4799,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4394
4799
  graphqlUrl,
4395
4800
  getHeaders,
4396
4801
  variantIds,
4397
- chainId
4802
+ chainId,
4803
+ ctx.websiteId
4398
4804
  );
4399
4805
  const applicableCategories = /* @__PURE__ */ new Set();
4400
4806
  const applicableSubcategoryIds = /* @__PURE__ */ new Set();
@@ -4512,7 +4918,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4512
4918
  graphqlUrl,
4513
4919
  getHeaders,
4514
4920
  variantIds,
4515
- chainId
4921
+ chainId,
4922
+ ctx.websiteId
4516
4923
  );
4517
4924
  const publicAttributeLabels = buildPublicAttributeLabels(
4518
4925
  data.catalog_attribute_lists
@@ -4617,7 +5024,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4617
5024
  min: minPrice,
4618
5025
  max: maxPrice
4619
5026
  },
4620
- attributes
5027
+ attributes,
5028
+ technical: []
4621
5029
  };
4622
5030
  },
4623
5031
  async getSearchSuggestions(opts) {
@@ -4671,6 +5079,14 @@ function mapCartItem2(raw) {
4671
5079
  id: raw.id,
4672
5080
  productVariantId: raw.product_variant_id,
4673
5081
  quantity: raw.quantity,
5082
+ unitPrice: raw.unit_price ?? null,
5083
+ lineTotal: raw.line_total ?? null,
5084
+ baseUnitPrice: raw.base_unit_price ?? raw.unit_price ?? null,
5085
+ unitSavings: raw.unit_savings ?? 0,
5086
+ lineSavings: raw.line_savings ?? 0,
5087
+ volumePricingEligible: raw.volume_pricing_eligible ?? false,
5088
+ resolvedPriceType: raw.resolved_price_type ?? null,
5089
+ volumePricingMinQuantity: raw.volume_pricing_min_quantity ?? null,
4674
5090
  productVariant: {
4675
5091
  sku: raw.product_variant.sku,
4676
5092
  product: raw.product_variant.product,
@@ -4695,7 +5111,20 @@ function mapCart2(raw) {
4695
5111
  },
4696
5112
  status: raw.status,
4697
5113
  createdAt: raw.created_at,
4698
- updatedAt: raw.updated_at
5114
+ updatedAt: raw.updated_at,
5115
+ volumePricing: raw.volume_pricing ? {
5116
+ ...raw.volume_pricing,
5117
+ totalSavings: raw.volume_pricing.totalSavings ?? 0
5118
+ } : {
5119
+ enabled: false,
5120
+ eligibleQuantity: 0,
5121
+ appliedPriceType: null,
5122
+ appliedMinQuantity: null,
5123
+ nextPriceType: null,
5124
+ nextMinQuantity: null,
5125
+ remainingQuantity: null,
5126
+ totalSavings: 0
5127
+ }
4699
5128
  };
4700
5129
  }
4701
5130
  function mapCouponResult(raw) {
@@ -6271,7 +6700,8 @@ function createBacanoClient(config) {
6271
6700
  setItem: cartModule.setItem.bind(cartModule),
6272
6701
  addItem: cartModule.addItem.bind(cartModule),
6273
6702
  updateItem: cartModule.updateItem.bind(cartModule),
6274
- removeItem: cartModule.removeItem.bind(cartModule)
6703
+ removeItem: cartModule.removeItem.bind(cartModule),
6704
+ clear: cartModule.clear.bind(cartModule)
6275
6705
  },
6276
6706
  checkout: {
6277
6707
  getLocationOptions: checkoutModule.getLocationOptions.bind(checkoutModule),