@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/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.9.7](https://github.com/vtex/faststore/compare/v1.9.6...v1.9.7) (2022-06-13)
7
+
8
+
9
+ ### Features
10
+
11
+ * Canonical PDP slugs ([#1338](https://github.com/vtex/faststore/issues/1338)) ([ec807bb](https://github.com/vtex/faststore/commit/ec807bb94dafaa72cdec82d53933ca0809bf01df))
12
+
13
+
14
+
15
+
16
+
17
+ ## [1.9.6](https://github.com/vtex/faststore/compare/v1.9.5...v1.9.6) (2022-06-13)
18
+
19
+
20
+ ### Features
21
+
22
+ * Export API Errors ([#1361](https://github.com/vtex/faststore/issues/1361)) ([853035a](https://github.com/vtex/faststore/commit/853035aec1ad94cefaf9eae340b625fb6c920ec2))
23
+
24
+
25
+
26
+
27
+
28
+ ## [1.9.5](https://github.com/vtex/faststore/compare/v1.9.4...v1.9.5) (2022-06-10)
29
+
30
+ **Note:** Version bump only for package @faststore/api
31
+
32
+
33
+
34
+
35
+
6
36
  ## 1.9.4 (2022-06-10)
7
37
 
8
38
 
@@ -348,38 +348,51 @@ const getSimulationLoader = (_, clients) => {
348
348
  });
349
349
  };
350
350
 
351
- class BadRequestError extends Error {
352
- constructor(message) {
351
+ const enhanceSku = (item, product) => ({ ...item,
352
+ isVariantOf: product
353
+ });
354
+
355
+ class FastStoreError extends Error {
356
+ constructor(extensions, message) {
353
357
  super(message);
354
- this.name = 'BadRequestError';
358
+ this.extensions = extensions;
359
+ this.name = 'FastStoreError';
355
360
  }
356
361
 
357
362
  }
358
- class NotFoundError extends Error {
363
+
364
+ class BadRequestError extends FastStoreError {
359
365
  constructor(message) {
360
- super(message);
361
- this.name = 'NotFoundError';
366
+ super({
367
+ status: 400,
368
+ type: 'BadRequestError'
369
+ }, message);
362
370
  }
363
371
 
364
372
  }
373
+ class NotFoundError extends FastStoreError {
374
+ constructor(message) {
375
+ super({
376
+ status: 404,
377
+ type: 'NotFoundError'
378
+ }, message);
379
+ }
365
380
 
366
- const enhanceSku = (item, product) => ({ ...item,
367
- isVariantOf: product
368
- });
381
+ }
382
+ const isFastStoreError = error => (error == null ? void 0 : error.name) === 'FastStoreError';
383
+ const isNotFoundError = error => {
384
+ var _error$extensions;
369
385
 
370
- const getSkuLoader = (_, clients) => {
371
- const loader = async facetsList => {
372
- const skuIds = facetsList.map(facets => {
373
- const maybeFacet = facets.find(({
374
- key
375
- }) => key === 'id');
376
-
377
- if (!maybeFacet) {
378
- throw new BadRequestError('Error while loading SKU. Needs to pass an id to selected facets');
379
- }
386
+ return (error == null ? void 0 : (_error$extensions = error.extensions) == null ? void 0 : _error$extensions.type) === 'NotFoundError';
387
+ };
388
+ const isBadRequestError = error => {
389
+ var _error$extensions2;
380
390
 
381
- return maybeFacet.value;
382
- });
391
+ return (error == null ? void 0 : (_error$extensions2 = error.extensions) == null ? void 0 : _error$extensions2.type) === 'BadRequestError';
392
+ };
393
+
394
+ const getSkuLoader = (_, clients) => {
395
+ const loader = async skuIds => {
383
396
  const {
384
397
  products
385
398
  } = await clients.search.products({
@@ -395,10 +408,10 @@ const getSkuLoader = (_, clients) => {
395
408
  return acc;
396
409
  }, {});
397
410
  const skus = skuIds.map(skuId => skuBySkuId[skuId]);
398
- const missingSkus = skus.filter(sku => !sku);
411
+ const missingSkus = skuIds.filter(skuId => !skuBySkuId[skuId]);
399
412
 
400
413
  if (missingSkus.length > 0) {
401
- throw new Error(`Search API did not return the following skus: ${missingSkus.join(',')}`);
414
+ throw new NotFoundError(`Search API did not found the following skus: ${missingSkus.join(',')}`);
402
415
  }
403
416
 
404
417
  return skus;
@@ -722,15 +735,12 @@ const equals = (storeOrder, orderForm) => {
722
735
  return isSameOrder && orderItemsAreSync;
723
736
  };
724
737
 
725
- const orderFormToCart = (form, skuLoader) => {
738
+ const orderFormToCart = async (form, skuLoader) => {
726
739
  return {
727
740
  order: {
728
741
  orderNumber: form.orderFormId,
729
- acceptedOffer: form.items.map(item => ({ ...item,
730
- product: skuLoader.load([{
731
- key: 'id',
732
- value: item.id
733
- }])
742
+ acceptedOffer: form.items.map(async item => ({ ...item,
743
+ product: await skuLoader.load(item.id)
734
744
  }))
735
745
  },
736
746
  messages: form.messages.map(({
@@ -1038,13 +1048,13 @@ const StoreOffer = {
1038
1048
 
1039
1049
  return null;
1040
1050
  },
1041
- itemOffered: async root => {
1051
+ itemOffered: root => {
1042
1052
  if (isSearchItem(root)) {
1043
1053
  return root.product;
1044
1054
  }
1045
1055
 
1046
1056
  if (isOrderFormItem(root)) {
1047
- return { ...(await root.product),
1057
+ return { ...root.product,
1048
1058
  attachmentsValues: root.attachments
1049
1059
  };
1050
1060
  }
@@ -1066,6 +1076,10 @@ const StoreOffer = {
1066
1076
  }
1067
1077
  };
1068
1078
 
1079
+ const canonicalFromProduct = ({
1080
+ linkText
1081
+ }) => `/${linkText}/p`;
1082
+
1069
1083
  const enhanceCommercialOffer = ({
1070
1084
  offer,
1071
1085
  seller,
@@ -1106,13 +1120,11 @@ const StoreProduct = {
1106
1120
  }
1107
1121
  }) => description,
1108
1122
  seo: ({
1109
- isVariantOf: {
1110
- description,
1111
- productName
1112
- }
1123
+ isVariantOf
1113
1124
  }) => ({
1114
- title: productName,
1115
- description
1125
+ title: isVariantOf.productName,
1126
+ description: isVariantOf.description,
1127
+ canonical: canonicalFromProduct(isVariantOf)
1116
1128
  }),
1117
1129
  brand: ({
1118
1130
  isVariantOf: {
@@ -1172,7 +1184,7 @@ const StoreProduct = {
1172
1184
  },
1173
1185
  review: () => [],
1174
1186
  aggregateRating: () => ({}),
1175
- offers: root => root.sellers.flatMap(seller => enhanceCommercialOffer({
1187
+ offers: root => root.sellers.map(seller => enhanceCommercialOffer({
1176
1188
  offer: seller.commertialOffer,
1177
1189
  seller,
1178
1190
  product: root
@@ -1270,15 +1282,25 @@ const transformSelectedFacet = ({
1270
1282
  };
1271
1283
  }
1272
1284
  };
1273
- const findLocale = facets => {
1285
+ const findSlug = facets => {
1274
1286
  var _facets$find$value, _facets$find;
1275
1287
 
1276
- 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;
1288
+ 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;
1277
1289
  };
1278
- const findChannel = facets => {
1290
+ const findSkuId = facets => {
1279
1291
  var _facets$find$value2, _facets$find2;
1280
1292
 
1281
- 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;
1293
+ 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;
1294
+ };
1295
+ const findLocale = facets => {
1296
+ var _facets$find$value3, _facets$find3;
1297
+
1298
+ 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;
1299
+ };
1300
+ const findChannel = facets => {
1301
+ var _facets$find$value4, _facets$find4;
1302
+
1303
+ 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;
1282
1304
  };
1283
1305
 
1284
1306
  const SORT_MAP = {
@@ -1292,6 +1314,42 @@ const SORT_MAP = {
1292
1314
  score_desc: ''
1293
1315
  };
1294
1316
 
1317
+ /**
1318
+ * More info at: https://en.wikipedia.org/wiki/Order_statistic
1319
+ */
1320
+ // O(n) search to find the max of an array
1321
+ const min = (array, cmp) => {
1322
+ let best = 0;
1323
+
1324
+ for (let curr = 1; curr < array.length; curr++) {
1325
+ if (cmp(array[best], array[curr]) > 0) {
1326
+ best = curr;
1327
+ }
1328
+ }
1329
+
1330
+ return array[best];
1331
+ };
1332
+
1333
+ /**
1334
+ * This function implements Portal heuristics for returning the best sku for a product.
1335
+ *
1336
+ * The best sku is the one with the best (cheapest available) offer
1337
+ * */
1338
+
1339
+ const pickBestSku = skus => {
1340
+ const offersBySku = skus.flatMap(sku => sku.sellers.map(seller => ({
1341
+ offer: seller.commertialOffer,
1342
+ sku
1343
+ })));
1344
+ const best = min(offersBySku, ({
1345
+ offer: o1
1346
+ }, {
1347
+ offer: o2
1348
+ }) => bestOfferFirst(o1, o2));
1349
+ return best.sku;
1350
+ };
1351
+ const isValidSkuId = skuId => skuId !== '' && !Number.isNaN(Number(skuId));
1352
+
1295
1353
  const Query = {
1296
1354
  product: async (_, {
1297
1355
  locator
@@ -1299,6 +1357,8 @@ const Query = {
1299
1357
  // Insert channel in context for later usage
1300
1358
  const channel = findChannel(locator);
1301
1359
  const locale = findLocale(locator);
1360
+ const id = findSkuId(locator);
1361
+ const slug = findSlug(locator);
1302
1362
 
1303
1363
  if (channel) {
1304
1364
  mutateChannelContext(ctx, channel);
@@ -1311,9 +1371,50 @@ const Query = {
1311
1371
  const {
1312
1372
  loaders: {
1313
1373
  skuLoader
1374
+ },
1375
+ clients: {
1376
+ commerce,
1377
+ search
1314
1378
  }
1315
1379
  } = ctx;
1316
- return skuLoader.load(locator);
1380
+
1381
+ try {
1382
+ var _ref;
1383
+
1384
+ const skuId = (_ref = id != null ? id : slug == null ? void 0 : slug.split('-').pop()) != null ? _ref : '';
1385
+
1386
+ if (!isValidSkuId(skuId)) {
1387
+ throw new Error('Invalid SkuId');
1388
+ }
1389
+
1390
+ const sku = await skuLoader.load(skuId);
1391
+ return sku;
1392
+ } catch (err) {
1393
+ if (slug == null) {
1394
+ throw new BadRequestError('Missing slug or id');
1395
+ }
1396
+
1397
+ const route = await commerce.catalog.portal.pagetype(`${slug}/p`);
1398
+
1399
+ if (route.pageType !== 'Product' || !route.id) {
1400
+ throw new NotFoundError(`No product found for slug ${slug}`);
1401
+ }
1402
+
1403
+ const {
1404
+ products: [product]
1405
+ } = await search.products({
1406
+ page: 0,
1407
+ count: 1,
1408
+ query: `product:${route.id}`
1409
+ });
1410
+
1411
+ if (!product) {
1412
+ throw new NotFoundError(`No product found for id ${route.id}`);
1413
+ }
1414
+
1415
+ const sku = pickBestSku(product.items);
1416
+ return enhanceSku(sku, product);
1417
+ }
1317
1418
  },
1318
1419
  collection: (_, {
1319
1420
  slug
@@ -1558,8 +1659,10 @@ const StoreSeo = {
1558
1659
  description: ({
1559
1660
  description
1560
1661
  }) => description != null ? description : '',
1561
- titleTemplate: () => '',
1562
- canonical: () => ''
1662
+ canonical: ({
1663
+ canonical
1664
+ }) => canonical != null ? canonical : '',
1665
+ titleTemplate: () => ''
1563
1666
  };
1564
1667
 
1565
1668
  const ObjectOrString = /*#__PURE__*/new graphql.GraphQLScalarType({
@@ -1738,8 +1841,13 @@ const getSchema = async options => schema.makeExecutableSchema({
1738
1841
  typeDefs
1739
1842
  });
1740
1843
 
1844
+ exports.BadRequestError = BadRequestError;
1845
+ exports.NotFoundError = NotFoundError;
1741
1846
  exports.getContextFactory = getContextFactory$1;
1742
1847
  exports.getResolvers = getResolvers$1;
1743
1848
  exports.getSchema = getSchema;
1744
1849
  exports.getTypeDefs = getTypeDefs;
1850
+ exports.isBadRequestError = isBadRequestError;
1851
+ exports.isFastStoreError = isFastStoreError;
1852
+ exports.isNotFoundError = isNotFoundError;
1745
1853
  //# sourceMappingURL=api.cjs.development.js.map