@faststore/api 1.5.10 → 1.5.14
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 +44 -0
- package/dist/api.cjs.development.js +52 -32
- 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 +52 -32
- package/dist/api.esm.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/platforms/vtex/clients/commerce/types/Portal.d.ts +11 -2
- package/dist/platforms/vtex/index.d.ts +2 -2
- package/dist/platforms/vtex/loaders/collection.d.ts +6 -0
- package/dist/platforms/vtex/loaders/index.d.ts +1 -0
- package/dist/platforms/vtex/resolvers/collection.d.ts +2 -2
- package/dist/platforms/vtex/resolvers/query.d.ts +1 -1
- package/package.json +4 -3
- package/src/platforms/vtex/clients/commerce/types/Portal.ts +13 -8
- package/src/platforms/vtex/loaders/collection.ts +50 -0
- package/src/platforms/vtex/loaders/index.ts +3 -0
- package/src/platforms/vtex/resolvers/collection.ts +15 -17
- package/src/platforms/vtex/resolvers/query.ts +16 -24
|
@@ -3,4 +3,5 @@ export declare type Loaders = ReturnType<typeof getLoaders>;
|
|
|
3
3
|
export declare const getLoaders: (options: Options, { clients }: Context) => {
|
|
4
4
|
skuLoader: import("dataloader")<import("../utils/facets").SelectedFacet[], import("../utils/enhanceSku").EnhancedSku, import("../utils/facets").SelectedFacet[]>;
|
|
5
5
|
simulationLoader: import("dataloader")<import("../clients/commerce/types/Simulation").PayloadItem[], import("../clients/commerce/types/Simulation").Simulation, import("../clients/commerce/types/Simulation").PayloadItem[]>;
|
|
6
|
+
collectionLoader: import("dataloader")<string, import("../clients/commerce/types/Portal").CollectionPageType, string>;
|
|
6
7
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Resolver } from '..';
|
|
2
2
|
import type { Brand } from '../clients/commerce/types/Brand';
|
|
3
3
|
import type { CategoryTree } from '../clients/commerce/types/CategoryTree';
|
|
4
|
-
import type {
|
|
4
|
+
import type { CollectionPageType } from '../clients/commerce/types/Portal';
|
|
5
5
|
declare type Root = Brand | (CategoryTree & {
|
|
6
6
|
level: number;
|
|
7
|
-
}) |
|
|
7
|
+
}) | CollectionPageType;
|
|
8
8
|
export declare const StoreCollection: Record<string, Resolver<Root>>;
|
|
9
9
|
export {};
|
|
@@ -3,7 +3,7 @@ import type { CategoryTree } from '../clients/commerce/types/CategoryTree';
|
|
|
3
3
|
import type { Context } from '../index';
|
|
4
4
|
export declare const Query: {
|
|
5
5
|
product: (_: unknown, { locator }: QueryProductArgs, ctx: Context) => Promise<import("../utils/enhanceSku").EnhancedSku>;
|
|
6
|
-
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => Promise<import("../clients/commerce/types/Portal").
|
|
6
|
+
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => Promise<import("../clients/commerce/types/Portal").CollectionPageType>;
|
|
7
7
|
search: (_: unknown, { first, after: maybeAfter, sort, term, selectedFacets }: QuerySearchArgs, ctx: Context) => Promise<{
|
|
8
8
|
page: number;
|
|
9
9
|
count: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/api",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"@sindresorhus/slugify": "^1.1.2",
|
|
27
27
|
"dataloader": "^2.0.0",
|
|
28
28
|
"fast-deep-equal": "^3.1.3",
|
|
29
|
-
"isomorphic-unfetch": "^3.1.0"
|
|
29
|
+
"isomorphic-unfetch": "^3.1.0",
|
|
30
|
+
"p-limit": "^3.1.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@graphql-codegen/cli": "2.2.0",
|
|
@@ -42,5 +43,5 @@
|
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"graphql": "^15.6.0"
|
|
44
45
|
},
|
|
45
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "a51e880c27a8a7a6bc1b685d1777e4525e3024e7"
|
|
46
47
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type PortalPagetype = CollectionPageType | FallbackPageType
|
|
2
|
+
|
|
3
|
+
export interface CollectionPageType {
|
|
2
4
|
id: number
|
|
3
5
|
name: string
|
|
4
6
|
url: string
|
|
5
7
|
title: string
|
|
6
8
|
metaTagDescription: string
|
|
7
|
-
pageType:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FallbackPageType {
|
|
13
|
+
id: null
|
|
14
|
+
name: null | string
|
|
15
|
+
url: null | string
|
|
16
|
+
title: null
|
|
17
|
+
metaTagDescription: null
|
|
18
|
+
pageType: 'NotFound' | 'FullText'
|
|
14
19
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import DataLoader from 'dataloader'
|
|
2
|
+
import pLimit from 'p-limit'
|
|
3
|
+
|
|
4
|
+
import { NotFoundError } from '../utils/errors'
|
|
5
|
+
import type { CollectionPageType } from '../clients/commerce/types/Portal'
|
|
6
|
+
import type { Options } from '..'
|
|
7
|
+
import type { Clients } from '../clients'
|
|
8
|
+
|
|
9
|
+
// Limits concurrent requests to 20 so that they don't timeout
|
|
10
|
+
const CONCURRENT_REQUESTS_MAX = 20
|
|
11
|
+
|
|
12
|
+
const collectionPageTypes = new Set([
|
|
13
|
+
'brand',
|
|
14
|
+
'category',
|
|
15
|
+
'department',
|
|
16
|
+
'subcategory',
|
|
17
|
+
] as const)
|
|
18
|
+
|
|
19
|
+
export const isCollectionPageType = (x: any): x is CollectionPageType =>
|
|
20
|
+
typeof x.pageType === 'string' &&
|
|
21
|
+
collectionPageTypes.has(x.pageType.toLowerCase())
|
|
22
|
+
|
|
23
|
+
export const getCollectionLoader = (_: Options, clients: Clients) => {
|
|
24
|
+
const limit = pLimit(CONCURRENT_REQUESTS_MAX)
|
|
25
|
+
|
|
26
|
+
const loader = async (
|
|
27
|
+
slugs: readonly string[]
|
|
28
|
+
): Promise<CollectionPageType[]> => {
|
|
29
|
+
return Promise.all(
|
|
30
|
+
slugs.map((slug: string) =>
|
|
31
|
+
limit(async () => {
|
|
32
|
+
const page = await clients.commerce.catalog.portal.pagetype(slug)
|
|
33
|
+
|
|
34
|
+
if (isCollectionPageType(page)) {
|
|
35
|
+
return page
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw new NotFoundError(
|
|
39
|
+
`Catalog returned ${page.pageType} for slug: ${slug}. This usually happens when there is more than one category with the same name in the same category tree level.`
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new DataLoader<string, CollectionPageType>(loader, {
|
|
47
|
+
// DataLoader is being used to cache requests, not to batch them
|
|
48
|
+
batch: false,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSimulationLoader } from './simulation'
|
|
2
2
|
import { getSkuLoader } from './sku'
|
|
3
|
+
import { getCollectionLoader } from './collection'
|
|
3
4
|
import type { Context, Options } from '..'
|
|
4
5
|
|
|
5
6
|
export type Loaders = ReturnType<typeof getLoaders>
|
|
@@ -7,9 +8,11 @@ export type Loaders = ReturnType<typeof getLoaders>
|
|
|
7
8
|
export const getLoaders = (options: Options, { clients }: Context) => {
|
|
8
9
|
const skuLoader = getSkuLoader(options, clients)
|
|
9
10
|
const simulationLoader = getSimulationLoader(options, clients)
|
|
11
|
+
const collectionLoader = getCollectionLoader(options, clients)
|
|
10
12
|
|
|
11
13
|
return {
|
|
12
14
|
skuLoader,
|
|
13
15
|
simulationLoader,
|
|
16
|
+
collectionLoader,
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
+
import { isCollectionPageType } from '../loaders/collection'
|
|
1
2
|
import { slugify as baseSlugify } from '../utils/slugify'
|
|
2
3
|
import type { Resolver } from '..'
|
|
3
4
|
import type { Brand } from '../clients/commerce/types/Brand'
|
|
4
5
|
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
|
|
5
|
-
import type {
|
|
6
|
+
import type { CollectionPageType } from '../clients/commerce/types/Portal'
|
|
6
7
|
|
|
7
|
-
type Root = Brand | (CategoryTree & { level: number }) |
|
|
8
|
+
type Root = Brand | (CategoryTree & { level: number }) | CollectionPageType
|
|
8
9
|
|
|
9
10
|
const isBrand = (x: any): x is Brand => x.type === 'brand'
|
|
10
11
|
|
|
11
|
-
const isPortalPageType = (x: any): x is PortalPagetype =>
|
|
12
|
-
typeof x.pageType === 'string'
|
|
13
|
-
|
|
14
12
|
const slugify = (root: Root) => {
|
|
15
13
|
if (isBrand(root)) {
|
|
16
|
-
return baseSlugify(root.name)
|
|
14
|
+
return baseSlugify(root.name.toLowerCase())
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
if (
|
|
17
|
+
if (isCollectionPageType(root)) {
|
|
20
18
|
return new URL(`https://${root.url}`).pathname.slice(1)
|
|
21
19
|
}
|
|
22
20
|
|
|
@@ -27,7 +25,7 @@ export const StoreCollection: Record<string, Resolver<Root>> = {
|
|
|
27
25
|
id: ({ id }) => id.toString(),
|
|
28
26
|
slug: (root) => slugify(root),
|
|
29
27
|
seo: (root) =>
|
|
30
|
-
isBrand(root) ||
|
|
28
|
+
isBrand(root) || isCollectionPageType(root)
|
|
31
29
|
? {
|
|
32
30
|
title: root.title,
|
|
33
31
|
description: root.metaTagDescription,
|
|
@@ -39,7 +37,7 @@ export const StoreCollection: Record<string, Resolver<Root>> = {
|
|
|
39
37
|
type: (root) =>
|
|
40
38
|
isBrand(root)
|
|
41
39
|
? 'Brand'
|
|
42
|
-
:
|
|
40
|
+
: isCollectionPageType(root)
|
|
43
41
|
? root.pageType
|
|
44
42
|
: root.level === 0
|
|
45
43
|
? 'Department'
|
|
@@ -51,7 +49,7 @@ export const StoreCollection: Record<string, Resolver<Root>> = {
|
|
|
51
49
|
}
|
|
52
50
|
: {
|
|
53
51
|
selectedFacets: new URL(
|
|
54
|
-
|
|
52
|
+
isCollectionPageType(root) ? `https://${root.url}` : root.url
|
|
55
53
|
).pathname
|
|
56
54
|
.slice(1)
|
|
57
55
|
.split('/')
|
|
@@ -62,7 +60,7 @@ export const StoreCollection: Record<string, Resolver<Root>> = {
|
|
|
62
60
|
},
|
|
63
61
|
breadcrumbList: async (root, _, ctx) => {
|
|
64
62
|
const {
|
|
65
|
-
|
|
63
|
+
loaders: { collectionLoader },
|
|
66
64
|
} = ctx
|
|
67
65
|
|
|
68
66
|
const slug = slugify(root)
|
|
@@ -78,17 +76,17 @@ export const StoreCollection: Record<string, Resolver<Root>> = {
|
|
|
78
76
|
segments.slice(0, index + 1).join('/')
|
|
79
77
|
)
|
|
80
78
|
|
|
81
|
-
const
|
|
82
|
-
slugs.map((s) =>
|
|
79
|
+
const collections = await Promise.all(
|
|
80
|
+
slugs.map((s) => collectionLoader.load(s))
|
|
83
81
|
)
|
|
84
82
|
|
|
85
83
|
return {
|
|
86
|
-
itemListElement:
|
|
87
|
-
item: new URL(`https://${
|
|
88
|
-
name:
|
|
84
|
+
itemListElement: collections.map((collection, index) => ({
|
|
85
|
+
item: new URL(`https://${collection.url}`).pathname.toLowerCase(),
|
|
86
|
+
name: collection.name,
|
|
89
87
|
position: index + 1,
|
|
90
88
|
})),
|
|
91
|
-
numberOfItems:
|
|
89
|
+
numberOfItems: collections.length,
|
|
92
90
|
}
|
|
93
91
|
},
|
|
94
92
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { enhanceSku } from '../utils/enhanceSku'
|
|
2
|
-
import { NotFoundError } from '../utils/errors'
|
|
3
2
|
import { transformSelectedFacet } from '../utils/facets'
|
|
4
3
|
import { SORT_MAP } from '../utils/sort'
|
|
5
4
|
import { StoreCollection } from './collection'
|
|
@@ -29,24 +28,12 @@ export const Query = {
|
|
|
29
28
|
|
|
30
29
|
return skuLoader.load(locator.map(transformSelectedFacet))
|
|
31
30
|
},
|
|
32
|
-
collection:
|
|
33
|
-
_: unknown,
|
|
34
|
-
{ slug }: QueryCollectionArgs,
|
|
35
|
-
ctx: Context
|
|
36
|
-
) => {
|
|
31
|
+
collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
|
|
37
32
|
const {
|
|
38
|
-
|
|
33
|
+
loaders: { collectionLoader },
|
|
39
34
|
} = ctx
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const whitelist = ['Brand', 'Category', 'Department', 'Subcategory']
|
|
44
|
-
|
|
45
|
-
if (whitelist.includes(result.pageType)) {
|
|
46
|
-
return result
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
throw new NotFoundError(`Not Found: ${slug}`)
|
|
36
|
+
return collectionLoader.load(slug)
|
|
50
37
|
},
|
|
51
38
|
search: async (
|
|
52
39
|
_: unknown,
|
|
@@ -100,6 +87,7 @@ export const Query = {
|
|
|
100
87
|
endCursor: products.total.toString(),
|
|
101
88
|
totalCount: products.total,
|
|
102
89
|
},
|
|
90
|
+
// after + index is bigger than after+first itself because of the array flat() above
|
|
103
91
|
edges: skus.map((sku, index) => ({
|
|
104
92
|
node: sku,
|
|
105
93
|
cursor: (after + index).toString(),
|
|
@@ -140,20 +128,24 @@ export const Query = {
|
|
|
140
128
|
...categories,
|
|
141
129
|
]
|
|
142
130
|
|
|
131
|
+
const validCollections = collections
|
|
132
|
+
// Nullable slugs may cause one route to override the other
|
|
133
|
+
.filter((node) => Boolean(StoreCollection.slug(node, null, ctx, null)))
|
|
134
|
+
|
|
143
135
|
return {
|
|
144
136
|
pageInfo: {
|
|
145
|
-
hasNextPage:
|
|
146
|
-
hasPreviousPage:
|
|
137
|
+
hasNextPage: validCollections.length - after > first,
|
|
138
|
+
hasPreviousPage: after > 0,
|
|
147
139
|
startCursor: '0',
|
|
148
|
-
endCursor:
|
|
140
|
+
endCursor: (
|
|
141
|
+
Math.min(first, validCollections.length - after) - 1
|
|
142
|
+
).toString(),
|
|
149
143
|
},
|
|
150
|
-
edges:
|
|
151
|
-
|
|
152
|
-
.filter((node) => Boolean(StoreCollection.slug(node, null, ctx, null)))
|
|
153
|
-
.slice(after, first)
|
|
144
|
+
edges: validCollections
|
|
145
|
+
.slice(after, after + first)
|
|
154
146
|
.map((node, index) => ({
|
|
155
147
|
node,
|
|
156
|
-
cursor: index.toString(),
|
|
148
|
+
cursor: (after + index).toString(),
|
|
157
149
|
})),
|
|
158
150
|
}
|
|
159
151
|
},
|