@reactionary/provider-algolia 0.3.18 → 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.
- package/core/initialize.js +52 -6
- package/core/initialize.types.js +13 -0
- package/factories/index.js +1 -0
- package/factories/product-search/product-search.factory.js +123 -0
- package/index.js +2 -2
- package/package.json +2 -2
- package/providers/analytics.provider.js +4 -4
- package/providers/product-search.provider.js +18 -114
- package/schema/capabilities.schema.js +14 -0
- package/src/core/initialize.d.ts +5 -4
- package/src/core/initialize.types.d.ts +43 -0
- package/src/factories/index.d.ts +1 -0
- package/src/factories/product-search/product-search.factory.d.ts +16 -0
- package/src/index.d.ts +2 -2
- package/src/providers/product-search.provider.d.ts +6 -48
- package/src/schema/capabilities.schema.d.ts +42 -5
- package/src/test/client-builder-product-search-extension.example.d.ts +1 -0
- package/test/client-builder-product-search-extension.example.js +61 -0
package/core/initialize.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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/
|
|
3
|
-
export * from "./
|
|
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.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@reactionary/core": "0.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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.
|
|
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(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
package/src/core/initialize.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Cache,
|
|
2
|
-
import type { AlgoliaCapabilities } from
|
|
3
|
-
import type { AlgoliaConfiguration } from
|
|
4
|
-
|
|
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/
|
|
3
|
-
export * from './
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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<
|
|
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
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|