Skip to content

Commit e77a1f3

Browse files
committed
✨ Introduce Hydrater
1 parent dee645d commit e77a1f3

File tree

6 files changed

+253
-2
lines changed

6 files changed

+253
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
},
2727
"license": "MIT",
2828
"name": "@crystallize/js-api-client",
29-
"version": "0.3.0"
29+
"version": "0.3.1"
3030
}

src/core/hydrate.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
2+
import { ClientInterface } from './client';
3+
4+
export type ProductHydrater = (
5+
items: string[],
6+
language: string,
7+
extraQuery?: any,
8+
perProduct?: (item: string, index: number) => any
9+
) => Promise<any>;
10+
11+
export function createProductHydraterByPaths(
12+
client: ClientInterface
13+
): ProductHydrater {
14+
return <T>(
15+
paths: string[],
16+
language: string,
17+
extraQuery?: any,
18+
perProduct?: (item: string, index: number) => any
19+
): Promise<T> => {
20+
const productListQuery = paths
21+
.map((path: string, index: number) => {
22+
return {
23+
[`product${index}`]: {
24+
__aliasFor: 'catalogue',
25+
__args: { path, language },
26+
name: true,
27+
path: true,
28+
__on: {
29+
__typeName: 'Product',
30+
id: true,
31+
vatType: {
32+
name: true,
33+
percent: true
34+
},
35+
variants: {
36+
id: true,
37+
sku: true,
38+
name: true,
39+
stock: true,
40+
priceVariants: {
41+
price: true,
42+
identifier: true,
43+
currency: true
44+
}
45+
},
46+
...(perProduct !== undefined
47+
? perProduct(path, index)
48+
: {})
49+
}
50+
}
51+
};
52+
})
53+
.reduce((acc, curr) => {
54+
return { ...acc, ...curr };
55+
}, {});
56+
57+
const query = {
58+
query: {
59+
...{ ...productListQuery },
60+
...(extraQuery !== undefined ? extraQuery : {})
61+
}
62+
};
63+
64+
const fetch = client.catalogueApi;
65+
return fetch(jsonToGraphQLQuery(query));
66+
};
67+
}
68+
69+
export function createProductHydraterBySkus(
70+
client: ClientInterface
71+
): ProductHydrater {
72+
async function getPathForSkus(
73+
skus: string[],
74+
language: string
75+
): Promise<string[]> {
76+
const search = client.searchApi;
77+
const pathsSet = new Set<string>();
78+
let searchAfterCursor: any;
79+
async function getNextSearchPage() {
80+
const searchAPIResponse = await search(
81+
`query GET_PRODUCTS_BY_SKU ($skus: [String!], $after: String, $language: String!) {
82+
search (
83+
after: $after
84+
language: $language
85+
filter: {
86+
include: {
87+
skus: $skus
88+
}
89+
}
90+
) {
91+
pageInfo {
92+
endCursor
93+
hasNextPage
94+
}
95+
edges {
96+
node {
97+
path
98+
}
99+
}
100+
}
101+
}`,
102+
{
103+
skus: skus,
104+
after: searchAfterCursor,
105+
language
106+
}
107+
);
108+
109+
const { edges, pageInfo } = searchAPIResponse.search || {};
110+
111+
edges?.forEach((edge: any) => pathsSet.add(edge.node.path));
112+
113+
if (pageInfo?.hasNextPage) {
114+
searchAfterCursor = pageInfo.endCursor;
115+
await getNextSearchPage();
116+
}
117+
}
118+
119+
await getNextSearchPage();
120+
121+
return Array.from(pathsSet);
122+
}
123+
return async <T>(
124+
skus: string[],
125+
language: string,
126+
extraQuery?: any,
127+
perProduct?: (item: string, index: number) => any
128+
): Promise<T> => {
129+
const paths = await getPathForSkus(skus, language);
130+
if (paths.length === 0) {
131+
const empty = skus
132+
.map((sku: string, index: number) => {
133+
return {
134+
[`product${index}`]: {}
135+
};
136+
})
137+
.reduce((acc, curr) => {
138+
return { ...acc, ...curr };
139+
});
140+
141+
return empty as any;
142+
}
143+
return createProductHydraterByPaths(client)(
144+
paths,
145+
language,
146+
extraQuery,
147+
perProduct
148+
);
149+
};
150+
}

