@reactionary/meilisearch 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.
- package/README.md +11 -0
- package/capabilities/index.js +3 -0
- package/capabilities/order-search.capability.js +165 -0
- package/capabilities/product-recommendations.capability.js +79 -0
- package/capabilities/product-search.capability.js +203 -0
- package/core/initialize.js +78 -0
- package/core/initialize.types.js +13 -0
- package/factories/index.js +2 -0
- package/factories/order-search/order-search.factory.js +12 -0
- package/factories/product-search/product-search.factory.js +12 -0
- package/index.js +8 -0
- package/package.json +15 -0
- package/schema/capabilities.schema.js +23 -0
- package/schema/configuration.schema.js +11 -0
- package/schema/index.js +3 -0
- package/schema/search.schema.js +13 -0
- package/src/capabilities/index.d.ts +3 -0
- package/src/capabilities/order-search.capability.d.ts +37 -0
- package/src/capabilities/product-recommendations.capability.d.ts +37 -0
- package/src/capabilities/product-search.capability.d.ts +53 -0
- package/src/core/initialize.d.ts +5 -0
- package/src/core/initialize.types.d.ts +45 -0
- package/src/factories/index.d.ts +2 -0
- package/src/factories/order-search/order-search.factory.d.ts +7 -0
- package/src/factories/product-search/product-search.factory.d.ts +7 -0
- package/src/index.d.ts +8 -0
- package/src/schema/capabilities.schema.d.ts +49 -0
- package/src/schema/configuration.schema.d.ts +9 -0
- package/src/schema/index.d.ts +3 -0
- package/src/schema/search.schema.d.ts +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# meilisearch
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build meilisearch` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test meilisearch` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
OrderSearchQueryByTermSchema,
|
|
14
|
+
OrderSearchResultSchema,
|
|
15
|
+
OrderSearchCapability,
|
|
16
|
+
Reactionary,
|
|
17
|
+
success,
|
|
18
|
+
AddressIdentifierSchema
|
|
19
|
+
} from "@reactionary/core";
|
|
20
|
+
import { MeiliSearch } from "meilisearch";
|
|
21
|
+
class MeilisearchOrderSearchCapability extends OrderSearchCapability {
|
|
22
|
+
config;
|
|
23
|
+
factory;
|
|
24
|
+
constructor(config, cache, context, factory) {
|
|
25
|
+
super(cache, context);
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.factory = factory;
|
|
28
|
+
}
|
|
29
|
+
queryByTermPayload(payload) {
|
|
30
|
+
const filters = [];
|
|
31
|
+
if (payload.search.orderStatus && payload.search.orderStatus.length > 0) {
|
|
32
|
+
const statusFilters = payload.search.orderStatus.map((status) => `orderStatus = "${this.mapOrderStatus(status)}"`).join(" OR ");
|
|
33
|
+
filters.push(`(${statusFilters})`);
|
|
34
|
+
}
|
|
35
|
+
if (payload.search.user) {
|
|
36
|
+
filters.push(`userIdentifier = "${payload.search.user.userId}"`);
|
|
37
|
+
}
|
|
38
|
+
if (payload.search.startDate) {
|
|
39
|
+
filters.push(`orderDateTimestamp >= ${new Date(payload.search.startDate).getTime()}`);
|
|
40
|
+
}
|
|
41
|
+
if (payload.search.endDate) {
|
|
42
|
+
filters.push(`orderDateTimestamp <= ${new Date(payload.search.endDate).getTime()}`);
|
|
43
|
+
}
|
|
44
|
+
if (payload.search.partNumber && payload.search.partNumber.length > 0) {
|
|
45
|
+
const partNumberFilters = payload.search.partNumber.map((partNumber) => `items.sku = "${partNumber}"`).join(" OR ");
|
|
46
|
+
filters.push(`(${partNumberFilters})`);
|
|
47
|
+
}
|
|
48
|
+
const searchOptions = {
|
|
49
|
+
offset: (payload.search.paginationOptions.pageNumber - 1) * payload.search.paginationOptions.pageSize,
|
|
50
|
+
limit: payload.search.paginationOptions.pageSize,
|
|
51
|
+
filter: filters.length > 0 ? filters.join(" AND ") : void 0,
|
|
52
|
+
sort: ["orderDateTimestamp:desc"]
|
|
53
|
+
};
|
|
54
|
+
return searchOptions;
|
|
55
|
+
}
|
|
56
|
+
async queryByTerm(payload) {
|
|
57
|
+
const client = new MeiliSearch({
|
|
58
|
+
host: this.config.apiUrl,
|
|
59
|
+
apiKey: this.config.apiKey
|
|
60
|
+
});
|
|
61
|
+
const index = client.index(this.config.orderIndexName);
|
|
62
|
+
const remote = await index.search(
|
|
63
|
+
payload.search.term || "",
|
|
64
|
+
this.queryByTermPayload(payload)
|
|
65
|
+
);
|
|
66
|
+
const result = this.parsePaginatedResult(remote, payload);
|
|
67
|
+
return success(this.factory.parseOrderSearchResult(this.context, result, payload));
|
|
68
|
+
}
|
|
69
|
+
mapOrderStatus(status) {
|
|
70
|
+
const statusMap = {
|
|
71
|
+
AwaitingPayment: "awaiting_payment",
|
|
72
|
+
ReleasedToFulfillment: "released_to_fulfillment",
|
|
73
|
+
Shipped: "shipped",
|
|
74
|
+
Cancelled: "cancelled"
|
|
75
|
+
};
|
|
76
|
+
return statusMap[status] || status;
|
|
77
|
+
}
|
|
78
|
+
mapFromNativeOrderStatus(nativeStatus) {
|
|
79
|
+
const statusMap = {
|
|
80
|
+
awaiting_payment: "AwaitingPayment",
|
|
81
|
+
released_to_fulfillment: "ReleasedToFulfillment",
|
|
82
|
+
shipped: "Shipped",
|
|
83
|
+
cancelled: "Cancelled"
|
|
84
|
+
};
|
|
85
|
+
return statusMap[nativeStatus] || "AwaitingPayment";
|
|
86
|
+
}
|
|
87
|
+
mapFromNativeInventoryStatus(nativeStatus) {
|
|
88
|
+
const statusMap = {
|
|
89
|
+
not_allocated: "NotAllocated",
|
|
90
|
+
allocated: "Allocated",
|
|
91
|
+
preordered: "Preordered",
|
|
92
|
+
backordered: "Backordered"
|
|
93
|
+
};
|
|
94
|
+
return statusMap[nativeStatus] || "NotAllocated";
|
|
95
|
+
}
|
|
96
|
+
composeAddressFromNativeAddress(nativeAddress) {
|
|
97
|
+
return {
|
|
98
|
+
identifier: AddressIdentifierSchema.parse({
|
|
99
|
+
nickName: "shipping"
|
|
100
|
+
}),
|
|
101
|
+
firstName: "",
|
|
102
|
+
lastName: "",
|
|
103
|
+
streetAddress: nativeAddress.address1,
|
|
104
|
+
streetNumber: nativeAddress.address2,
|
|
105
|
+
city: nativeAddress.city,
|
|
106
|
+
postalCode: nativeAddress.postalCode,
|
|
107
|
+
countryCode: nativeAddress.country,
|
|
108
|
+
region: ""
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
parseSingle(body) {
|
|
112
|
+
const identifier = { key: body.orderIdentifier };
|
|
113
|
+
const userId = {
|
|
114
|
+
userId: body.userIdentifier
|
|
115
|
+
};
|
|
116
|
+
const customerName = body.customerName;
|
|
117
|
+
const shippingAddress = this.composeAddressFromNativeAddress(body.shippingAddress);
|
|
118
|
+
const orderDate = body.orderDate;
|
|
119
|
+
const orderStatus = this.mapFromNativeOrderStatus(body.orderStatus);
|
|
120
|
+
const inventoryStatus = this.mapFromNativeInventoryStatus(body.inventoryStatus);
|
|
121
|
+
const totalAmount = {
|
|
122
|
+
currency: body.currency || this.context.languageContext.currencyCode,
|
|
123
|
+
value: body.totalAmount
|
|
124
|
+
};
|
|
125
|
+
const order = {
|
|
126
|
+
identifier,
|
|
127
|
+
userId,
|
|
128
|
+
customerName,
|
|
129
|
+
shippingAddress,
|
|
130
|
+
orderDate,
|
|
131
|
+
orderStatus,
|
|
132
|
+
inventoryStatus,
|
|
133
|
+
totalAmount
|
|
134
|
+
};
|
|
135
|
+
return order;
|
|
136
|
+
}
|
|
137
|
+
parsePaginatedResult(body, query) {
|
|
138
|
+
const identifier = {
|
|
139
|
+
...query.search
|
|
140
|
+
};
|
|
141
|
+
const orders = body.hits.map((hit) => {
|
|
142
|
+
return this.parseSingle(hit);
|
|
143
|
+
});
|
|
144
|
+
const totalCount = body.estimatedTotalHits || body.hits.length;
|
|
145
|
+
const totalPages = Math.ceil(totalCount / (body.limit || 1));
|
|
146
|
+
const result = {
|
|
147
|
+
identifier,
|
|
148
|
+
pageNumber: Math.floor((body.offset || 0) / (body.limit || 1)) + 1,
|
|
149
|
+
pageSize: body.limit || orders.length,
|
|
150
|
+
totalCount,
|
|
151
|
+
totalPages,
|
|
152
|
+
items: orders
|
|
153
|
+
};
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
__decorateClass([
|
|
158
|
+
Reactionary({
|
|
159
|
+
inputSchema: OrderSearchQueryByTermSchema,
|
|
160
|
+
outputSchema: OrderSearchResultSchema
|
|
161
|
+
})
|
|
162
|
+
], MeilisearchOrderSearchCapability.prototype, "queryByTerm", 1);
|
|
163
|
+
export {
|
|
164
|
+
MeilisearchOrderSearchCapability
|
|
165
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProductRecommendationsCapability,
|
|
3
|
+
ImageSchema,
|
|
4
|
+
ProductSearchResultItemVariantSchema
|
|
5
|
+
} from "@reactionary/core";
|
|
6
|
+
import { MeiliSearch } from "meilisearch";
|
|
7
|
+
class MeilisearchProductRecommendationsCapability extends ProductRecommendationsCapability {
|
|
8
|
+
config;
|
|
9
|
+
constructor(config, cache, context) {
|
|
10
|
+
super(cache, context);
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
getSimilarProductsRecommendationsPayload(query) {
|
|
14
|
+
return {
|
|
15
|
+
id: query.sourceProduct.key,
|
|
16
|
+
limit: query.numberOfRecommendations,
|
|
17
|
+
embedder: this.config.useAIEmbedding
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get similar product recommendations
|
|
22
|
+
* Uses semantic search to find visually or data-wise similar products
|
|
23
|
+
*/
|
|
24
|
+
async getSimilarProductsRecommendations(query) {
|
|
25
|
+
const client = new MeiliSearch({
|
|
26
|
+
host: this.config.apiUrl,
|
|
27
|
+
apiKey: this.config.apiKey
|
|
28
|
+
});
|
|
29
|
+
const index = client.index(this.config.indexName);
|
|
30
|
+
if (!this.config.useAIEmbedding) {
|
|
31
|
+
console.warn("AI embedding is not enabled in configuration. Similar product recommendations will be based on keyword matching, which may not provide optimal results.");
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const response = await index.searchSimilarDocuments(this.getSimilarProductsRecommendationsPayload(query));
|
|
36
|
+
return this.parseRecommendations(response, "similar");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Error fetching similar product recommendations:", error);
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Maps Meilisearch search results to ProductRecommendation format
|
|
44
|
+
*/
|
|
45
|
+
parseRecommendations(recommendation, algorithm) {
|
|
46
|
+
return recommendation.hits.map((hit) => ({
|
|
47
|
+
recommendationIdentifier: {
|
|
48
|
+
key: hit.objectID,
|
|
49
|
+
algorithm
|
|
50
|
+
},
|
|
51
|
+
recommendationReturnType: "productSearchResultItem",
|
|
52
|
+
product: this.parseSearchResultItem(hit)
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
parseSearchResultItem(body) {
|
|
56
|
+
const product = {
|
|
57
|
+
identifier: { key: body.objectID },
|
|
58
|
+
name: body.name || body.objectID,
|
|
59
|
+
slug: body.slug || body.objectID,
|
|
60
|
+
variants: [...body.variants || []].map((variant) => this.parseVariant(variant, body))
|
|
61
|
+
};
|
|
62
|
+
return product;
|
|
63
|
+
}
|
|
64
|
+
parseVariant(variant, product) {
|
|
65
|
+
const result = ProductSearchResultItemVariantSchema.parse({
|
|
66
|
+
variant: {
|
|
67
|
+
sku: variant.sku
|
|
68
|
+
},
|
|
69
|
+
image: ImageSchema.parse({
|
|
70
|
+
sourceUrl: variant.image,
|
|
71
|
+
altText: product.name || ""
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
MeilisearchProductRecommendationsCapability
|
|
79
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
ImageSchema,
|
|
16
|
+
ProductSearchCapability,
|
|
17
|
+
ProductSearchQueryByTermSchema,
|
|
18
|
+
ProductSearchResultFacetSchema,
|
|
19
|
+
ProductSearchResultFacetValueSchema,
|
|
20
|
+
ProductSearchResultItemVariantSchema,
|
|
21
|
+
ProductSearchResultSchema,
|
|
22
|
+
Reactionary,
|
|
23
|
+
success
|
|
24
|
+
} from "@reactionary/core";
|
|
25
|
+
import { MeiliSearch } from "meilisearch";
|
|
26
|
+
class MeilisearchProductSearchCapability extends ProductSearchCapability {
|
|
27
|
+
config;
|
|
28
|
+
factory;
|
|
29
|
+
constructor(config, cache, context, factory) {
|
|
30
|
+
super(cache, context);
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.factory = factory;
|
|
33
|
+
}
|
|
34
|
+
queryByTermPayload(payload) {
|
|
35
|
+
const facetsThatAreNotCategory = payload.search.facets.filter((x) => x.facet.key !== "categories");
|
|
36
|
+
const categoryFacet = payload.search.facets.find((x) => x.facet.key === "categories") || payload.search.categoryFilter;
|
|
37
|
+
const finalFilters = [...payload.search.filters || []];
|
|
38
|
+
const finalFacetFilters = [
|
|
39
|
+
...facetsThatAreNotCategory.map(
|
|
40
|
+
(x) => `${x.facet.key}="${x.key}"`
|
|
41
|
+
)
|
|
42
|
+
];
|
|
43
|
+
if (categoryFacet) {
|
|
44
|
+
finalFilters.push(`categories = "${categoryFacet.key}"`);
|
|
45
|
+
}
|
|
46
|
+
let filterString;
|
|
47
|
+
if (finalFilters.length > 0 || finalFacetFilters.length > 0) {
|
|
48
|
+
const allFilters = [...finalFilters, ...finalFacetFilters];
|
|
49
|
+
filterString = allFilters.join(" AND ");
|
|
50
|
+
}
|
|
51
|
+
const searchOptions = {
|
|
52
|
+
offset: (payload.search.paginationOptions.pageNumber - 1) * payload.search.paginationOptions.pageSize,
|
|
53
|
+
limit: payload.search.paginationOptions.pageSize,
|
|
54
|
+
facets: ["*"],
|
|
55
|
+
filter: filterString
|
|
56
|
+
};
|
|
57
|
+
if (this.config.useAIEmbedding) {
|
|
58
|
+
searchOptions.hybrid = {
|
|
59
|
+
embedder: this.config.useAIEmbedding
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return searchOptions;
|
|
63
|
+
}
|
|
64
|
+
async queryByTerm(payload) {
|
|
65
|
+
const client = new MeiliSearch({
|
|
66
|
+
host: this.config.apiUrl,
|
|
67
|
+
apiKey: this.config.apiKey
|
|
68
|
+
});
|
|
69
|
+
const index = client.index(this.config.indexName);
|
|
70
|
+
const remote = await index.search(payload.search.term, this.queryByTermPayload(payload));
|
|
71
|
+
const result = this.parsePaginatedResult(remote, payload);
|
|
72
|
+
for (const selectedFacet of payload.search.facets) {
|
|
73
|
+
const facet = result.facets.find((f) => f.identifier.key === selectedFacet.facet.key);
|
|
74
|
+
if (facet) {
|
|
75
|
+
const value = facet.values.find((v) => v.identifier.key === selectedFacet.key);
|
|
76
|
+
if (value) {
|
|
77
|
+
value.active = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return success(this.factory.parseSearchResult(this.context, result, payload));
|
|
82
|
+
}
|
|
83
|
+
async createCategoryNavigationFilter(payload) {
|
|
84
|
+
const facetIdentifier = FacetIdentifierSchema.parse({
|
|
85
|
+
key: "categories"
|
|
86
|
+
});
|
|
87
|
+
const facetValueIdentifier = FacetValueIdentifierSchema.parse({
|
|
88
|
+
facet: facetIdentifier,
|
|
89
|
+
key: payload.categoryPath.map((c) => c.name).join(" > ")
|
|
90
|
+
});
|
|
91
|
+
return success(facetValueIdentifier);
|
|
92
|
+
}
|
|
93
|
+
parseSingle(body) {
|
|
94
|
+
const product = {
|
|
95
|
+
identifier: { key: body.objectID },
|
|
96
|
+
name: body.name || body.objectID,
|
|
97
|
+
slug: body.slug || body.objectID,
|
|
98
|
+
variants: [...body.variants || []].map((variant) => this.parseVariant(variant, body))
|
|
99
|
+
};
|
|
100
|
+
return product;
|
|
101
|
+
}
|
|
102
|
+
parseVariant(variant, product) {
|
|
103
|
+
const result = ProductSearchResultItemVariantSchema.parse({
|
|
104
|
+
variant: {
|
|
105
|
+
sku: variant.sku
|
|
106
|
+
},
|
|
107
|
+
image: ImageSchema.parse({
|
|
108
|
+
sourceUrl: variant.image,
|
|
109
|
+
altText: product.name || ""
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
parsePaginatedResult(body, query) {
|
|
115
|
+
const items = body.hits.map((hit) => this.parseSingle(hit));
|
|
116
|
+
let facets = [];
|
|
117
|
+
if (body.facetDistribution) {
|
|
118
|
+
for (const id in body.facetDistribution) {
|
|
119
|
+
const f = body.facetDistribution[id];
|
|
120
|
+
const facetId = FacetIdentifierSchema.parse({
|
|
121
|
+
key: id
|
|
122
|
+
});
|
|
123
|
+
const facet = this.parseFacet(facetId, f);
|
|
124
|
+
if (facet.values.length > 0) {
|
|
125
|
+
facets.push(facet);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const selectedCategoryFacet = query.search.facets.find((x) => x.facet.key === "categories") || query.search.categoryFilter;
|
|
130
|
+
let subCategoryFacet;
|
|
131
|
+
if (selectedCategoryFacet) {
|
|
132
|
+
const valueDepth = selectedCategoryFacet.key.split(" > ").length;
|
|
133
|
+
subCategoryFacet = facets.find((f) => f.identifier.key === `hierarchy.lvl${valueDepth}`);
|
|
134
|
+
} else {
|
|
135
|
+
subCategoryFacet = facets.find((f) => f.identifier.key === "hierarchy.lvl0");
|
|
136
|
+
}
|
|
137
|
+
if (subCategoryFacet) {
|
|
138
|
+
subCategoryFacet.identifier = FacetIdentifierSchema.parse({
|
|
139
|
+
key: "categories"
|
|
140
|
+
});
|
|
141
|
+
subCategoryFacet.name = "Categories";
|
|
142
|
+
for (const v of subCategoryFacet.values) {
|
|
143
|
+
const pathParts = v.identifier.key.split(" > ");
|
|
144
|
+
v.identifier.facet = subCategoryFacet.identifier;
|
|
145
|
+
v.name = pathParts[pathParts.length - 1];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
facets = facets.filter((f) => !f.identifier.key.startsWith("hierarchy.lvl"));
|
|
149
|
+
const totalPages = Math.ceil((body.estimatedTotalHits || 0) / query.search.paginationOptions.pageSize);
|
|
150
|
+
const result = {
|
|
151
|
+
identifier: {
|
|
152
|
+
term: query.search.term,
|
|
153
|
+
facets: query.search.facets,
|
|
154
|
+
filters: query.search.filters,
|
|
155
|
+
paginationOptions: query.search.paginationOptions
|
|
156
|
+
},
|
|
157
|
+
pageNumber: query.search.paginationOptions.pageNumber,
|
|
158
|
+
pageSize: query.search.paginationOptions.pageSize,
|
|
159
|
+
totalCount: body.estimatedTotalHits || 0,
|
|
160
|
+
totalPages,
|
|
161
|
+
items,
|
|
162
|
+
facets
|
|
163
|
+
};
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
parseFacet(facetIdentifier, facetValues) {
|
|
167
|
+
const result = ProductSearchResultFacetSchema.parse({
|
|
168
|
+
identifier: facetIdentifier,
|
|
169
|
+
name: facetIdentifier.key.replace(/_/g, " "),
|
|
170
|
+
values: []
|
|
171
|
+
});
|
|
172
|
+
for (const vid in facetValues) {
|
|
173
|
+
const fv = facetValues[vid];
|
|
174
|
+
const facetValueIdentifier = FacetValueIdentifierSchema.parse({
|
|
175
|
+
facet: facetIdentifier,
|
|
176
|
+
key: vid
|
|
177
|
+
});
|
|
178
|
+
result.values.push(this.parseFacetValue(facetValueIdentifier, vid, fv));
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
parseFacetValue(facetValueIdentifier, label, count) {
|
|
183
|
+
return ProductSearchResultFacetValueSchema.parse({
|
|
184
|
+
identifier: facetValueIdentifier,
|
|
185
|
+
name: label,
|
|
186
|
+
count,
|
|
187
|
+
active: false
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
__decorateClass([
|
|
192
|
+
Reactionary({
|
|
193
|
+
inputSchema: ProductSearchQueryByTermSchema,
|
|
194
|
+
outputSchema: ProductSearchResultSchema,
|
|
195
|
+
cache: true,
|
|
196
|
+
cacheTimeToLiveInSeconds: 300,
|
|
197
|
+
currencyDependentCaching: false,
|
|
198
|
+
localeDependentCaching: true
|
|
199
|
+
})
|
|
200
|
+
], MeilisearchProductSearchCapability.prototype, "queryByTerm", 1);
|
|
201
|
+
export {
|
|
202
|
+
MeilisearchProductSearchCapability
|
|
203
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { OrderSearchResultSchema, ProductSearchResultSchema } from "@reactionary/core";
|
|
2
|
+
import { MeilisearchProductSearchCapability } from "../capabilities/product-search.capability.js";
|
|
3
|
+
import { MeilisearchProductRecommendationsCapability } from "../capabilities/product-recommendations.capability.js";
|
|
4
|
+
import { MeilisearchOrderSearchCapability } from "../capabilities/order-search.capability.js";
|
|
5
|
+
import {
|
|
6
|
+
MeilisearchCapabilitiesSchema
|
|
7
|
+
} from "../schema/capabilities.schema.js";
|
|
8
|
+
import { MeilisearchOrderSearchFactory } from "../factories/order-search/order-search.factory.js";
|
|
9
|
+
import { MeilisearchProductSearchFactory } from "../factories/product-search/product-search.factory.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveCapabilityWithFactory,
|
|
12
|
+
resolveDirectCapability
|
|
13
|
+
} from "./initialize.types.js";
|
|
14
|
+
function withMeilisearchCapabilities(configuration, capabilities) {
|
|
15
|
+
return (cache, context) => {
|
|
16
|
+
const client = {};
|
|
17
|
+
const caps = MeilisearchCapabilitiesSchema.parse(capabilities);
|
|
18
|
+
if (caps.productSearch?.enabled) {
|
|
19
|
+
client.productSearch = resolveCapabilityWithFactory(
|
|
20
|
+
capabilities.productSearch,
|
|
21
|
+
{
|
|
22
|
+
factory: new MeilisearchProductSearchFactory(ProductSearchResultSchema),
|
|
23
|
+
capability: (args) => new MeilisearchProductSearchCapability(
|
|
24
|
+
args.config,
|
|
25
|
+
args.cache,
|
|
26
|
+
args.context,
|
|
27
|
+
args.factory
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
(factory) => ({
|
|
31
|
+
cache,
|
|
32
|
+
context,
|
|
33
|
+
config: configuration,
|
|
34
|
+
factory
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (caps.orderSearch?.enabled) {
|
|
39
|
+
client.orderSearch = resolveCapabilityWithFactory(
|
|
40
|
+
capabilities.orderSearch,
|
|
41
|
+
{
|
|
42
|
+
factory: new MeilisearchOrderSearchFactory(OrderSearchResultSchema),
|
|
43
|
+
capability: (args) => new MeilisearchOrderSearchCapability(
|
|
44
|
+
args.config,
|
|
45
|
+
args.cache,
|
|
46
|
+
args.context,
|
|
47
|
+
args.factory
|
|
48
|
+
)
|
|
49
|
+
},
|
|
50
|
+
(factory) => ({
|
|
51
|
+
cache,
|
|
52
|
+
context,
|
|
53
|
+
config: configuration,
|
|
54
|
+
factory
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (caps.productRecommendations?.enabled) {
|
|
59
|
+
client.productRecommendations = resolveDirectCapability(
|
|
60
|
+
capabilities.productRecommendations,
|
|
61
|
+
(args) => new MeilisearchProductRecommendationsCapability(
|
|
62
|
+
args.config,
|
|
63
|
+
args.cache,
|
|
64
|
+
args.context
|
|
65
|
+
),
|
|
66
|
+
{
|
|
67
|
+
cache,
|
|
68
|
+
context,
|
|
69
|
+
config: configuration
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return client;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
withMeilisearchCapabilities
|
|
78
|
+
};
|
|
@@ -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,12 @@
|
|
|
1
|
+
class MeilisearchOrderSearchFactory {
|
|
2
|
+
orderSearchResultSchema;
|
|
3
|
+
constructor(orderSearchResultSchema) {
|
|
4
|
+
this.orderSearchResultSchema = orderSearchResultSchema;
|
|
5
|
+
}
|
|
6
|
+
parseOrderSearchResult(_context, data, _query) {
|
|
7
|
+
return this.orderSearchResultSchema.parse(data);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
MeilisearchOrderSearchFactory
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class MeilisearchProductSearchFactory {
|
|
2
|
+
productSearchResultSchema;
|
|
3
|
+
constructor(productSearchResultSchema) {
|
|
4
|
+
this.productSearchResultSchema = productSearchResultSchema;
|
|
5
|
+
}
|
|
6
|
+
parseSearchResult(_context, data, _query) {
|
|
7
|
+
return this.productSearchResultSchema.parse(data);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
MeilisearchProductSearchFactory
|
|
12
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./core/initialize.js";
|
|
2
|
+
export * from "./core/initialize.types.js";
|
|
3
|
+
export * from "./factories/index.js";
|
|
4
|
+
export * from "./capabilities/product-search.capability.js";
|
|
5
|
+
export * from "./capabilities/product-recommendations.capability.js";
|
|
6
|
+
export * from "./capabilities/order-search.capability.js";
|
|
7
|
+
export * from "./schema/configuration.schema.js";
|
|
8
|
+
export * from "./schema/capabilities.schema.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reactionary/meilisearch",
|
|
3
|
+
"version": "0.6.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"vitest": "^4.0.9",
|
|
9
|
+
"@nx/vite": "22.4.5",
|
|
10
|
+
"@reactionary/core": "0.6.3",
|
|
11
|
+
"zod": "4.1.9",
|
|
12
|
+
"meilisearch": "^0.55.0"
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CapabilitiesSchema } from "@reactionary/core";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
const SearchCapabilitySchema = z.looseObject({
|
|
4
|
+
enabled: z.boolean(),
|
|
5
|
+
factory: z.unknown().optional(),
|
|
6
|
+
capability: z.unknown().optional()
|
|
7
|
+
});
|
|
8
|
+
const DirectCapabilitySchema = z.looseObject({
|
|
9
|
+
enabled: z.boolean(),
|
|
10
|
+
capability: z.unknown().optional()
|
|
11
|
+
});
|
|
12
|
+
const MeilisearchCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
13
|
+
productSearch: true,
|
|
14
|
+
productRecommendations: true,
|
|
15
|
+
orderSearch: true
|
|
16
|
+
}).extend({
|
|
17
|
+
productSearch: SearchCapabilitySchema.optional(),
|
|
18
|
+
orderSearch: SearchCapabilitySchema.optional(),
|
|
19
|
+
productRecommendations: DirectCapabilitySchema.optional()
|
|
20
|
+
}).partial();
|
|
21
|
+
export {
|
|
22
|
+
MeilisearchCapabilitiesSchema
|
|
23
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
const MeilisearchConfigurationSchema = z.looseObject({
|
|
3
|
+
apiUrl: z.string(),
|
|
4
|
+
apiKey: z.string(),
|
|
5
|
+
indexName: z.string(),
|
|
6
|
+
orderIndexName: z.string(),
|
|
7
|
+
useAIEmbedding: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
export {
|
|
10
|
+
MeilisearchConfigurationSchema
|
|
11
|
+
};
|
package/schema/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ProductSearchIdentifierSchema, ProductSearchResultSchema } from "@reactionary/core";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
const MeilisearchProductSearchIdentifierSchema = ProductSearchIdentifierSchema.extend({
|
|
4
|
+
key: z.string(),
|
|
5
|
+
index: z.string()
|
|
6
|
+
});
|
|
7
|
+
const MeilisearchProductSearchResultSchema = ProductSearchResultSchema.extend({
|
|
8
|
+
identifier: MeilisearchProductSearchIdentifierSchema.default(() => MeilisearchProductSearchIdentifierSchema.parse({}))
|
|
9
|
+
});
|
|
10
|
+
export {
|
|
11
|
+
MeilisearchProductSearchIdentifierSchema,
|
|
12
|
+
MeilisearchProductSearchResultSchema
|
|
13
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Cache, type OrderSearchQueryByTerm, type OrderSearchResult, type OrderSearchResultItem, OrderSearchCapability, type RequestContext, type Result, type OrderStatus, type Address, type OrderInventoryStatus, type OrderSearchFactory, type OrderSearchFactoryOutput, type OrderSearchFactoryWithOutput } from '@reactionary/core';
|
|
2
|
+
import { type SearchParams, type SearchResponse } from 'meilisearch';
|
|
3
|
+
import type { MeilisearchConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import type { MeilisearchOrderSearchFactory } from '../factories/order-search/order-search.factory.js';
|
|
5
|
+
interface MeilisearchNativeOrderAddress {
|
|
6
|
+
address1: string;
|
|
7
|
+
address2: string;
|
|
8
|
+
city: string;
|
|
9
|
+
postalCode: string;
|
|
10
|
+
country: string;
|
|
11
|
+
}
|
|
12
|
+
interface MeilisearchNativeOrderRecord {
|
|
13
|
+
orderIdentifier: string;
|
|
14
|
+
userIdentifier: string;
|
|
15
|
+
customerName: string;
|
|
16
|
+
shippingAddress: MeilisearchNativeOrderAddress;
|
|
17
|
+
orderDate: string;
|
|
18
|
+
orderDateTimestamp: number;
|
|
19
|
+
orderStatus: string;
|
|
20
|
+
inventoryStatus: string;
|
|
21
|
+
totalAmount: number;
|
|
22
|
+
currency: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class MeilisearchOrderSearchCapability<TFactory extends OrderSearchFactory = MeilisearchOrderSearchFactory> extends OrderSearchCapability<OrderSearchFactoryOutput<TFactory>> {
|
|
25
|
+
protected config: MeilisearchConfiguration;
|
|
26
|
+
protected factory: OrderSearchFactoryWithOutput<TFactory>;
|
|
27
|
+
constructor(config: MeilisearchConfiguration, cache: Cache, context: RequestContext, factory: OrderSearchFactoryWithOutput<TFactory>);
|
|
28
|
+
protected queryByTermPayload(payload: OrderSearchQueryByTerm): SearchParams;
|
|
29
|
+
queryByTerm(payload: OrderSearchQueryByTerm): Promise<Result<OrderSearchFactoryOutput<TFactory>>>;
|
|
30
|
+
protected mapOrderStatus(status: OrderStatus): string;
|
|
31
|
+
protected mapFromNativeOrderStatus(nativeStatus: string): OrderStatus;
|
|
32
|
+
protected mapFromNativeInventoryStatus(nativeStatus: string): OrderInventoryStatus;
|
|
33
|
+
protected composeAddressFromNativeAddress(nativeAddress: MeilisearchNativeOrderAddress): Address;
|
|
34
|
+
protected parseSingle(body: MeilisearchNativeOrderRecord): OrderSearchResultItem;
|
|
35
|
+
protected parsePaginatedResult(body: SearchResponse<MeilisearchNativeOrderRecord>, query: OrderSearchQueryByTerm): OrderSearchResult;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Cache, ProductRecommendationsCapability, type ProductRecommendation, type ProductRecommendationAlgorithmSimilarProductsQuery, type RequestContext, type ProductSearchResultItemVariant } from '@reactionary/core';
|
|
2
|
+
import { type SearchResponse, type SearchSimilarDocumentsParams } from 'meilisearch';
|
|
3
|
+
import type { MeilisearchConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import type { MeilisearchNativeRecord, MeilisearchNativeVariant } from '../schema/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* MeilisearchProductRecommendationsCapability
|
|
7
|
+
*
|
|
8
|
+
* Provides product recommendations using Meilisearch's hybrid search and filtering capabilities.
|
|
9
|
+
* Supports frequentlyBoughtTogether, similar, related, and trendingInCategory algorithms.
|
|
10
|
+
*
|
|
11
|
+
* Note: This implementation uses semantic search (if AI embedding is enabled) and facet-based filtering.
|
|
12
|
+
* For production use, consider implementing more sophisticated recommendation logic or integrating
|
|
13
|
+
* with a dedicated recommendation engine.
|
|
14
|
+
*/
|
|
15
|
+
export declare class MeilisearchProductRecommendationsCapability extends ProductRecommendationsCapability {
|
|
16
|
+
protected config: MeilisearchConfiguration;
|
|
17
|
+
constructor(config: MeilisearchConfiguration, cache: Cache, context: RequestContext);
|
|
18
|
+
protected getSimilarProductsRecommendationsPayload(query: ProductRecommendationAlgorithmSimilarProductsQuery): SearchSimilarDocumentsParams;
|
|
19
|
+
/**
|
|
20
|
+
* Get similar product recommendations
|
|
21
|
+
* Uses semantic search to find visually or data-wise similar products
|
|
22
|
+
*/
|
|
23
|
+
protected getSimilarProductsRecommendations(query: ProductRecommendationAlgorithmSimilarProductsQuery): Promise<ProductRecommendation[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Maps Meilisearch search results to ProductRecommendation format
|
|
26
|
+
*/
|
|
27
|
+
protected parseRecommendations(recommendation: SearchResponse<MeilisearchNativeRecord>, algorithm: string): ProductRecommendation[];
|
|
28
|
+
protected parseSearchResultItem(body: MeilisearchNativeRecord): {
|
|
29
|
+
identifier: {
|
|
30
|
+
key: string;
|
|
31
|
+
};
|
|
32
|
+
name: string;
|
|
33
|
+
slug: string;
|
|
34
|
+
variants: ProductSearchResultItemVariant[];
|
|
35
|
+
};
|
|
36
|
+
protected parseVariant(variant: MeilisearchNativeVariant, product: MeilisearchNativeRecord): ProductSearchResultItemVariant;
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Cache, type FacetIdentifier, type FacetValueIdentifier, ProductSearchCapability, type ProductSearchFactory, type ProductSearchFactoryOutput, type ProductSearchFactoryWithOutput, type ProductSearchQueryByTerm, type ProductSearchQueryCreateNavigationFilter, type ProductSearchResultFacet, type ProductSearchResultFacetValue, type ProductSearchResultItemVariant, type RequestContext, type Result } from '@reactionary/core';
|
|
2
|
+
import { type SearchParams, type SearchResponse } from 'meilisearch';
|
|
3
|
+
import type { MeilisearchConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import type { MeilisearchNativeRecord, MeilisearchNativeVariant } from '../schema/search.schema.js';
|
|
5
|
+
import type { MeilisearchProductSearchFactory } from '../factories/product-search/product-search.factory.js';
|
|
6
|
+
export declare class MeilisearchProductSearchCapability<TFactory extends ProductSearchFactory = MeilisearchProductSearchFactory> extends ProductSearchCapability<ProductSearchFactoryOutput<TFactory>> {
|
|
7
|
+
protected config: MeilisearchConfiguration;
|
|
8
|
+
protected factory: ProductSearchFactoryWithOutput<TFactory>;
|
|
9
|
+
constructor(config: MeilisearchConfiguration, cache: Cache, context: RequestContext, factory: ProductSearchFactoryWithOutput<TFactory>);
|
|
10
|
+
protected queryByTermPayload(payload: ProductSearchQueryByTerm): SearchParams;
|
|
11
|
+
queryByTerm(payload: ProductSearchQueryByTerm): Promise<Result<ProductSearchFactoryOutput<TFactory>>>;
|
|
12
|
+
createCategoryNavigationFilter(payload: ProductSearchQueryCreateNavigationFilter): Promise<Result<FacetValueIdentifier>>;
|
|
13
|
+
protected parseSingle(body: MeilisearchNativeRecord): {
|
|
14
|
+
identifier: {
|
|
15
|
+
key: string;
|
|
16
|
+
};
|
|
17
|
+
name: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
variants: ProductSearchResultItemVariant[];
|
|
20
|
+
};
|
|
21
|
+
protected parseVariant(variant: MeilisearchNativeVariant, product: MeilisearchNativeRecord): ProductSearchResultItemVariant;
|
|
22
|
+
protected parsePaginatedResult(body: SearchResponse<MeilisearchNativeRecord>, query: ProductSearchQueryByTerm): {
|
|
23
|
+
identifier: {
|
|
24
|
+
term: string;
|
|
25
|
+
facets: {
|
|
26
|
+
facet: {
|
|
27
|
+
key: string;
|
|
28
|
+
};
|
|
29
|
+
key: string;
|
|
30
|
+
}[];
|
|
31
|
+
filters: string[];
|
|
32
|
+
paginationOptions: {
|
|
33
|
+
pageNumber: number;
|
|
34
|
+
pageSize: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
pageNumber: number;
|
|
38
|
+
pageSize: number;
|
|
39
|
+
totalCount: number;
|
|
40
|
+
totalPages: number;
|
|
41
|
+
items: {
|
|
42
|
+
identifier: {
|
|
43
|
+
key: string;
|
|
44
|
+
};
|
|
45
|
+
name: string;
|
|
46
|
+
slug: string;
|
|
47
|
+
variants: ProductSearchResultItemVariant[];
|
|
48
|
+
}[];
|
|
49
|
+
facets: ProductSearchResultFacet[];
|
|
50
|
+
};
|
|
51
|
+
protected parseFacet(facetIdentifier: FacetIdentifier, facetValues: Record<string, number>): ProductSearchResultFacet;
|
|
52
|
+
protected parseFacetValue(facetValueIdentifier: FacetValueIdentifier, label: string, count: number): ProductSearchResultFacetValue;
|
|
53
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Cache, RequestContext } from '@reactionary/core';
|
|
2
|
+
import { type MeilisearchCapabilities } from '../schema/capabilities.schema.js';
|
|
3
|
+
import type { MeilisearchConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import { type MeilisearchClientFromCapabilities } from './initialize.types.js';
|
|
5
|
+
export declare function withMeilisearchCapabilities<T extends MeilisearchCapabilities>(configuration: MeilisearchConfiguration, capabilities: T): (cache: Cache, context: RequestContext) => MeilisearchClientFromCapabilities<T>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ClientFromCapabilities, OrderSearchFactory, ProductSearchFactory } from '@reactionary/core';
|
|
2
|
+
import type { MeilisearchCapabilities } from '../schema/capabilities.schema.js';
|
|
3
|
+
import type { MeilisearchOrderSearchFactory } from '../factories/order-search/order-search.factory.js';
|
|
4
|
+
import type { MeilisearchProductSearchFactory } from '../factories/product-search/product-search.factory.js';
|
|
5
|
+
import type { MeilisearchOrderSearchCapability } from '../capabilities/order-search.capability.js';
|
|
6
|
+
import type { MeilisearchProductRecommendationsCapability } from '../capabilities/product-recommendations.capability.js';
|
|
7
|
+
import type { MeilisearchProductSearchCapability } from '../capabilities/product-search.capability.js';
|
|
8
|
+
type EnabledCapability<TCapability> = TCapability extends {
|
|
9
|
+
enabled: true;
|
|
10
|
+
} ? true : false;
|
|
11
|
+
type NormalizeConfiguredCapabilities<T extends MeilisearchCapabilities> = Omit<T, 'productSearch' | 'orderSearch' | 'productRecommendations'> & {
|
|
12
|
+
productSearch?: EnabledCapability<T['productSearch']>;
|
|
13
|
+
orderSearch?: EnabledCapability<T['orderSearch']>;
|
|
14
|
+
productRecommendations?: EnabledCapability<T['productRecommendations']>;
|
|
15
|
+
};
|
|
16
|
+
type ExtractCapabilityFactory<TCapability, TContract, TDefaultFactory> = TCapability extends {
|
|
17
|
+
enabled: true;
|
|
18
|
+
factory?: infer TFactory;
|
|
19
|
+
} ? TFactory extends TContract ? TFactory : TDefaultFactory : TDefaultFactory;
|
|
20
|
+
type ExtractCapabilityImplementation<TCapability, TDefaultCapability> = TCapability extends {
|
|
21
|
+
enabled: true;
|
|
22
|
+
capability?: infer TCapabilityFactory;
|
|
23
|
+
} ? TCapabilityFactory extends (...args: unknown[]) => infer TResolvedCapability ? TResolvedCapability : TDefaultCapability : TDefaultCapability;
|
|
24
|
+
type CapabilityOverride<TCapability, TKey extends string, TResolvedCapability> = TCapability extends {
|
|
25
|
+
enabled: true;
|
|
26
|
+
} ? {
|
|
27
|
+
[K in TKey]: TResolvedCapability;
|
|
28
|
+
} : Record<never, never>;
|
|
29
|
+
type ProductSearchFactoryFor<T extends MeilisearchCapabilities> = ExtractCapabilityFactory<T['productSearch'], ProductSearchFactory, MeilisearchProductSearchFactory>;
|
|
30
|
+
type OrderSearchFactoryFor<T extends MeilisearchCapabilities> = ExtractCapabilityFactory<T['orderSearch'], OrderSearchFactory, MeilisearchOrderSearchFactory>;
|
|
31
|
+
type ProductSearchCapabilityFor<T extends MeilisearchCapabilities> = ExtractCapabilityImplementation<T['productSearch'], MeilisearchProductSearchCapability<ProductSearchFactoryFor<T>>>;
|
|
32
|
+
type OrderSearchCapabilityFor<T extends MeilisearchCapabilities> = ExtractCapabilityImplementation<T['orderSearch'], MeilisearchOrderSearchCapability<OrderSearchFactoryFor<T>>>;
|
|
33
|
+
type ProductRecommendationsCapabilityFor<T extends MeilisearchCapabilities> = ExtractCapabilityImplementation<T['productRecommendations'], MeilisearchProductRecommendationsCapability>;
|
|
34
|
+
export type MeilisearchClientFromCapabilities<T extends MeilisearchCapabilities> = Omit<ClientFromCapabilities<NormalizeConfiguredCapabilities<T>>, 'productSearch' | 'orderSearch' | 'productRecommendations'> & CapabilityOverride<T['productSearch'], 'productSearch', ProductSearchCapabilityFor<T>> & CapabilityOverride<T['orderSearch'], 'orderSearch', OrderSearchCapabilityFor<T>> & CapabilityOverride<T['productRecommendations'], 'productRecommendations', ProductRecommendationsCapabilityFor<T>>;
|
|
35
|
+
export declare function resolveCapabilityWithFactory<TFactory, TResolvedCapability, TCapabilityArgs>(capability: {
|
|
36
|
+
factory?: TFactory;
|
|
37
|
+
capability?: (args: TCapabilityArgs) => TResolvedCapability;
|
|
38
|
+
} | undefined, defaults: {
|
|
39
|
+
factory: TFactory;
|
|
40
|
+
capability: (args: TCapabilityArgs) => TResolvedCapability;
|
|
41
|
+
}, buildCapabilityArgs: (factory: TFactory) => TCapabilityArgs): TResolvedCapability;
|
|
42
|
+
export declare function resolveDirectCapability<TResolvedCapability, TCapabilityArgs>(capability: {
|
|
43
|
+
capability?: (args: TCapabilityArgs) => TResolvedCapability;
|
|
44
|
+
} | undefined, defaultCapability: (args: TCapabilityArgs) => TResolvedCapability, args: TCapabilityArgs): TResolvedCapability;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AnyOrderSearchResultSchema, OrderSearchFactory, OrderSearchQueryByTerm, OrderSearchResultSchema, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
|
+
export declare class MeilisearchOrderSearchFactory<TOrderSearchResultSchema extends AnyOrderSearchResultSchema = typeof OrderSearchResultSchema> implements OrderSearchFactory<TOrderSearchResultSchema> {
|
|
4
|
+
readonly orderSearchResultSchema: TOrderSearchResultSchema;
|
|
5
|
+
constructor(orderSearchResultSchema: TOrderSearchResultSchema);
|
|
6
|
+
parseOrderSearchResult(_context: RequestContext, data: unknown, _query: OrderSearchQueryByTerm): z.output<TOrderSearchResultSchema>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AnyProductSearchResultSchema, ProductSearchFactory, ProductSearchQueryByTerm, ProductSearchResultSchema, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
|
+
export declare class MeilisearchProductSearchFactory<TProductSearchResultSchema extends AnyProductSearchResultSchema = typeof ProductSearchResultSchema> implements ProductSearchFactory<TProductSearchResultSchema> {
|
|
4
|
+
readonly productSearchResultSchema: TProductSearchResultSchema;
|
|
5
|
+
constructor(productSearchResultSchema: TProductSearchResultSchema);
|
|
6
|
+
parseSearchResult(_context: RequestContext, data: unknown, _query: ProductSearchQueryByTerm): z.output<TProductSearchResultSchema>;
|
|
7
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './core/initialize.js';
|
|
2
|
+
export * from './core/initialize.types.js';
|
|
3
|
+
export * from './factories/index.js';
|
|
4
|
+
export * from './capabilities/product-search.capability.js';
|
|
5
|
+
export * from './capabilities/product-recommendations.capability.js';
|
|
6
|
+
export * from './capabilities/order-search.capability.js';
|
|
7
|
+
export * from './schema/configuration.schema.js';
|
|
8
|
+
export * from './schema/capabilities.schema.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Cache, OrderSearchFactory, OrderSearchFactoryWithOutput, OrderSearchCapability, ProductRecommendationsCapability, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type { MeilisearchConfiguration } from './configuration.schema.js';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
export declare const MeilisearchCapabilitiesSchema: 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
|
+
orderSearch: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
11
|
+
enabled: z.ZodBoolean;
|
|
12
|
+
factory: z.ZodOptional<z.ZodUnknown>;
|
|
13
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
14
|
+
}, z.core.$loose>>>;
|
|
15
|
+
productRecommendations: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
16
|
+
enabled: z.ZodBoolean;
|
|
17
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
18
|
+
}, z.core.$loose>>>;
|
|
19
|
+
}, z.core.$loose>;
|
|
20
|
+
export interface MeilisearchCapabilityFactoryArgs {
|
|
21
|
+
cache: Cache;
|
|
22
|
+
context: RequestContext;
|
|
23
|
+
config: MeilisearchConfiguration;
|
|
24
|
+
}
|
|
25
|
+
export interface MeilisearchProductSearchCapabilityFactoryArgs<TFactory extends ProductSearchFactory = ProductSearchFactory> extends MeilisearchCapabilityFactoryArgs {
|
|
26
|
+
factory: ProductSearchFactoryWithOutput<TFactory>;
|
|
27
|
+
}
|
|
28
|
+
export interface MeilisearchOrderSearchCapabilityFactoryArgs<TFactory extends OrderSearchFactory = OrderSearchFactory> extends MeilisearchCapabilityFactoryArgs {
|
|
29
|
+
factory: OrderSearchFactoryWithOutput<TFactory>;
|
|
30
|
+
}
|
|
31
|
+
export interface MeilisearchProductSearchCapabilityConfig<TFactory extends ProductSearchFactory = ProductSearchFactory, TCapability extends ProductSearchCapability = ProductSearchCapability> {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
factory?: ProductSearchFactoryWithOutput<TFactory>;
|
|
34
|
+
capability?: (args: MeilisearchProductSearchCapabilityFactoryArgs<TFactory>) => TCapability;
|
|
35
|
+
}
|
|
36
|
+
export interface MeilisearchOrderSearchCapabilityConfig<TFactory extends OrderSearchFactory = OrderSearchFactory, TCapability extends OrderSearchCapability = OrderSearchCapability> {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
factory?: OrderSearchFactoryWithOutput<TFactory>;
|
|
39
|
+
capability?: (args: MeilisearchOrderSearchCapabilityFactoryArgs<TFactory>) => TCapability;
|
|
40
|
+
}
|
|
41
|
+
export interface MeilisearchProductRecommendationsCapabilityConfig<TCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability> {
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
capability?: (args: MeilisearchCapabilityFactoryArgs) => TCapability;
|
|
44
|
+
}
|
|
45
|
+
export type MeilisearchCapabilities<TProductSearchFactory extends ProductSearchFactory = ProductSearchFactory, TProductSearchCapability extends ProductSearchCapability = ProductSearchCapability, TOrderSearchFactory extends OrderSearchFactory = OrderSearchFactory, TOrderSearchCapability extends OrderSearchCapability = OrderSearchCapability, TProductRecommendationsCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability> = {
|
|
46
|
+
productSearch?: MeilisearchProductSearchCapabilityConfig<TProductSearchFactory, TProductSearchCapability>;
|
|
47
|
+
orderSearch?: MeilisearchOrderSearchCapabilityConfig<TOrderSearchFactory, TOrderSearchCapability>;
|
|
48
|
+
productRecommendations?: MeilisearchProductRecommendationsCapabilityConfig<TProductRecommendationsCapability>;
|
|
49
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
export declare const MeilisearchConfigurationSchema: z.ZodObject<{
|
|
3
|
+
apiUrl: z.ZodString;
|
|
4
|
+
apiKey: z.ZodString;
|
|
5
|
+
indexName: z.ZodString;
|
|
6
|
+
orderIndexName: z.ZodString;
|
|
7
|
+
useAIEmbedding: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$loose>;
|
|
9
|
+
export type MeilisearchConfiguration = z.infer<typeof MeilisearchConfigurationSchema>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
export declare const MeilisearchProductSearchIdentifierSchema: 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 MeilisearchProductSearchResultSchema: 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 MeilisearchProductSearchResult = z.infer<typeof MeilisearchProductSearchResultSchema>;
|
|
103
|
+
export type MeilisearchProductSearchIdentifier = z.infer<typeof MeilisearchProductSearchIdentifierSchema>;
|
|
104
|
+
export interface MeilisearchNativeVariant {
|
|
105
|
+
sku: string;
|
|
106
|
+
image: string;
|
|
107
|
+
}
|
|
108
|
+
export interface MeilisearchNativeRecord {
|
|
109
|
+
objectID: string;
|
|
110
|
+
slug?: string;
|
|
111
|
+
name?: string;
|
|
112
|
+
variants: Array<MeilisearchNativeVariant>;
|
|
113
|
+
}
|