@reactionary/algolia 0.6.3

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.
Files changed (32) hide show
  1. package/README.md +11 -0
  2. package/capabilities/analytics.capability.js +77 -0
  3. package/capabilities/index.js +3 -0
  4. package/capabilities/product-recommendations.capability.js +195 -0
  5. package/capabilities/product-search.capability.js +94 -0
  6. package/core/initialize.js +67 -0
  7. package/core/initialize.types.js +13 -0
  8. package/factories/index.js +1 -0
  9. package/factories/product-search/product-search.factory.js +124 -0
  10. package/index.js +5 -0
  11. package/package.json +15 -0
  12. package/schema/capabilities.schema.js +23 -0
  13. package/schema/configuration.schema.js +9 -0
  14. package/schema/index.js +3 -0
  15. package/schema/product-recommendation.schema.js +9 -0
  16. package/schema/search.schema.js +13 -0
  17. package/src/capabilities/analytics.capability.d.ts +12 -0
  18. package/src/capabilities/index.d.ts +3 -0
  19. package/src/capabilities/product-recommendations.capability.d.ts +103 -0
  20. package/src/capabilities/product-search.capability.d.ts +21 -0
  21. package/src/core/initialize.d.ts +5 -0
  22. package/src/core/initialize.types.d.ts +43 -0
  23. package/src/factories/index.d.ts +1 -0
  24. package/src/factories/product-search/product-search.factory.d.ts +16 -0
  25. package/src/index.d.ts +5 -0
  26. package/src/schema/capabilities.schema.d.ts +44 -0
  27. package/src/schema/configuration.schema.d.ts +7 -0
  28. package/src/schema/index.d.ts +3 -0
  29. package/src/schema/product-recommendation.schema.d.ts +8 -0
  30. package/src/schema/search.schema.d.ts +113 -0
  31. package/src/test/client-builder-product-search-extension.example.d.ts +1 -0
  32. package/test/client-builder-product-search-extension.example.js +61 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # algolia
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build algolia` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test algolia` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,77 @@
1
+ import {
2
+ AnalyticsCapability
3
+ } from "@reactionary/core";
4
+ import {
5
+ algoliasearch
6
+ } from "algoliasearch";
7
+ class AlgoliaAnalyticsCapability extends AnalyticsCapability {
8
+ client;
9
+ config;
10
+ constructor(cache, requestContext, config) {
11
+ super(cache, requestContext);
12
+ this.config = config;
13
+ this.client = algoliasearch(this.config.appId, this.config.apiKey).initInsights({});
14
+ }
15
+ async processProductAddToCart(event) {
16
+ if (event.source && event.source.type === "search") {
17
+ const algoliaEvent = {
18
+ eventName: "addToCart",
19
+ eventType: "conversion",
20
+ eventSubtype: "addToCart",
21
+ index: this.config.indexName,
22
+ objectIDs: [event.product.key],
23
+ userToken: this.context.session.identityContext.personalizationKey,
24
+ queryID: event.source.identifier.key
25
+ };
26
+ await this.client.pushEvents({
27
+ events: [algoliaEvent]
28
+ });
29
+ }
30
+ }
31
+ async processProductSummaryClick(event) {
32
+ if (event.source && event.source.type === "search") {
33
+ const algoliaEvent = {
34
+ eventName: "click",
35
+ eventType: "click",
36
+ index: this.config.indexName,
37
+ objectIDs: [event.product.key],
38
+ userToken: this.context.session.identityContext.personalizationKey,
39
+ positions: [event.position],
40
+ queryID: event.source.identifier.key
41
+ };
42
+ await this.client.pushEvents({
43
+ events: [algoliaEvent]
44
+ });
45
+ }
46
+ }
47
+ async processProductSummaryView(event) {
48
+ if (event.source && event.source.type === "search") {
49
+ const algoliaEvent = {
50
+ eventName: "view",
51
+ eventType: "view",
52
+ index: this.config.indexName,
53
+ objectIDs: event.products.map((x) => x.key),
54
+ userToken: this.context.session.identityContext.personalizationKey
55
+ };
56
+ await this.client.pushEvents({
57
+ events: [algoliaEvent]
58
+ });
59
+ }
60
+ }
61
+ async processPurchase(event) {
62
+ const algoliaEvent = {
63
+ eventName: "purchase",
64
+ eventType: "conversion",
65
+ eventSubtype: "purchase",
66
+ index: this.config.indexName,
67
+ objectIDs: event.order.items.map((x) => x.variant.sku),
68
+ userToken: this.context.session.identityContext.personalizationKey
69
+ };
70
+ await this.client.pushEvents({
71
+ events: [algoliaEvent]
72
+ });
73
+ }
74
+ }
75
+ export {
76
+ AlgoliaAnalyticsCapability
77
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./analytics.capability.js";
2
+ export * from "./product-search.capability.js";
3
+ export * from "./product-recommendations.capability.js";
@@ -0,0 +1,195 @@
1
+ import {
2
+ ProductRecommendationsCapability,
3
+ ProductSearchResultItemVariantSchema,
4
+ ImageSchema
5
+ } from "@reactionary/core";
6
+ import {
7
+ liteClient
8
+ } from "algoliasearch/lite";
9
+ class AlgoliaProductRecommendationsCapability extends ProductRecommendationsCapability {
10
+ config;
11
+ client;
12
+ constructor(config, cache, context) {
13
+ super(cache, context);
14
+ this.config = config;
15
+ this.client = liteClient(this.config.appId, this.config.apiKey);
16
+ }
17
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
18
+ getRecommendationThreshold(_algorithm) {
19
+ return 10;
20
+ }
21
+ getQueryParametersForRecommendations(algorithm) {
22
+ return {
23
+ userToken: this.context.session.identityContext?.personalizationKey || "anonymous",
24
+ analytics: true,
25
+ analyticsTags: ["reactionary", algorithm],
26
+ clickAnalytics: true
27
+ };
28
+ }
29
+ /**
30
+ * Get frequently bought together recommendations using Algolia Recommend
31
+ */
32
+ async getFrequentlyBoughtTogetherRecommendations(query) {
33
+ try {
34
+ const response = await this.client.getRecommendations({
35
+ requests: [
36
+ {
37
+ indexName: this.config.indexName,
38
+ model: "bought-together",
39
+ objectID: query.sourceProduct.key,
40
+ maxRecommendations: query.numberOfRecommendations,
41
+ threshold: this.getRecommendationThreshold("bought-together"),
42
+ queryParameters: this.getQueryParametersForRecommendations("bought-together")
43
+ }
44
+ ]
45
+ });
46
+ const result = [];
47
+ if (response.results) {
48
+ for (const res of response.results) {
49
+ result.push(...this.parseRecommendation(res, query));
50
+ }
51
+ }
52
+ return result;
53
+ } catch (error) {
54
+ console.error("Error fetching frequently bought together recommendations:", error);
55
+ return [];
56
+ }
57
+ }
58
+ /**
59
+ * Get similar product recommendations using Algolia Recommend
60
+ */
61
+ async getSimilarProductsRecommendations(query) {
62
+ try {
63
+ const response = await this.client.getRecommendations({
64
+ requests: [
65
+ {
66
+ indexName: this.config.indexName,
67
+ model: "looking-similar",
68
+ objectID: query.sourceProduct.key,
69
+ maxRecommendations: query.numberOfRecommendations,
70
+ threshold: this.getRecommendationThreshold("looking-similar"),
71
+ queryParameters: this.getQueryParametersForRecommendations("looking-similar")
72
+ }
73
+ ]
74
+ });
75
+ const result = [];
76
+ if (response.results) {
77
+ for (const res of response.results) {
78
+ result.push(...this.parseRecommendation(res, query));
79
+ }
80
+ }
81
+ return result;
82
+ } catch (error) {
83
+ console.error("Error fetching similar product recommendations:", error);
84
+ return [];
85
+ }
86
+ }
87
+ /**
88
+ * Get related product recommendations using Algolia Recommend
89
+ */
90
+ async getRelatedProductsRecommendations(query) {
91
+ try {
92
+ const response = await this.client.getRecommendations({
93
+ requests: [
94
+ {
95
+ indexName: this.config.indexName,
96
+ model: "related-products",
97
+ objectID: query.sourceProduct.key,
98
+ maxRecommendations: query.numberOfRecommendations,
99
+ threshold: this.getRecommendationThreshold("related-products"),
100
+ queryParameters: this.getQueryParametersForRecommendations("related-products")
101
+ }
102
+ ]
103
+ });
104
+ const result = [];
105
+ if (response.results) {
106
+ for (const res of response.results) {
107
+ result.push(...this.parseRecommendation(res, query));
108
+ }
109
+ }
110
+ return result;
111
+ } catch (error) {
112
+ console.error("Error fetching related product recommendations:", error);
113
+ return [];
114
+ }
115
+ }
116
+ /**
117
+ * Get trending in category recommendations using Algolia Recommend
118
+ */
119
+ async getTrendingInCategoryRecommendations(query) {
120
+ try {
121
+ const response = await this.client.getRecommendations({
122
+ requests: [
123
+ {
124
+ indexName: this.config.indexName,
125
+ model: "trending-items",
126
+ facetName: "categories",
127
+ facetValue: query.sourceCategory.key,
128
+ maxRecommendations: query.numberOfRecommendations,
129
+ threshold: this.getRecommendationThreshold("trending-items"),
130
+ queryParameters: this.getQueryParametersForRecommendations("trending-items")
131
+ }
132
+ ]
133
+ });
134
+ const result = [];
135
+ if (response.results) {
136
+ for (const res of response.results) {
137
+ result.push(...this.parseRecommendation(res, query));
138
+ }
139
+ }
140
+ return result;
141
+ } catch (error) {
142
+ console.error("Error fetching trending in category recommendations:", error);
143
+ return [];
144
+ }
145
+ }
146
+ parseRecommendation(res, query) {
147
+ const result = [];
148
+ for (const hit of res.hits) {
149
+ const recommendationIdentifier = {
150
+ key: res.queryID || "x",
151
+ algorithm: query.algorithm,
152
+ abTestID: res.abTestID,
153
+ abTestVariantID: res.abTestVariantID
154
+ };
155
+ const recommendation = this.parseSingle(hit, recommendationIdentifier);
156
+ result.push(recommendation);
157
+ }
158
+ return result;
159
+ }
160
+ /**
161
+ * Maps Algolia recommendation results to ProductRecommendation format
162
+ */
163
+ parseSingle(hit, recommendationIdentifier) {
164
+ const product = this.parseSearchResultItem(hit);
165
+ return {
166
+ recommendationIdentifier,
167
+ recommendationReturnType: "productSearchResultItem",
168
+ product
169
+ };
170
+ }
171
+ parseSearchResultItem(body) {
172
+ const product = {
173
+ identifier: { key: body.objectID },
174
+ name: body.name || body.objectID,
175
+ slug: body.slug || body.objectID,
176
+ variants: [...body.variants || []].map((variant) => this.parseVariant(variant, body))
177
+ };
178
+ return product;
179
+ }
180
+ parseVariant(variant, product) {
181
+ const result = ProductSearchResultItemVariantSchema.parse({
182
+ variant: {
183
+ sku: variant.sku
184
+ },
185
+ image: ImageSchema.parse({
186
+ sourceUrl: variant.image,
187
+ altText: product.name || ""
188
+ })
189
+ });
190
+ return result;
191
+ }
192
+ }
193
+ export {
194
+ AlgoliaProductRecommendationsCapability
195
+ };
@@ -0,0 +1,94 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result)
9
+ __defProp(target, key, result);
10
+ return result;
11
+ };
12
+ import {
13
+ FacetIdentifierSchema,
14
+ FacetValueIdentifierSchema,
15
+ ProductSearchCapability,
16
+ ProductSearchQueryByTermSchema,
17
+ ProductSearchResultSchema,
18
+ Reactionary,
19
+ success
20
+ } from "@reactionary/core";
21
+ import { algoliasearch } from "algoliasearch";
22
+ class AlgoliaProductSearchCapability extends ProductSearchCapability {
23
+ config;
24
+ factory;
25
+ constructor(cache, context, config, factory) {
26
+ super(cache, context);
27
+ this.config = config;
28
+ this.factory = factory;
29
+ }
30
+ queryByTermPayload(payload) {
31
+ const facetsThatAreNotCategory = payload.search.facets.filter(
32
+ (x) => x.facet.key !== "categories"
33
+ );
34
+ const categoryFacet = payload.search.facets.find((x) => x.facet.key === "categories") || payload.search.categoryFilter;
35
+ const finalFilters = [...payload.search.filters || []];
36
+ const finalFacetFilters = [
37
+ ...facetsThatAreNotCategory.map((x) => `${x.facet.key}:${x.key}`)
38
+ ];
39
+ if (categoryFacet) {
40
+ finalFilters.push(`categories:"${categoryFacet.key}"`);
41
+ }
42
+ return {
43
+ indexName: this.config.indexName,
44
+ query: payload.search.term,
45
+ page: payload.search.paginationOptions.pageNumber - 1,
46
+ hitsPerPage: payload.search.paginationOptions.pageSize,
47
+ facets: ["*"],
48
+ analytics: true,
49
+ clickAnalytics: true,
50
+ facetFilters: finalFacetFilters,
51
+ filters: finalFilters.join(" AND ")
52
+ };
53
+ }
54
+ async queryByTerm(payload) {
55
+ const client = algoliasearch(this.config.appId, this.config.apiKey);
56
+ const remote = await client.search({
57
+ requests: [this.queryByTermPayload(payload)]
58
+ });
59
+ const input = remote.results[0];
60
+ const result = this.factory.parseSearchResult(this.context, input, payload);
61
+ for (const selectedFacet of payload.search.facets) {
62
+ const facet = result.facets.find(
63
+ (f) => f.identifier.key === selectedFacet.facet.key
64
+ );
65
+ if (!facet) {
66
+ continue;
67
+ }
68
+ const value = facet.values.find((v) => v.identifier.key === selectedFacet.key);
69
+ if (value) {
70
+ value.active = true;
71
+ }
72
+ }
73
+ return success(result);
74
+ }
75
+ async createCategoryNavigationFilter(payload) {
76
+ const facetIdentifier = FacetIdentifierSchema.parse({
77
+ key: "categories"
78
+ });
79
+ const facetValueIdentifier = FacetValueIdentifierSchema.parse({
80
+ facet: facetIdentifier,
81
+ key: payload.categoryPath.map((c) => c.name).join(" > ")
82
+ });
83
+ return success(facetValueIdentifier);
84
+ }
85
+ }
86
+ __decorateClass([
87
+ Reactionary({
88
+ inputSchema: ProductSearchQueryByTermSchema,
89
+ outputSchema: ProductSearchResultSchema
90
+ })
91
+ ], AlgoliaProductSearchCapability.prototype, "queryByTerm", 1);
92
+ export {
93
+ AlgoliaProductSearchCapability
94
+ };
@@ -0,0 +1,67 @@
1
+ import { AlgoliaProductSearchCapability } from "../capabilities/product-search.capability.js";
2
+ import { AlgoliaAnalyticsCapability } from "../capabilities/analytics.capability.js";
3
+ import { AlgoliaProductRecommendationsCapability } from "../capabilities/product-recommendations.capability.js";
4
+ import { AlgoliaProductSearchFactory } from "../factories/product-search/product-search.factory.js";
5
+ import { AlgoliaProductSearchResultSchema } from "../schema/search.schema.js";
6
+ import {
7
+ resolveCapabilityWithFactory,
8
+ resolveDirectCapability
9
+ } from "./initialize.types.js";
10
+ function withAlgoliaCapabilities(configuration, capabilities) {
11
+ return (cache, context) => {
12
+ const client = {};
13
+ if (capabilities.productSearch?.enabled) {
14
+ const defaultFactory = new AlgoliaProductSearchFactory(
15
+ AlgoliaProductSearchResultSchema
16
+ );
17
+ client.productSearch = resolveCapabilityWithFactory(
18
+ capabilities.productSearch,
19
+ {
20
+ factory: defaultFactory,
21
+ capability: (args) => new AlgoliaProductSearchCapability(
22
+ args.cache,
23
+ args.context,
24
+ args.config,
25
+ args.factory
26
+ )
27
+ },
28
+ (factory) => ({
29
+ cache,
30
+ context,
31
+ config: configuration,
32
+ factory
33
+ })
34
+ );
35
+ }
36
+ if (capabilities.analytics?.enabled) {
37
+ client.analytics = resolveDirectCapability(
38
+ capabilities.analytics,
39
+ (args) => new AlgoliaAnalyticsCapability(args.cache, args.context, args.config),
40
+ {
41
+ cache,
42
+ context,
43
+ config: configuration
44
+ }
45
+ );
46
+ }
47
+ if (capabilities.productRecommendations?.enabled) {
48
+ client.productRecommendations = resolveDirectCapability(
49
+ capabilities.productRecommendations,
50
+ (args) => new AlgoliaProductRecommendationsCapability(
51
+ args.config,
52
+ args.cache,
53
+ args.context
54
+ ),
55
+ {
56
+ cache,
57
+ context,
58
+ config: configuration
59
+ }
60
+ );
61
+ }
62
+ return client;
63
+ };
64
+ }
65
+ export {
66
+ withAlgoliaCapabilities
67
+ };
@@ -0,0 +1,13 @@
1
+ function resolveCapabilityWithFactory(capability, defaults, buildCapabilityArgs) {
2
+ const factory = capability?.factory ?? defaults.factory;
3
+ const capabilityFactory = capability?.capability ?? defaults.capability;
4
+ return capabilityFactory(buildCapabilityArgs(factory));
5
+ }
6
+ function resolveDirectCapability(capability, defaultCapability, args) {
7
+ const capabilityFactory = capability?.capability ?? defaultCapability;
8
+ return capabilityFactory(args);
9
+ }
10
+ export {
11
+ resolveCapabilityWithFactory,
12
+ resolveDirectCapability
13
+ };
@@ -0,0 +1 @@
1
+ export * from "./product-search/product-search.factory.js";
@@ -0,0 +1,124 @@
1
+ import {
2
+ FacetIdentifierSchema,
3
+ FacetValueIdentifierSchema,
4
+ ImageSchema,
5
+ ProductSearchResultFacetSchema,
6
+ ProductSearchResultFacetValueSchema,
7
+ ProductSearchResultItemVariantSchema
8
+ } from "@reactionary/core";
9
+ class AlgoliaProductSearchFactory {
10
+ productSearchResultSchema;
11
+ constructor(productSearchResultSchema) {
12
+ this.productSearchResultSchema = productSearchResultSchema;
13
+ }
14
+ parseSearchResult(_context, data, query) {
15
+ const body = this.parseInput(data);
16
+ const items = body.hits.map((hit) => this.parseSingle(hit));
17
+ let facets = [];
18
+ for (const id in body.facets) {
19
+ const values = body.facets[id];
20
+ const facetId = FacetIdentifierSchema.parse({ key: id });
21
+ facets.push(this.parseFacet(facetId, values));
22
+ }
23
+ const selectedCategoryFacet = query.search.facets.find((x) => x.facet.key === "categories") || query.search.categoryFilter;
24
+ let subCategoryFacet;
25
+ if (selectedCategoryFacet) {
26
+ const valueDepth = selectedCategoryFacet.key.split(" > ").length;
27
+ subCategoryFacet = facets.find(
28
+ (f) => f.identifier.key === `hierarchy.lvl${valueDepth}`
29
+ );
30
+ } else {
31
+ subCategoryFacet = facets.find((f) => f.identifier.key === "hierarchy.lvl0");
32
+ }
33
+ if (subCategoryFacet) {
34
+ subCategoryFacet.identifier = FacetIdentifierSchema.parse({ key: "categories" });
35
+ subCategoryFacet.name = "Categories";
36
+ for (const value of subCategoryFacet.values) {
37
+ const pathParts = value.identifier.key.split(" > ");
38
+ value.identifier.facet = subCategoryFacet.identifier;
39
+ value.name = pathParts[pathParts.length - 1];
40
+ }
41
+ }
42
+ facets = facets.filter((f) => !f.identifier.key.startsWith("hierarchy.lvl"));
43
+ const result = {
44
+ identifier: {
45
+ term: query.search.term,
46
+ facets: query.search.facets,
47
+ filters: query.search.filters,
48
+ paginationOptions: query.search.paginationOptions,
49
+ index: body.index || "",
50
+ key: body.queryID || ""
51
+ },
52
+ pageNumber: (body.page || 0) + 1,
53
+ pageSize: body.hitsPerPage || 0,
54
+ totalCount: body.nbHits || 0,
55
+ totalPages: body.nbPages || 0,
56
+ items,
57
+ facets
58
+ };
59
+ return this.productSearchResultSchema.parse(result);
60
+ }
61
+ parseSingle(body) {
62
+ return {
63
+ identifier: { key: body.objectID },
64
+ name: body.name || body.objectID,
65
+ slug: body.slug || body.objectID,
66
+ variants: [...body.variants || []].map(
67
+ (variant) => this.parseVariant(variant, body)
68
+ )
69
+ };
70
+ }
71
+ parseVariant(variant, product) {
72
+ return ProductSearchResultItemVariantSchema.parse({
73
+ variant: {
74
+ sku: variant.sku
75
+ },
76
+ image: ImageSchema.parse({
77
+ sourceUrl: variant.image,
78
+ altText: product.name || ""
79
+ })
80
+ });
81
+ }
82
+ parseFacet(facetIdentifier, facetValues) {
83
+ const result = ProductSearchResultFacetSchema.parse({
84
+ identifier: facetIdentifier,
85
+ name: facetIdentifier.key,
86
+ values: []
87
+ });
88
+ for (const valueId in facetValues) {
89
+ const count = facetValues[valueId];
90
+ const facetValueIdentifier = FacetValueIdentifierSchema.parse({
91
+ facet: facetIdentifier,
92
+ key: valueId
93
+ });
94
+ result.values.push(this.parseFacetValue(facetValueIdentifier, valueId, count));
95
+ }
96
+ return result;
97
+ }
98
+ parseFacetValue(facetValueIdentifier, label, count) {
99
+ return ProductSearchResultFacetValueSchema.parse({
100
+ identifier: facetValueIdentifier,
101
+ name: label,
102
+ count,
103
+ active: false
104
+ });
105
+ }
106
+ parseInput(data) {
107
+ if (!this.isSearchResponse(data)) {
108
+ throw new Error("Invalid Algolia search response");
109
+ }
110
+ return data;
111
+ }
112
+ isSearchResponse(data) {
113
+ if (!data || typeof data !== "object") {
114
+ return false;
115
+ }
116
+ if (!("hits" in data) || !Array.isArray(data.hits)) {
117
+ return false;
118
+ }
119
+ return true;
120
+ }
121
+ }
122
+ export {
123
+ AlgoliaProductSearchFactory
124
+ };
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./core/initialize.js";
2
+ export * from "./capabilities/index.js";
3
+ export * from "./factories/index.js";
4
+ export * from "./schema/configuration.schema.js";
5
+ export * from "./schema/capabilities.schema.js";
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@reactionary/algolia",
3
+ "version": "0.6.3",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "types": "./src/index.d.ts",
7
+ "dependencies": {
8
+ "@reactionary/core": "0.6.3",
9
+ "zod": "4.1.9",
10
+ "algoliasearch": "^5.48.0",
11
+ "vitest": "^4.0.9",
12
+ "@nx/vite": "22.4.5"
13
+ },
14
+ "sideEffects": false
15
+ }
@@ -0,0 +1,23 @@
1
+ import { CapabilitiesSchema } from "@reactionary/core";
2
+ import * as z from "zod";
3
+ const ProductSearchCapabilitySchema = z.looseObject({
4
+ enabled: z.boolean(),
5
+ factory: z.unknown().optional(),
6
+ capability: z.unknown().optional()
7
+ });
8
+ const CapabilityOverrideSchema = z.looseObject({
9
+ enabled: z.boolean(),
10
+ capability: z.unknown().optional()
11
+ });
12
+ const AlgoliaCapabilitiesSchema = CapabilitiesSchema.pick({
13
+ productSearch: true,
14
+ analytics: true,
15
+ productRecommendations: true
16
+ }).extend({
17
+ productSearch: ProductSearchCapabilitySchema.optional(),
18
+ analytics: CapabilityOverrideSchema.optional(),
19
+ productRecommendations: CapabilityOverrideSchema.optional()
20
+ }).partial();
21
+ export {
22
+ AlgoliaCapabilitiesSchema
23
+ };
@@ -0,0 +1,9 @@
1
+ import * as z from "zod";
2
+ const AlgoliaConfigurationSchema = z.looseObject({
3
+ appId: z.string(),
4
+ apiKey: z.string(),
5
+ indexName: z.string()
6
+ });
7
+ export {
8
+ AlgoliaConfigurationSchema
9
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./capabilities.schema.js";
2
+ export * from "./configuration.schema.js";
3
+ export * from "./search.schema.js";
@@ -0,0 +1,9 @@
1
+ import { ProductRecommendationIdentifierSchema } from "@reactionary/core";
2
+ import * as z from "zod";
3
+ const AlgoliaProductSearchIdentifierSchema = ProductRecommendationIdentifierSchema.extend({
4
+ abTestID: z.number().optional(),
5
+ abTestVariantID: z.number().optional()
6
+ });
7
+ export {
8
+ AlgoliaProductSearchIdentifierSchema
9
+ };
@@ -0,0 +1,13 @@
1
+ import { ProductSearchIdentifierSchema, ProductSearchResultSchema } from "@reactionary/core";
2
+ import * as z from "zod";
3
+ const AlgoliaProductSearchIdentifierSchema = ProductSearchIdentifierSchema.extend({
4
+ key: z.string(),
5
+ index: z.string()
6
+ });
7
+ const AlgoliaProductSearchResultSchema = ProductSearchResultSchema.extend({
8
+ identifier: AlgoliaProductSearchIdentifierSchema.default(() => AlgoliaProductSearchIdentifierSchema.parse({}))
9
+ });
10
+ export {
11
+ AlgoliaProductSearchIdentifierSchema,
12
+ AlgoliaProductSearchResultSchema
13
+ };
@@ -0,0 +1,12 @@
1
+ import { AnalyticsCapability, type AnalyticsMutationProductAddToCartEvent, type AnalyticsMutationProductSummaryClickEvent, type AnalyticsMutationProductSummaryViewEvent, type AnalyticsMutationPurchaseEvent, type Cache, type RequestContext } from '@reactionary/core';
2
+ import { type InsightsClient } from 'algoliasearch';
3
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
4
+ export declare class AlgoliaAnalyticsCapability extends AnalyticsCapability {
5
+ protected client: InsightsClient;
6
+ protected config: AlgoliaConfiguration;
7
+ constructor(cache: Cache, requestContext: RequestContext, config: AlgoliaConfiguration);
8
+ protected processProductAddToCart(event: AnalyticsMutationProductAddToCartEvent): Promise<void>;
9
+ protected processProductSummaryClick(event: AnalyticsMutationProductSummaryClickEvent): Promise<void>;
10
+ protected processProductSummaryView(event: AnalyticsMutationProductSummaryViewEvent): Promise<void>;
11
+ protected processPurchase(event: AnalyticsMutationPurchaseEvent): Promise<void>;
12
+ }
@@ -0,0 +1,3 @@
1
+ export * from './analytics.capability.js';
2
+ export * from './product-search.capability.js';
3
+ export * from './product-recommendations.capability.js';
@@ -0,0 +1,103 @@
1
+ import { type Cache, ProductRecommendationsCapability, type ProductRecommendation, type ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery, type ProductRecommendationAlgorithmSimilarProductsQuery, type ProductRecommendationAlgorithmRelatedProductsQuery, type ProductRecommendationAlgorithmTrendingInCategoryQuery, type RequestContext, type ProductRecommendationsQuery, type ProductSearchResultItemVariant } 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
+ import type { AlgoliaNativeRecord, AlgoliaNativeVariant } from '../schema/search.schema.js';
6
+ /**
7
+ * AlgoliaProductRecommendationsCapability
8
+ *
9
+ * Provides product recommendations using Algolia's Recommend API.
10
+ * Supports frequentlyBoughtTogether, similar, related, and trendingInCategory algorithms.
11
+ *
12
+ * Note: This requires Algolia Recommend to be enabled and AI models to be trained.
13
+ * See: https://www.algolia.com/doc/guides/algolia-recommend/overview/
14
+ */
15
+ export declare class AlgoliaProductRecommendationsCapability extends ProductRecommendationsCapability {
16
+ protected config: AlgoliaConfiguration;
17
+ protected client: LiteClient;
18
+ constructor(config: AlgoliaConfiguration, cache: Cache, context: RequestContext);
19
+ protected getRecommendationThreshold(_algorithm: string): number;
20
+ protected getQueryParametersForRecommendations(algorithm: string): RecommendSearchParams;
21
+ /**
22
+ * Get frequently bought together recommendations using Algolia Recommend
23
+ */
24
+ protected getFrequentlyBoughtTogetherRecommendations(query: ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery): Promise<ProductRecommendation[]>;
25
+ /**
26
+ * Get similar product recommendations using Algolia Recommend
27
+ */
28
+ protected getSimilarProductsRecommendations(query: ProductRecommendationAlgorithmSimilarProductsQuery): Promise<ProductRecommendation[]>;
29
+ /**
30
+ * Get related product recommendations using Algolia Recommend
31
+ */
32
+ protected getRelatedProductsRecommendations(query: ProductRecommendationAlgorithmRelatedProductsQuery): Promise<ProductRecommendation[]>;
33
+ /**
34
+ * Get trending in category recommendations using Algolia Recommend
35
+ */
36
+ protected getTrendingInCategoryRecommendations(query: ProductRecommendationAlgorithmTrendingInCategoryQuery): Promise<ProductRecommendation[]>;
37
+ protected parseRecommendation(res: RecommendationsResults, query: ProductRecommendationsQuery): (({
38
+ recommendationIdentifier: {
39
+ key: string;
40
+ algorithm: string;
41
+ };
42
+ recommendationReturnType: "idOnly";
43
+ product: {
44
+ key: string;
45
+ };
46
+ } & {
47
+ _?: never;
48
+ }) | ({
49
+ recommendationIdentifier: {
50
+ key: string;
51
+ algorithm: string;
52
+ };
53
+ recommendationReturnType: "productSearchResultItem";
54
+ product: {
55
+ identifier: {
56
+ key: string;
57
+ };
58
+ name: string;
59
+ slug: string;
60
+ variants: {
61
+ variant: {
62
+ sku: string;
63
+ };
64
+ image: {
65
+ sourceUrl: string;
66
+ altText: string;
67
+ width?: number | undefined;
68
+ height?: number | undefined;
69
+ };
70
+ options?: {
71
+ identifier: {
72
+ key: string;
73
+ };
74
+ name: string;
75
+ value: {
76
+ identifier: {
77
+ option: {
78
+ key: string;
79
+ };
80
+ key: string;
81
+ };
82
+ label: string;
83
+ };
84
+ } | undefined;
85
+ }[];
86
+ };
87
+ } & {
88
+ _?: never;
89
+ }))[];
90
+ /**
91
+ * Maps Algolia recommendation results to ProductRecommendation format
92
+ */
93
+ protected parseSingle(hit: AlgoliaNativeRecord, recommendationIdentifier: AlgoliaProductRecommendationIdentifier): ProductRecommendation;
94
+ protected parseSearchResultItem(body: AlgoliaNativeRecord): {
95
+ identifier: {
96
+ key: string;
97
+ };
98
+ name: string;
99
+ slug: string;
100
+ variants: ProductSearchResultItemVariant[];
101
+ };
102
+ protected parseVariant(variant: AlgoliaNativeVariant, product: AlgoliaNativeRecord): ProductSearchResultItemVariant;
103
+ }
@@ -0,0 +1,21 @@
1
+ import { type Cache, type FacetValueIdentifier, ProductSearchCapability, type ProductSearchFactory, type ProductSearchFactoryOutput, type ProductSearchFactoryWithOutput, type ProductSearchQueryByTerm, type ProductSearchQueryCreateNavigationFilter, type RequestContext, type Result } from '@reactionary/core';
2
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
3
+ import type { AlgoliaProductSearchFactory } from '../factories/product-search/product-search.factory.js';
4
+ export declare class AlgoliaProductSearchCapability<TFactory extends ProductSearchFactory = AlgoliaProductSearchFactory> extends ProductSearchCapability<ProductSearchFactoryOutput<TFactory>> {
5
+ protected config: AlgoliaConfiguration;
6
+ protected factory: ProductSearchFactoryWithOutput<TFactory>;
7
+ constructor(cache: Cache, context: RequestContext, config: AlgoliaConfiguration, factory: ProductSearchFactoryWithOutput<TFactory>);
8
+ protected queryByTermPayload(payload: ProductSearchQueryByTerm): {
9
+ indexName: string;
10
+ query: string;
11
+ page: number;
12
+ hitsPerPage: number;
13
+ facets: string[];
14
+ analytics: boolean;
15
+ clickAnalytics: boolean;
16
+ facetFilters: string[];
17
+ filters: string;
18
+ };
19
+ queryByTerm(payload: ProductSearchQueryByTerm): Promise<Result<ProductSearchFactoryOutput<TFactory>>>;
20
+ createCategoryNavigationFilter(payload: ProductSearchQueryCreateNavigationFilter): Promise<Result<FacetValueIdentifier>>;
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { Cache, RequestContext } from '@reactionary/core';
2
+ import type { AlgoliaCapabilities } from '../schema/capabilities.schema.js';
3
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
4
+ import { type AlgoliaClientFromCapabilities } from './initialize.types.js';
5
+ export declare function withAlgoliaCapabilities<T extends AlgoliaCapabilities>(configuration: AlgoliaConfiguration, capabilities: T): (cache: Cache, context: RequestContext) => AlgoliaClientFromCapabilities<T>;
@@ -0,0 +1,43 @@
1
+ import type { ClientFromCapabilities, ProductSearchFactory } from '@reactionary/core';
2
+ import type { AlgoliaCapabilities } from '../schema/capabilities.schema.js';
3
+ import type { AlgoliaProductSearchFactory } from '../factories/product-search/product-search.factory.js';
4
+ import type { AlgoliaAnalyticsCapability } from '../capabilities/analytics.capability.js';
5
+ import type { AlgoliaProductRecommendationsCapability } from '../capabilities/product-recommendations.capability.js';
6
+ import type { AlgoliaProductSearchCapability } from '../capabilities/product-search.capability.js';
7
+ type EnabledCapability<TCapability> = TCapability extends {
8
+ enabled: true;
9
+ } ? true : false;
10
+ type NormalizeConfiguredCapabilities<T extends AlgoliaCapabilities> = Omit<T, 'productSearch' | 'analytics' | 'productRecommendations'> & {
11
+ productSearch?: EnabledCapability<T['productSearch']>;
12
+ analytics?: EnabledCapability<T['analytics']>;
13
+ productRecommendations?: EnabledCapability<T['productRecommendations']>;
14
+ };
15
+ type ExtractCapabilityFactory<TCapability, TContract, TDefaultFactory> = TCapability extends {
16
+ enabled: true;
17
+ factory?: infer TFactory;
18
+ } ? TFactory extends TContract ? TFactory : TDefaultFactory : TDefaultFactory;
19
+ type ExtractCapabilityImplementation<TCapability, TDefaultCapability> = TCapability extends {
20
+ enabled: true;
21
+ capability?: infer TCapabilityFactory;
22
+ } ? TCapabilityFactory extends (...args: unknown[]) => infer TResolvedCapability ? TResolvedCapability : TDefaultCapability : TDefaultCapability;
23
+ type CapabilityOverride<TCapability, TKey extends string, TResolvedCapability> = TCapability extends {
24
+ enabled: true;
25
+ } ? {
26
+ [K in TKey]: TResolvedCapability;
27
+ } : Record<never, never>;
28
+ type ProductSearchFactoryFor<T extends AlgoliaCapabilities> = ExtractCapabilityFactory<T['productSearch'], ProductSearchFactory, AlgoliaProductSearchFactory>;
29
+ type ProductSearchCapabilityFor<T extends AlgoliaCapabilities> = ExtractCapabilityImplementation<T['productSearch'], AlgoliaProductSearchCapability<ProductSearchFactoryFor<T>>>;
30
+ type AnalyticsCapabilityFor<T extends AlgoliaCapabilities> = ExtractCapabilityImplementation<T['analytics'], AlgoliaAnalyticsCapability>;
31
+ type ProductRecommendationsCapabilityFor<T extends AlgoliaCapabilities> = ExtractCapabilityImplementation<T['productRecommendations'], AlgoliaProductRecommendationsCapability>;
32
+ export type AlgoliaClientFromCapabilities<T extends AlgoliaCapabilities> = Omit<ClientFromCapabilities<NormalizeConfiguredCapabilities<T>>, 'productSearch' | 'analytics' | 'productRecommendations'> & CapabilityOverride<T['productSearch'], 'productSearch', ProductSearchCapabilityFor<T>> & CapabilityOverride<T['analytics'], 'analytics', AnalyticsCapabilityFor<T>> & CapabilityOverride<T['productRecommendations'], 'productRecommendations', ProductRecommendationsCapabilityFor<T>>;
33
+ export declare function resolveCapabilityWithFactory<TFactory, TResolvedCapability, TCapabilityArgs>(capability: {
34
+ factory?: TFactory;
35
+ capability?: (args: TCapabilityArgs) => TResolvedCapability;
36
+ } | undefined, defaults: {
37
+ factory: TFactory;
38
+ capability: (args: TCapabilityArgs) => TResolvedCapability;
39
+ }, buildCapabilityArgs: (factory: TFactory) => TCapabilityArgs): TResolvedCapability;
40
+ export declare function resolveDirectCapability<TResolvedCapability, TCapabilityArgs>(capability: {
41
+ capability?: (args: TCapabilityArgs) => TResolvedCapability;
42
+ } | undefined, defaultCapability: (args: TCapabilityArgs) => TResolvedCapability, args: TCapabilityArgs): TResolvedCapability;
43
+ export {};
@@ -0,0 +1 @@
1
+ export * from './product-search/product-search.factory.js';
@@ -0,0 +1,16 @@
1
+ import type { AnyProductSearchResultSchema, FacetIdentifier, FacetValueIdentifier, ProductSearchFactory, ProductSearchQueryByTerm, ProductSearchResultFacet, ProductSearchResultFacetValue, ProductSearchResultItem, ProductSearchResultItemVariant, RequestContext } from '@reactionary/core';
2
+ import type * as z from 'zod';
3
+ import type { SearchResponse } from 'algoliasearch';
4
+ import type { AlgoliaNativeRecord, AlgoliaNativeVariant } from '../../schema/search.schema.js';
5
+ import type { AlgoliaProductSearchResultSchema } from '../../schema/search.schema.js';
6
+ export declare class AlgoliaProductSearchFactory<TProductSearchResultSchema extends AnyProductSearchResultSchema = typeof AlgoliaProductSearchResultSchema> implements ProductSearchFactory<TProductSearchResultSchema> {
7
+ readonly productSearchResultSchema: TProductSearchResultSchema;
8
+ constructor(productSearchResultSchema: TProductSearchResultSchema);
9
+ parseSearchResult(_context: RequestContext, data: unknown, query: ProductSearchQueryByTerm): z.output<TProductSearchResultSchema>;
10
+ protected parseSingle(body: AlgoliaNativeRecord): ProductSearchResultItem;
11
+ protected parseVariant(variant: AlgoliaNativeVariant, product: AlgoliaNativeRecord): ProductSearchResultItemVariant;
12
+ protected parseFacet(facetIdentifier: FacetIdentifier, facetValues: Record<string, number>): ProductSearchResultFacet;
13
+ protected parseFacetValue(facetValueIdentifier: FacetValueIdentifier, label: string, count: number): ProductSearchResultFacetValue;
14
+ protected parseInput(data: unknown): SearchResponse<AlgoliaNativeRecord>;
15
+ protected isSearchResponse(data: unknown): data is SearchResponse<AlgoliaNativeRecord>;
16
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './core/initialize.js';
2
+ export * from './capabilities/index.js';
3
+ export * from './factories/index.js';
4
+ export * from './schema/configuration.schema.js';
5
+ export * from './schema/capabilities.schema.js';
@@ -0,0 +1,44 @@
1
+ import type { AnalyticsCapability, Cache, ProductRecommendationsCapability, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, RequestContext } from '@reactionary/core';
2
+ import type { AlgoliaConfiguration } from './configuration.schema.js';
3
+ import * as z from 'zod';
4
+ export declare const AlgoliaCapabilitiesSchema: z.ZodObject<{
5
+ productSearch: z.ZodOptional<z.ZodOptional<z.ZodObject<{
6
+ enabled: z.ZodBoolean;
7
+ factory: z.ZodOptional<z.ZodUnknown>;
8
+ capability: z.ZodOptional<z.ZodUnknown>;
9
+ }, z.core.$loose>>>;
10
+ analytics: z.ZodOptional<z.ZodOptional<z.ZodObject<{
11
+ enabled: z.ZodBoolean;
12
+ capability: z.ZodOptional<z.ZodUnknown>;
13
+ }, z.core.$loose>>>;
14
+ productRecommendations: z.ZodOptional<z.ZodOptional<z.ZodObject<{
15
+ enabled: z.ZodBoolean;
16
+ capability: z.ZodOptional<z.ZodUnknown>;
17
+ }, z.core.$loose>>>;
18
+ }, z.core.$loose>;
19
+ export interface AlgoliaCapabilityFactoryArgs {
20
+ cache: Cache;
21
+ context: RequestContext;
22
+ config: AlgoliaConfiguration;
23
+ }
24
+ export interface AlgoliaProductSearchCapabilityFactoryArgs<TFactory extends ProductSearchFactory = ProductSearchFactory> extends AlgoliaCapabilityFactoryArgs {
25
+ factory: ProductSearchFactoryWithOutput<TFactory>;
26
+ }
27
+ export interface AlgoliaProductSearchCapabilityConfig<TFactory extends ProductSearchFactory = ProductSearchFactory, TCapability extends ProductSearchCapability = ProductSearchCapability> {
28
+ enabled: boolean;
29
+ factory?: ProductSearchFactoryWithOutput<TFactory>;
30
+ capability?: (args: AlgoliaProductSearchCapabilityFactoryArgs<TFactory>) => TCapability;
31
+ }
32
+ export interface AlgoliaAnalyticsCapabilityConfig<TCapability extends AnalyticsCapability = AnalyticsCapability> {
33
+ enabled: boolean;
34
+ capability?: (args: AlgoliaCapabilityFactoryArgs) => TCapability;
35
+ }
36
+ export interface AlgoliaProductRecommendationsCapabilityConfig<TCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability> {
37
+ enabled: boolean;
38
+ capability?: (args: AlgoliaCapabilityFactoryArgs) => TCapability;
39
+ }
40
+ export type AlgoliaCapabilities<TProductSearchFactory extends ProductSearchFactory = ProductSearchFactory, TProductSearchCapability extends ProductSearchCapability = ProductSearchCapability, TAnalyticsCapability extends AnalyticsCapability = AnalyticsCapability, TProductRecommendationsCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability> = {
41
+ productSearch?: AlgoliaProductSearchCapabilityConfig<TProductSearchFactory, TProductSearchCapability>;
42
+ analytics?: AlgoliaAnalyticsCapabilityConfig<TAnalyticsCapability>;
43
+ productRecommendations?: AlgoliaProductRecommendationsCapabilityConfig<TProductRecommendationsCapability>;
44
+ };
@@ -0,0 +1,7 @@
1
+ import * as z from 'zod';
2
+ export declare const AlgoliaConfigurationSchema: z.ZodObject<{
3
+ appId: z.ZodString;
4
+ apiKey: z.ZodString;
5
+ indexName: z.ZodString;
6
+ }, z.core.$loose>;
7
+ export type AlgoliaConfiguration = z.infer<typeof AlgoliaConfigurationSchema>;
@@ -0,0 +1,3 @@
1
+ export * from './capabilities.schema.js';
2
+ export * from './configuration.schema.js';
3
+ export * from './search.schema.js';
@@ -0,0 +1,8 @@
1
+ import * as 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.core.$loose>;
8
+ export type AlgoliaProductRecommendationIdentifier = z.infer<typeof AlgoliaProductSearchIdentifierSchema>;
@@ -0,0 +1,113 @@
1
+ import * as z from 'zod';
2
+ export declare const AlgoliaProductSearchIdentifierSchema: z.ZodObject<{
3
+ term: z.ZodString;
4
+ facets: z.ZodArray<z.ZodObject<{
5
+ facet: z.ZodObject<{
6
+ key: z.ZodString;
7
+ }, z.core.$loose>;
8
+ key: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ filters: z.ZodArray<z.ZodString>;
11
+ paginationOptions: z.ZodObject<{
12
+ pageNumber: z.ZodDefault<z.ZodNumber>;
13
+ pageSize: z.ZodDefault<z.ZodNumber>;
14
+ }, z.core.$loose>;
15
+ categoryFilter: z.ZodOptional<z.ZodObject<{
16
+ facet: z.ZodObject<{
17
+ key: z.ZodString;
18
+ }, z.core.$loose>;
19
+ key: z.ZodString;
20
+ }, z.core.$strip>>;
21
+ key: z.ZodString;
22
+ index: z.ZodString;
23
+ }, z.core.$loose>;
24
+ export declare const AlgoliaProductSearchResultSchema: z.ZodObject<{
25
+ pageNumber: z.ZodNumber;
26
+ pageSize: z.ZodNumber;
27
+ totalCount: z.ZodNumber;
28
+ totalPages: z.ZodNumber;
29
+ items: z.ZodArray<z.ZodObject<{
30
+ identifier: z.ZodObject<{
31
+ key: z.ZodString;
32
+ }, z.core.$loose>;
33
+ name: z.ZodString;
34
+ slug: z.ZodString;
35
+ variants: z.ZodArray<z.ZodObject<{
36
+ variant: z.ZodObject<{
37
+ sku: z.ZodString;
38
+ }, z.core.$loose>;
39
+ image: z.ZodObject<{
40
+ sourceUrl: z.ZodDefault<z.ZodString>;
41
+ altText: z.ZodDefault<z.ZodString>;
42
+ width: z.ZodOptional<z.ZodNumber>;
43
+ height: z.ZodOptional<z.ZodNumber>;
44
+ }, z.core.$loose>;
45
+ options: z.ZodOptional<z.ZodObject<{
46
+ identifier: z.ZodObject<{
47
+ key: z.ZodString;
48
+ }, z.core.$loose>;
49
+ name: z.ZodString;
50
+ value: z.ZodObject<{
51
+ identifier: z.ZodObject<{
52
+ option: z.ZodObject<{
53
+ key: z.ZodString;
54
+ }, z.core.$loose>;
55
+ key: z.ZodString;
56
+ }, z.core.$loose>;
57
+ label: z.ZodString;
58
+ }, z.core.$loose>;
59
+ }, z.core.$loose>>;
60
+ }, z.core.$loose>>;
61
+ }, z.core.$loose>>;
62
+ facets: z.ZodArray<z.ZodObject<{
63
+ identifier: z.ZodObject<{
64
+ key: z.ZodString;
65
+ }, z.core.$loose>;
66
+ name: z.ZodString;
67
+ values: z.ZodArray<z.ZodObject<{
68
+ identifier: z.ZodObject<{
69
+ facet: z.ZodObject<{
70
+ key: z.ZodString;
71
+ }, z.core.$loose>;
72
+ key: z.ZodString;
73
+ }, z.core.$strip>;
74
+ name: z.ZodString;
75
+ count: z.ZodNumber;
76
+ active: z.ZodBoolean;
77
+ }, z.core.$loose>>;
78
+ }, z.core.$loose>>;
79
+ identifier: z.ZodDefault<z.ZodObject<{
80
+ term: z.ZodString;
81
+ facets: z.ZodArray<z.ZodObject<{
82
+ facet: z.ZodObject<{
83
+ key: z.ZodString;
84
+ }, z.core.$loose>;
85
+ key: z.ZodString;
86
+ }, z.core.$strip>>;
87
+ filters: z.ZodArray<z.ZodString>;
88
+ paginationOptions: z.ZodObject<{
89
+ pageNumber: z.ZodDefault<z.ZodNumber>;
90
+ pageSize: z.ZodDefault<z.ZodNumber>;
91
+ }, z.core.$loose>;
92
+ categoryFilter: z.ZodOptional<z.ZodObject<{
93
+ facet: z.ZodObject<{
94
+ key: z.ZodString;
95
+ }, z.core.$loose>;
96
+ key: z.ZodString;
97
+ }, z.core.$strip>>;
98
+ key: z.ZodString;
99
+ index: z.ZodString;
100
+ }, z.core.$loose>>;
101
+ }, z.core.$strip>;
102
+ export type AlgoliaProductSearchResult = z.infer<typeof AlgoliaProductSearchResultSchema>;
103
+ export type AlgoliaProductSearchIdentifier = z.infer<typeof AlgoliaProductSearchIdentifierSchema>;
104
+ export interface AlgoliaNativeVariant {
105
+ sku: string;
106
+ image: string;
107
+ }
108
+ export interface AlgoliaNativeRecord {
109
+ objectID: string;
110
+ slug?: string;
111
+ name?: string;
112
+ variants: Array<AlgoliaNativeVariant>;
113
+ }
@@ -0,0 +1,61 @@
1
+ import {
2
+ ClientBuilder,
3
+ NoOpCache,
4
+ createInitialRequestContext
5
+ } from "@reactionary/core";
6
+ import * as z from "zod";
7
+ import { withAlgoliaCapabilities } from "../core/initialize.js";
8
+ import { AlgoliaProductSearchFactory } from "../factories/product-search/product-search.factory.js";
9
+ import { AlgoliaProductSearchResultSchema } from "../schema/search.schema.js";
10
+ const assertType = (_value) => {
11
+ };
12
+ const assertNotAny = (_value) => {
13
+ };
14
+ const ExtendedProductSearchResultSchema = AlgoliaProductSearchResultSchema.safeExtend(
15
+ {
16
+ extendedMeta: z.string()
17
+ }
18
+ );
19
+ class ExtendedAlgoliaProductSearchFactory extends AlgoliaProductSearchFactory {
20
+ constructor() {
21
+ super(ExtendedProductSearchResultSchema);
22
+ }
23
+ parseSearchResult(context, data, query) {
24
+ const base = super.parseSearchResult(context, data, query);
25
+ return this.productSearchResultSchema.parse({
26
+ ...base,
27
+ extendedMeta: "from-factory"
28
+ });
29
+ }
30
+ }
31
+ const config = {
32
+ appId: "ALGOLIA_APP_ID",
33
+ apiKey: "ALGOLIA_API_KEY",
34
+ indexName: "ALGOLIA_INDEX"
35
+ };
36
+ const client = new ClientBuilder(createInitialRequestContext()).withCache(new NoOpCache()).withCapability(
37
+ withAlgoliaCapabilities(config, {
38
+ productSearch: {
39
+ enabled: true,
40
+ factory: new ExtendedAlgoliaProductSearchFactory()
41
+ }
42
+ })
43
+ ).build();
44
+ client.productSearch.queryByTerm({
45
+ search: {
46
+ term: "test",
47
+ facets: [],
48
+ filters: [],
49
+ paginationOptions: {
50
+ pageNumber: 1,
51
+ pageSize: 10
52
+ }
53
+ }
54
+ }).then((result) => {
55
+ assertNotAny(result);
56
+ if (result.success) {
57
+ assertNotAny(result.value);
58
+ assertNotAny(result.value.extendedMeta);
59
+ assertType(result.value.extendedMeta);
60
+ }
61
+ });