src/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
export * from './core/client';
22
export * from './core/navigation';
3+
export * from './core/hydrate';
34
import { createClient } from './core/client';
4-
import { createNavigationByFoldersFetcher } from './core/navigation';
5+
import {
6+
createNavigationByFoldersFetcher,
7+
createNavigationByTopicsFetcher
8+
} from './core/navigation';
9+
import {
10+
createProductHydraterByPaths,
11+
createProductHydraterBySkus
12+
} from './core/hydrate';
513

614
export const CrystallizeClient = createClient({
715
tenantIdentifier: process.env.CRYSTALLIZE_TENANT_IDENTIFIER || '',
@@ -11,3 +19,9 @@ export const CrystallizeClient = createClient({
1119

1220
export const CrystallizeNavigationTreeFetcher =
1321
createNavigationByFoldersFetcher(CrystallizeClient);
22+
export const CrystallizeNavigationTopicsFetcher =
23+
createNavigationByTopicsFetcher(CrystallizeClient);
24+
export const CrystallizeHydraterByPaths =
25+
createProductHydraterByPaths(CrystallizeClient);
26+
export const CrystallizeHydraterBySkus =
27+
createProductHydraterBySkus(CrystallizeClient);

tests/call.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,41 @@ test('callCatalogueApi: Raw fetch Error', async () => {
4141
});
4242
expect(response.length).toBeGreaterThanOrEqual(1);
4343
});
44+
45+
test('callSearchApi: Raw fetch Skus', async () => {
46+
const CrystallizeClient = createClient({
47+
tenantIdentifier: 'furniture'
48+
});
49+
50+
const caller = CrystallizeClient.searchApi;
51+
52+
const query = `query GET_PRODUCTS_BY_SKU ($skus: [String!], $after: String, $language: String!) {
53+
search (
54+
after: $after
55+
language: $language
56+
filter: {
57+
include: {
58+
skus: $skus
59+
}
60+
}
61+
) {
62+
pageInfo {
63+
endCursor
64+
hasNextPage
65+
}
66+
edges {
67+
node {
68+
path
69+
}
70+
}
71+
}
72+
}`;
73+
const response = await caller(query, {
74+
skus: ['b-1628520141076'],
75+
language: 'en'
76+
});
77+
78+
expect(response.search.edges[0].node.path).toBe(
79+
'/shop/bathroom-fitting/large-mounted-cabinet-in-treated-wood'
80+
);
81+
});

tests/hydrate.paths.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const {
2+
createClient,
3+
createProductHydraterByPaths
4+
} = require('../dist/index.js');
5+
6+
test('Hydrate Paths', async () => {
7+
const CrystallizeClient = createClient({
8+
tenantIdentifier: 'furniture'
9+
});
10+
11+
const hydrater = createProductHydraterByPaths(CrystallizeClient);
12+
const response = await hydrater(
13+
[
14+
'/shop/bathroom-fitting/large-mounted-cabinet-in-treated-wood',
15+
'/shop/bathroom-fitting/mounted-bathroom-counter-with-shelf'
16+
],
17+
'en'
18+
);
19+
20+
expect(response.product0.path).toBe(
21+
'/shop/bathroom-fitting/large-mounted-cabinet-in-treated-wood'
22+
);
23+
expect(response.product1.path).toBe(
24+
'/shop/bathroom-fitting/mounted-bathroom-counter-with-shelf'
25+
);
26+
});

tests/hydrate.skus.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const {
2+
createClient,
3+
createProductHydraterBySkus
4+
} = require('../dist/index.js');
5+
6+
test('Hydrate Skus', async () => {
7+
const CrystallizeClient = createClient({
8+
tenantIdentifier: 'furniture'
9+
});
10+
11+
const hydrater = createProductHydraterBySkus(CrystallizeClient);
12+
const response = await hydrater(
13+
['b-1628520141076', 'b-1628514494819'],
14+
'en'
15+
);
16+
17+
expect(response.product0.path).toBe(
18+
'/shop/bathroom-fitting/large-mounted-cabinet-in-treated-wood'
19+
);
20+
expect(response.product1.path).toBe(
21+
'/shop/bathroom-fitting/mounted-bathroom-counter-with-shelf'
22+
);
23+
});

0 commit comments

Comments
 (0)