@faststore/api 1.9.4 → 1.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/api.cjs.development.js +153 -45
  3. package/dist/api.cjs.development.js.map +1 -1
  4. package/dist/api.cjs.production.min.js +1 -1
  5. package/dist/api.cjs.production.min.js.map +1 -1
  6. package/dist/api.esm.js +149 -46
  7. package/dist/api.esm.js.map +1 -1
  8. package/dist/index.d.ts +6 -4
  9. package/dist/platforms/errors.d.ts +19 -0
  10. package/dist/platforms/vtex/clients/commerce/types/Portal.d.ts +1 -1
  11. package/dist/platforms/vtex/index.d.ts +5 -4
  12. package/dist/platforms/vtex/loaders/index.d.ts +1 -1
  13. package/dist/platforms/vtex/loaders/sku.d.ts +1 -2
  14. package/dist/platforms/vtex/resolvers/mutation.d.ts +3 -3
  15. package/dist/platforms/vtex/resolvers/offer.d.ts +1 -1
  16. package/dist/platforms/vtex/resolvers/seo.d.ts +1 -0
  17. package/dist/platforms/vtex/resolvers/validateCart.d.ts +3 -3
  18. package/dist/platforms/vtex/utils/canonical.d.ts +2 -0
  19. package/dist/platforms/vtex/utils/facets.d.ts +2 -0
  20. package/dist/platforms/vtex/utils/orderStatistics.d.ts +4 -0
  21. package/dist/platforms/vtex/utils/sku.d.ts +8 -0
  22. package/package.json +2 -2
  23. package/src/index.ts +1 -0
  24. package/src/platforms/errors.ts +34 -0
  25. package/src/platforms/vtex/clients/commerce/index.ts +1 -1
  26. package/src/platforms/vtex/clients/commerce/types/Portal.ts +1 -1
  27. package/src/platforms/vtex/loaders/collection.ts +1 -1
  28. package/src/platforms/vtex/loaders/sku.ts +6 -19
  29. package/src/platforms/vtex/resolvers/offer.ts +6 -3
  30. package/src/platforms/vtex/resolvers/product.ts +6 -4
  31. package/src/platforms/vtex/resolvers/query.ts +44 -1
  32. package/src/platforms/vtex/resolvers/seo.ts +2 -2
  33. package/src/platforms/vtex/resolvers/validateCart.ts +3 -3
  34. package/src/platforms/vtex/utils/canonical.ts +3 -0
  35. package/src/platforms/vtex/utils/facets.ts +6 -0
  36. package/src/platforms/vtex/utils/orderStatistics.ts +16 -0
  37. package/src/platforms/vtex/utils/sku.ts +26 -0
  38. package/dist/platforms/vtex/utils/errors.d.ts +0 -6
  39. package/src/platforms/vtex/utils/errors.ts +0 -13
package/dist/api.esm.js CHANGED
@@ -342,38 +342,51 @@ const getSimulationLoader = (_, clients) => {
342
342
  });
343
343
  };
344
344
 
