@graphcommerce/algolia-recommend 9.0.0-canary.84
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 +7 -0
- package/Config.graphqls +44 -0
- package/README.md +10 -0
- package/algolia-recommend-spec.yaml +2720 -0
- package/mesh/createProductMapper.ts +19 -0
- package/mesh/createValueFacetMapper.ts +13 -0
- package/mesh/getRecommendationArgs.ts +49 -0
- package/mesh/getRecommendations.ts +81 -0
- package/mesh/resolvers.ts +239 -0
- package/package.json +29 -0
- package/plugins/meshConfigAlgoliaRecommend.ts +63 -0
- package/schema/AlgoliaProductRecommendations.graphqls +137 -0
- package/schema/AlgoliaTrending.graphqls +74 -0
- package/scripts/generate-recommend-spec.mts +131 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { algoliaHitToMagentoProduct } from '@graphcommerce/algolia-products'
|
|
2
|
+
import { getGroupId } from '@graphcommerce/algolia-products/mesh/getGroupId'
|
|
3
|
+
import { getStoreConfig } from '@graphcommerce/algolia-products/mesh/getStoreConfig'
|
|
4
|
+
import type {
|
|
5
|
+
MeshContext,
|
|
6
|
+
AlgoliarecommendationsHit,
|
|
7
|
+
Algoliahit,
|
|
8
|
+
} from '@graphcommerce/graphql-mesh'
|
|
9
|
+
|
|
10
|
+
export async function createProductMapper(context: MeshContext) {
|
|
11
|
+
const storeConfig = await getStoreConfig(context)
|
|
12
|
+
const groupId = getGroupId(context)
|
|
13
|
+
|
|
14
|
+
const isAlgoliaRecommendHit = (hit: AlgoliarecommendationsHit | Algoliahit): hit is Algoliahit =>
|
|
15
|
+
'__typename' in hit && hit.__typename === 'AlgoliarecommendHit'
|
|
16
|
+
|
|
17
|
+
return (hit: AlgoliarecommendationsHit) =>
|
|
18
|
+
isAlgoliaRecommendHit(hit) ? algoliaHitToMagentoProduct(hit, storeConfig, groupId) : null
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AlgoliarecommendationsHit,
|
|
3
|
+
AlgoliatrendingFacetHit,
|
|
4
|
+
TrendingFacetValue,
|
|
5
|
+
} from '@graphcommerce/graphql-mesh'
|
|
6
|
+
|
|
7
|
+
export function createFacetValueMapper() {
|
|
8
|
+
const isAlgoliaRecommendHit = (hit: AlgoliarecommendationsHit): hit is AlgoliatrendingFacetHit =>
|
|
9
|
+
!!hit && '__typename' in hit && hit.__typename === 'AlgoliatrendingFacetHit'
|
|
10
|
+
|
|
11
|
+
return (hit: AlgoliarecommendationsHit): TrendingFacetValue | null =>
|
|
12
|
+
isAlgoliaRecommendHit(hit) ? hit : null
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getSearchResultsInput } from '@graphcommerce/algolia-products'
|
|
2
|
+
import {
|
|
3
|
+
QueryproductsArgs,
|
|
4
|
+
MeshContext,
|
|
5
|
+
AlgoliasearchParamsObject_Input,
|
|
6
|
+
AlgoliaLookingSimilarInput,
|
|
7
|
+
} from '@graphcommerce/graphql-mesh'
|
|
8
|
+
|
|
9
|
+
export async function getRecommendationQueryInput(
|
|
10
|
+
args: QueryproductsArgs,
|
|
11
|
+
context: MeshContext,
|
|
12
|
+
): Promise<AlgoliasearchParamsObject_Input> {
|
|
13
|
+
const queryParameters = await getSearchResultsInput(args, context)
|
|
14
|
+
|
|
15
|
+
if (queryParameters?.facets) delete queryParameters.facets
|
|
16
|
+
if (queryParameters?.hitsPerPage) delete queryParameters.hitsPerPage
|
|
17
|
+
if (queryParameters?.page || queryParameters?.page === 0) {
|
|
18
|
+
delete queryParameters.page
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return queryParameters
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getRecommendationsArgs(
|
|
25
|
+
root: { uid?: string },
|
|
26
|
+
args: { input?: AlgoliaLookingSimilarInput | null },
|
|
27
|
+
context: MeshContext,
|
|
28
|
+
) {
|
|
29
|
+
const { fallback, filter, maxRecommendations = 8, search, threshold = 75 } = args.input ?? {}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
objectID: atob(root.uid ?? ''),
|
|
33
|
+
threshold,
|
|
34
|
+
maxRecommendations,
|
|
35
|
+
queryParameters: await getRecommendationQueryInput(
|
|
36
|
+
{ filter, pageSize: maxRecommendations, search },
|
|
37
|
+
context,
|
|
38
|
+
),
|
|
39
|
+
fallbackParameters: await getRecommendationQueryInput(
|
|
40
|
+
{
|
|
41
|
+
filter: fallback?.filter,
|
|
42
|
+
pageSize: maxRecommendations,
|
|
43
|
+
search: fallback?.search,
|
|
44
|
+
sort: fallback?.sort,
|
|
45
|
+
},
|
|
46
|
+
context,
|
|
47
|
+
),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getIndexName } from '@graphcommerce/algolia-products/mesh/getIndexName'
|
|
2
|
+
import type {
|
|
3
|
+
AlgoliarecommendationsRequest_Input,
|
|
4
|
+
MeshContext,
|
|
5
|
+
AlgoliarecommendationsHit,
|
|
6
|
+
} from '@graphcommerce/graphql-mesh'
|
|
7
|
+
import { nonNullable } from '@graphcommerce/next-ui'
|
|
8
|
+
import type { GraphQLResolveInfo } from 'graphql'
|
|
9
|
+
import type { Simplify } from 'type-fest'
|
|
10
|
+
|
|
11
|
+
const inputToModel = {
|
|
12
|
+
Trending_items_Input: 'trending_items' as const,
|
|
13
|
+
Trending_facet_values_Input: 'trending_facets' as const,
|
|
14
|
+
Frequently_bought_together_Input: 'bought_together' as const,
|
|
15
|
+
Looking_similar_Input: 'looking_similar' as const,
|
|
16
|
+
Related_products_Input: 'related_products' as const,
|
|
17
|
+
}
|
|
18
|
+
function isAlgoliaResponse<T extends object>(root: T): root is T & { uid: string } {
|
|
19
|
+
return 'uid' in root
|
|
20
|
+
}
|
|
21
|
+
function argsFromKeysInput(keys, args, context) {
|
|
22
|
+
const body = keys
|
|
23
|
+
.map(
|
|
24
|
+
(key) =>
|
|
25
|
+
({
|
|
26
|
+
[key.keyInput]: {
|
|
27
|
+
model: inputToModel[key.keyInput as string],
|
|
28
|
+
indexName: getIndexName(context),
|
|
29
|
+
|
|
30
|
+
...args,
|
|
31
|
+
objectID: key.objectId,
|
|
32
|
+
},
|
|
33
|
+
}) as unknown as AlgoliarecommendationsRequest_Input,
|
|
34
|
+
)
|
|
35
|
+
.filter(nonNullable)
|
|
36
|
+
|
|
37
|
+
const returnObject = { input: { requests: body } }
|
|
38
|
+
|
|
39
|
+
return returnObject
|
|
40
|
+
}
|
|
41
|
+
export async function getRecommendations<
|
|
42
|
+
K extends keyof AlgoliarecommendationsRequest_Input,
|
|
43
|
+
Input extends AlgoliarecommendationsRequest_Input[K],
|
|
44
|
+
R,
|
|
45
|
+
>(
|
|
46
|
+
root: object,
|
|
47
|
+
keyInput: K,
|
|
48
|
+
args: Simplify<Omit<NonNullable<Input>, 'indexName' | 'model'>>,
|
|
49
|
+
context: MeshContext,
|
|
50
|
+
info: GraphQLResolveInfo,
|
|
51
|
+
mapper: (hit: AlgoliarecommendationsHit) => R,
|
|
52
|
+
) {
|
|
53
|
+
if (!isAlgoliaResponse(root)) {
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
return (
|
|
57
|
+
(await context.algoliaRecommend.Query.algolia_getRecommendations({
|
|
58
|
+
key: { keyInput, objectId: atob(root.uid) },
|
|
59
|
+
argsFromKeys: (keys) => argsFromKeysInput(keys, args, context),
|
|
60
|
+
valuesFromResults: (res, keys) =>
|
|
61
|
+
keys
|
|
62
|
+
.map((_key, index) => res?.results[index])
|
|
63
|
+
.map((r) => r?.hits.map((hit) => hit && mapper(hit)).filter(nonNullable)) ?? null,
|
|
64
|
+
selectionSet: /* GraphQL */ `
|
|
65
|
+
{
|
|
66
|
+
results {
|
|
67
|
+
nbHits
|
|
68
|
+
hits {
|
|
69
|
+
... on AlgoliarecommendHit {
|
|
70
|
+
objectID
|
|
71
|
+
additionalProperties
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`,
|
|
77
|
+
context,
|
|
78
|
+
info,
|
|
79
|
+
})) ?? null
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import fragments from '@graphcommerce/graphql/generated/fragments.json'
|
|
3
|
+
import type {
|
|
4
|
+
AlgoliaLookingSimilarInput,
|
|
5
|
+
AlgoliaRelatedProductsInput,
|
|
6
|
+
MeshContext,
|
|
7
|
+
ProductInterfaceResolvers,
|
|
8
|
+
ResolverFn,
|
|
9
|
+
Resolvers,
|
|
10
|
+
ResolversParentTypes,
|
|
11
|
+
ResolversTypes,
|
|
12
|
+
} from '@graphcommerce/graphql-mesh'
|
|
13
|
+
import {
|
|
14
|
+
GraphCommerceAlgoliaRecommendationLocation,
|
|
15
|
+
InputMaybe,
|
|
16
|
+
Maybe,
|
|
17
|
+
} from '@graphcommerce/next-config'
|
|
18
|
+
import { createProductMapper } from './createProductMapper'
|
|
19
|
+
import { createFacetValueMapper } from './createValueFacetMapper'
|
|
20
|
+
import { getRecommendationsArgs } from './getRecommendationArgs'
|
|
21
|
+
import { getRecommendations } from './getRecommendations'
|
|
22
|
+
|
|
23
|
+
type ProductTypes = NonNullable<Awaited<ReturnType<ProductInterfaceResolvers['__resolveType']>>>
|
|
24
|
+
const productInterfaceTypes = fragments.possibleTypes.ProductInterface as ProductTypes[]
|
|
25
|
+
|
|
26
|
+
const resolvers: Resolvers = {
|
|
27
|
+
Query: {
|
|
28
|
+
trendingProducts: async (root, args, context, info) => {
|
|
29
|
+
const { facetName, facetValue } = args.input
|
|
30
|
+
const { threshold, fallbackParameters, maxRecommendations, queryParameters } =
|
|
31
|
+
await getRecommendationsArgs(root, args, context)
|
|
32
|
+
return getRecommendations(
|
|
33
|
+
root,
|
|
34
|
+
'Trending_items_Input',
|
|
35
|
+
{
|
|
36
|
+
threshold,
|
|
37
|
+
facetName,
|
|
38
|
+
facetValue,
|
|
39
|
+
fallbackParameters,
|
|
40
|
+
maxRecommendations,
|
|
41
|
+
queryParameters,
|
|
42
|
+
},
|
|
43
|
+
context,
|
|
44
|
+
info,
|
|
45
|
+
await createProductMapper(context),
|
|
46
|
+
)
|
|
47
|
+
},
|
|
48
|
+
trendingFacetValues: async (root, args, context, info) => {
|
|
49
|
+
const { threshold, fallbackParameters, maxRecommendations, queryParameters } =
|
|
50
|
+
await getRecommendationsArgs(root, args, context)
|
|
51
|
+
return getRecommendations(
|
|
52
|
+
root,
|
|
53
|
+
'Trending_facet_values_Input',
|
|
54
|
+
{
|
|
55
|
+
facetName: args.input.facetName,
|
|
56
|
+
threshold,
|
|
57
|
+
fallbackParameters,
|
|
58
|
+
maxRecommendations,
|
|
59
|
+
queryParameters,
|
|
60
|
+
},
|
|
61
|
+
context,
|
|
62
|
+
info,
|
|
63
|
+
createFacetValueMapper(),
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isEnabled(location: InputMaybe<GraphCommerceAlgoliaRecommendationLocation> | undefined) {
|
|
70
|
+
return location && location !== 'DISABLED'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function enumToLocation(
|
|
74
|
+
location: InputMaybe<GraphCommerceAlgoliaRecommendationLocation> | undefined,
|
|
75
|
+
) {
|
|
76
|
+
if (!isEnabled(location)) throw Error('Check for isEnabled before calling this function')
|
|
77
|
+
if (location === 'CROSSSELL_PRODUCTS') return 'crosssell_products' as const
|
|
78
|
+
if (location === 'UPSELL_PRODUCTS') return 'upsell_products' as const
|
|
79
|
+
return 'related_products' as const
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type ProductResolver = ResolverFn<
|
|
83
|
+
Maybe<Array<Maybe<ResolversTypes['ProductInterface']>>>,
|
|
84
|
+
ResolversParentTypes['ProductInterface'],
|
|
85
|
+
MeshContext,
|
|
86
|
+
Record<string, never>
|
|
87
|
+
>
|
|
88
|
+
|
|
89
|
+
if (isEnabled(import.meta.graphCommerce.algolia.relatedProducts)) {
|
|
90
|
+
const resolve: ProductResolver = async (root, args, context, info) => {
|
|
91
|
+
const { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters } =
|
|
92
|
+
await getRecommendationsArgs(root, args, context)
|
|
93
|
+
|
|
94
|
+
return getRecommendations(
|
|
95
|
+
root,
|
|
96
|
+
'Related_products_Input',
|
|
97
|
+
{ objectID, threshold, fallbackParameters, maxRecommendations, queryParameters },
|
|
98
|
+
context,
|
|
99
|
+
info,
|
|
100
|
+
await createProductMapper(context),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
productInterfaceTypes.forEach((productType) => {
|
|
105
|
+
if (!resolvers[productType]) resolvers[productType] = {}
|
|
106
|
+
resolvers[productType][enumToLocation(import.meta.graphCommerce.algolia.relatedProducts)] = {
|
|
107
|
+
selectionSet: `{ uid }`,
|
|
108
|
+
resolve,
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isEnabled(import.meta.graphCommerce.algolia.lookingSimilar)) {
|
|
114
|
+
const resolve: ProductResolver = async (root, args, context, info) => {
|
|
115
|
+
const { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters } =
|
|
116
|
+
await getRecommendationsArgs(root, args, context)
|
|
117
|
+
return getRecommendations(
|
|
118
|
+
root,
|
|
119
|
+
'Looking_similar_Input',
|
|
120
|
+
{ objectID, threshold, fallbackParameters, maxRecommendations, queryParameters },
|
|
121
|
+
context,
|
|
122
|
+
info,
|
|
123
|
+
await createProductMapper(context),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
productInterfaceTypes.forEach((productType) => {
|
|
128
|
+
if (!resolvers[productType]) resolvers[productType] = {}
|
|
129
|
+
resolvers[productType][enumToLocation(import.meta.graphCommerce.algolia.lookingSimilar)] = {
|
|
130
|
+
selectionSet: `{ uid }`,
|
|
131
|
+
resolve,
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isEnabled(import.meta.graphCommerce.algolia.frequentlyBoughtTogether)) {
|
|
137
|
+
const resolver: ProductResolver = async (root, args, context, info) => {
|
|
138
|
+
const { objectID, threshold, maxRecommendations, queryParameters } =
|
|
139
|
+
await getRecommendationsArgs(root, args, context)
|
|
140
|
+
|
|
141
|
+
return getRecommendations(
|
|
142
|
+
root,
|
|
143
|
+
'Frequently_bought_together_Input',
|
|
144
|
+
{ objectID, threshold, maxRecommendations, queryParameters },
|
|
145
|
+
context,
|
|
146
|
+
info,
|
|
147
|
+
await createProductMapper(context),
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
productInterfaceTypes.forEach((productType) => {
|
|
152
|
+
if (!resolvers[productType]) resolvers[productType] = {}
|
|
153
|
+
resolvers[productType][
|
|
154
|
+
enumToLocation(import.meta.graphCommerce.algolia.frequentlyBoughtTogether)
|
|
155
|
+
] = resolver
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const similar: ResolverFn<
|
|
160
|
+
Maybe<Array<Maybe<ResolversTypes['ProductInterface']>>>,
|
|
161
|
+
ResolversParentTypes['ProductInterface'],
|
|
162
|
+
MeshContext,
|
|
163
|
+
{ input?: AlgoliaLookingSimilarInput | null }
|
|
164
|
+
> = async (root, args, context, info) => {
|
|
165
|
+
const { objectID, threshold, fallbackParameters, maxRecommendations, queryParameters } =
|
|
166
|
+
await getRecommendationsArgs(root, args, context)
|
|
167
|
+
|
|
168
|
+
return getRecommendations(
|
|
169
|
+
root,
|
|
170
|
+
'Looking_similar_Input',
|
|
171
|
+
{ objectID, threshold, fallbackParameters, maxRecommendations, queryParameters },
|
|
172
|
+
context,
|
|
173
|
+
info,
|
|
174
|
+
await createProductMapper(context),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const related: ResolverFn<
|
|
179
|
+
Maybe<Array<Maybe<ResolversTypes['ProductInterface']>>>,
|
|
180
|
+
ResolversParentTypes['ProductInterface'],
|
|
181
|
+
MeshContext,
|
|
182
|
+
{ input?: AlgoliaRelatedProductsInput | null }
|
|
183
|
+
> = async (root, args, context, info) => {
|
|
184
|
+
const { objectID, threshold, maxRecommendations, queryParameters } = await getRecommendationsArgs(
|
|
185
|
+
root,
|
|
186
|
+
args,
|
|
187
|
+
context,
|
|
188
|
+
)
|
|
189
|
+
return getRecommendations(
|
|
190
|
+
root,
|
|
191
|
+
'Related_products_Input',
|
|
192
|
+
{
|
|
193
|
+
objectID,
|
|
194
|
+
threshold,
|
|
195
|
+
// fallbackParameters,
|
|
196
|
+
maxRecommendations,
|
|
197
|
+
queryParameters,
|
|
198
|
+
},
|
|
199
|
+
context,
|
|
200
|
+
info,
|
|
201
|
+
await createProductMapper(context),
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const together: ResolverFn<
|
|
206
|
+
Maybe<Array<Maybe<ResolversTypes['ProductInterface']>>>,
|
|
207
|
+
ResolversParentTypes['ProductInterface'],
|
|
208
|
+
MeshContext,
|
|
209
|
+
{ input?: AlgoliaRelatedProductsInput | null }
|
|
210
|
+
> = async (root, args, context, info) => {
|
|
211
|
+
const { objectID, threshold, maxRecommendations, queryParameters } = await getRecommendationsArgs(
|
|
212
|
+
root,
|
|
213
|
+
args,
|
|
214
|
+
context,
|
|
215
|
+
)
|
|
216
|
+
return getRecommendations(
|
|
217
|
+
root,
|
|
218
|
+
'Frequently_bought_together_Input',
|
|
219
|
+
{ objectID, threshold, maxRecommendations, queryParameters },
|
|
220
|
+
context,
|
|
221
|
+
info,
|
|
222
|
+
await createProductMapper(context),
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
productInterfaceTypes.forEach((productType) => {
|
|
227
|
+
if (!resolvers[productType]) resolvers[productType] = {}
|
|
228
|
+
|
|
229
|
+
resolvers[productType].algolia_looking_similar = { selectionSet: `{ uid }`, resolve: similar }
|
|
230
|
+
|
|
231
|
+
resolvers[productType].algolia_related_products = { selectionSet: `{ uid }`, resolve: related }
|
|
232
|
+
|
|
233
|
+
resolvers[productType].algolia_frequently_bought_together = {
|
|
234
|
+
selectionSet: `{ uid }`,
|
|
235
|
+
resolve: together,
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
export default resolvers
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphcommerce/algolia-recommend",
|
|
3
|
+
"homepage": "https://www.graphcommerce.org/",
|
|
4
|
+
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
+
"version": "9.0.0-canary.84",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
|
+
"eslintConfig": {
|
|
9
|
+
"extends": "@graphcommerce/eslint-config-pwa",
|
|
10
|
+
"parserOptions": {
|
|
11
|
+
"project": "./tsconfig.json"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"generate": "tsx scripts/generate-recommend-spec.mts"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@graphcommerce/algolia-products": "^9.0.0-canary.84",
|
|
19
|
+
"@graphcommerce/graphql": "^9.0.0-canary.84",
|
|
20
|
+
"@graphcommerce/graphql-mesh": "^9.0.0-canary.84",
|
|
21
|
+
"@graphcommerce/next-config": "^9.0.0-canary.84",
|
|
22
|
+
"@graphcommerce/next-ui": "^9.0.0-canary.84",
|
|
23
|
+
"react": "^18.2.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"graphql": "^16.0.0",
|
|
27
|
+
"tsx": "^4.16.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig'
|
|
2
|
+
import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
|
|
3
|
+
|
|
4
|
+
export const config: PluginConfig = {
|
|
5
|
+
module: '@graphcommerce/graphql-mesh/meshConfig',
|
|
6
|
+
type: 'function',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
|
|
10
|
+
prev,
|
|
11
|
+
baseConfig,
|
|
12
|
+
graphCommerceConfig,
|
|
13
|
+
) =>
|
|
14
|
+
prev(
|
|
15
|
+
{
|
|
16
|
+
...baseConfig,
|
|
17
|
+
sources: [
|
|
18
|
+
...baseConfig.sources,
|
|
19
|
+
{
|
|
20
|
+
name: 'algoliaRecommend',
|
|
21
|
+
handler: {
|
|
22
|
+
openapi: {
|
|
23
|
+
endpoint: `https://${graphCommerceConfig.algolia.applicationId}.algolia.net/`,
|
|
24
|
+
source: '@graphcommerce/algolia-recommend/algolia-recommend-spec.yaml',
|
|
25
|
+
ignoreErrorResponses: true,
|
|
26
|
+
schemaHeaders: {
|
|
27
|
+
'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
|
|
28
|
+
'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
|
|
29
|
+
},
|
|
30
|
+
operationHeaders: {
|
|
31
|
+
'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
|
|
32
|
+
'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
|
|
33
|
+
},
|
|
34
|
+
selectQueryOrMutationField: [{ type: 'Query', fieldName: 'getRecommendations' }],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
transforms: [
|
|
38
|
+
{
|
|
39
|
+
prefix: {
|
|
40
|
+
value: 'algolia_',
|
|
41
|
+
includeRootOperations: true,
|
|
42
|
+
includeTypes: false,
|
|
43
|
+
mode: 'bare',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
prefix: {
|
|
48
|
+
value: 'Algolia',
|
|
49
|
+
includeRootOperations: false,
|
|
50
|
+
includeTypes: true,
|
|
51
|
+
mode: 'bare',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
additionalResolvers: [
|
|
58
|
+
...(baseConfig.additionalResolvers ?? []),
|
|
59
|
+
'@graphcommerce/algolia-recommend/mesh/resolvers.ts',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
graphCommerceConfig,
|
|
63
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
input AlgoliaFallbackParams {
|
|
2
|
+
"""
|
|
3
|
+
One or more keywords to use in a full-text search.
|
|
4
|
+
"""
|
|
5
|
+
search: String
|
|
6
|
+
"""
|
|
7
|
+
The product attributes to search for and return.
|
|
8
|
+
"""
|
|
9
|
+
filter: ProductAttributeFilterInput
|
|
10
|
+
"""
|
|
11
|
+
Specifies which attributes to sort on, and whether to return the results in ascending or descending order.
|
|
12
|
+
"""
|
|
13
|
+
sort: ProductAttributeSortInput
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
input AlgoliaLookingSimilarInput {
|
|
17
|
+
maxRecommendations: Int = 8
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Minimum score a recommendation must have to be included in the response.
|
|
21
|
+
"""
|
|
22
|
+
threshold: Float! = 75
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
One or more keywords to use in a full-text search.
|
|
26
|
+
"""
|
|
27
|
+
search: String
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
The product attributes to search for and return.
|
|
31
|
+
"""
|
|
32
|
+
filter: ProductAttributeFilterInput
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
When there are no related products, use this fallback query
|
|
36
|
+
"""
|
|
37
|
+
fallback: AlgoliaFallbackParams
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
input AlgoliaFrequentlyBoughtTogetherInput {
|
|
41
|
+
maxRecommendations: Int = 8
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
Minimum score a recommendation must have to be included in the response.
|
|
45
|
+
"""
|
|
46
|
+
threshold: Float! = 75
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
One or more keywords to use in a full-text search.
|
|
50
|
+
"""
|
|
51
|
+
search: String
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
The product attributes to search for and return.
|
|
55
|
+
"""
|
|
56
|
+
filter: ProductAttributeFilterInput
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
input AlgoliaRelatedProductsInput {
|
|
60
|
+
maxRecommendations: Int = 8
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
Minimum score a recommendation must have to be included in the response.
|
|
64
|
+
"""
|
|
65
|
+
threshold: Float! = 75
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
One or more keywords to use in a full-text search.
|
|
69
|
+
"""
|
|
70
|
+
search: String
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
The product attributes to search for and return.
|
|
74
|
+
"""
|
|
75
|
+
filter: ProductAttributeFilterInput
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
When there are no related products, use this fallback query
|
|
79
|
+
"""
|
|
80
|
+
fallback: AlgoliaFallbackParams
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface ProductInterface {
|
|
84
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
85
|
+
algolia_frequently_bought_together(
|
|
86
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
87
|
+
): [ProductInterface]
|
|
88
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type SimpleProduct implements ProductInterface {
|
|
92
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
93
|
+
algolia_frequently_bought_together(
|
|
94
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
95
|
+
): [ProductInterface]
|
|
96
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type VirtualProduct implements ProductInterface {
|
|
100
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
101
|
+
algolia_frequently_bought_together(
|
|
102
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
103
|
+
): [ProductInterface]
|
|
104
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type ConfigurableProduct implements ProductInterface {
|
|
108
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
109
|
+
algolia_frequently_bought_together(
|
|
110
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
111
|
+
): [ProductInterface]
|
|
112
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type BundleProduct implements ProductInterface {
|
|
116
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
117
|
+
algolia_frequently_bought_together(
|
|
118
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
119
|
+
): [ProductInterface]
|
|
120
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type DownloadableProduct implements ProductInterface {
|
|
124
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
125
|
+
algolia_frequently_bought_together(
|
|
126
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
127
|
+
): [ProductInterface]
|
|
128
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type GroupedProduct implements ProductInterface {
|
|
132
|
+
algolia_looking_similar(input: AlgoliaLookingSimilarInput): [ProductInterface]
|
|
133
|
+
algolia_frequently_bought_together(
|
|
134
|
+
input: AlgoliaFrequentlyBoughtTogetherInput
|
|
135
|
+
): [ProductInterface]
|
|
136
|
+
algolia_related_products(input: AlgoliaRelatedProductsInput): [ProductInterface]
|
|
137
|
+
}
|