@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,74 @@
|
|
|
1
|
+
input TrendingProductsInput {
|
|
2
|
+
maxRecommendations: Int = 8
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Minimum score a recommendation must have to be included in the response.
|
|
6
|
+
"""
|
|
7
|
+
threshold: Float! = 75
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Facet attribute. To be used in combination with `facetValue`.
|
|
11
|
+
If specified, only recommendations matching the facet filter will be returned.
|
|
12
|
+
"""
|
|
13
|
+
facetName: String!
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Facet value. To be used in combination with `facetName`.
|
|
17
|
+
If specified, only recommendations matching the facet filter will be returned.
|
|
18
|
+
"""
|
|
19
|
+
facetValue: String!
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
One or more keywords to use in a full-text search.
|
|
23
|
+
"""
|
|
24
|
+
search: String
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
The product attributes to search for and return.
|
|
28
|
+
"""
|
|
29
|
+
filter: ProductAttributeFilterInput
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
When there are no related products, use this fallback query
|
|
33
|
+
"""
|
|
34
|
+
fallback: AlgoliaFallbackParams
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
input TrendingFacetValuesInput {
|
|
38
|
+
maxRecommendations: Int = 8
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
Minimum score a recommendation must have to be included in the response.
|
|
42
|
+
"""
|
|
43
|
+
threshold: Float! = 75
|
|
44
|
+
"""
|
|
45
|
+
Facet attribute for which to retrieve trending facet values.
|
|
46
|
+
"""
|
|
47
|
+
facetName: String!
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
One or more keywords to use in a full-text search.
|
|
51
|
+
"""
|
|
52
|
+
search: String
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
The product attributes to search for and return.
|
|
56
|
+
"""
|
|
57
|
+
filter: ProductAttributeFilterInput
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
When there are no related products, use this fallback query
|
|
61
|
+
"""
|
|
62
|
+
fallback: AlgoliaFallbackParams
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type TrendingFacetValue {
|
|
66
|
+
facetName: String!
|
|
67
|
+
facetValue: String!
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
extend type Query {
|
|
71
|
+
trendingProducts(input: TrendingProductsInput!): [ProductInterface]
|
|
72
|
+
|
|
73
|
+
trendingFacetValues(input: TrendingFacetValuesInput!): [TrendingFacetValue]
|
|
74
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import yaml from 'js-yaml'
|
|
2
|
+
import { writeFile, readFile } from 'node:fs/promises'
|
|
3
|
+
import { OpenAPIV3 } from 'openapi-types'
|
|
4
|
+
import prettier from 'prettier'
|
|
5
|
+
import conf from '@graphcommerce/prettier-config-pwa'
|
|
6
|
+
import { algoliaSchemaBaseFilter } from '@graphcommerce/algolia-products/scripts/base-schema-filter.mjs'
|
|
7
|
+
|
|
8
|
+
const response = await fetch(
|
|
9
|
+
'https://raw.githubusercontent.com/algolia/api-clients-automation/main/specs/bundled/recommend.yml',
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const openApiSchema = yaml.load(await response.text()) as OpenAPIV3.Document
|
|
13
|
+
|
|
14
|
+
const allMethods = [
|
|
15
|
+
OpenAPIV3.HttpMethods.TRACE,
|
|
16
|
+
OpenAPIV3.HttpMethods.POST,
|
|
17
|
+
OpenAPIV3.HttpMethods.PUT,
|
|
18
|
+
OpenAPIV3.HttpMethods.GET,
|
|
19
|
+
OpenAPIV3.HttpMethods.DELETE,
|
|
20
|
+
OpenAPIV3.HttpMethods.PATCH,
|
|
21
|
+
OpenAPIV3.HttpMethods.OPTIONS,
|
|
22
|
+
OpenAPIV3.HttpMethods.HEAD,
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const { info, openapi, components, tags, ...rest } = openApiSchema
|
|
26
|
+
|
|
27
|
+
function filterPaths(
|
|
28
|
+
paths: OpenAPIV3.PathsObject,
|
|
29
|
+
allow: Record<string, OpenAPIV3.HttpMethods[]>,
|
|
30
|
+
): OpenAPIV3.PathsObject {
|
|
31
|
+
const allowedEntries = Object.entries(allow)
|
|
32
|
+
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(paths)
|
|
35
|
+
.map(([path, pathItem]) => {
|
|
36
|
+
if (!pathItem) return [path, pathItem]
|
|
37
|
+
const newValue = pathItem
|
|
38
|
+
|
|
39
|
+
const [allowedPath, allowedMethods] =
|
|
40
|
+
allowedEntries.find(([allowedPath]) => allowedPath === path) ?? []
|
|
41
|
+
|
|
42
|
+
if (!allowedPath || !allowedMethods) return [path, undefined]
|
|
43
|
+
|
|
44
|
+
allMethods
|
|
45
|
+
.filter((method) => !allowedMethods.includes(method))
|
|
46
|
+
.forEach((method) => {
|
|
47
|
+
newValue[method] = undefined
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return [path, newValue]
|
|
51
|
+
})
|
|
52
|
+
.filter(([path, pathItem]) => {
|
|
53
|
+
if (!pathItem) return false
|
|
54
|
+
if (allMethods.every((key) => !pathItem[key])) return false
|
|
55
|
+
return true
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isRef(value: any): value is OpenAPIV3.ReferenceObject {
|
|
61
|
+
return typeof value === 'object' && '$ref' in value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const newSchema: OpenAPIV3.Document = {
|
|
65
|
+
openapi,
|
|
66
|
+
info,
|
|
67
|
+
paths: filterPaths(openApiSchema.paths, {
|
|
68
|
+
'/1/indexes/*/recommendations': [OpenAPIV3.HttpMethods.POST],
|
|
69
|
+
}),
|
|
70
|
+
components: {
|
|
71
|
+
...openApiSchema.components,
|
|
72
|
+
schemas: Object.fromEntries(
|
|
73
|
+
Object.entries(openApiSchema.components?.schemas ?? {}).map(
|
|
74
|
+
([incomingKey, incomingSchema]) => {
|
|
75
|
+
const [schemaKey, schema] = algoliaSchemaBaseFilter(incomingKey, incomingSchema)
|
|
76
|
+
if (isRef(schema)) return [schemaKey, schema]
|
|
77
|
+
|
|
78
|
+
// Some object have an addition type 'object' which removes all types of the object, we only add known properties here.
|
|
79
|
+
const ref = schema.allOf?.find((item) => isRef(item))
|
|
80
|
+
const obj = schema.allOf?.find((item) => !isRef(item) && item.type === 'object')
|
|
81
|
+
if (ref && obj) {
|
|
82
|
+
return [schemaKey, { ...schema, allOf: [ref satisfies OpenAPIV3.ReferenceObject] }]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (schemaKey === 'recommendedForYouQuery') {
|
|
86
|
+
return [
|
|
87
|
+
schemaKey,
|
|
88
|
+
{
|
|
89
|
+
...schema,
|
|
90
|
+
oneOf: schema.oneOf?.filter(
|
|
91
|
+
(item) =>
|
|
92
|
+
!isRef(item) || item.$ref !== '#/components/schemas/recommendedForYouQuery',
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (schemaKey === 'fallbackParams') {
|
|
99
|
+
return [schemaKey, undefined]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [
|
|
103
|
+
schemaKey,
|
|
104
|
+
{
|
|
105
|
+
...schema,
|
|
106
|
+
properties: schema.properties
|
|
107
|
+
? {
|
|
108
|
+
...schema.properties,
|
|
109
|
+
...(schema?.properties?.fallbackParameters
|
|
110
|
+
? { fallbackParameters: { $ref: '#/components/schemas/searchParamsObject' } }
|
|
111
|
+
: {}),
|
|
112
|
+
...(schema?.properties?.queryParameters
|
|
113
|
+
? { queryParameters: { $ref: '#/components/schemas/searchParamsObject' } }
|
|
114
|
+
: {}),
|
|
115
|
+
}
|
|
116
|
+
: undefined,
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await writeFile(
|
|
126
|
+
'./algolia-recommend-spec.yaml',
|
|
127
|
+
await prettier.format(yaml.dump(newSchema), {
|
|
128
|
+
parser: 'yaml',
|
|
129
|
+
...conf,
|
|
130
|
+
}),
|
|
131
|
+
)
|