@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.
@@ -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
@@ -1,4 +1,5 @@
1
1
  export * from "./core/initialize.js";
2
2
  export * from "./providers/product-search.provider.js";
3
+ export * from "./providers/product-recommendations.provider.js";
3
4
  export * from "./schema/configuration.schema.js";
4
5
  export * from "./schema/capabilities.schema.js";
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@reactionary/provider-algolia",
3
- "version": "0.3.2",
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.2",
8
- "algoliasearch": "^5.23.4",
7
+ "@reactionary/core": "0.3.4",
8
+ "algoliasearch": "^5.48.0",
9
9
  "zod": "4.1.9"
10
10
  },
11
11
  "type": "module",
@@ -1,2 +1,3 @@
1
1
  export * from "./analytics.provider.js";
2
2
  export * from "./product-search.provider.js";
3
+ export * from "./product-recommendations.provider.js";
@@ -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
+ };
@@ -1,7 +1,8 @@
1
1
  import { CapabilitiesSchema } from "@reactionary/core";
2
2
  const AlgoliaCapabilitiesSchema = CapabilitiesSchema.pick({
3
3
  productSearch: true,
4
- analytics: true
4
+ analytics: true,
5
+ productRecommendations: true
5
6
  }).partial();
6
7
  export {
7
8
  AlgoliaCapabilitiesSchema
@@ -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
@@ -1,4 +1,5 @@
1
1
  export * from './core/initialize.js';
2
2
  export * from './providers/product-search.provider.js';
3
+ export * from './providers/product-recommendations.provider.js';
3
4
  export * from './schema/configuration.schema.js';
4
5
  export * from './schema/capabilities.schema.js';
@@ -1,2 +1,3 @@
1
1
  export * from './analytics.provider.js';
2
2
  export * from './product-search.provider.js';
3
+ export * from './product-recommendations.provider.js';
@@ -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>;