@faststore/api 1.9.6 → 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 +11 -0
- package/dist/api.cjs.development.js +113 -34
- 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 +113 -34
- package/dist/api.esm.js.map +1 -1
- package/dist/index.d.ts +5 -4
- 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/platforms/vtex/clients/commerce/index.ts +1 -1
- package/src/platforms/vtex/clients/commerce/types/Portal.ts +1 -1
- package/src/platforms/vtex/loaders/sku.ts +3 -16
- 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/index.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare const getResolvers: (options: Options) => {
|
|
|
49
49
|
StoreSeo: Record<string, import("./platforms/vtex").Resolver<{
|
|
50
50
|
title?: string | undefined;
|
|
51
51
|
description?: string | undefined;
|
|
52
|
+
canonical?: string | undefined;
|
|
52
53
|
}, unknown, any>>;
|
|
53
54
|
StoreFacet: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/FacetSearchResult").Facet, unknown, any>>;
|
|
54
55
|
StoreFacetValue: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/FacetSearchResult").FacetValue, unknown, any>>;
|
|
@@ -57,7 +58,7 @@ export declare const getResolvers: (options: Options) => {
|
|
|
57
58
|
} & {
|
|
58
59
|
attachmentsValues?: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[] | undefined;
|
|
59
60
|
}> | (import("./platforms/vtex/clients/commerce/types/OrderForm").OrderFormItem & {
|
|
60
|
-
product:
|
|
61
|
+
product: import("./platforms/vtex/utils/enhanceSku").EnhancedSku;
|
|
61
62
|
}), unknown, any>>;
|
|
62
63
|
StoreAggregateRating: Record<string, import("./platforms/vtex").Resolver<unknown, unknown, any>>;
|
|
63
64
|
StoreReview: Record<string, import("./platforms/vtex").Resolver<unknown, unknown, any>>;
|
|
@@ -125,8 +126,8 @@ export declare const getResolvers: (options: Options) => {
|
|
|
125
126
|
}, ctx: import("./platforms/vtex").Context) => Promise<{
|
|
126
127
|
order: {
|
|
127
128
|
orderNumber: string;
|
|
128
|
-
acceptedOffer: {
|
|
129
|
-
product:
|
|
129
|
+
acceptedOffer: Promise<{
|
|
130
|
+
product: import("./platforms/vtex/utils/enhanceSku").EnhancedSku;
|
|
130
131
|
id: string;
|
|
131
132
|
name: string;
|
|
132
133
|
detailUrl: string;
|
|
@@ -170,7 +171,7 @@ export declare const getResolvers: (options: Options) => {
|
|
|
170
171
|
total: number;
|
|
171
172
|
};
|
|
172
173
|
attachments: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[];
|
|
173
|
-
}[];
|
|
174
|
+
}>[];
|
|
174
175
|
};
|
|
175
176
|
messages: {
|
|
176
177
|
text: any;
|
|
@@ -5,7 +5,7 @@ export interface CollectionPageType {
|
|
|
5
5
|
url: string;
|
|
6
6
|
title: string;
|
|
7
7
|
metaTagDescription: string;
|
|
8
|
-
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory';
|
|
8
|
+
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory' | 'Product';
|
|
9
9
|
}
|
|
10
10
|
export interface FallbackPageType {
|
|
11
11
|
id: null;
|
|
@@ -77,6 +77,7 @@ export declare const getResolvers: (_: Options) => {
|
|
|
77
77
|
StoreSeo: Record<string, Resolver<{
|
|
78
78
|
title?: string | undefined;
|
|
79
79
|
description?: string | undefined;
|
|
80
|
+
canonical?: string | undefined;
|
|
80
81
|
}, unknown, any>>;
|
|
81
82
|
StoreFacet: Record<string, Resolver<import("./clients/search/types/FacetSearchResult").Facet, unknown, any>>;
|
|
82
83
|
StoreFacetValue: Record<string, Resolver<import("./clients/search/types/FacetSearchResult").FacetValue, unknown, any>>;
|
|
@@ -85,7 +86,7 @@ export declare const getResolvers: (_: Options) => {
|
|
|
85
86
|
} & {
|
|
86
87
|
attachmentsValues?: import("./clients/commerce/types/OrderForm").Attachment[] | undefined;
|
|
87
88
|
}> | (import("./clients/commerce/types/OrderForm").OrderFormItem & {
|
|
88
|
-
product:
|
|
89
|
+
product: import("./utils/enhanceSku").EnhancedSku;
|
|
89
90
|
}), unknown, any>>;
|
|
90
91
|
StoreAggregateRating: Record<string, Resolver<unknown, unknown, any>>;
|
|
91
92
|
StoreReview: Record<string, Resolver<unknown, unknown, any>>;
|
|
@@ -153,8 +154,8 @@ export declare const getResolvers: (_: Options) => {
|
|
|
153
154
|
}, ctx: Context) => Promise<{
|
|
154
155
|
order: {
|
|
155
156
|
orderNumber: string;
|
|
156
|
-
acceptedOffer: {
|
|
157
|
-
product:
|
|
157
|
+
acceptedOffer: Promise<{
|
|
158
|
+
product: import("./utils/enhanceSku").EnhancedSku;
|
|
158
159
|
id: string;
|
|
159
160
|
name: string;
|
|
160
161
|
detailUrl: string;
|
|
@@ -198,7 +199,7 @@ export declare const getResolvers: (_: Options) => {
|
|
|
198
199
|
total: number;
|
|
199
200
|
};
|
|
200
201
|
attachments: import("./clients/commerce/types/OrderForm").Attachment[];
|
|
201
|
-
}[];
|
|
202
|
+
}>[];
|
|
202
203
|
};
|
|
203
204
|
messages: {
|
|
204
205
|
text: any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context, Options } from '..';
|
|
2
2
|
export declare type Loaders = ReturnType<typeof getLoaders>;
|
|
3
3
|
export declare const getLoaders: (options: Options, { clients }: Context) => {
|
|
4
|
-
skuLoader: import("dataloader")<
|
|
4
|
+
skuLoader: import("dataloader")<string, import("../utils/enhanceSku").EnhancedSku, string>;
|
|
5
5
|
simulationLoader: import("dataloader")<import("../clients/commerce/types/Simulation").PayloadItem[], import("../clients/commerce/types/Simulation").Simulation, import("../clients/commerce/types/Simulation").PayloadItem[]>;
|
|
6
6
|
collectionLoader: import("dataloader")<string, import("../clients/commerce/types/Portal").CollectionPageType, string>;
|
|
7
7
|
};
|
|
@@ -2,5 +2,4 @@ import DataLoader from 'dataloader';
|
|
|
2
2
|
import type { EnhancedSku } from '../utils/enhanceSku';
|
|
3
3
|
import type { Options } from '..';
|
|
4
4
|
import type { Clients } from '../clients';
|
|
5
|
-
|
|
6
|
-
export declare const getSkuLoader: (_: Options, clients: Clients) => DataLoader<SelectedFacet[], EnhancedSku, SelectedFacet[]>;
|
|
5
|
+
export declare const getSkuLoader: (_: Options, clients: Clients) => DataLoader<string, EnhancedSku, string>;
|
|
@@ -4,8 +4,8 @@ export declare const Mutation: {
|
|
|
4
4
|
}, ctx: import("..").Context) => Promise<{
|
|
5
5
|
order: {
|
|
6
6
|
orderNumber: string;
|
|
7
|
-
acceptedOffer: {
|
|
8
|
-
product:
|
|
7
|
+
acceptedOffer: Promise<{
|
|
8
|
+
product: import("../utils/enhanceSku").EnhancedSku;
|
|
9
9
|
id: string;
|
|
10
10
|
name: string;
|
|
11
11
|
detailUrl: string;
|
|
@@ -49,7 +49,7 @@ export declare const Mutation: {
|
|
|
49
49
|
total: number;
|
|
50
50
|
};
|
|
51
51
|
attachments: import("../clients/commerce/types/OrderForm").Attachment[];
|
|
52
|
-
}[];
|
|
52
|
+
}>[];
|
|
53
53
|
};
|
|
54
54
|
messages: {
|
|
55
55
|
text: any;
|
|
@@ -4,7 +4,7 @@ import type { ArrayElementType } from '../../../typings';
|
|
|
4
4
|
import type { EnhancedSku } from '../utils/enhanceSku';
|
|
5
5
|
import type { OrderFormItem } from '../clients/commerce/types/OrderForm';
|
|
6
6
|
declare type OrderFormProduct = OrderFormItem & {
|
|
7
|
-
product:
|
|
7
|
+
product: EnhancedSku;
|
|
8
8
|
};
|
|
9
9
|
declare type SearchProduct = ArrayElementType<ReturnType<typeof StoreAggregateOffer.offers>>;
|
|
10
10
|
declare type Root = SearchProduct | OrderFormProduct;
|
|
@@ -18,8 +18,8 @@ export declare const validateCart: (_: unknown, { cart: { order } }: {
|
|
|
18
18
|
}, ctx: Context) => Promise<{
|
|
19
19
|
order: {
|
|
20
20
|
orderNumber: string;
|
|
21
|
-
acceptedOffer: {
|
|
22
|
-
product:
|
|
21
|
+
acceptedOffer: Promise<{
|
|
22
|
+
product: import("../utils/enhanceSku").EnhancedSku;
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
25
25
|
detailUrl: string;
|
|
@@ -63,7 +63,7 @@ export declare const validateCart: (_: unknown, { cart: { order } }: {
|
|
|
63
63
|
total: number;
|
|
64
64
|
};
|
|
65
65
|
attachments: import("../clients/commerce/types/OrderForm").Attachment[];
|
|
66
|
-
}[];
|
|
66
|
+
}>[];
|
|
67
67
|
};
|
|
68
68
|
messages: {
|
|
69
69
|
text: any;
|
|
@@ -13,5 +13,7 @@ export declare const transformSelectedFacet: ({ key, value }: SelectedFacet) =>
|
|
|
13
13
|
key: string;
|
|
14
14
|
value: string;
|
|
15
15
|
};
|
|
16
|
+
export declare const findSlug: (facets?: SelectedFacet[] | null | undefined) => string | null;
|
|
17
|
+
export declare const findSkuId: (facets?: SelectedFacet[] | null | undefined) => string | null;
|
|
16
18
|
export declare const findLocale: (facets?: SelectedFacet[] | null | undefined) => string | null;
|
|
17
19
|
export declare const findChannel: (facets?: SelectedFacet[] | null | undefined) => string | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Item } from '../clients/search/types/ProductSearchResult';
|
|
2
|
+
/**
|
|
3
|
+
* This function implements Portal heuristics for returning the best sku for a product.
|
|
4
|
+
*
|
|
5
|
+
* The best sku is the one with the best (cheapest available) offer
|
|
6
|
+
* */
|
|
7
|
+
export declare const pickBestSku: (skus: Item[]) => Item;
|
|
8
|
+
export declare const isValidSkuId: (skuId: string) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/api",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"graphql": "^15.6.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "a0c8cbd82dc0d83b4ca9f186fd974e09a2bbd768"
|
|
48
48
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Context, Options } from '../../index'
|
|
2
1
|
import { fetchAPI } from '../fetch'
|
|
2
|
+
import type { Context, Options } from '../../index'
|
|
3
3
|
import type { Brand } from './types/Brand'
|
|
4
4
|
import type { CategoryTree } from './types/CategoryTree'
|
|
5
5
|
import type { OrderForm, OrderFormInputItem } from './types/OrderForm'
|
|
@@ -6,7 +6,7 @@ export interface CollectionPageType {
|
|
|
6
6
|
url: string
|
|
7
7
|
title: string
|
|
8
8
|
metaTagDescription: string
|
|
9
|
-
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory'
|
|
9
|
+
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory' | 'Product'
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface FallbackPageType {
|
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
import DataLoader from 'dataloader'
|
|
2
2
|
|
|
3
3
|
import { enhanceSku } from '../utils/enhanceSku'
|
|
4
|
-
import {
|
|
4
|
+
import { NotFoundError } from '../../errors'
|
|
5
5
|
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
6
6
|
import type { Options } from '..'
|
|
7
7
|
import type { Clients } from '../clients'
|
|
8
|
-
import type { SelectedFacet } from '../utils/facets'
|
|
9
8
|
|
|
10
9
|
export const getSkuLoader = (_: Options, clients: Clients) => {
|
|
11
|
-
const loader = async (
|
|
12
|
-
const skuIds = facetsList.map((facets) => {
|
|
13
|
-
const maybeFacet = facets.find(({ key }) => key === 'id')
|
|
14
|
-
|
|
15
|
-
if (!maybeFacet) {
|
|
16
|
-
throw new BadRequestError(
|
|
17
|
-
'Error while loading SKU. Needs to pass an id to selected facets'
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return maybeFacet.value
|
|
22
|
-
})
|
|
23
|
-
|
|
10
|
+
const loader = async (skuIds: readonly string[]) => {
|
|
24
11
|
const { products } = await clients.search.products({
|
|
25
12
|
query: `sku:${skuIds.join(';')}`,
|
|
26
13
|
page: 0,
|
|
@@ -47,7 +34,7 @@ export const getSkuLoader = (_: Options, clients: Clients) => {
|
|
|
47
34
|
return skus
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
return new DataLoader<
|
|
37
|
+
return new DataLoader<string, EnhancedSku>(loader, {
|
|
51
38
|
maxBatchSize: 99, // Max allowed batch size of Search API
|
|
52
39
|
})
|
|
53
40
|
}
|
|
@@ -11,7 +11,7 @@ import type { ArrayElementType } from '../../../typings'
|
|
|
11
11
|
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
12
12
|
import type { OrderFormItem } from '../clients/commerce/types/OrderForm'
|
|
13
13
|
|
|
14
|
-
type OrderFormProduct = OrderFormItem & { product:
|
|
14
|
+
type OrderFormProduct = OrderFormItem & { product: EnhancedSku }
|
|
15
15
|
type SearchProduct = ArrayElementType<
|
|
16
16
|
ReturnType<typeof StoreAggregateOffer.offers>
|
|
17
17
|
>
|
|
@@ -96,13 +96,16 @@ export const StoreOffer: Record<string, Resolver<Root>> = {
|
|
|
96
96
|
|
|
97
97
|
return null
|
|
98
98
|
},
|
|
99
|
-
itemOffered:
|
|
99
|
+
itemOffered: (root) => {
|
|
100
100
|
if (isSearchItem(root)) {
|
|
101
101
|
return root.product
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (isOrderFormItem(root)) {
|
|
105
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
...root.product,
|
|
107
|
+
attachmentsValues: root.attachments,
|
|
108
|
+
}
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
return null
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalFromProduct } from '../utils/canonical'
|
|
1
2
|
import { enhanceCommercialOffer } from '../utils/enhanceCommercialOffer'
|
|
2
3
|
import { bestOfferFirst } from '../utils/productStock'
|
|
3
4
|
import { slugify } from '../utils/slugify'
|
|
@@ -41,9 +42,10 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
|
|
|
41
42
|
name: ({ isVariantOf, name }) => name ?? isVariantOf.productName,
|
|
42
43
|
slug: ({ isVariantOf: { linkText }, itemId }) => getSlug(linkText, itemId),
|
|
43
44
|
description: ({ isVariantOf: { description } }) => description,
|
|
44
|
-
seo: ({ isVariantOf
|
|
45
|
-
title: productName,
|
|
46
|
-
description,
|
|
45
|
+
seo: ({ isVariantOf }) => ({
|
|
46
|
+
title: isVariantOf.productName,
|
|
47
|
+
description: isVariantOf.description,
|
|
48
|
+
canonical: canonicalFromProduct(isVariantOf),
|
|
47
49
|
}),
|
|
48
50
|
brand: ({ isVariantOf: { brand } }) => ({ name: brand }),
|
|
49
51
|
breadcrumbList: ({
|
|
@@ -85,7 +87,7 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
|
|
|
85
87
|
aggregateRating: () => ({}),
|
|
86
88
|
offers: (root) =>
|
|
87
89
|
root.sellers
|
|
88
|
-
.
|
|
90
|
+
.map((seller) =>
|
|
89
91
|
enhanceCommercialOffer({
|
|
90
92
|
offer: seller.commertialOffer,
|
|
91
93
|
seller,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { NotFoundError, BadRequestError } from '../../errors'
|
|
1
2
|
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
|
|
2
3
|
import { enhanceSku } from '../utils/enhanceSku'
|
|
3
4
|
import {
|
|
4
5
|
findChannel,
|
|
5
6
|
findLocale,
|
|
7
|
+
findSkuId,
|
|
8
|
+
findSlug,
|
|
6
9
|
transformSelectedFacet,
|
|
7
10
|
} from '../utils/facets'
|
|
8
11
|
import { SORT_MAP } from '../utils/sort'
|
|
@@ -16,12 +19,15 @@ import type {
|
|
|
16
19
|
} from '../../../__generated__/schema'
|
|
17
20
|
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
|
|
18
21
|
import type { Context } from '../index'
|
|
22
|
+
import { isValidSkuId, pickBestSku } from '../utils/sku'
|
|
19
23
|
|
|
20
24
|
export const Query = {
|
|
21
25
|
product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => {
|
|
22
26
|
// Insert channel in context for later usage
|
|
23
27
|
const channel = findChannel(locator)
|
|
24
28
|
const locale = findLocale(locator)
|
|
29
|
+
const id = findSkuId(locator)
|
|
30
|
+
const slug = findSlug(locator)
|
|
25
31
|
|
|
26
32
|
if (channel) {
|
|
27
33
|
mutateChannelContext(ctx, channel)
|
|
@@ -33,9 +39,46 @@ export const Query = {
|
|
|
33
39
|
|
|
34
40
|
const {
|
|
35
41
|
loaders: { skuLoader },
|
|
42
|
+
clients: { commerce, search },
|
|
36
43
|
} = ctx
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
try {
|
|
46
|
+
const skuId = id ?? slug?.split('-').pop() ?? ''
|
|
47
|
+
|
|
48
|
+
if (!isValidSkuId(skuId)) {
|
|
49
|
+
throw new Error('Invalid SkuId')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sku = await skuLoader.load(skuId)
|
|
53
|
+
|
|
54
|
+
return sku
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (slug == null) {
|
|
57
|
+
throw new BadRequestError('Missing slug or id')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const route = await commerce.catalog.portal.pagetype(`${slug}/p`)
|
|
61
|
+
|
|
62
|
+
if (route.pageType !== 'Product' || !route.id) {
|
|
63
|
+
throw new NotFoundError(`No product found for slug ${slug}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const {
|
|
67
|
+
products: [product],
|
|
68
|
+
} = await search.products({
|
|
69
|
+
page: 0,
|
|
70
|
+
count: 1,
|
|
71
|
+
query: `product:${route.id}`,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (!product) {
|
|
75
|
+
throw new NotFoundError(`No product found for id ${route.id}`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sku = pickBestSku(product.items)
|
|
79
|
+
|
|
80
|
+
return enhanceSku(sku, product)
|
|
81
|
+
}
|
|
39
82
|
},
|
|
40
83
|
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
|
|
41
84
|
const {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Resolver } from '..'
|
|
2
2
|
|
|
3
|
-
type Root = { title?: string; description?: string }
|
|
3
|
+
type Root = { title?: string; description?: string; canonical?: string }
|
|
4
4
|
|
|
5
5
|
export const StoreSeo: Record<string, Resolver<Root>> = {
|
|
6
6
|
title: ({ title }) => title ?? '',
|
|
7
7
|
description: ({ description }) => description ?? '',
|
|
8
|
+
canonical: ({ canonical }) => canonical ?? '',
|
|
8
9
|
titleTemplate: () => '',
|
|
9
|
-
canonical: () => '',
|
|
10
10
|
}
|
|
@@ -96,16 +96,16 @@ const equals = (storeOrder: IStoreOrder, orderForm: OrderForm) => {
|
|
|
96
96
|
return isSameOrder && orderItemsAreSync
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const orderFormToCart = (
|
|
99
|
+
const orderFormToCart = async (
|
|
100
100
|
form: OrderForm,
|
|
101
101
|
skuLoader: Context['loaders']['skuLoader']
|
|
102
102
|
) => {
|
|
103
103
|
return {
|
|
104
104
|
order: {
|
|
105
105
|
orderNumber: form.orderFormId,
|
|
106
|
-
acceptedOffer: form.items.map((item) => ({
|
|
106
|
+
acceptedOffer: form.items.map(async (item) => ({
|
|
107
107
|
...item,
|
|
108
|
-
product: skuLoader.load(
|
|
108
|
+
product: await skuLoader.load(item.id), // TODO: add channel
|
|
109
109
|
})),
|
|
110
110
|
},
|
|
111
111
|
messages: form.messages.map(({ text, status }) => ({
|
|
@@ -34,6 +34,12 @@ export const transformSelectedFacet = ({ key, value }: SelectedFacet) => {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export const findSlug = (facets?: Maybe<SelectedFacet[]>) =>
|
|
38
|
+
facets?.find((x) => x.key === 'slug')?.value ?? null
|
|
39
|
+
|
|
40
|
+
export const findSkuId = (facets?: Maybe<SelectedFacet[]>) =>
|
|
41
|
+
facets?.find((x) => x.key === 'id')?.value ?? null
|
|
42
|
+
|
|
37
43
|
export const findLocale = (facets?: Maybe<SelectedFacet[]>) =>
|
|
38
44
|
facets?.find((x) => x.key === 'locale')?.value ?? null
|
|
39
45
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* More info at: https://en.wikipedia.org/wiki/Order_statistic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// O(n) search to find the max of an array
|
|
6
|
+
export const min = <T>(array: T[], cmp: (a: T, b: T) => number) => {
|
|
7
|
+
let best = 0
|
|
8
|
+
|
|
9
|
+
for (let curr = 1; curr < array.length; curr++) {
|
|
10
|
+
if (cmp(array[best], array[curr]) > 0) {
|
|
11
|
+
best = curr
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return array[best]
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { min } from './orderStatistics'
|
|
2
|
+
import { bestOfferFirst } from './productStock'
|
|
3
|
+
import type { Item } from '../clients/search/types/ProductSearchResult'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This function implements Portal heuristics for returning the best sku for a product.
|
|
7
|
+
*
|
|
8
|
+
* The best sku is the one with the best (cheapest available) offer
|
|
9
|
+
* */
|
|
10
|
+
export const pickBestSku = (skus: Item[]) => {
|
|
11
|
+
const offersBySku = skus.flatMap((sku) =>
|
|
12
|
+
sku.sellers.map((seller) => ({
|
|
13
|
+
offer: seller.commertialOffer,
|
|
14
|
+
sku,
|
|
15
|
+
}))
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const best = min(offersBySku, ({ offer: o1 }, { offer: o2 }) =>
|
|
19
|
+
bestOfferFirst(o1, o2)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return best.sku
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const isValidSkuId = (skuId: string) =>
|
|
26
|
+
skuId !== '' && !Number.isNaN(Number(skuId))
|