@reactionary/provider-algolia 0.3.2 → 0.3.4
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/core/initialize.js +4 -0
- package/index.js +1 -0
- package/package.json +3 -3
- package/providers/index.js +1 -0
- package/providers/product-recommendations.provider.js +170 -0
- package/schema/capabilities.schema.js +2 -1
- package/schema/product-recommendation.schema.js +9 -0
- package/src/index.d.ts +1 -0
- package/src/providers/index.d.ts +1 -0
- package/src/providers/product-recommendations.provider.d.ts +58 -0
- package/src/schema/capabilities.schema.d.ts +1 -0
- package/src/schema/product-recommendation.schema.d.ts +8 -0
package/core/initialize.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AlgoliaProductSearchProvider } from "../providers/product-search.provider.js";
|
|
2
2
|
import { AlgoliaAnalyticsProvider } from "../providers/analytics.provider.js";
|
|
3
|
+
import { AlgoliaProductRecommendationsProvider } from "../providers/product-recommendations.provider.js";
|
|
3
4
|
function withAlgoliaCapabilities(configuration, capabilities) {
|
|
4
5
|
return (cache, context) => {
|
|
5
6
|
const client = {};
|
|
@@ -9,6 +10,9 @@ function withAlgoliaCapabilities(configuration, capabilities) {
|
|
|
9
10
|
if (capabilities.analytics) {
|
|
10
11
|
client.analytics = new AlgoliaAnalyticsProvider(cache, context, configuration);
|
|
11
12
|
}
|
|
13
|
+
if (capabilities.productRecommendations) {
|
|
14
|
+
client.productRecommendations = new AlgoliaProductRecommendationsProvider(configuration, cache, context);
|
|
15
|
+
}
|
|
12
16
|
return client;
|
|
13
17
|
};
|
|
14
18
|
}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/provider-algolia",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@reactionary/core": "0.3.
|
|
8
|
-
"algoliasearch": "^5.
|
|
7
|
+
"@reactionary/core": "0.3.4",
|
|
8
|
+
"algoliasearch": "^5.48.0",
|
|
9
9
|
"zod": "4.1.9"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
package/providers/index.js
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProductRecommendationsProvider
|
|
3
|
+
} from "@reactionary/core";
|
|
4
|
+
import {
|
|
5
|
+
liteClient
|
|
6
|
+
} from "algoliasearch/lite";
|
|
7
|
+
class AlgoliaProductRecommendationsProvider extends ProductRecommendationsProvider {
|
|
8
|
+
constructor(config, cache, context) {
|
|
9
|
+
super(cache, context);
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.client = liteClient(this.config.appId, this.config.apiKey);
|
|
12
|
+
}
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
14
|
+
getRecommendationThreshold(_algorithm) {
|
|
15
|
+
return 10;
|
|
16
|
+
}
|
|
17
|
+
getQueryParametersForRecommendations(algorithm) {
|
|
18
|
+
return {
|
|
19
|
+
userToken: this.context.session.identityContext?.personalizationKey || "anonymous",
|
|
20
|
+
analytics: true,
|
|
21
|
+
analyticsTags: ["reactionary", algorithm],
|
|
22
|
+
clickAnalytics: true
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get frequently bought together recommendations using Algolia Recommend
|
|
27
|
+
*/
|
|
28
|
+
async getFrequentlyBoughtTogetherRecommendations(query) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await this.client.getRecommendations({
|
|
31
|
+
requests: [
|
|
32
|
+
{
|
|
33
|
+
indexName: this.config.indexName,
|
|
34
|
+
model: "bought-together",
|
|
35
|
+
objectID: query.sourceProduct.key,
|
|
36
|
+
maxRecommendations: query.numberOfRecommendations,
|
|
37
|
+
threshold: this.getRecommendationThreshold("bought-together"),
|
|
38
|
+
queryParameters: this.getQueryParametersForRecommendations("bought-together")
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
const result = [];
|
|
43
|
+
if (response.results) {
|
|
44
|
+
for (const res of response.results) {
|
|
45
|
+
result.push(...this.parseRecommendation(res, query));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error("Error fetching frequently bought together recommendations:", error);
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get similar product recommendations using Algolia Recommend
|
|
56
|
+
*/
|
|
57
|
+
async getSimilarProductsRecommendations(query) {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.client.getRecommendations({
|
|
60
|
+
requests: [
|
|
61
|
+
{
|
|
62
|
+
indexName: this.config.indexName,
|
|
63
|
+
model: "looking-similar",
|
|
64
|
+
objectID: query.sourceProduct.key,
|
|
65
|
+
maxRecommendations: query.numberOfRecommendations,
|
|
66
|
+
threshold: this.getRecommendationThreshold("looking-similar"),
|
|
67
|
+
queryParameters: this.getQueryParametersForRecommendations("looking-similar")
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
const result = [];
|
|
72
|
+
if (response.results) {
|
|
73
|
+
for (const res of response.results) {
|
|
74
|
+
result.push(...this.parseRecommendation(res, query));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Error fetching similar product recommendations:", error);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get related product recommendations using Algolia Recommend
|
|
85
|
+
*/
|
|
86
|
+
async getRelatedProductsRecommendations(query) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await this.client.getRecommendations({
|
|
89
|
+
requests: [
|
|
90
|
+
{
|
|
91
|
+
indexName: this.config.indexName,
|
|
92
|
+
model: "related-products",
|
|
93
|
+
objectID: query.sourceProduct.key,
|
|
94
|
+
maxRecommendations: query.numberOfRecommendations,
|
|
95
|
+
threshold: this.getRecommendationThreshold("related-products"),
|
|
96
|
+
queryParameters: this.getQueryParametersForRecommendations("related-products")
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
const result = [];
|
|
101
|
+
if (response.results) {
|
|
102
|
+
for (const res of response.results) {
|
|
103
|
+
result.push(...this.parseRecommendation(res, query));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("Error fetching related product recommendations:", error);
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get trending in category recommendations using Algolia Recommend
|
|
114
|
+
*/
|
|
115
|
+
async getTrendingInCategoryRecommendations(query) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await this.client.getRecommendations({
|
|
118
|
+
requests: [
|
|
119
|
+
{
|
|
120
|
+
indexName: this.config.indexName,
|
|
121
|
+
model: "trending-items",
|
|
122
|
+
facetName: "categories",
|
|
123
|
+
facetValue: query.sourceCategory.key,
|
|
124
|
+
maxRecommendations: query.numberOfRecommendations,
|
|
125
|
+
threshold: this.getRecommendationThreshold("trending-items"),
|
|
126
|
+
queryParameters: this.getQueryParametersForRecommendations("trending-items")
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
const result = [];
|
|
131
|
+
if (response.results) {
|
|
132
|
+
for (const res of response.results) {
|
|
133
|
+
result.push(...this.parseRecommendation(res, query));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Error fetching trending in category recommendations:", error);
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
parseRecommendation(res, query) {
|
|
143
|
+
const result = [];
|
|
144
|
+
for (const hit of res.hits) {
|
|
145
|
+
const recommendationIdentifier = {
|
|
146
|
+
key: res.queryID || "x",
|
|
147
|
+
algorithm: query.algorithm,
|
|
148
|
+
abTestID: res.abTestID,
|
|
149
|
+
abTestVariantID: res.abTestVariantID
|
|
150
|
+
};
|
|
151
|
+
const recommendation = this.parseSingle(hit, recommendationIdentifier);
|
|
152
|
+
result.push(recommendation);
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Maps Algolia recommendation results to ProductRecommendation format
|
|
158
|
+
*/
|
|
159
|
+
parseSingle(hit, recommendationIdentifier) {
|
|
160
|
+
return {
|
|
161
|
+
recommendationIdentifier,
|
|
162
|
+
product: {
|
|
163
|
+
key: hit.objectID
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
AlgoliaProductRecommendationsProvider
|
|
170
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ProductRecommendationIdentifierSchema } from "@reactionary/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
const AlgoliaProductSearchIdentifierSchema = ProductRecommendationIdentifierSchema.extend({
|
|
4
|
+
abTestID: z.number().optional(),
|
|
5
|
+
abTestVariantID: z.number().optional()
|
|
6
|
+
});
|
|
7
|
+
export {
|
|
8
|
+
AlgoliaProductSearchIdentifierSchema
|
|
9
|
+
};
|
package/src/index.d.ts
CHANGED
package/src/providers/index.d.ts
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Cache, ProductRecommendationsProvider, type ProductRecommendation, type ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery, type ProductRecommendationAlgorithmSimilarProductsQuery, type ProductRecommendationAlgorithmRelatedProductsQuery, type ProductRecommendationAlgorithmTrendingInCategoryQuery, type RequestContext, type ProductRecommendationsQuery } from '@reactionary/core';
|
|
2
|
+
import { type RecommendationsResults, type RecommendSearchParams, type LiteClient } from 'algoliasearch/lite';
|
|
3
|
+
import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import type { AlgoliaProductRecommendationIdentifier } from '../schema/product-recommendation.schema.js';
|
|
5
|
+
interface AlgoliaRecommendHit {
|
|
6
|
+
objectID: string;
|
|
7
|
+
sku?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* AlgoliaProductRecommendationsProvider
|
|
12
|
+
*
|
|
13
|
+
* Provides product recommendations using Algolia's Recommend API.
|
|
14
|
+
* Supports frequentlyBoughtTogether, similar, related, and trendingInCategory algorithms.
|
|
15
|
+
*
|
|
16
|
+
* Note: This requires Algolia Recommend to be enabled and AI models to be trained.
|
|
17
|
+
* See: https://www.algolia.com/doc/guides/algolia-recommend/overview/
|
|
18
|
+
*/
|
|
19
|
+
export declare class AlgoliaProductRecommendationsProvider extends ProductRecommendationsProvider {
|
|
20
|
+
protected config: AlgoliaConfiguration;
|
|
21
|
+
protected client: LiteClient;
|
|
22
|
+
constructor(config: AlgoliaConfiguration, cache: Cache, context: RequestContext);
|
|
23
|
+
protected getRecommendationThreshold(_algorithm: string): number;
|
|
24
|
+
protected getQueryParametersForRecommendations(algorithm: string): RecommendSearchParams;
|
|
25
|
+
/**
|
|
26
|
+
* Get frequently bought together recommendations using Algolia Recommend
|
|
27
|
+
*/
|
|
28
|
+
protected getFrequentlyBoughtTogetherRecommendations(query: ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery): Promise<ProductRecommendation[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Get similar product recommendations using Algolia Recommend
|
|
31
|
+
*/
|
|
32
|
+
protected getSimilarProductsRecommendations(query: ProductRecommendationAlgorithmSimilarProductsQuery): Promise<ProductRecommendation[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Get related product recommendations using Algolia Recommend
|
|
35
|
+
*/
|
|
36
|
+
protected getRelatedProductsRecommendations(query: ProductRecommendationAlgorithmRelatedProductsQuery): Promise<ProductRecommendation[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Get trending in category recommendations using Algolia Recommend
|
|
39
|
+
*/
|
|
40
|
+
protected getTrendingInCategoryRecommendations(query: ProductRecommendationAlgorithmTrendingInCategoryQuery): Promise<ProductRecommendation[]>;
|
|
41
|
+
protected parseRecommendation(res: RecommendationsResults, query: ProductRecommendationsQuery): {
|
|
42
|
+
[x: string]: unknown;
|
|
43
|
+
recommendationIdentifier: {
|
|
44
|
+
[x: string]: unknown;
|
|
45
|
+
key: string;
|
|
46
|
+
algorithm: string;
|
|
47
|
+
};
|
|
48
|
+
product: {
|
|
49
|
+
[x: string]: unknown;
|
|
50
|
+
key: string;
|
|
51
|
+
};
|
|
52
|
+
}[];
|
|
53
|
+
/**
|
|
54
|
+
* Maps Algolia recommendation results to ProductRecommendation format
|
|
55
|
+
*/
|
|
56
|
+
protected parseSingle(hit: AlgoliaRecommendHit, recommendationIdentifier: AlgoliaProductRecommendationIdentifier): ProductRecommendation;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -2,5 +2,6 @@ import type { z } from 'zod';
|
|
|
2
2
|
export declare const AlgoliaCapabilitiesSchema: z.ZodObject<{
|
|
3
3
|
analytics: z.ZodOptional<z.ZodBoolean>;
|
|
4
4
|
productSearch: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
productRecommendations: z.ZodOptional<z.ZodBoolean>;
|
|
5
6
|
}, z.core.$loose>;
|
|
6
7
|
export type AlgoliaCapabilities = z.infer<typeof AlgoliaCapabilitiesSchema>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export declare const AlgoliaProductSearchIdentifierSchema: z.ZodObject<{
|
|
3
|
+
key: z.ZodString;
|
|
4
|
+
algorithm: z.ZodString;
|
|
5
|
+
abTestID: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
abTestVariantID: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
}, z.z.core.$loose>;
|
|
8
|
+
export type AlgoliaProductRecommendationIdentifier = z.infer<typeof AlgoliaProductSearchIdentifierSchema>;
|