@reactionary/provider-algolia 0.6.1 → 0.6.2

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,17 +1,63 @@
1
1
  import { AlgoliaProductSearchProvider } from "../providers/product-search.provider.js";
2
2
  import { AlgoliaAnalyticsProvider } from "../providers/analytics.provider.js";
3
3
  import { AlgoliaProductRecommendationsProvider } from "../providers/product-recommendations.provider.js";
4
+ import { AlgoliaProductSearchFactory } from "../factories/product-search/product-search.factory.js";
5
+ import { AlgoliaProductSearchResultSchema } from "../schema/search.schema.js";
6
+ import {
7
+ resolveCapabilityProvider,
8
+ resolveProviderOnlyCapability
9
+ } from "./initialize.types.js";
4
10
  function withAlgoliaCapabilities(configuration, capabilities) {
5
11
  return (cache, context) => {
6
12
  const client = {};
7
- if (capabilities.productSearch) {
8
- client.productSearch = new AlgoliaProductSearchProvider(cache, context, configuration);
13
+ if (capabilities.productSearch?.enabled) {
14
+ const defaultFactory = new AlgoliaProductSearchFactory(
15
+ AlgoliaProductSearchResultSchema
16
+ );
17
+ client.productSearch = resolveCapabilityProvider(
18
+ capabilities.productSearch,
19
+ {
20
+ factory: defaultFactory,
21
+ provider: (args) => new AlgoliaProductSearchProvider(
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
+ );
9
35
  }
10
- if (capabilities.analytics) {
11
- client.analytics = new AlgoliaAnalyticsProvider(cache, context, configuration);
36
+ if (capabilities.analytics?.enabled) {
37
+ client.analytics = resolveProviderOnlyCapability(
38
+ capabilities.analytics,
39
+ (args) => new AlgoliaAnalyticsProvider(args.cache, args.context, args.config),
40
+ {
41
+ cache,
42
+ context,
43
+ config: configuration
44
+ }
45
+ );
12
46
  }
13
- if (capabilities.productRecommendations) {
14
- client.productRecommendations = new AlgoliaProductRecommendationsProvider(configuration, cache, context);
47
+ if (capabilities.productRecommendations?.enabled) {
48
+ client.productRecommendations = resolveProviderOnlyCapability(
49
+ capabilities.productRecommendations,
50
+ (args) => new AlgoliaProductRecommendationsProvider(
51
+ args.config,
52
+ args.cache,
53
+ args.context
54
+ ),
55
+ {
56
+ cache,
57
+ context,
58
+ config: configuration
59
+ }
60
+ );
15
61
  }
16
62
  return client;
17
63
  };
@@ -0,0 +1,13 @@
1
+ function resolveCapabilityProvider(capability, defaults, buildProviderArgs) {
2
+ const factory = capability?.factory ?? defaults.factory;
3
+ const provider = capability?.provider ?? defaults.provider;
4
+ return provider(buildProviderArgs(factory));
5
+ }
6
+ function resolveProviderOnlyCapability(capability, defaultProvider, args) {
7
+ const provider = capability?.provider ?? defaultProvider;
8
+ return provider(args);
9
+ }
10
+ export {
11
+ resolveCapabilityProvider,
12
+ resolveProviderOnlyCapability
13
+ };
@@ -0,0 +1 @@
1
+ export * from "./product-search/product-search.factory.js";
@@ -0,0 +1,123 @@
1
+ import {
2
+ FacetIdentifierSchema,
3
+ FacetValueIdentifierSchema,
4
+ ImageSchema,
5
+ ProductSearchResultFacetSchema,
6
+ ProductSearchResultFacetValueSchema,
7
+ ProductSearchResultItemVariantSchema
8
+ } from "@reactionary/core";
9
+ class AlgoliaProductSearchFactory {
10
+ constructor(productSearchResultSchema) {
11
+ this.productSearchResultSchema = productSearchResultSchema;
12
+ }
13
+ parseSearchResult(_context, data, query) {
14
+ const body = this.parseInput(data);
15
+ const items = body.hits.map((hit) => this.parseSingle(hit));
16
+ let facets = [];
17
+ for (const id in body.facets) {
18
+ const values = body.facets[id];
19
+ const facetId = FacetIdentifierSchema.parse({ key: id });
20
+ facets.push(this.parseFacet(facetId, values));
21
+ }
22
+ const selectedCategoryFacet = query.search.facets.find((x) => x.facet.key === "categories") || query.search.categoryFilter;
23
+ let subCategoryFacet;
24
+ if (selectedCategoryFacet) {
25
+ const valueDepth = selectedCategoryFacet.key.split(" > ").length;
26
+ subCategoryFacet = facets.find(
27
+ (f) => f.identifier.key === `hierarchy.lvl${valueDepth}`
28
+ );
29
+ } else {
30
+ subCategoryFacet = facets.find((f) => f.identifier.key === "hierarchy.lvl0");
31
+ }
32
+ if (subCategoryFacet) {
33
+ subCategoryFacet.identifier = FacetIdentifierSchema.parse({ key: "categories" });
34
+ subCategoryFacet.name = "Categories";
35
+ for (const value of subCategoryFacet.values) {
36
+ const pathParts = value.identifier.key.split(" > ");
37
+ value.identifier.facet = subCategoryFacet.identifier;
38
+ value.name = pathParts[pathParts.length - 1];
39
+ }
40
+ }
41
+ facets = facets.filter((f) => !f.identifier.key.startsWith("hierarchy.lvl"));
42
+ const result = {
43
+ identifier: {
44
+ term: query.search.term,
45
+ facets: query.search.facets,
46
+ filters: query.search.filters,
47
+ paginationOptions: query.search.paginationOptions,
48
+ index: body.index || "",
49
+ key: body.queryID || ""
50
+ },
51
+ pageNumber: (body.page || 0) + 1,
52
+ pageSize: body.hitsPerPage || 0,
53
+ totalCount: body.nbHits || 0,
54
+ totalPages: body.nbPages || 0,
55
+ items,
56
+ facets
57
+ };
58
+ return this.productSearchResultSchema.parse(result);
59
+ }
60
+ parseSingle(body) {
61
+ return {
62
+ identifier: { key: body.objectID },
63
+ name: body.name || body.objectID,
64
+ slug: body.slug || body.objectID,
65
+ variants: [...body.variants || []].map(
66
+ (variant) => this.parseVariant(variant, body)
67
+ )
68
+ };
69
+ }
70
+ parseVariant(variant, product) {
71
+ return ProductSearchResultItemVariantSchema.parse({
72
+ variant: {
73
+ sku: variant.sku
74
+ },
75
+ image: ImageSchema.parse({
76
+ sourceUrl: variant.image,
77
+ altText: product.name || ""
78
+ })
79
+ });
80
+ }
81
+ parseFacet(facetIdentifier, facetValues) {
82
+ const result = ProductSearchResultFacetSchema.parse({
83
+ identifier: facetIdentifier,
84
+ name: facetIdentifier.key,
85
+ values: []
86
+ });
87
+ for (const valueId in facetValues) {
88
+ const count = facetValues[valueId];
89
+ const facetValueIdentifier = FacetValueIdentifierSchema.parse({
90
+ facet: facetIdentifier,
91
+ key: valueId
92
+ });
93
+ result.values.push(this.parseFacetValue(facetValueIdentifier, valueId, count));
94
+ }
95
+ return result;
96
+ }
97
+ parseFacetValue(facetValueIdentifier, label, count) {
98
+ return ProductSearchResultFacetValueSchema.parse({
99
+ identifier: facetValueIdentifier,
100
+ name: label,
101
+ count,
102
+ active: false
103
+ });
104
+ }
105
+ parseInput(data) {
106
+ if (!this.isSearchResponse(data)) {
107
+ throw new Error("Invalid Algolia search response");
108
+ }
109
+ return data;
110
+ }
111
+ isSearchResponse(data) {
112
+ if (!data || typeof data !== "object") {
113
+ return false;
114
+ }
115
+ if (!("hits" in data) || !Array.isArray(data.hits)) {
116
+ return false;
117
+ }
118
+ return true;
119
+ }
120
+ }
121
+ export {
122
+ AlgoliaProductSearchFactory
123
+ };
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./core/initialize.js";
2
- export * from "./providers/product-search.provider.js";
3
- export * from "./providers/product-recommendations.provider.js";
2
+ export * from "./providers/index.js";
3
+ export * from "./factories/index.js";
4
4
  export * from "./schema/configuration.schema.js";
5
5
  export * from "./schema/capabilities.schema.js";
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@reactionary/provider-algolia",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "main": "index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
- "@reactionary/core": "0.6.1",
7
+ "@reactionary/core": "0.6.2",
8
8
  "algoliasearch": "^5.48.0",
9
9
  "zod": "4.1.9"
10
10
  },
@@ -21,7 +21,7 @@ class AlgoliaAnalyticsProvider extends AnalyticsProvider {
21
21
  userToken: this.context.session.identityContext.personalizationKey,
22
22
  queryID: event.source.identifier.key
23
23
  };
24
- const response = await this.client.pushEvents({
24
+ await this.client.pushEvents({
25
25
  events: [algoliaEvent]
26
26
  });
27
27
  }
@@ -37,7 +37,7 @@ class AlgoliaAnalyticsProvider extends AnalyticsProvider {
37
37
  positions: [event.position],
38
38
  queryID: event.source.identifier.key
39
39
  };
40
- const response = await this.client.pushEvents({
40
+ await this.client.pushEvents({
41
41
  events: [algoliaEvent]
42
42
  });
43
43
  }
@@ -51,7 +51,7 @@ class AlgoliaAnalyticsProvider extends AnalyticsProvider {
51
51
  objectIDs: event.products.map((x) => x.key),
52
52
  userToken: this.context.session.identityContext.personalizationKey
53
53
  };
54
- const response = await this.client.pushEvents({
54
+ await this.client.pushEvents({
55
55
  events: [algoliaEvent]
56
56
  });
57
57
  }
@@ -65,7 +65,7 @@ class AlgoliaAnalyticsProvider extends AnalyticsProvider {
65
65
  objectIDs: event.order.items.map((x) => x.variant.sku),
66
66
  userToken: this.context.session.identityContext.personalizationKey
67
67
  };
68
- const response = await this.client.pushEvents({
68
+ await this.client.pushEvents({
69
69
  events: [algoliaEvent]
70
70
  });
71
71
  }
@@ -12,30 +12,27 @@ var __decorateClass = (decorators, target, key, kind) => {
12
12
  import {
13
13
  FacetIdentifierSchema,
14
14
  FacetValueIdentifierSchema,
15
- ImageSchema,
16
15
  ProductSearchProvider,
17
16
  ProductSearchQueryByTermSchema,
18
- ProductSearchResultFacetSchema,
19
- ProductSearchResultFacetValueSchema,
20
- ProductSearchResultItemVariantSchema,
21
17
  ProductSearchResultSchema,
22
18
  Reactionary,
23
19
  success
24
20
  } from "@reactionary/core";
25
21
  import { algoliasearch } from "algoliasearch";
26
22
  class AlgoliaProductSearchProvider extends ProductSearchProvider {
27
- constructor(cache, context, config) {
23
+ constructor(cache, context, config, factory) {
28
24
  super(cache, context);
29
25
  this.config = config;
26
+ this.factory = factory;
30
27
  }
31
28
  queryByTermPayload(payload) {
32
- const facetsThatAreNotCategory = payload.search.facets.filter((x) => x.facet.key !== "categories");
29
+ const facetsThatAreNotCategory = payload.search.facets.filter(
30
+ (x) => x.facet.key !== "categories"
31
+ );
33
32
  const categoryFacet = payload.search.facets.find((x) => x.facet.key === "categories") || payload.search.categoryFilter;
34
33
  const finalFilters = [...payload.search.filters || []];
35
34
  const finalFacetFilters = [
36
- ...facetsThatAreNotCategory.map(
37
- (x) => `${x.facet.key}:${x.key}`
38
- )
35
+ ...facetsThatAreNotCategory.map((x) => `${x.facet.key}:${x.key}`)
39
36
  ];
40
37
  if (categoryFacet) {
41
38
  finalFilters.push(`categories:"${categoryFacet.key}"`);
@@ -49,25 +46,26 @@ class AlgoliaProductSearchProvider extends ProductSearchProvider {
49
46
  analytics: true,
50
47
  clickAnalytics: true,
51
48
  facetFilters: finalFacetFilters,
52
- filters: (finalFilters || []).join(" AND ")
49
+ filters: finalFilters.join(" AND ")
53
50
  };
54
51
  }
55
52
  async queryByTerm(payload) {
56
53
  const client = algoliasearch(this.config.appId, this.config.apiKey);
57
54
  const remote = await client.search({
58
- requests: [
59
- this.queryByTermPayload(payload)
60
- ]
55
+ requests: [this.queryByTermPayload(payload)]
61
56
  });
62
57
  const input = remote.results[0];
63
- const result = this.parsePaginatedResult(input, payload);
58
+ const result = this.factory.parseSearchResult(this.context, input, payload);
64
59
  for (const selectedFacet of payload.search.facets) {
65
- const facet = result.facets.find((f) => f.identifier.key === selectedFacet.facet.key);
66
- if (facet) {
67
- const value = facet.values.find((v) => v.identifier.key === selectedFacet.key);
68
- if (value) {
69
- value.active = true;
70
- }
60
+ const facet = result.facets.find(
61
+ (f) => f.identifier.key === selectedFacet.facet.key
62
+ );
63
+ if (!facet) {
64
+ continue;
65
+ }
66
+ const value = facet.values.find((v) => v.identifier.key === selectedFacet.key);
67
+ if (value) {
68
+ value.active = true;
71
69
  }
72
70
  }
73
71
  return success(result);
@@ -82,100 +80,6 @@ class AlgoliaProductSearchProvider extends ProductSearchProvider {
82
80
  });
83
81
  return success(facetValueIdentifier);
84
82
  }
85
- parseSingle(body) {
86
- const product = {
87
- identifier: { key: body.objectID },
88
- name: body.name || body.objectID,
89
- slug: body.slug || body.objectID,
90
- variants: [...body.variants || []].map((variant) => this.parseVariant(variant, body))
91
- };
92
- return product;
93
- }
94
- parseVariant(variant, product) {
95
- const result = ProductSearchResultItemVariantSchema.parse({
96
- variant: {
97
- sku: variant.sku
98
- },
99
- image: ImageSchema.parse({
100
- sourceUrl: variant.image,
101
- altText: product.name || ""
102
- })
103
- });
104
- return result;
105
- }
106
- parsePaginatedResult(body, query) {
107
- const items = body.hits.map((hit) => this.parseSingle(hit));
108
- let facets = [];
109
- for (const id in body.facets) {
110
- const f = body.facets[id];
111
- const facetId = FacetIdentifierSchema.parse({
112
- key: id
113
- });
114
- const facet = this.parseFacet(facetId, f);
115
- facets.push(facet);
116
- }
117
- const selectedCategoryFacet = query.search.facets.find((x) => x.facet.key === "categories") || query.search.categoryFilter;
118
- let subCategoryFacet;
119
- if (selectedCategoryFacet) {
120
- const valueDepth = selectedCategoryFacet.key.split(" > ").length;
121
- subCategoryFacet = facets.find((f) => f.identifier.key === `hierarchy.lvl${valueDepth}`);
122
- } else {
123
- subCategoryFacet = facets.find((f) => f.identifier.key === "hierarchy.lvl0");
124
- }
125
- if (subCategoryFacet) {
126
- subCategoryFacet.identifier = FacetIdentifierSchema.parse({
127
- key: "categories"
128
- });
129
- subCategoryFacet.name = "Categories";
130
- for (const v of subCategoryFacet.values) {
131
- const pathParts = v.identifier.key.split(" > ");
132
- v.identifier.facet = subCategoryFacet.identifier;
133
- v.name = pathParts[pathParts.length - 1];
134
- }
135
- }
136
- facets = facets.filter((f) => !f.identifier.key.startsWith("hierarchy.lvl"));
137
- const result = {
138
- identifier: {
139
- term: query.search.term,
140
- facets: query.search.facets,
141
- filters: query.search.filters,
142
- paginationOptions: query.search.paginationOptions,
143
- index: body.index || "",
144
- key: body.queryID || ""
145
- },
146
- pageNumber: (body.page || 0) + 1,
147
- pageSize: body.hitsPerPage || 0,
148
- totalCount: body.nbHits || 0,
149
- totalPages: body.nbPages || 0,
150
- items,
151
- facets
152
- };
153
- return result;
154
- }
155
- parseFacet(facetIdentifier, facetValues) {
156
- const result = ProductSearchResultFacetSchema.parse({
157
- identifier: facetIdentifier,
158
- name: facetIdentifier.key,
159
- values: []
160
- });
161
- for (const vid in facetValues) {
162
- const fv = facetValues[vid];
163
- const facetValueIdentifier = FacetValueIdentifierSchema.parse({
164
- facet: facetIdentifier,
165
- key: vid
166
- });
167
- result.values.push(this.parseFacetValue(facetValueIdentifier, vid, fv));
168
- }
169
- return result;
170
- }
171
- parseFacetValue(facetValueIdentifier, label, count) {
172
- return ProductSearchResultFacetValueSchema.parse({
173
- identifier: facetValueIdentifier,
174
- name: label,
175
- count,
176
- active: false
177
- });
178
- }
179
83
  }
180
84
  __decorateClass([
181
85
  Reactionary({
@@ -1,8 +1,22 @@
1
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
+ provider: z.unknown().optional()
7
+ });
8
+ const ProviderCapabilitySchema = z.looseObject({
9
+ enabled: z.boolean(),
10
+ provider: z.unknown().optional()
11
+ });
2
12
  const AlgoliaCapabilitiesSchema = CapabilitiesSchema.pick({
3
13
  productSearch: true,
4
14
  analytics: true,
5
15
  productRecommendations: true
16
+ }).extend({
17
+ productSearch: ProductSearchCapabilitySchema.optional(),
18
+ analytics: ProviderCapabilitySchema.optional(),
19
+ productRecommendations: ProviderCapabilitySchema.optional()
6
20
  }).partial();
7
21
  export {
8
22
  AlgoliaCapabilitiesSchema
@@ -1,4 +1,5 @@
1
- import type { Cache, ClientFromCapabilities, RequestContext } from "@reactionary/core";
2
- import type { AlgoliaCapabilities } from "../schema/capabilities.schema.js";
3
- import type { AlgoliaConfiguration } from "../schema/configuration.schema.js";
4
- export declare function withAlgoliaCapabilities<T extends AlgoliaCapabilities>(configuration: AlgoliaConfiguration, capabilities: T): (cache: Cache, context: RequestContext) => ClientFromCapabilities<T>;
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 { AlgoliaAnalyticsProvider } from '../providers/analytics.provider.js';
5
+ import type { AlgoliaProductRecommendationsProvider } from '../providers/product-recommendations.provider.js';
6
+ import type { AlgoliaProductSearchProvider } from '../providers/product-search.provider.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 ExtractCapabilityProvider<TCapability, TDefaultProvider> = TCapability extends {
20
+ enabled: true;
21
+ provider?: infer TProviderFactory;
22
+ } ? TProviderFactory extends (...args: unknown[]) => infer TProvider ? TProvider : TDefaultProvider : TDefaultProvider;
23
+ type CapabilityOverride<TCapability, TKey extends string, TProvider> = TCapability extends {
24
+ enabled: true;
25
+ } ? {
26
+ [K in TKey]: TProvider;
27
+ } : Record<never, never>;
28
+ type ProductSearchFactoryFor<T extends AlgoliaCapabilities> = ExtractCapabilityFactory<T['productSearch'], ProductSearchFactory, AlgoliaProductSearchFactory>;
29
+ type ProductSearchProviderFor<T extends AlgoliaCapabilities> = ExtractCapabilityProvider<T['productSearch'], AlgoliaProductSearchProvider<ProductSearchFactoryFor<T>>>;
30
+ type AnalyticsProviderFor<T extends AlgoliaCapabilities> = ExtractCapabilityProvider<T['analytics'], AlgoliaAnalyticsProvider>;
31
+ type ProductRecommendationsProviderFor<T extends AlgoliaCapabilities> = ExtractCapabilityProvider<T['productRecommendations'], AlgoliaProductRecommendationsProvider>;
32
+ export type AlgoliaClientFromCapabilities<T extends AlgoliaCapabilities> = Omit<ClientFromCapabilities<NormalizeConfiguredCapabilities<T>>, 'productSearch' | 'analytics' | 'productRecommendations'> & CapabilityOverride<T['productSearch'], 'productSearch', ProductSearchProviderFor<T>> & CapabilityOverride<T['analytics'], 'analytics', AnalyticsProviderFor<T>> & CapabilityOverride<T['productRecommendations'], 'productRecommendations', ProductRecommendationsProviderFor<T>>;
33
+ export declare function resolveCapabilityProvider<TFactory, TProvider, TProviderArgs>(capability: {
34
+ factory?: TFactory;
35
+ provider?: (args: TProviderArgs) => TProvider;
36
+ } | undefined, defaults: {
37
+ factory: TFactory;
38
+ provider: (args: TProviderArgs) => TProvider;
39
+ }, buildProviderArgs: (factory: TFactory) => TProviderArgs): TProvider;
40
+ export declare function resolveProviderOnlyCapability<TProvider, TProviderArgs>(capability: {
41
+ provider?: (args: TProviderArgs) => TProvider;
42
+ } | undefined, defaultProvider: (args: TProviderArgs) => TProvider, args: TProviderArgs): TProvider;
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 CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './core/initialize.js';
2
- export * from './providers/product-search.provider.js';
3
- export * from './providers/product-recommendations.provider.js';
2
+ export * from './providers/index.js';
3
+ export * from './factories/index.js';
4
4
  export * from './schema/configuration.schema.js';
5
5
  export * from './schema/capabilities.schema.js';
@@ -1,10 +1,10 @@
1
- import { type Cache, type FacetIdentifier, type FacetValueIdentifier, ProductSearchProvider, type ProductSearchQueryByTerm, type ProductSearchQueryCreateNavigationFilter, type ProductSearchResult, type ProductSearchResultFacet, type ProductSearchResultFacetValue, type ProductSearchResultItemVariant, type RequestContext, type Result } from '@reactionary/core';
2
- import { type SearchResponse } from 'algoliasearch';
1
+ import { type Cache, type FacetValueIdentifier, ProductSearchProvider, type ProductSearchFactory, type ProductSearchFactoryOutput, type ProductSearchFactoryWithOutput, type ProductSearchQueryByTerm, type ProductSearchQueryCreateNavigationFilter, type RequestContext, type Result } from '@reactionary/core';
3
2
  import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
4
- import type { AlgoliaNativeRecord, AlgoliaNativeVariant } from '../schema/search.schema.js';
5
- export declare class AlgoliaProductSearchProvider extends ProductSearchProvider {
3
+ import type { AlgoliaProductSearchFactory } from '../factories/product-search/product-search.factory.js';
4
+ export declare class AlgoliaProductSearchProvider<TFactory extends ProductSearchFactory = AlgoliaProductSearchFactory> extends ProductSearchProvider<ProductSearchFactoryOutput<TFactory>> {
6
5
  protected config: AlgoliaConfiguration;
7
- constructor(cache: Cache, context: RequestContext, config: AlgoliaConfiguration);
6
+ protected factory: ProductSearchFactoryWithOutput<TFactory>;
7
+ constructor(cache: Cache, context: RequestContext, config: AlgoliaConfiguration, factory: ProductSearchFactoryWithOutput<TFactory>);
8
8
  protected queryByTermPayload(payload: ProductSearchQueryByTerm): {
9
9
  indexName: string;
10
10
  query: string;
@@ -16,48 +16,6 @@ export declare class AlgoliaProductSearchProvider extends ProductSearchProvider
16
16
  facetFilters: string[];
17
17
  filters: string;
18
18
  };
19
- queryByTerm(payload: ProductSearchQueryByTerm): Promise<Result<ProductSearchResult>>;
19
+ queryByTerm(payload: ProductSearchQueryByTerm): Promise<Result<ProductSearchFactoryOutput<TFactory>>>;
20
20
  createCategoryNavigationFilter(payload: ProductSearchQueryCreateNavigationFilter): Promise<Result<FacetValueIdentifier>>;
21
- protected parseSingle(body: AlgoliaNativeRecord): {
22
- identifier: {
23
- key: string;
24
- };
25
- name: string;
26
- slug: string;
27
- variants: ProductSearchResultItemVariant[];
28
- };
29
- protected parseVariant(variant: AlgoliaNativeVariant, product: AlgoliaNativeRecord): ProductSearchResultItemVariant;
30
- protected parsePaginatedResult(body: SearchResponse<AlgoliaNativeRecord>, query: ProductSearchQueryByTerm): {
31
- identifier: {
32
- term: string;
33
- facets: {
34
- facet: {
35
- key: string;
36
- };
37
- key: string;
38
- }[];
39
- filters: string[];
40
- paginationOptions: {
41
- pageNumber: number;
42
- pageSize: number;
43
- };
44
- index: string;
45
- key: string;
46
- };
47
- pageNumber: number;
48
- pageSize: number;
49
- totalCount: number;
50
- totalPages: number;
51
- items: {
52
- identifier: {
53
- key: string;
54
- };
55
- name: string;
56
- slug: string;
57
- variants: ProductSearchResultItemVariant[];
58
- }[];
59
- facets: ProductSearchResultFacet[];
60
- };
61
- protected parseFacet(facetIdentifier: FacetIdentifier, facetValues: Record<string, number>): ProductSearchResultFacet;
62
- protected parseFacetValue(facetValueIdentifier: FacetValueIdentifier, label: string, count: number): ProductSearchResultFacetValue;
63
21
  }
@@ -1,7 +1,44 @@
1
- import type * as z from 'zod';
1
+ import type { AnalyticsProvider, Cache, ProductRecommendationsProvider, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchProvider, RequestContext } from '@reactionary/core';
2
+ import type { AlgoliaConfiguration } from './configuration.schema.js';
3
+ import * as z from 'zod';
2
4
  export declare const AlgoliaCapabilitiesSchema: z.ZodObject<{
3
- analytics: z.ZodOptional<z.ZodBoolean>;
4
- productSearch: z.ZodOptional<z.ZodBoolean>;
5
- productRecommendations: z.ZodOptional<z.ZodBoolean>;
5
+ productSearch: z.ZodOptional<z.ZodOptional<z.ZodObject<{
6
+ enabled: z.ZodBoolean;
7
+ factory: z.ZodOptional<z.ZodUnknown>;
8
+ provider: z.ZodOptional<z.ZodUnknown>;
9
+ }, z.core.$loose>>>;
10
+ analytics: z.ZodOptional<z.ZodOptional<z.ZodObject<{
11
+ enabled: z.ZodBoolean;
12
+ provider: z.ZodOptional<z.ZodUnknown>;
13
+ }, z.core.$loose>>>;
14
+ productRecommendations: z.ZodOptional<z.ZodOptional<z.ZodObject<{
15
+ enabled: z.ZodBoolean;
16
+ provider: z.ZodOptional<z.ZodUnknown>;
17
+ }, z.core.$loose>>>;
6
18
  }, z.core.$loose>;
7
- export type AlgoliaCapabilities = z.infer<typeof AlgoliaCapabilitiesSchema>;
19
+ export interface AlgoliaProviderFactoryArgs {
20
+ cache: Cache;
21
+ context: RequestContext;
22
+ config: AlgoliaConfiguration;
23
+ }
24
+ export interface AlgoliaProductSearchProviderFactoryArgs<TFactory extends ProductSearchFactory = ProductSearchFactory> extends AlgoliaProviderFactoryArgs {
25
+ factory: ProductSearchFactoryWithOutput<TFactory>;
26
+ }
27
+ export interface AlgoliaProductSearchCapabilityConfig<TFactory extends ProductSearchFactory = ProductSearchFactory, TProvider extends ProductSearchProvider = ProductSearchProvider> {
28
+ enabled: boolean;
29
+ factory?: ProductSearchFactoryWithOutput<TFactory>;
30
+ provider?: (args: AlgoliaProductSearchProviderFactoryArgs<TFactory>) => TProvider;
31
+ }
32
+ export interface AlgoliaAnalyticsCapabilityConfig<TProvider extends AnalyticsProvider = AnalyticsProvider> {
33
+ enabled: boolean;
34
+ provider?: (args: AlgoliaProviderFactoryArgs) => TProvider;
35
+ }
36
+ export interface AlgoliaProductRecommendationsCapabilityConfig<TProvider extends ProductRecommendationsProvider = ProductRecommendationsProvider> {
37
+ enabled: boolean;
38
+ provider?: (args: AlgoliaProviderFactoryArgs) => TProvider;
39
+ }
40
+ export type AlgoliaCapabilities<TProductSearchFactory extends ProductSearchFactory = ProductSearchFactory, TProductSearchProvider extends ProductSearchProvider = ProductSearchProvider, TAnalyticsProvider extends AnalyticsProvider = AnalyticsProvider, TProductRecommendationsProvider extends ProductRecommendationsProvider = ProductRecommendationsProvider> = {
41
+ productSearch?: AlgoliaProductSearchCapabilityConfig<TProductSearchFactory, TProductSearchProvider>;
42
+ analytics?: AlgoliaAnalyticsCapabilityConfig<TAnalyticsProvider>;
43
+ productRecommendations?: AlgoliaProductRecommendationsCapabilityConfig<TProductRecommendationsProvider>;
44
+ };
@@ -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
+ });