@moonbase.sh/storefront-api 2.5.0 → 3.1.0

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/dist/index.cjs CHANGED
@@ -25,10 +25,12 @@ __export(index_exports, {
25
25
  ActivationRequestStatus: () => ActivationRequestStatus,
26
26
  ActivationStatus: () => ActivationStatus,
27
27
  Architecture: () => Architecture,
28
+ CartUtils: () => CartUtils,
28
29
  ConnectableAccountProvider: () => ConnectableAccountProvider,
29
30
  ConsoleLogger: () => ConsoleLogger,
30
31
  CycleLength: () => CycleLength,
31
32
  DiscountUtils: () => DiscountUtils,
33
+ ImageUtils: () => ImageUtils,
32
34
  InMemoryStore: () => InMemoryStore,
33
35
  LicenseStatus: () => LicenseStatus,
34
36
  LocalStorageStore: () => LocalStorageStore,
@@ -46,9 +48,14 @@ __export(index_exports, {
46
48
  Platform: () => Platform,
47
49
  SubscriptionStatus: () => SubscriptionStatus,
48
50
  TokenStore: () => TokenStore,
51
+ imageWidths: () => imageWidths,
52
+ isPromotionDisplay: () => isPromotionDisplay,
53
+ isTransformableImageUrl: () => isTransformableImageUrl,
54
+ maxImageDimension: () => maxImageDimension,
49
55
  objectToQuery: () => objectToQuery,
50
56
  parseOffSiteReferrer: () => parseOffSiteReferrer,
51
57
  problemDetailsSchema: () => problemDetailsSchema,
58
+ promotionDisplays: () => promotionDisplays,
52
59
  resolveUtm: () => resolveUtm,
53
60
  schemas: () => schemas_exports12,
54
61
  utmToObject: () => utmToObject
@@ -69,10 +76,14 @@ var import_zod4 = require("zod");
69
76
  var schemas_exports2 = {};
70
77
  __export(schemas_exports2, {
71
78
  offerConditionSchema: () => offerConditionSchema,
79
+ offerScopeSchema: () => offerScopeSchema,
72
80
  storefrontBundleSchema: () => storefrontBundleSchema,
73
81
  storefrontOfferSchema: () => storefrontOfferSchema,
74
82
  storefrontOfferTargetSchema: () => storefrontOfferTargetSchema,
75
83
  storefrontProductSchema: () => storefrontProductSchema,
84
+ storefrontPromotionCtaSchema: () => storefrontPromotionCtaSchema,
85
+ storefrontPromotionSchema: () => storefrontPromotionSchema,
86
+ storefrontPromotionTargetSchema: () => storefrontPromotionTargetSchema,
76
87
  storefrontSchema: () => storefrontSchema,
77
88
  taxEstimateSchema: () => taxEstimateSchema
78
89
  });
@@ -112,6 +123,11 @@ function utmToObject(utm) {
112
123
 
113
124
  // src/globalSchemas.ts
114
125
  var priceCollectionSchema = import_zod.z.record(import_zod.z.number());
126
+ var discountPromotionFields = {
127
+ promotionId: import_zod.z.string().optional(),
128
+ validFrom: import_zod.z.string().optional(),
129
+ validUntil: import_zod.z.string().optional()
130
+ };
115
131
  var percentageOffDiscountSchema = import_zod.z.object({
116
132
  type: import_zod.z.literal("PercentageOffDiscount"),
117
133
  name: import_zod.z.string(),
@@ -119,7 +135,8 @@ var percentageOffDiscountSchema = import_zod.z.object({
119
135
  percentage: import_zod.z.number(),
120
136
  total: priceCollectionSchema.optional(),
121
137
  isExclusive: import_zod.z.boolean(),
122
- recurringPaymentUseCount: import_zod.z.number().optional()
138
+ recurringPaymentUseCount: import_zod.z.number().optional(),
139
+ ...discountPromotionFields
123
140
  });
124
141
  var flatAmountOffDiscountSchema = import_zod.z.object({
125
142
  type: import_zod.z.literal("FlatAmountOffDiscount"),
@@ -127,7 +144,8 @@ var flatAmountOffDiscountSchema = import_zod.z.object({
127
144
  description: import_zod.z.string().optional(),
128
145
  total: priceCollectionSchema.optional(),
129
146
  isExclusive: import_zod.z.boolean(),
130
- recurringPaymentUseCount: import_zod.z.number().optional()
147
+ recurringPaymentUseCount: import_zod.z.number().optional(),
148
+ ...discountPromotionFields
131
149
  });
132
150
  var moneySchema = import_zod.z.object({
133
151
  currency: import_zod.z.string(),
@@ -161,6 +179,7 @@ var rawPropertyValueSchema = import_zod.z.lazy(
161
179
  import_zod.z.string(),
162
180
  import_zod.z.number(),
163
181
  import_zod.z.boolean(),
182
+ import_zod.z.null(),
164
183
  import_zod.z.record(rawPropertyValueSchema),
165
184
  import_zod.z.array(rawPropertyValueSchema)
166
185
  ])
@@ -247,6 +266,10 @@ var productSummarySchema = import_zod2.z.object({
247
266
  releaseDescription: import_zod2.z.string().optional(),
248
267
  downloadsNeedsUser: import_zod2.z.boolean(),
249
268
  downloadsNeedsOwnership: import_zod2.z.boolean(),
269
+ downloadsNeedsGroupMembership: import_zod2.z.boolean(),
270
+ // Whether this caller clears every requirement above, including the group membership the client
271
+ // can't see for itself.
272
+ downloadsAllowed: import_zod2.z.boolean(),
250
273
  downloads: import_zod2.z.array(downloadSchema).optional()
251
274
  });
252
275
 
@@ -287,7 +310,22 @@ var cartContainsItemsConditionSchema = import_zod3.z.object({
287
310
  minimumItems: import_zod3.z.number(),
288
311
  relevantItemVariations: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string().array())
289
312
  });
290
- var offerConditionSchema = import_zod3.z.discriminatedUnion("type", [cartContainsItemsConditionSchema]);
313
+ var cartTotalConditionSchema = import_zod3.z.object({
314
+ type: import_zod3.z.literal("CartTotal"),
315
+ // Both bounds are optional, but never both absent. They serialize as an explicit `null` when
316
+ // unset, hence `.nullish()` rather than `.optional()`. The API converts them into every enabled
317
+ // currency up front, precisely so we can tell the buyer how much further they have to go without
318
+ // doing any currency conversion ourselves.
319
+ minimumTotal: priceCollectionSchema.nullish(),
320
+ maximumTotal: priceCollectionSchema.nullish()
321
+ });
322
+ var knownOfferConditionSchema = import_zod3.z.discriminatedUnion("type", [
323
+ cartContainsItemsConditionSchema,
324
+ cartTotalConditionSchema
325
+ ]);
326
+ var unsupportedOfferConditionSchema = import_zod3.z.object({ type: import_zod3.z.string() }).passthrough().transform(({ type }) => ({ type: "Unsupported", unsupportedType: type }));
327
+ var offerConditionSchema = import_zod3.z.union([knownOfferConditionSchema, unsupportedOfferConditionSchema]);
328
+ var offerScopeSchema = import_zod3.z.enum(["Item", "Cart"]).catch("Item").optional().default("Item");
291
329
  var offerTargetItemSchema = import_zod3.z.union([storefrontProductSchema, storefrontBundleSchema]);
292
330
  var storefrontOfferTargetSchema = import_zod3.z.object({
293
331
  // The product or bundle the offer applies to.
@@ -303,8 +341,10 @@ function legacyOfferTargets(target, targetVariations) {
303
341
  }
304
342
  var storefrontOfferSchema = import_zod3.z.object({
305
343
  id: import_zod3.z.string(),
344
+ scope: offerScopeSchema,
306
345
  // Each target pairs a product/bundle with the pricing variations the offer applies to (an empty
307
- // list means all variations). This is the model to consume.
346
+ // list means all variations). This is the model to consume. A cart offer may have no targets at
347
+ // all, which means it discounts every line item.
308
348
  targets: import_zod3.z.array(storefrontOfferTargetSchema).optional(),
309
349
  // Legacy fields, kept optional only so old cached storefront responses still parse. The API now
310
350
  // exposes the unified `targets` model; we derive `targets` from these when a response predates it.
@@ -318,6 +358,57 @@ var storefrontOfferSchema = import_zod3.z.object({
318
358
  ...rest,
319
359
  targets: targets != null ? targets : legacyOfferTargets(target, targetVariations)
320
360
  }));
361
+ var storefrontPromotionTargetSchema = import_zod3.z.object({
362
+ // A prefixed reference to the item the sale covers, `Product/<id>` or `Bundle/<id>`. The item
363
+ // itself is already serialized in `products`/`bundles`; embedding it again would duplicate the
364
+ // bulk of the payload for a sale spanning dozens of products.
365
+ referenceId: import_zod3.z.string(),
366
+ // The pricing variation IDs the sale is restricted to; an empty list means all variations.
367
+ variations: import_zod3.z.string().array()
368
+ });
369
+ var storefrontPromotionCtaSchema = import_zod3.z.object({
370
+ url: import_zod3.z.string(),
371
+ label: import_zod3.z.string()
372
+ });
373
+ var storefrontPromotionSchema = import_zod3.z.object({
374
+ id: import_zod3.z.string(),
375
+ name: import_zod3.z.string(),
376
+ description: import_zod3.z.string().nullish(),
377
+ imageUrl: import_zod3.z.string().nullish(),
378
+ // Absent when the merchant has not set one, in which case the storefront decides whether the
379
+ // sale leads anywhere.
380
+ cta: storefrontPromotionCtaSchema.nullish(),
381
+ // How the merchant wants the sale surfaced, e.g. `['Banner', 'Popup']`; an empty list means no
382
+ // preference. Kept as free-form strings and narrowed with `isPromotionDisplay`, so a display
383
+ // mode added server-side later is ignored rather than failing the whole storefront.
384
+ display: import_zod3.z.string().array(),
385
+ // The promotion's *intended* scope, not what is actually showing the sale price: each variation
386
+ // displays only its single best discount, so a targeted item may be showing a better one. The
387
+ // authoritative signal is `promotionId` on a variation's own discount.
388
+ targets: storefrontPromotionTargetSchema.array(),
389
+ // The promotion's own discount, for banner copy ("20% off"). Per-item prices are already
390
+ // discounted on each variation.
391
+ discount: discountSchema,
392
+ validFrom: import_zod3.z.string().nullish(),
393
+ validUntil: import_zod3.z.string().nullish(),
394
+ properties: import_zod3.z.record(rawPropertyValueSchema).nullish()
395
+ });
396
+ var storefrontOffersSchema = import_zod3.z.array(import_zod3.z.unknown()).transform((offers) => offers.flatMap((offer) => {
397
+ const parsed = storefrontOfferSchema.safeParse(offer);
398
+ if (!parsed.success) {
399
+ console.warn("Ignoring storefront offer that could not be parsed:", parsed.error.issues);
400
+ return [];
401
+ }
402
+ return [parsed.data];
403
+ }));
404
+ var storefrontPromotionsSchema = import_zod3.z.array(import_zod3.z.unknown()).transform((promotions) => promotions.flatMap((promotion) => {
405
+ const parsed = storefrontPromotionSchema.safeParse(promotion);
406
+ if (!parsed.success) {
407
+ console.warn("Ignoring storefront promotion that could not be parsed:", parsed.error.issues);
408
+ return [];
409
+ }
410
+ return [parsed.data];
411
+ }));
321
412
  var taxEstimateSchema = import_zod3.z.object({
322
413
  rate: import_zod3.z.number(),
323
414
  mode: import_zod3.z.enum(["Exclusive", "Inclusive"]),
@@ -331,7 +422,9 @@ var storefrontSchema = import_zod3.z.object({
331
422
  products: storefrontProductSchema.array(),
332
423
  bundles: storefrontBundleSchema.array(),
333
424
  // Offers need to be optional since we may still have old, cached representations in browsers
334
- offers: storefrontOfferSchema.array().optional(),
425
+ offers: storefrontOffersSchema.optional(),
426
+ // Promotions need to be optional since we may still have old, cached representations in browsers
427
+ promotions: storefrontPromotionsSchema.optional(),
335
428
  // Depending on circumstances, we may have a tax estimate available
336
429
  estimatedTax: taxEstimateSchema.optional()
337
430
  });
@@ -1077,6 +1170,7 @@ var import_zod13 = require("zod");
1077
1170
  var schemas_exports7 = {};
1078
1171
  __export(schemas_exports7, {
1079
1172
  completedOrderSchema: () => completedOrderSchema,
1173
+ lineItemTotalSchema: () => lineItemTotalSchema,
1080
1174
  openBundleLineItem: () => openBundleLineItem,
1081
1175
  openOrderLineItem: () => openOrderLineItem,
1082
1176
  openOrderSchema: () => openOrderSchema,
@@ -1112,7 +1206,10 @@ var lineItemTotalSchema = import_zod12.z.object({
1112
1206
  original: moneySchema,
1113
1207
  discount: moneySchema,
1114
1208
  subtotal: moneySchema,
1115
- due: moneySchema
1209
+ due: moneySchema,
1210
+ // This line's per-unit share of the order's cart offer, kept apart from the offer the buyer picked
1211
+ // for this item. Both are part of `discount`. Absent on orders priced before cart offers existed.
1212
+ cartOfferDiscount: moneySchema.optional()
1116
1213
  });
1117
1214
  var openProductLineItem = import_zod12.z.object({
1118
1215
  id: import_zod12.z.string(),
@@ -1164,7 +1261,10 @@ var orderTotalSchema = import_zod12.z.object({
1164
1261
  discount: moneySchema,
1165
1262
  subtotal: moneySchema,
1166
1263
  taxes: moneySchema,
1167
- due: moneySchema
1264
+ due: moneySchema,
1265
+ // How much of `discount` came from the cart offer, so it can be shown as one order-wide saving
1266
+ // rather than a share on every line. Absent when no cart offer applied.
1267
+ cartOfferDiscount: moneySchema.optional()
1168
1268
  });
1169
1269
  var openOrderSchema = import_zod12.z.object({
1170
1270
  id: import_zod12.z.string(),
@@ -1172,6 +1272,12 @@ var openOrderSchema = import_zod12.z.object({
1172
1272
  total: orderTotalSchema.nullish(),
1173
1273
  items: openOrderLineItem.array(),
1174
1274
  couponsApplied: couponSchema.array(),
1275
+ // The cart offer on the order as a whole: the buyer's intent. It is part of the cart payload, so
1276
+ // a content push that leaves it out clears it. The API echoes it back even while the cart sits
1277
+ // below the offer's threshold.
1278
+ offerId: import_zod12.z.string().optional(),
1279
+ // The cart offer as actually applied: the result. Absent while the offer's condition doesn't hold.
1280
+ appliedOffer: discountSchema.optional(),
1175
1281
  checkoutUrl: import_zod12.z.string().optional(),
1176
1282
  hostedCheckoutUrl: import_zod12.z.string().optional(),
1177
1283
  embeddedCheckoutUrl: import_zod12.z.string().optional()
@@ -1191,7 +1297,8 @@ var completedOrderSchema = import_zod12.z.object({
1191
1297
  customer: customerSnapshotSchema,
1192
1298
  total: orderTotalSchema,
1193
1299
  items: openOrderLineItem.array(),
1194
- couponsApplied: couponSchema.array()
1300
+ couponsApplied: couponSchema.array(),
1301
+ appliedOffer: discountSchema.optional()
1195
1302
  });
1196
1303
  var orderSchema = import_zod12.z.discriminatedUnion("status", [
1197
1304
  openOrderSchema.extend({
@@ -1327,6 +1434,9 @@ var OrderEndpoints = class {
1327
1434
  if (options.checkout.returnUrl) {
1328
1435
  query.append("returnUrl", options.checkout.returnUrl);
1329
1436
  }
1437
+ if (options.checkout.embedded !== void 0) {
1438
+ query.append("embedded", options.checkout.embedded ? "true" : "false");
1439
+ }
1330
1440
  }
1331
1441
  const response = await this.api.fetch(`/api/customer/orders/${orderId}?${query.toString()}`, orderSchema, {
1332
1442
  abort: options == null ? void 0 : options.abort
@@ -1341,6 +1451,8 @@ var OrderEndpoints = class {
1341
1451
  query.checkout = "true";
1342
1452
  if (checkout.returnUrl)
1343
1453
  query.returnUrl = checkout.returnUrl;
1454
+ if (checkout.embedded !== void 0)
1455
+ query.embedded = checkout.embedded ? "true" : "false";
1344
1456
  }
1345
1457
  const response = await this.api.fetch(
1346
1458
  `/api/customer/orders/${order.id}?${new URLSearchParams(query).toString()}`,
@@ -1349,6 +1461,7 @@ var OrderEndpoints = class {
1349
1461
  method: "PATCH",
1350
1462
  body: {
1351
1463
  currency: order.currency || void 0,
1464
+ offerId: order.offerId,
1352
1465
  items: order.items
1353
1466
  }
1354
1467
  }
@@ -1363,6 +1476,12 @@ var OrderEndpoints = class {
1363
1476
  const response = await this.api.fetch(`/api/customer/orders/${orderId}/billing-details`, openOrderSchema, { method: "DELETE" });
1364
1477
  return response.data;
1365
1478
  }
1479
+ // Takes the cart offer off the order without re-sending the cart. Offers picked for a single item
1480
+ // come off by pushing that item without its `offerId` instead.
1481
+ async removeOffer(orderId) {
1482
+ const response = await this.api.fetch(`/api/customer/orders/${orderId}/offer`, openOrderSchema, { method: "DELETE" });
1483
+ return response.data;
1484
+ }
1366
1485
  async addCouponCode(orderId, code) {
1367
1486
  const response = await this.api.fetch(`/api/customer/orders/${orderId}/coupon?code=${encodeURIComponent(code)}`, openOrderSchema, { method: "PATCH" });
1368
1487
  return response.data;
@@ -1667,6 +1786,12 @@ __export(schemas_exports11, {
1667
1786
  subscriptions: () => schemas_exports8
1668
1787
  });
1669
1788
 
1789
+ // src/storefront/models.ts
1790
+ var promotionDisplays = ["Banner", "Popup"];
1791
+ function isPromotionDisplay(value) {
1792
+ return promotionDisplays.includes(value);
1793
+ }
1794
+
1670
1795
  // src/utils/discount.ts
1671
1796
  var DiscountUtils = class {
1672
1797
  static apply(discount, price) {
@@ -1693,25 +1818,68 @@ var MoneyCollectionUtils = class {
1693
1818
  };
1694
1819
 
1695
1820
  // src/utils/offer.ts
1821
+ function evaluated(unit, currency, current, target, eligible) {
1822
+ return {
1823
+ eligible,
1824
+ progress: {
1825
+ unit,
1826
+ currency,
1827
+ current,
1828
+ target,
1829
+ remaining: Math.max(0, target - current),
1830
+ fraction: target <= 0 ? 1 : Math.min(1, Math.max(0, current / target)),
1831
+ eligible
1832
+ }
1833
+ };
1834
+ }
1696
1835
  var OfferUtils = class _OfferUtils {
1697
- static eligible(offer, order) {
1698
- switch (offer.condition.type) {
1836
+ // Callers that only have an order keep working and simply have no cart total, so a monetary
1837
+ // condition fails closed for them. Pass the full context to have `CartTotal` actually evaluate.
1838
+ static eligible(offer, context) {
1839
+ return _OfferUtils.evaluate(offer, context).eligible;
1840
+ }
1841
+ // How close the cart is to satisfying the offer, or `null` when there is nothing to show progress
1842
+ // toward: an unsupported condition, an unknown cart total, or a condition with only an upper
1843
+ // bound (which the buyer starts out satisfying rather than working toward).
1844
+ static progress(offer, context) {
1845
+ return _OfferUtils.evaluate(offer, context).progress;
1846
+ }
1847
+ // The single evaluator behind both faces, so the "spend €12 more" nudge and the gate that unlocks
1848
+ // the discount can never disagree about the same cart.
1849
+ static evaluate(offer, context) {
1850
+ var _a, _b;
1851
+ const { order, cartTotal } = "items" in context ? { order: context, cartTotal: void 0 } : context;
1852
+ const condition = offer.condition;
1853
+ switch (condition.type) {
1699
1854
  case "CartContainsItems": {
1700
1855
  const relevantItems = order.items.filter(
1701
- (i) => i.type === "Product" && offer.condition.relevantItemVariations[`Product/${i.productId}`] && offer.condition.relevantItemVariations[`Product/${i.productId}`].includes(i.variationId) || i.type === "Bundle" && offer.condition.relevantItemVariations[`Bundle/${i.bundleId}`] && offer.condition.relevantItemVariations[`Bundle/${i.bundleId}`].includes(i.variationId)
1856
+ (i) => i.type === "Product" && condition.relevantItemVariations[`Product/${i.productId}`] && condition.relevantItemVariations[`Product/${i.productId}`].includes(i.variationId) || i.type === "Bundle" && condition.relevantItemVariations[`Bundle/${i.bundleId}`] && condition.relevantItemVariations[`Bundle/${i.bundleId}`].includes(i.variationId)
1702
1857
  );
1703
1858
  const relevantReplacedItems = order.items.flatMap((i) => {
1704
- var _a;
1705
- return offer.id === i.offerId && i.type === "Bundle" && _OfferUtils.targetContainsBundle(offer, i.id) ? (_a = i.replaced) != null ? _a : [] : [];
1859
+ var _a2;
1860
+ return offer.id === i.offerId && i.type === "Bundle" && _OfferUtils.targetContainsBundle(offer, i.id) ? (_a2 = i.replaced) != null ? _a2 : [] : [];
1706
1861
  }).filter((ref) => {
1707
1862
  const [productId, variationId] = ref.split("/");
1708
- return offer.condition.relevantItemVariations[`Product/${productId}`] && offer.condition.relevantItemVariations[`Product/${productId}`].includes(variationId);
1863
+ return condition.relevantItemVariations[`Product/${productId}`] && condition.relevantItemVariations[`Product/${productId}`].includes(variationId);
1709
1864
  });
1710
- return relevantItems.length + relevantReplacedItems.length >= offer.condition.minimumItems;
1865
+ const current = relevantItems.length + relevantReplacedItems.length;
1866
+ return evaluated("Items", void 0, current, condition.minimumItems, current >= condition.minimumItems);
1867
+ }
1868
+ case "CartTotal": {
1869
+ if (!cartTotal)
1870
+ return { eligible: false, progress: null };
1871
+ const minimum = (_a = condition.minimumTotal) == null ? void 0 : _a[cartTotal.currency];
1872
+ const maximum = (_b = condition.maximumTotal) == null ? void 0 : _b[cartTotal.currency];
1873
+ if (minimum === void 0 && maximum === void 0)
1874
+ return { eligible: false, progress: null };
1875
+ const eligible = (minimum === void 0 || cartTotal.amount >= minimum) && (maximum === void 0 || cartTotal.amount <= maximum);
1876
+ if (minimum === void 0)
1877
+ return { eligible, progress: null };
1878
+ return evaluated("Money", cartTotal.currency, cartTotal.amount, minimum, eligible);
1711
1879
  }
1712
1880
  }
1713
- console.warn("Unsupported offer condition found:", offer.condition);
1714
- return false;
1881
+ console.warn("Unsupported offer condition found:", condition);
1882
+ return { eligible: false, progress: null };
1715
1883
  }
1716
1884
  // The relevant targets of an offer with more than a single target needs to:
1717
1885
  // a. Not be owned, or all targets need to be owned
@@ -1719,10 +1887,29 @@ var OfferUtils = class _OfferUtils {
1719
1887
  static relevantTargets(offer, cartItems) {
1720
1888
  if (offer.targets.length > 1) {
1721
1889
  const allOwned = offer.targets.every((t) => t.item.owned);
1722
- return offer.targets.filter((t) => (allOwned || !t.item.owned) && !cartItems.some((l) => l.type === t.item.type && t.item.id === (l.type === "Product" ? l.productId : l.bundleId)));
1890
+ return offer.targets.filter((t) => (allOwned || !t.item.owned) && !_OfferUtils.targetInCart(t, cartItems));
1723
1891
  }
1724
1892
  return offer.targets;
1725
1893
  }
1894
+ /**
1895
+ * Whether every item an offer targets is already in the cart, so the offer has no upsell left to
1896
+ * show.
1897
+ *
1898
+ * This is the set of offers the cart hides: a card inviting the buyer to add what they have already
1899
+ * added says nothing, so it is dropped. Which makes it exactly the set whose discount nothing can
1900
+ * currently claim: an offer is only good for one line, and there is no card left to spend it from.
1901
+ *
1902
+ * An offer with no targets names nothing and so is never in this set.
1903
+ */
1904
+ static allTargetsInCart(offer, cartItems) {
1905
+ return offer.targets.length > 0 && offer.targets.every((target) => _OfferUtils.targetInCart(target, cartItems));
1906
+ }
1907
+ // Matched on the item alone, not the variation: a target narrowed to one variation still counts as
1908
+ // in the cart when another variation of it is. That is the reading the cart's own upsell has always
1909
+ // taken, and the two have to agree or an offer could be hidden and auto-applied at once.
1910
+ static targetInCart(target, cartItems) {
1911
+ return cartItems.some((l) => l.type === target.item.type && target.item.id === (l.type === "Product" ? l.productId : l.bundleId));
1912
+ }
1726
1913
  // The pricing variations a target restricts the offer to. An empty (or missing) variation list
1727
1914
  // means the offer is not narrowed to specific variations, in which case we keep the established
1728
1915
  // single default-variation upsell.
@@ -1752,6 +1939,428 @@ var OfferUtils = class _OfferUtils {
1752
1939
  }
1753
1940
  };
1754
1941
 
1942
+ // src/utils/cart.ts
1943
+ var _CartUtils = class _CartUtils {
1944
+ // The pricing variation a line item points at, falling back to the item's default variation when
1945
+ // the variation id doesn't resolve, which is what the cart has always displayed.
1946
+ static resolveVariation(catalog, item) {
1947
+ var _a, _b;
1948
+ const source = item.type === "Product" ? catalog.products.find((p) => p.id === item.productId) : catalog.bundles.find((b) => b.id === item.bundleId);
1949
+ return (_b = (_a = source == null ? void 0 : source.variations) == null ? void 0 : _a.find((v) => v.id === item.variationId)) != null ? _b : source == null ? void 0 : source.defaultVariation;
1950
+ }
1951
+ // A line's unit price after its product discount and any pricing tier, and *before* any offer.
1952
+ // This is the basis the API measures cart total conditions on, which is why it reads the catalog
1953
+ // rather than the line item's own `price`: `enrichLineItems` rewrites that to the offer-discounted
1954
+ // price, and reading it here would take the offer off twice.
1955
+ static unitPrice(catalog, item, currency) {
1956
+ var _a;
1957
+ return (_a = _CartUtils.unitPricing(catalog, item, currency)) == null ? void 0 : _a.price;
1958
+ }
1959
+ // A line's list and post-product-discount unit prices, from whichever pricing tier applies. Both
1960
+ // come from the same tier so they can be subtracted from one another: taking the list price off one
1961
+ // tier and the paid price off another would invent a discount neither of them offers.
1962
+ static unitPricing(catalog, item, currency) {
1963
+ var _a, _b, _c, _d, _e, _f;
1964
+ const variation = _CartUtils.resolveVariation(catalog, item);
1965
+ const price = variation == null ? void 0 : variation.price[currency];
1966
+ if (price === void 0 || variation === void 0)
1967
+ return void 0;
1968
+ const eligiblePricingTiers = (_b = (_a = variation.pricingTiers) == null ? void 0 : _a.filter((tier) => item.quantity >= tier.minQuantity)) != null ? _b : [];
1969
+ const matchingPricingTier = eligiblePricingTiers.length > 0 ? eligiblePricingTiers[eligiblePricingTiers.length - 1] : null;
1970
+ const tierPrice = matchingPricingTier == null ? void 0 : matchingPricingTier.price[currency];
1971
+ if (tierPrice !== void 0 && tierPrice < price) {
1972
+ return {
1973
+ price: tierPrice,
1974
+ originalPrice: (_d = (_c = matchingPricingTier == null ? void 0 : matchingPricingTier.originalPrice) == null ? void 0 : _c[currency]) != null ? _d : tierPrice
1975
+ };
1976
+ }
1977
+ return { price, originalPrice: (_f = (_e = variation.originalPrice) == null ? void 0 : _e[currency]) != null ? _f : price };
1978
+ }
1979
+ // The number the API measures cart conditions against: each line's price after its product
1980
+ // discount, times quantity, before offers and coupons. `undefined` when any line's price can't be
1981
+ // resolved in `currency`. One unreadable line makes the whole measurement a guess, and a guess
1982
+ // must not unlock an offer.
1983
+ static measure(catalog, items, currency) {
1984
+ if (!currency)
1985
+ return void 0;
1986
+ let amount = 0;
1987
+ for (const item of items) {
1988
+ const unitPrice = _CartUtils.unitPrice(catalog, item, currency);
1989
+ if (unitPrice === void 0)
1990
+ return void 0;
1991
+ amount += unitPrice * item.quantity;
1992
+ }
1993
+ return { currency, amount };
1994
+ }
1995
+ static conditionContext(catalog, order, currency) {
1996
+ return { order, cartTotal: _CartUtils.measure(catalog, order.items, currency) };
1997
+ }
1998
+ // Which lines a cart offer discounts. No targets means the whole cart, a reading that only holds
1999
+ // for cart-scope offers, where an empty target list is the API's way of saying "every line".
2000
+ static cartOfferAppliesToLine(offer, item) {
2001
+ return offer.targets.length === 0 || _CartUtils.offerTargetsLine(offer, item);
2002
+ }
2003
+ // Whether an offer names this line among its targets. An empty variation list on a target leaves
2004
+ // the offer open to every variation of that item. An offer with no targets at all names nothing
2005
+ // here, which is why the "every line" reading stays in `cartOfferAppliesToLine`: it is only true
2006
+ // of a cart offer.
2007
+ static offerTargetsLine(offer, item) {
2008
+ return offer.targets.some(({ item: target, variations }) => target.type === item.type && target.id === (item.type === "Product" ? item.productId : item.bundleId) && (variations.length === 0 || variations.includes(item.variationId)));
2009
+ }
2010
+ // The cart offer actually discounting this cart, as opposed to the one attached to the order. An
2011
+ // offer whose targets name nothing in the cart discounts nothing however healthy the total looks,
2012
+ // so it has not really applied.
2013
+ static appliedCartOffer(catalog, order, context) {
2014
+ var _a;
2015
+ if (!order.offerId)
2016
+ return void 0;
2017
+ const offer = (_a = catalog.offers) == null ? void 0 : _a.find((o) => o.id === order.offerId);
2018
+ if (!offer || offer.scope !== "Cart" || !OfferUtils.eligible(offer, context))
2019
+ return void 0;
2020
+ return order.items.some((item) => _CartUtils.cartOfferAppliesToLine(offer, item)) ? offer : void 0;
2021
+ }
2022
+ // What the cart is worth to the buyer, broken down so the cart can show the saving on its own row.
2023
+ // Unlike `measure`, an unresolvable price counts as zero here: this number fills a label, so a
2024
+ // half-loaded catalog should still render a total rather than nothing.
2025
+ static total(catalog, order, currency) {
2026
+ var _a, _b;
2027
+ const context = _CartUtils.conditionContext(catalog, order, currency);
2028
+ const cartOffer = _CartUtils.appliedCartOffer(catalog, order, context);
2029
+ let original = 0;
2030
+ let subtotal = 0;
2031
+ let cartOfferDiscount = 0;
2032
+ for (const item of order.items) {
2033
+ const unitPrice = _CartUtils.unitPriceAfterItemOffer(catalog, item, currency, context);
2034
+ original += ((_b = (_a = _CartUtils.unitPricing(catalog, item, currency)) == null ? void 0 : _a.originalPrice) != null ? _b : unitPrice) * item.quantity;
2035
+ subtotal += unitPrice * item.quantity;
2036
+ if (cartOffer && _CartUtils.cartOfferAppliesToLine(cartOffer, item))
2037
+ cartOfferDiscount += DiscountUtils.apply(cartOffer.discount, { [currency]: unitPrice })[currency] * item.quantity;
2038
+ }
2039
+ const amount = subtotal - cartOfferDiscount;
2040
+ return {
2041
+ currency,
2042
+ original,
2043
+ subtotal,
2044
+ cartOffer: cartOffer ? { offer: cartOffer, discount: cartOfferDiscount } : null,
2045
+ discount: original - amount,
2046
+ amount
2047
+ };
2048
+ }
2049
+ // The cart offer worth attaching to the order. Cart offers are always percentages, but 10% off
2050
+ // everything can still beat 50% off one cheap line, so they are ranked by what they would actually
2051
+ // take off this cart rather than by percentage.
2052
+ static bestCartOffer(catalog, order, currency) {
2053
+ var _a;
2054
+ const context = _CartUtils.conditionContext(catalog, order, currency);
2055
+ let best;
2056
+ for (const offer of (_a = catalog.offers) != null ? _a : []) {
2057
+ if (offer.scope !== "Cart" || !OfferUtils.eligible(offer, context))
2058
+ continue;
2059
+ const value = _CartUtils.cartOfferValue(catalog, order, offer, currency, context);
2060
+ if (value <= 0)
2061
+ continue;
2062
+ if (!best || value > best.value || value === best.value && offer.id === order.offerId)
2063
+ best = { offer, value };
2064
+ }
2065
+ return best == null ? void 0 : best.offer;
2066
+ }
2067
+ /**
2068
+ * What a cart offer would take off this cart, whether or not its condition is met yet.
2069
+ *
2070
+ * Computed on the same post-line-offer price `total` discounts, so "which offer do we attach" and
2071
+ * "what does the cart actually save" can never disagree. Ranking on the list price instead would
2072
+ * over-value an offer aimed at a line that already carries a steep offer of its own: 50% of a
2073
+ * line discounted to a tenth of its price is worth less than 10% of a full-price one.
2074
+ */
2075
+ static cartOfferValue(catalog, order, offer, currency, context) {
2076
+ const conditionContext = context != null ? context : _CartUtils.conditionContext(catalog, order, currency);
2077
+ return order.items.reduce((aggregate, item) => {
2078
+ if (!_CartUtils.cartOfferAppliesToLine(offer, item))
2079
+ return aggregate;
2080
+ const unitPrice = _CartUtils.unitPriceAfterItemOffer(catalog, item, currency, conditionContext);
2081
+ return aggregate + DiscountUtils.apply(offer.discount, { [currency]: unitPrice })[currency] * item.quantity;
2082
+ }, 0);
2083
+ }
2084
+ /**
2085
+ * The item-scope offer worth attaching to a single line, or `undefined` when none is.
2086
+ *
2087
+ * Ranked by what it would actually take off this line rather than by percentage, the same way
2088
+ * `bestCartOffer` ranks a cart: a flat amount off can beat a bigger-looking percentage on a cheap
2089
+ * line, and the other way round on an expensive one.
2090
+ *
2091
+ * `unavailableOfferIds` are offers the rest of the cart has already claimed. An offer is only ever
2092
+ * good for one line, since the cart treats an offer held by any line as in use. So a caller walking
2093
+ * the cart must reserve each one as it goes, or two lines would both count on the same offer and
2094
+ * the API would honour it once. `assignItemOffers` does that bookkeeping for a whole cart.
2095
+ */
2096
+ static bestItemOffer(catalog, item, currency, context, unavailableOfferIds) {
2097
+ let best;
2098
+ for (const candidate of _CartUtils.itemOfferCandidates(catalog, [item], currency, context, unavailableOfferIds)) {
2099
+ if (!best || candidate.value > best.value)
2100
+ best = candidate;
2101
+ }
2102
+ return best == null ? void 0 : best.offer;
2103
+ }
2104
+ /**
2105
+ * Which item-scope offer to attach to which line, for the lines not already carrying one.
2106
+ *
2107
+ * Chosen for what the pairings are worth together rather than by cart order. An offer is only good
2108
+ * for one line, so walking the cart and taking the best offer for each line in turn hands a
2109
+ * multi-target offer to whichever line the buyer happened to add first, which is arbitrary and can
2110
+ * be the cheapest one. A buyer claiming these from the upsell cards would have placed them where
2111
+ * they are worth most, so that is what this does.
2112
+ *
2113
+ * Lines already holding an offer are left alone and their offers reserved: an offer on a line is
2114
+ * intent, whether the buyer's from the upsell or the API's from a refreshed order, and this is not
2115
+ * the place to second-guess it.
2116
+ */
2117
+ static assignItemOffers(catalog, order, currency, context) {
2118
+ const conditionContext = context != null ? context : _CartUtils.conditionContext(catalog, order, currency);
2119
+ const claimed = new Set(order.items.map((i) => i.offerId).filter((id) => !!id));
2120
+ return _CartUtils.maximalItemOfferAssignment(
2121
+ _CartUtils.itemOfferCandidates(catalog, order.items.filter((i) => !i.offerId), currency, conditionContext, claimed)
2122
+ );
2123
+ }
2124
+ /**
2125
+ * The pairings worth the most together, given each offer can only be spent once.
2126
+ *
2127
+ * Taking the most valuable pairing first is not enough: with one offer worth 100 on line A and 99 on
2128
+ * line B, and a second offer worth 98 on line A alone, claiming the 100 strands the second offer and
2129
+ * saves 100, where putting the first offer on B and the second on A saves 197. The pairings have to
2130
+ * be chosen as a set.
2131
+ *
2132
+ * Walked line by line, memoised on which offers are still free. The candidates have already been
2133
+ * through targeting and eligibility, so in a real cart there are a handful; past `searchableOffers`
2134
+ * distinct offers it takes the most valuable pairing first instead, which is never worse than
2135
+ * applying nothing.
2136
+ */
2137
+ static maximalItemOfferAssignment(candidates) {
2138
+ var _a;
2139
+ const optionsByItem = /* @__PURE__ */ new Map();
2140
+ const offerBits = /* @__PURE__ */ new Map();
2141
+ for (const { item, offer, value } of candidates) {
2142
+ let bit = offerBits.get(offer.id);
2143
+ if (bit === void 0) {
2144
+ bit = offerBits.size;
2145
+ offerBits.set(offer.id, bit);
2146
+ }
2147
+ const options = (_a = optionsByItem.get(item.id)) != null ? _a : [];
2148
+ options.push({ offerId: offer.id, bit, value });
2149
+ optionsByItem.set(item.id, options);
2150
+ }
2151
+ if (offerBits.size > _CartUtils.searchableOffers)
2152
+ return _CartUtils.mostValuableItemOfferPairings(candidates);
2153
+ const itemIds = [...optionsByItem.keys()];
2154
+ const memo = /* @__PURE__ */ new Map();
2155
+ const from = (index, spent) => {
2156
+ var _a2;
2157
+ if (index >= itemIds.length)
2158
+ return { value: 0, assignments: [] };
2159
+ const key = `${index}:${spent}`;
2160
+ const memoised = memo.get(key);
2161
+ if (memoised)
2162
+ return memoised;
2163
+ let best = from(index + 1, spent);
2164
+ const itemId = itemIds[index];
2165
+ for (const option of (_a2 = optionsByItem.get(itemId)) != null ? _a2 : []) {
2166
+ if (spent & 1 << option.bit)
2167
+ continue;
2168
+ const rest = from(index + 1, spent | 1 << option.bit);
2169
+ const value = option.value + rest.value;
2170
+ if (value > best.value)
2171
+ best = { value, assignments: [{ itemId, offerId: option.offerId }, ...rest.assignments] };
2172
+ }
2173
+ memo.set(key, best);
2174
+ return best;
2175
+ };
2176
+ return from(0, 0).assignments;
2177
+ }
2178
+ // The fallback for a candidate set too large to search: repeatedly take the most valuable pairing
2179
+ // still available. Can strand a better combination, which is the whole reason it is only a fallback.
2180
+ static mostValuableItemOfferPairings(candidates) {
2181
+ const assignments = [];
2182
+ const assignedItemIds = /* @__PURE__ */ new Set();
2183
+ const spentOfferIds = /* @__PURE__ */ new Set();
2184
+ for (const { item, offer } of [...candidates].sort((a, b) => b.value - a.value)) {
2185
+ if (assignedItemIds.has(item.id) || spentOfferIds.has(offer.id))
2186
+ continue;
2187
+ assignedItemIds.add(item.id);
2188
+ spentOfferIds.add(offer.id);
2189
+ assignments.push({ itemId: item.id, offerId: offer.id });
2190
+ }
2191
+ return assignments;
2192
+ }
2193
+ // Every line-and-offer pairing the cart could make, and what each would save. Enumerated in cart
2194
+ // order and then catalog order, which `sort` preserves for equal savings, so an unchanged cart
2195
+ // always resolves the same way.
2196
+ static itemOfferCandidates(catalog, items, currency, context, unavailableOfferIds) {
2197
+ var _a;
2198
+ const candidates = [];
2199
+ for (const item of items) {
2200
+ const unitPrice = _CartUtils.unitPrice(catalog, item, currency);
2201
+ if (unitPrice === void 0)
2202
+ continue;
2203
+ for (const offer of (_a = catalog.offers) != null ? _a : []) {
2204
+ if (offer.scope === "Cart" || (unavailableOfferIds == null ? void 0 : unavailableOfferIds.has(offer.id)))
2205
+ continue;
2206
+ if (!_CartUtils.offerTargetsLine(offer, item))
2207
+ continue;
2208
+ if (!OfferUtils.allTargetsInCart(offer, context.order.items))
2209
+ continue;
2210
+ if (!OfferUtils.eligible(offer, context))
2211
+ continue;
2212
+ const value = _CartUtils.itemOfferUnitDiscount(offer, unitPrice, currency) * item.quantity;
2213
+ if (value <= 0)
2214
+ continue;
2215
+ candidates.push({ item, offer, value });
2216
+ }
2217
+ }
2218
+ return candidates;
2219
+ }
2220
+ // What an item offer takes off one unit of a line. Measured off the post-product-discount price,
2221
+ // which is what an item offer comes off in the API's order: product discount -> line offer -> cart
2222
+ // offer -> coupons.
2223
+ static itemOfferUnitDiscount(offer, unitPrice, currency) {
2224
+ return DiscountUtils.apply(offer.discount, { [currency]: unitPrice })[currency];
2225
+ }
2226
+ // A line's unit price once its own offer has come off, which is what a cart offer is applied to:
2227
+ // the API discounts in the order product discount -> line offer -> cart offer -> coupons. An
2228
+ // unresolvable price counts as zero, matching `total`'s "still render something" stance rather
2229
+ // than `measure`'s "refuse to guess".
2230
+ static unitPriceAfterItemOffer(catalog, item, currency, context) {
2231
+ var _a, _b;
2232
+ const unitPrice = (_a = _CartUtils.unitPrice(catalog, item, currency)) != null ? _a : 0;
2233
+ const itemOffer = item.offerId ? (_b = catalog.offers) == null ? void 0 : _b.find((o) => o.id === item.offerId) : void 0;
2234
+ if (!itemOffer || !OfferUtils.eligible(itemOffer, context))
2235
+ return unitPrice;
2236
+ return unitPrice - _CartUtils.itemOfferUnitDiscount(itemOffer, unitPrice, currency);
2237
+ }
2238
+ };
2239
+ // How many distinct offers can compete for the same cart before the exact search is given up on.
2240
+ // 12 leaves 4096 states per line, which is nothing, and no real storefront has a dozen item offers
2241
+ // eligible against one cart at once.
2242
+ _CartUtils.searchableOffers = 12;
2243
+ var CartUtils = _CartUtils;
2244
+
2245
+ // src/utils/image.ts
2246
+ var imageWidths = [16, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048];
2247
+ var maxImageDimension = imageWidths[imageWidths.length - 1];
2248
+ var transformPathSegment = "_i";
2249
+ var maxCoverAspectRatio = 4;
2250
+ function isAssetUrl(url) {
2251
+ let parsed;
2252
+ try {
2253
+ parsed = new URL(url);
2254
+ } catch (e) {
2255
+ return false;
2256
+ }
2257
+ return parsed.hostname === "assets.moonbase.sh" || parsed.hostname.startsWith("assets.") && parsed.hostname.endsWith(".moonbase.sh");
2258
+ }
2259
+ function isRendition(url) {
2260
+ try {
2261
+ return new URL(url).pathname.startsWith(`/${transformPathSegment}/`);
2262
+ } catch (e) {
2263
+ return false;
2264
+ }
2265
+ }
2266
+ function isTransformableImageUrl(url) {
2267
+ return isAssetUrl(url) && !isRendition(url);
2268
+ }
2269
+ var warned = /* @__PURE__ */ new Set();
2270
+ function warnOnce(reason, url, message) {
2271
+ const key = `${reason}:${url}`;
2272
+ if (warned.has(key))
2273
+ return;
2274
+ warned.add(key);
2275
+ console.warn(message, url);
2276
+ }
2277
+ function snap(value) {
2278
+ var _a;
2279
+ if (value === void 0 || !Number.isFinite(value) || value <= 0)
2280
+ return void 0;
2281
+ return (_a = imageWidths.find((width) => value <= width)) != null ? _a : maxImageDimension;
2282
+ }
2283
+ var ImageUtils = class _ImageUtils {
2284
+ /**
2285
+ * Rewrites an asset URL to ask the CDN for a rendition at the given size.
2286
+ *
2287
+ * Returns the URL untouched — never a broken one — when there is nothing to do: a URL that
2288
+ * isn't a Moonbase asset, one that is already a rendition, an empty transform, or a `cover`
2289
+ * crop too extreme for the CDN to serve. Callers can apply it unconditionally.
2290
+ */
2291
+ static transform(url, transform) {
2292
+ if (!url)
2293
+ return void 0;
2294
+ if (!isAssetUrl(url)) {
2295
+ warnOnce(
2296
+ "host",
2297
+ url,
2298
+ "Only images hosted by Moonbase can be resized on the fly. Serving this one at its original size; host it in Moonbase, or size it yourself before uploading:"
2299
+ );
2300
+ return url;
2301
+ }
2302
+ if (isRendition(url))
2303
+ return url;
2304
+ const query = _ImageUtils.toQuery(transform);
2305
+ if (query === null) {
2306
+ warnOnce(
2307
+ "aspect-ratio",
2308
+ url,
2309
+ `A 'cover' crop more lopsided than ${maxCoverAspectRatio}:1 is not served. Serving this image at its original size instead:`
2310
+ );
2311
+ return url;
2312
+ }
2313
+ if (query.length === 0)
2314
+ return url;
2315
+ const parsed = new URL(url);
2316
+ const separator = parsed.search ? "&" : "?";
2317
+ return `${parsed.origin}/${transformPathSegment}${parsed.pathname}${parsed.search}${separator}${query.map(([key, value]) => `${key}=${value}`).join("&")}`;
2318
+ }
2319
+ /**
2320
+ * A `srcset` covering the given pixel densities, so a retina screen gets the sharper rendition
2321
+ * without every screen paying for it. Pair it with a `src` built from the same transform.
2322
+ */
2323
+ static srcSet(url, transform, ...scales) {
2324
+ if (!url || scales.length === 0)
2325
+ return void 0;
2326
+ return scales.map((scale) => `${_ImageUtils.transform(url, _ImageUtils.atScale(transform, scale))} ${scale}x`).join(", ");
2327
+ }
2328
+ /** The same transform at a different pixel density. */
2329
+ static atScale(transform, scale) {
2330
+ return {
2331
+ ...transform,
2332
+ width: transform.width === void 0 ? void 0 : transform.width * scale,
2333
+ height: transform.height === void 0 ? void 0 : transform.height * scale
2334
+ };
2335
+ }
2336
+ /**
2337
+ * The query parameters for a transform, in the CDN's own order, with defaults left out so two
2338
+ * equivalent transforms always produce the same URL — and therefore the same cache entry.
2339
+ * `null` when the CDN would refuse the transform outright.
2340
+ */
2341
+ static toQuery(transform) {
2342
+ var _a, _b;
2343
+ const fit = (_a = transform.fit) != null ? _a : "contain";
2344
+ const width = snap(transform.width);
2345
+ const height = fit === "cover" ? (_b = snap(transform.height)) != null ? _b : width : snap(transform.height);
2346
+ if (fit === "cover" && width && height) {
2347
+ const ratio = width > height ? width / height : height / width;
2348
+ if (ratio > maxCoverAspectRatio)
2349
+ return null;
2350
+ }
2351
+ const query = [];
2352
+ if (width)
2353
+ query.push(["w", String(width)]);
2354
+ if (height && (fit === "cover" || transform.height !== void 0))
2355
+ query.push(["h", String(height)]);
2356
+ if (fit !== "contain")
2357
+ query.push(["fit", fit]);
2358
+ if (transform.format)
2359
+ query.push(["f", transform.format]);
2360
+ return query;
2361
+ }
2362
+ };
2363
+
1755
2364
  // src/utils/utm.ts
1756
2365
  function parseOffSiteReferrer(rawReferrer, currentOrigin) {
1757
2366
  if (!rawReferrer)
@@ -1799,10 +2408,12 @@ var MoonbaseClient = class {
1799
2408
  ActivationRequestStatus,
1800
2409
  ActivationStatus,
1801
2410
  Architecture,
2411
+ CartUtils,
1802
2412
  ConnectableAccountProvider,
1803
2413
  ConsoleLogger,
1804
2414
  CycleLength,
1805
2415
  DiscountUtils,
2416
+ ImageUtils,
1806
2417
  InMemoryStore,
1807
2418
  LicenseStatus,
1808
2419
  LocalStorageStore,
@@ -1820,9 +2431,14 @@ var MoonbaseClient = class {
1820
2431
  Platform,
1821
2432
  SubscriptionStatus,
1822
2433
  TokenStore,
2434
+ imageWidths,
2435
+ isPromotionDisplay,
2436
+ isTransformableImageUrl,
2437
+ maxImageDimension,
1823
2438
  objectToQuery,
1824
2439
  parseOffSiteReferrer,
1825
2440
  problemDetailsSchema,
2441
+ promotionDisplays,
1826
2442
  resolveUtm,
1827
2443
  schemas,
1828
2444
  utmToObject