@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.
- package/CHANGELOG.md +30 -0
- package/dist/api.cjs.development.js +153 -45
- package/dist/api.cjs.development.js.map +1 -1
- package/dist/api.cjs.production.min.js +1 -1
- package/dist/api.cjs.production.min.js.map +1 -1
- package/dist/api.esm.js +149 -46
- package/dist/api.esm.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/platforms/errors.d.ts +19 -0
- package/dist/platforms/vtex/clients/commerce/types/Portal.d.ts +1 -1
- package/dist/platforms/vtex/index.d.ts +5 -4
- package/dist/platforms/vtex/loaders/index.d.ts +1 -1
- package/dist/platforms/vtex/loaders/sku.d.ts +1 -2
- package/dist/platforms/vtex/resolvers/mutation.d.ts +3 -3
- package/dist/platforms/vtex/resolvers/offer.d.ts +1 -1
- package/dist/platforms/vtex/resolvers/seo.d.ts +1 -0
- package/dist/platforms/vtex/resolvers/validateCart.d.ts +3 -3
- package/dist/platforms/vtex/utils/canonical.d.ts +2 -0
- package/dist/platforms/vtex/utils/facets.d.ts +2 -0
- package/dist/platforms/vtex/utils/orderStatistics.d.ts +4 -0
- package/dist/platforms/vtex/utils/sku.d.ts +8 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/platforms/errors.ts +34 -0
- package/src/platforms/vtex/clients/commerce/index.ts +1 -1
- package/src/platforms/vtex/clients/commerce/types/Portal.ts +1 -1
- package/src/platforms/vtex/loaders/collection.ts +1 -1
- package/src/platforms/vtex/loaders/sku.ts +6 -19
- package/src/platforms/vtex/resolvers/offer.ts +6 -3
- package/src/platforms/vtex/resolvers/product.ts +6 -4
- package/src/platforms/vtex/resolvers/query.ts +44 -1
- package/src/platforms/vtex/resolvers/seo.ts +2 -2
- package/src/platforms/vtex/resolvers/validateCart.ts +3 -3
- package/src/platforms/vtex/utils/canonical.ts +3 -0
- package/src/platforms/vtex/utils/facets.ts +6 -0
- package/src/platforms/vtex/utils/orderStatistics.ts +16 -0
- package/src/platforms/vtex/utils/sku.ts +26 -0
- package/dist/platforms/vtex/utils/errors.d.ts +0 -6
- 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
|
-
|
|
352
|
-
|
|
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.
|
|
358
|
+
this.extensions = extensions;
|
|
359
|
+
this.name = 'FastStoreError';
|
|
355
360
|
}
|
|
356
361
|
|
|
357
362
|
}
|
|
358
|
-
|
|
363
|
+
|
|
364
|
+
class BadRequestError extends FastStoreError {
|
|
359
365
|
constructor(message) {
|
|
360
|
-
super(
|
|
361
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
381
|
+
}
|
|
382
|
+
const isFastStoreError = error => (error == null ? void 0 : error.name) === 'FastStoreError';
|
|
383
|
+
const isNotFoundError = error => {
|
|
384
|
+
var _error$extensions;
|
|
369
385
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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 =
|
|
411
|
+
const missingSkus = skuIds.filter(skuId => !skuBySkuId[skuId]);
|
|
399
412
|
|
|
400
413
|
if (missingSkus.length > 0) {
|
|
401
|
-
throw new
|
|
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:
|
|
1051
|
+
itemOffered: root => {
|
|
1042
1052
|
if (isSearchItem(root)) {
|
|
1043
1053
|
return root.product;
|
|
1044
1054
|
}
|
|
1045
1055
|
|
|
1046
1056
|
if (isOrderFormItem(root)) {
|
|
1047
|
-
return { ...
|
|
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.
|
|
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
|
|
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 === '
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1562
|
-
|
|
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
|