@moonbase.sh/storefront-api 2.3.0 → 2.4.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.js CHANGED
@@ -20,6 +20,7 @@ __export(schemas_exports2, {
20
20
  offerConditionSchema: () => offerConditionSchema,
21
21
  storefrontBundleSchema: () => storefrontBundleSchema,
22
22
  storefrontOfferSchema: () => storefrontOfferSchema,
23
+ storefrontOfferTargetSchema: () => storefrontOfferTargetSchema,
23
24
  storefrontProductSchema: () => storefrontProductSchema,
24
25
  storefrontSchema: () => storefrontSchema,
25
26
  taxEstimateSchema: () => taxEstimateSchema
@@ -236,13 +237,36 @@ var cartContainsItemsConditionSchema = z3.object({
236
237
  relevantItemVariations: z3.record(z3.string(), z3.string().array())
237
238
  });
238
239
  var offerConditionSchema = z3.discriminatedUnion("type", [cartContainsItemsConditionSchema]);
240
+ var offerTargetItemSchema = z3.union([storefrontProductSchema, storefrontBundleSchema]);
241
+ var storefrontOfferTargetSchema = z3.object({
242
+ // The product or bundle the offer applies to.
243
+ item: offerTargetItemSchema,
244
+ // The pricing variation IDs the offer is restricted to; an empty list means all variations.
245
+ variations: z3.string().array()
246
+ });
247
+ function legacyOfferTargets(target, targetVariations) {
248
+ if (!target)
249
+ return [];
250
+ const items = Array.isArray(target) ? target : [target];
251
+ return items.map((item) => ({ item, variations: items.length === 1 ? targetVariations != null ? targetVariations : [] : [] }));
252
+ }
239
253
  var storefrontOfferSchema = z3.object({
240
254
  id: z3.string(),
241
- target: singleOrMultiple(z3.union([storefrontProductSchema, storefrontBundleSchema])),
242
- targetVariations: z3.string().array(),
255
+ // Each target pairs a product/bundle with the pricing variations the offer applies to (an empty
256
+ // list means all variations). This is the model to consume.
257
+ targets: z3.array(storefrontOfferTargetSchema).optional(),
258
+ // Legacy fields, kept optional only so old cached storefront responses still parse. The API now
259
+ // exposes the unified `targets` model; we derive `targets` from these when a response predates it.
260
+ // `.catch` guards against the deprecated shape drifting: a malformed legacy field is ignored
261
+ // rather than failing the whole response (newer responses carry `targets` anyway).
262
+ target: singleOrMultiple(offerTargetItemSchema).optional().catch(void 0),
263
+ targetVariations: z3.string().array().optional().catch(void 0),
243
264
  condition: offerConditionSchema,
244
265
  discount: discountSchema
245
- });
266
+ }).transform(({ target, targetVariations, targets, ...rest }) => ({
267
+ ...rest,
268
+ targets: targets != null ? targets : legacyOfferTargets(target, targetVariations)
269
+ }));
246
270
  var taxEstimateSchema = z3.object({
247
271
  rate: z3.number(),
248
272
  mode: z3.enum(["Exclusive", "Inclusive"]),
@@ -464,11 +488,11 @@ async function handleResponseProblem(response, logger) {
464
488
  throw new NotAuthorizedError();
465
489
  }
466
490
  let problemDetails;
491
+ const body = await response.text();
467
492
  try {
468
- const json = await response.json();
469
- problemDetails = problemDetailsSchema.parse(json);
493
+ problemDetails = problemDetailsSchema.parse(JSON.parse(body));
470
494
  } catch (err) {
471
- logger.warn("Could not handle response", { response, err, content: await response.text() });
495
+ logger.warn("Could not handle response", { response, err, content: body });
472
496
  throw new Error("An unknown problem occurred");
473
497
  }
474
498
  logger.debug("The response indicates a problem", problemDetails);
@@ -532,7 +556,8 @@ var connectionUrlSchema = z8.object({
532
556
  var userSchema = z8.object({
533
557
  id: z8.string(),
534
558
  email: z8.string(),
535
- name: z8.string(),
559
+ // Nullable: customers created via import may have no name.
560
+ name: z8.string().nullable(),
536
561
  tenantId: z8.string(),
537
562
  address: addressSchema.optional(),
538
563
  phone: z8.string().optional(),
@@ -861,6 +886,9 @@ var ProductEndpoints = class {
861
886
  }
862
887
  };
863
888
 
889
+ // src/inventory/subscriptions/endpoints.ts
890
+ import { z as z14 } from "zod";
891
+
864
892
  // src/utils/api.ts
865
893
  function objectToQuery(obj) {
866
894
  return Object.entries(obj != null ? obj : {}).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}=${encodeURIComponent(value instanceof Date ? value.toISOString() : value)}`).join("&");
@@ -986,9 +1014,6 @@ var MoonbaseApi = class {
986
1014
  }
987
1015
  };
988
1016
 
989
- // src/inventory/subscriptions/endpoints.ts
990
- import { z as z14 } from "zod";
991
-
992
1017
  // src/inventory/subscriptions/schemas.ts
993
1018
  var schemas_exports8 = {};
994
1019
  __export(schemas_exports8, {
@@ -997,15 +1022,6 @@ __export(schemas_exports8, {
997
1022
  });
998
1023
  import { z as z13 } from "zod";
999
1024
 
1000
- // src/inventory/subscriptions/models.ts
1001
- var SubscriptionStatus = /* @__PURE__ */ ((SubscriptionStatus2) => {
1002
- SubscriptionStatus2["Active"] = "Active";
1003
- SubscriptionStatus2["Expired"] = "Expired";
1004
- SubscriptionStatus2["Cancelled"] = "Cancelled";
1005
- SubscriptionStatus2["Completed"] = "Completed";
1006
- return SubscriptionStatus2;
1007
- })(SubscriptionStatus || {});
1008
-
1009
1025
  // src/orders/schemas.ts
1010
1026
  var schemas_exports7 = {};
1011
1027
  __export(schemas_exports7, {
@@ -1142,6 +1158,15 @@ var orderSchema = z12.discriminatedUnion("status", [
1142
1158
  completedOrderSchema
1143
1159
  ]);
1144
1160
 
1161
+ // src/inventory/subscriptions/models.ts
1162
+ var SubscriptionStatus = /* @__PURE__ */ ((SubscriptionStatus2) => {
1163
+ SubscriptionStatus2["Active"] = "Active";
1164
+ SubscriptionStatus2["Expired"] = "Expired";
1165
+ SubscriptionStatus2["Cancelled"] = "Cancelled";
1166
+ SubscriptionStatus2["Completed"] = "Completed";
1167
+ return SubscriptionStatus2;
1168
+ })(SubscriptionStatus || {});
1169
+
1145
1170
  // src/inventory/subscriptions/schemas.ts
1146
1171
  var baseMilestoneProgressEventSchema = z13.object({
1147
1172
  milestoneId: z13.string(),
@@ -1317,6 +1342,36 @@ var StorefrontEndpoints = class {
1317
1342
  }
1318
1343
  };
1319
1344
 
1345
+ // src/utils/logger.ts
1346
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
1347
+ LogLevel2[LogLevel2["Debug"] = 0] = "Debug";
1348
+ LogLevel2[LogLevel2["Information"] = 1] = "Information";
1349
+ LogLevel2[LogLevel2["Warn"] = 2] = "Warn";
1350
+ LogLevel2[LogLevel2["Error"] = 3] = "Error";
1351
+ return LogLevel2;
1352
+ })(LogLevel || {});
1353
+ var ConsoleLogger = class {
1354
+ constructor(level = 2 /* Warn */) {
1355
+ this.level = level;
1356
+ }
1357
+ debug(message, ...optionalParams) {
1358
+ if (this.level <= 0 /* Debug */)
1359
+ console.debug(message, ...optionalParams);
1360
+ }
1361
+ log(message, ...optionalParams) {
1362
+ if (this.level <= 1 /* Information */)
1363
+ console.log(message, ...optionalParams);
1364
+ }
1365
+ warn(message, ...optionalParams) {
1366
+ if (this.level <= 2 /* Warn */)
1367
+ console.warn(message, ...optionalParams);
1368
+ }
1369
+ error(message, ...optionalParams) {
1370
+ if (this.level <= 3 /* Error */)
1371
+ console.error(message, ...optionalParams);
1372
+ }
1373
+ };
1374
+
1320
1375
  // src/utils/store.ts
1321
1376
  var LocalStorageStore = class {
1322
1377
  get(key) {
@@ -1364,7 +1419,8 @@ var InMemoryStore = class {
1364
1419
  }
1365
1420
  }
1366
1421
  listen(key, callback) {
1367
- if (!this.listeners[key]) this.listeners[key] = [];
1422
+ if (!this.listeners[key])
1423
+ this.listeners[key] = [];
1368
1424
  this.listeners[key].push(callback);
1369
1425
  }
1370
1426
  };
@@ -1408,7 +1464,8 @@ var _TokenStore = class _TokenStore {
1408
1464
  setUser(user) {
1409
1465
  const identity = user;
1410
1466
  if (!identity.accessToken || !identity.refreshToken) {
1411
- if (!this.tokens) return null;
1467
+ if (!this.tokens)
1468
+ return null;
1412
1469
  this.tokens = {
1413
1470
  ...user,
1414
1471
  accessToken: this.tokens.accessToken,
@@ -1538,32 +1595,6 @@ var VoucherEndpoints = class {
1538
1595
  }
1539
1596
  };
1540
1597
 
1541
- // src/utils/logger.ts
1542
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
1543
- LogLevel2[LogLevel2["Debug"] = 0] = "Debug";
1544
- LogLevel2[LogLevel2["Information"] = 1] = "Information";
1545
- LogLevel2[LogLevel2["Warn"] = 2] = "Warn";
1546
- LogLevel2[LogLevel2["Error"] = 3] = "Error";
1547
- return LogLevel2;
1548
- })(LogLevel || {});
1549
- var ConsoleLogger = class {
1550
- constructor(level = 2 /* Warn */) {
1551
- this.level = level;
1552
- }
1553
- debug(message, ...optionalParams) {
1554
- if (this.level <= 0 /* Debug */) console.debug(message, ...optionalParams);
1555
- }
1556
- log(message, ...optionalParams) {
1557
- if (this.level <= 1 /* Information */) console.log(message, ...optionalParams);
1558
- }
1559
- warn(message, ...optionalParams) {
1560
- if (this.level <= 2 /* Warn */) console.warn(message, ...optionalParams);
1561
- }
1562
- error(message, ...optionalParams) {
1563
- if (this.level <= 3 /* Error */) console.error(message, ...optionalParams);
1564
- }
1565
- };
1566
-
1567
1598
  // src/schemas.ts
1568
1599
  var schemas_exports12 = {};
1569
1600
  __export(schemas_exports12, {
@@ -1614,7 +1645,7 @@ var MoneyCollectionUtils = class {
1614
1645
  var OfferUtils = class _OfferUtils {
1615
1646
  static eligible(offer, order) {
1616
1647
  switch (offer.condition.type) {
1617
- case "CartContainsItems":
1648
+ case "CartContainsItems": {
1618
1649
  const relevantItems = order.items.filter(
1619
1650
  (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)
1620
1651
  );
@@ -1626,6 +1657,7 @@ var OfferUtils = class _OfferUtils {
1626
1657
  return offer.condition.relevantItemVariations[`Product/${productId}`] && offer.condition.relevantItemVariations[`Product/${productId}`].includes(variationId);
1627
1658
  });
1628
1659
  return relevantItems.length + relevantReplacedItems.length >= offer.condition.minimumItems;
1660
+ }
1629
1661
  }
1630
1662
  console.warn("Unsupported offer condition found:", offer.condition);
1631
1663
  return false;
@@ -1634,11 +1666,27 @@ var OfferUtils = class _OfferUtils {
1634
1666
  // a. Not be owned, or all targets need to be owned
1635
1667
  // b. Not already in the cart
1636
1668
  static relevantTargets(offer, cartItems) {
1637
- if (Array.isArray(offer.target)) {
1638
- const allOwned = offer.target.every((i) => i.owned);
1639
- return offer.target.filter((i) => (allOwned || !i.owned) && !cartItems.some((l) => l.type === i.type && i.id === (l.type === "Product" ? l.productId : l.bundleId)));
1669
+ if (offer.targets.length > 1) {
1670
+ const allOwned = offer.targets.every((t) => t.item.owned);
1671
+ 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)));
1640
1672
  }
1641
- return [offer.target];
1673
+ return offer.targets;
1674
+ }
1675
+ // The pricing variations a target restricts the offer to. An empty (or missing) variation list
1676
+ // means the offer is not narrowed to specific variations, in which case we keep the established
1677
+ // single default-variation upsell.
1678
+ static targetedVariations(target) {
1679
+ var _a;
1680
+ const { item, variations } = target;
1681
+ if (variations.length === 0)
1682
+ return item.defaultVariation ? [item.defaultVariation] : [];
1683
+ const itemVariations = (_a = item.variations) != null ? _a : item.defaultVariation ? [item.defaultVariation] : [];
1684
+ return itemVariations.filter((v) => variations.includes(v.id));
1685
+ }
1686
+ // Expands the relevant targets into one `{ target, variation }` entry per targeted variation, so
1687
+ // an offer targeting several variations (or several products) surfaces each as its own upsell row.
1688
+ static relevantTargetVariations(offer, cartItems) {
1689
+ return _OfferUtils.relevantTargets(offer, cartItems).flatMap((target) => _OfferUtils.targetedVariations(target).map((variation) => ({ target: target.item, variation })));
1642
1690
  }
1643
1691
  static applyToVariation(offer, variation) {
1644
1692
  const discount = DiscountUtils.apply(offer.discount, variation.price);
@@ -1649,7 +1697,7 @@ var OfferUtils = class _OfferUtils {
1649
1697
  };
1650
1698
  }
1651
1699
  static targetContainsBundle(offer, bundleId) {
1652
- if (Array.isArray(offer.target)) return offer.target.some((i) => i.type === "Bundle" && i.id === bundleId);
1700
+ return offer.targets.some((t) => t.item.type === "Bundle" && t.item.id === bundleId);
1653
1701
  }
1654
1702
  };
1655
1703
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@moonbase.sh/storefront-api",
3
3
  "type": "module",
4
- "version": "2.3.0",
4
+ "version": "2.4.0",
5
5
  "description": "Package to let you build storefronts with Moonbase.sh as payment and delivery provider",
6
6
  "author": "Tobias Lønnerød Madsen <m@dsen.tv>",
7
7
  "license": "MIT",
@@ -26,6 +26,9 @@
26
26
  "build": "tsup src/index.ts --format esm,cjs --dts",
27
27
  "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
28
28
  "version": "npm version",
29
- "test": "vitest"
29
+ "type-check": "tsc --noEmit -p tsconfig.typecheck.json",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "test:integration": "TEST_INTEGRATION=true vitest run"
30
33
  }
31
34
  }