345
- class BadRequestError extends Error {
346
- constructor(message) {
345
+ const enhanceSku = (item, product) => ({ ...item,
346
+ isVariantOf: product
347
+ });
348
+
349
+ class FastStoreError extends Error {
350
+ constructor(extensions, message) {
347
351
  super(message);
348
- this.name = 'BadRequestError';
352
+ this.extensions = extensions;
353
+ this.name = 'FastStoreError';
349
354
  }
350
355
 
351
356
  }
352
- class NotFoundError extends Error {
357
+
358
+ class BadRequestError extends FastStoreError {
353
359
  constructor(message) {
354
- super(message);
355
- this.name = 'NotFoundError';
360
+ super({
361
+ status: 400,
362
+ type: 'BadRequestError'
363
+ }, message);
356
364
  }
357
365
 
358
366
  }
367
+ class NotFoundError extends FastStoreError {
368
+ constructor(message) {
369
+ super({
370
+ status: 404,
371
+ type: 'NotFoundError'
372
+ }, message);
373
+ }
359
374
 
360
- const enhanceSku = (item, product) => ({ ...item,
361
- isVariantOf: product
362
- });
375
+ }
376
+ const isFastStoreError = error => (error == null ? void 0 : error.name) === 'FastStoreError';
377
+ const isNotFoundError = error => {
378
+ var _error$extensions;
363
379
 
364
- const getSkuLoader = (_, clients) => {
365
- const loader = async facetsList => {
366
- const skuIds = facetsList.map(facets => {
367
- const maybeFacet = facets.find(({
368
- key
369
- }) => key === 'id');
370
-
371
- if (!maybeFacet) {
372
- throw new BadRequestError('Error while loading SKU. Needs to pass an id to selected facets');
373
- }
380
+ return (error == null ? void 0 : (_error$extensions = error.extensions) == null ? void 0 : _error$extensions.type) === 'NotFoundError';
381
+ };
382
+ const isBadRequestError = error => {
383
+ var _error$extensions2;
374
384
 
375
- return maybeFacet.value;
376
- });
385
+ return (error == null ? void 0 : (_error$extensions2 = error.extensions) == null ? void 0 : _error$extensions2.type) === 'BadRequestError';
386
+ };
387
+
388
+ const getSkuLoader = (_, clients) => {
389
+ const loader = async skuIds => {
377
390
  const {
378
391
  products
379
392
  } = await clients.search.products({
@@ -389,10 +402,10 @@ const getSkuLoader = (_, clients) => {
389
402
  return acc;
390
403
  }, {});
391
404
  const skus = skuIds.map(skuId => skuBySkuId[skuId]);
392
- const missingSkus = skus.filter(sku => !sku);
405
+ const missingSkus = skuIds.filter(skuId => !skuBySkuId[skuId]);
393
406
 
394
407
  if (missingSkus.length > 0) {
395
- throw new Error(`Search API did not return the following skus: ${missingSkus.join(',')}`);
408
+ throw new NotFoundError(`Search API did not found the following skus: ${missingSkus.join(',')}`);
396
409
  }
397
410
 
398
411
  return skus;
@@ -716,15 +729,12 @@ const equals = (storeOrder, orderForm) => {
716
729
  return isSameOrder && orderItemsAreSync;
717
730
  };
718
731
 
719
- const orderFormToCart = (form, skuLoader) => {
732
+ const orderFormToCart = async (form, skuLoader) => {
720
733
  return {
721
734
  order: {
722
735
  orderNumber: form.orderFormId,
723
- acceptedOffer: form.items.map(item => ({ ...item,
724
- product: skuLoader.load([{
725
- key: 'id',
726
- value: item.id
727
- }])
736
+ acceptedOffer: form.items.map(async item => ({ ...item,
737
+ product: await skuLoader.load(item.id)
728
738
  }))
729
739
  },
730
740
  messages: form.messages.map(({
@@ -1032,13 +1042,13 @@ const StoreOffer = {
1032
1042
 
1033
1043
  return null;
1034
1044
  },
1035
- itemOffered: async root => {
1045
+ itemOffered: root => {
1036
1046
  if (isSearchItem(root)) {
1037
1047
  return root.product;
1038
1048
  }
1039
1049
 
1040
1050
  if (isOrderFormItem(root)) {
1041
- return { ...(await root.product),
1051
+ return { ...root.product,
1042
1052
  attachmentsValues: root.attachments
1043
1053
  };
1044
1054
  }
@@ -1060,6 +1070,10 @@ const StoreOffer = {
1060
1070
  }
1061
1071
  };
1062
1072
 
1073
+ const canonicalFromProduct = ({
1074
+ linkText
1075
+ }) => `/${linkText}/p`;
1076
+
1063
1077
  const enhanceCommercialOffer = ({
1064
1078
  offer,
1065
1079
  seller,
@@ -1100,13 +1114,11 @@ const StoreProduct = {
1100
1114
  }
1101
1115
  }) => description,
1102
1116
  seo: ({
1103
- isVariantOf: {
1104
- description,
1105
- productName
1106
- }
1117
+ isVariantOf
1107
1118
  }) => ({
1108
- title: productName,
1109
- description
1119
+ title: isVariantOf.productName,
1120
+ description: isVariantOf.description,
1121
+ canonical: canonicalFromProduct(isVariantOf)
1110
1122
  }),
1111
1123
  brand: ({
1112
1124
  isVariantOf: {
@@ -1166,7 +1178,7 @@ const StoreProduct = {
1166
1178
  },
1167
1179
  review: () => [],
1168
1180
  aggregateRating: () => ({}),
1169
- offers: root => root.sellers.flatMap(seller => enhanceCommercialOffer({
1181
+ offers: root => root.sellers.map(seller => enhanceCommercialOffer({
1170
1182
  offer: seller.commertialOffer,
1171
1183
  seller,
1172
1184
  product: root
@@ -1264,15 +1276,25 @@ const transformSelectedFacet = ({
1264
1276
  };
1265
1277
  }
1266
1278
  };
1267
- const findLocale = facets => {
1279
+ const findSlug = facets => {
1268
1280
  var _facets$find$value, _facets$find;
1269
1281
 
1270
- return (_facets$find$value = facets == null ? void 0 : (_facets$find = facets.find(x => x.key === 'locale')) == null ? void 0 : _facets$find.value) != null ? _facets$find$value : null;
1282
+ return (_facets$find$value = facets == null ? void 0 : (_facets$find = facets.find(x => x.key === 'slug')) == null ? void 0 : _facets$find.value) != null ? _facets$find$value : null;
1271
1283
  };
1272
- const findChannel = facets => {
1284
+ const findSkuId = facets => {
1273
1285
  var _facets$find$value2, _facets$find2;
1274
1286
 
1275
- return (_facets$find$value2 = facets == null ? void 0 : (_facets$find2 = facets.find(facet => facet.key === 'channel')) == null ? void 0 : _facets$find2.value) != null ? _facets$find$value2 : null;
1287
+ return (_facets$find$value2 = facets == null ? void 0 : (_facets$find2 = facets.find(x => x.key === 'id')) == null ? void 0 : _facets$find2.value) != null ? _facets$find$value2 : null;
1288
+ };
1289
+ const findLocale = facets => {
1290
+ var _facets$find$value3, _facets$find3;
1291
+
1292
+ return (_facets$find$value3 = facets == null ? void 0 : (_facets$find3 = facets.find(x => x.key === 'locale')) == null ? void 0 : _facets$find3.value) != null ? _facets$find$value3 : null;
1293
+ };
1294
+ const findChannel = facets => {
1295
+ var _facets$find$value4, _facets$find4;
1296
+
1297
+ return (_facets$find$value4 = facets == null ? void 0 : (_facets$find4 = facets.find(facet => facet.key === 'channel')) == null ? void 0 : _facets$find4.value) != null ? _facets$find$value4 : null;
1276
1298
  };
1277
1299
 
1278
1300
  const SORT_MAP = {
@@ -1286,6 +1308,42 @@ const SORT_MAP = {
1286
1308
  score_desc: ''
1287
1309
  };
1288
1310
 
1311
+ /**
1312
+ * More info at: https://en.wikipedia.org/wiki/Order_statistic
1313
+ */
1314
+ // O(n) search to find the max of an array
1315
+ const min = (array, cmp) => {
1316
+ let best = 0;
1317
+
1318
+ for (let curr = 1; curr < array.length; curr++) {
1319
+ if (cmp(array[best], array[curr]) > 0) {
1320
+ best = curr;
1321
+ }
1322
+ }
1323
+
1324
+ return array[best];
1325
+ };
1326
+
1327
+ /**
1328
+ * This function implements Portal heuristics for returning the best sku for a product.
1329
+ *
1330
+ * The best sku is the one with the best (cheapest available) offer
1331
+ * */
1332
+
1333
+ const pickBestSku = skus => {
1334
+ const offersBySku = skus.flatMap(sku => sku.sellers.map(seller => ({
1335
+ offer: seller.commertialOffer,
1336
+ sku
1337
+ })));
1338
+ const best = min(offersBySku, ({
1339
+ offer: o1
1340
+ }, {
1341
+ offer: o2
1342
+ }) => bestOfferFirst(o1, o2));
1343
+ return best.sku;
1344
+ };
1345
+ const isValidSkuId = skuId => skuId !== '' && !Number.isNaN(Number(skuId));
1346
+
1289
1347
  const Query = {
1290
1348
  product: async (_, {
1291
1349
  locator
@@ -1293,6 +1351,8 @@ const Query = {
1293
1351
  // Insert channel in context for later usage
1294
1352
  const channel = findChannel(locator);
1295
1353
  const locale = findLocale(locator);
1354
+ const id = findSkuId(locator);
1355
+ const slug = findSlug(locator);
1296
1356
 
1297
1357
  if (channel) {
1298
1358
  mutateChannelContext(ctx, channel);
@@ -1305,9 +1365,50 @@ const Query = {
1305
1365
  const {
1306
1366
  loaders: {
1307
1367
  skuLoader
1368
+ },
1369
+ clients: {
1370
+ commerce,
1371
+ search
1308
1372
  }
1309
1373
  } = ctx;
1310
- return skuLoader.load(locator);
1374
+
1375
+ try {
1376
+ var _ref;
1377
+
1378
+ const skuId = (_ref = id != null ? id : slug == null ? void 0 : slug.split('-').pop()) != null ? _ref : '';
1379
+
1380
+ if (!isValidSkuId(skuId)) {
1381
+ throw new Error('Invalid SkuId');
1382
+ }
1383
+
1384
+ const sku = await skuLoader.load(skuId);
1385
+ return sku;
1386
+ } catch (err) {
1387
+ if (slug == null) {
1388
+ throw new BadRequestError('Missing slug or id');
1389
+ }
1390
+
1391
+ const route = await commerce.catalog.portal.pagetype(`${slug}/p`);
1392
+
1393
+ if (route.pageType !== 'Product' || !route.id) {
1394
+ throw new NotFoundError(`No product found for slug ${slug}`);
1395
+ }
1396
+
1397
+ const {
1398
+ products: [product]
1399
+ } = await search.products({
1400
+ page: 0,
1401
+ count: 1,
1402
+ query: `product:${route.id}`
1403
+ });
1404
+
1405
+ if (!product) {
1406
+ throw new NotFoundError(`No product found for id ${route.id}`);
1407
+ }
1408
+
1409
+ const sku = pickBestSku(product.items);
1410
+ return enhanceSku(sku, product);
1411
+ }
1311
1412
  },
1312
1413
  collection: (_, {
1313
1414
  slug
@@ -1552,8 +1653,10 @@ const StoreSeo = {
1552
1653
  description: ({
1553
1654
  description
1554
1655
  }) => description != null ? description : '',
1555
- titleTemplate: () => '',
1556
- canonical: () => ''
1656
+ canonical: ({
1657
+ canonical
1658
+ }) => canonical != null ? canonical : '',
1659
+ titleTemplate: () => ''
1557
1660
  };
1558
1661
 
1559
1662
  const ObjectOrString = /*#__PURE__*/new GraphQLScalarType({
@@ -1732,5 +1835,5 @@ const getSchema = async options => makeExecutableSchema({
1732
1835
  typeDefs
1733
1836
  });
1734
1837
 
1735
- export { getContextFactory$1 as getContextFactory, getResolvers$1 as getResolvers, getSchema, getTypeDefs };
1838
+ export { BadRequestError, NotFoundError, getContextFactory$1 as getContextFactory, getResolvers$1 as getResolvers, getSchema, getTypeDefs, isBadRequestError, isFastStoreError, isNotFoundError };
1736
1839
  //# sourceMappingURL=api.esm.js.map