Core interfaces for Medusa e-commerce framework service implementations
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Interface for search service implementations providing full-text search capabilities including index management, document operations, and search queries across different search engines.
Deprecation Notice: Use AbstractSearchService from @medusajs/utils instead.
Type checking and identification methods for search services.
/**
* Static property identifying this as a search service
*/
static _isSearchService: boolean;
/**
* Checks if an object is a search service
* @param {object} obj - Object to check
* @returns {boolean} True if obj is a search service
*/
static isSearchService(obj: object): boolean;Access to service configuration options.
/**
* Getter for service options configuration
* @returns {object} Service options or empty object
*/
get options(): object;Methods for creating, retrieving, and configuring search indexes.
/**
* Creates a search index with optional configuration
* @param {string} indexName - The name of the index to create
* @param {object} options - Optional index configuration
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
createIndex(indexName: string, options?: object): Promise<object>;
/**
* Retrieves information about an existing index
* @param {string} indexName - The name of the index to retrieve
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
getIndex(indexName: string): Promise<object>;
/**
* Updates the settings of an existing index
* @param {string} indexName - The name of the index to update
* @param {object} settings - Settings object for index configuration
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
updateSettings(indexName: string, settings: object): Promise<object>;Methods for adding, replacing, and deleting documents in search indexes.
/**
* Adds documents to a search index
* @param {string} indexName - The name of the index
* @param {array} documents - Array of document objects to be indexed
* @param {string} type - Type of documents (e.g: products, regions, orders)
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
addDocuments(indexName: string, documents: array, type: string): Promise<object>;
/**
* Replaces documents in a search index
* @param {string} indexName - The name of the index
* @param {array} documents - Array of document objects to replace existing documents
* @param {string} type - Type of documents to be replaced
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
replaceDocuments(indexName: string, documents: array, type: string): Promise<object>;
/**
* Deletes a single document from the index
* @param {string} indexName - The name of the index
* @param {string} document_id - The ID of the document to delete
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
deleteDocument(indexName: string, document_id: string): Promise<object>;
/**
* Deletes all documents from an index
* @param {string} indexName - The name of the index
* @returns {Promise<object>} Response from search engine provider
* @throws {Error} If not overridden by child class
*/
deleteAllDocuments(indexName: string): Promise<object>;Methods for performing search queries on indexed documents.
/**
* Searches for documents in an index
* @param {string} indexName - The name of the index to search
* @param {string} query - The search query string
* @param {object} options - Search options including pagination, filters, and provider-specific options
* @returns {Promise<object>} Search results with hits array and metadata
* @throws {Error} If not overridden by child class
*/
search(indexName: string, query: string, options?: SearchOptions): Promise<SearchResult>;/**
* Search options configuration
*/
interface SearchOptions {
paginationOptions?: {
limit: number;
offset: number;
};
filter?: any;
additionalOptions?: any;
}
/**
* Search result structure
*/
interface SearchResult {
hits: any[];
[key: string]: any;
}import { SearchService } from "medusa-interfaces";
// Elasticsearch implementation
class ElasticsearchService extends SearchService {
constructor(options) {
super();
this.client = options.client;
this.options_ = options;
}
async createIndex(indexName, options = {}) {
const indexConfig = {
index: indexName,
body: {
settings: {
number_of_shards: options.shards || 1,
number_of_replicas: options.replicas || 0,
...options.settings
},
mappings: options.mappings || {}
}
};
try {
const response = await this.client.indices.create(indexConfig);
return {
acknowledged: response.acknowledged,
index: indexName,
shards_acknowledged: response.shards_acknowledged
};
} catch (error) {
throw new Error(`Failed to create index: ${error.message}`);
}
}
async getIndex(indexName) {
try {
const response = await this.client.indices.get({ index: indexName });
return response[indexName];
} catch (error) {
throw new Error(`Failed to get index: ${error.message}`);
}
}
async updateSettings(indexName, settings) {
try {
const response = await this.client.indices.putSettings({
index: indexName,
body: { settings }
});
return { acknowledged: response.acknowledged };
} catch (error) {
throw new Error(`Failed to update settings: ${error.message}`);
}
}
async addDocuments(indexName, documents, type) {
const body = documents.flatMap(doc => [
{ index: { _index: indexName, _type: type, _id: doc.id } },
doc
]);
try {
const response = await this.client.bulk({ body });
return {
took: response.took,
errors: response.errors,
items: response.items
};
} catch (error) {
throw new Error(`Failed to add documents: ${error.message}`);
}
}
async replaceDocuments(indexName, documents, type) {
// Delete all documents of the type first
await this.client.deleteByQuery({
index: indexName,
body: {
query: { term: { _type: type } }
}
});
// Add new documents
return await this.addDocuments(indexName, documents, type);
}
async deleteDocument(indexName, document_id) {
try {
const response = await this.client.delete({
index: indexName,
id: document_id
});
return {
result: response.result,
version: response._version
};
} catch (error) {
throw new Error(`Failed to delete document: ${error.message}`);
}
}
async deleteAllDocuments(indexName) {
try {
const response = await this.client.deleteByQuery({
index: indexName,
body: { query: { match_all: {} } }
});
return {
deleted: response.deleted,
took: response.took
};
} catch (error) {
throw new Error(`Failed to delete all documents: ${error.message}`);
}
}
async search(indexName, query, options = {}) {
const searchBody = {
query: {
multi_match: {
query: query,
fields: ["*"]
}
}
};
// Add filters if provided
if (options.filter) {
searchBody.query = {
bool: {
must: searchBody.query,
filter: options.filter
}
};
}
const searchParams = {
index: indexName,
body: searchBody,
from: options.paginationOptions?.offset || 0,
size: options.paginationOptions?.limit || 20
};
// Add any additional provider-specific options
if (options.additionalOptions) {
Object.assign(searchParams, options.additionalOptions);
}
try {
const response = await this.client.search(searchParams);
return {
hits: response.hits.hits.map(hit => ({
id: hit._id,
score: hit._score,
source: hit._source
})),
total: response.hits.total.value,
took: response.took,
max_score: response.hits.max_score
};
} catch (error) {
throw new Error(`Search failed: ${error.message}`);
}
}
}
// Algolia implementation example
class AlgoliaSearchService extends SearchService {
constructor(options) {
super();
this.client = options.client;
this.options_ = options;
}
async createIndex(indexName, options = {}) {
const index = this.client.initIndex(indexName);
if (options.settings) {
await index.setSettings(options.settings);
}
return {
acknowledged: true,
index: indexName
};
}
async getIndex(indexName) {
const index = this.client.initIndex(indexName);
const settings = await index.getSettings();
return { settings };
}
async updateSettings(indexName, settings) {
const index = this.client.initIndex(indexName);
await index.setSettings(settings);
return { acknowledged: true };
}
async addDocuments(indexName, documents, type) {
const index = this.client.initIndex(indexName);
// Add type field to documents
const typedDocuments = documents.map(doc => ({ ...doc, _type: type }));
const response = await index.saveObjects(typedDocuments);
return {
objectIDs: response.objectIDs,
taskID: response.taskID
};
}
async replaceDocuments(indexName, documents, type) {
const index = this.client.initIndex(indexName);
// Clear existing documents of this type
await index.deleteBy({ filters: `_type:${type}` });
// Add new documents
return await this.addDocuments(indexName, documents, type);
}
async deleteDocument(indexName, document_id) {
const index = this.client.initIndex(indexName);
const response = await index.deleteObject(document_id);
return { taskID: response.taskID };
}
async deleteAllDocuments(indexName) {
const index = this.client.initIndex(indexName);
const response = await index.clearObjects();
return { taskID: response.taskID };
}
async search(indexName, query, options = {}) {
const index = this.client.initIndex(indexName);
const searchOptions = {
offset: options.paginationOptions?.offset || 0,
length: options.paginationOptions?.limit || 20,
filters: options.filter || "",
...options.additionalOptions
};
const response = await index.search(query, searchOptions);
return {
hits: response.hits.map(hit => ({
id: hit.objectID,
score: hit._rankingInfo?.nbTypos || 0,
source: hit
})),
total: response.nbHits,
took: response.processingTimeMs,
page: response.page
};
}
}Search services are typically used for:
Basic Usage Pattern:
// In a Medusa service
class ProductSearchService {
constructor({ searchService }) {
this.searchService_ = searchService;
this.productIndex = "products";
}
async indexProducts(products) {
// Transform products for search indexing
const searchDocuments = products.map(product => ({
id: product.id,
title: product.title,
description: product.description,
tags: product.tags?.map(tag => tag.value) || [],
price: product.variants?.[0]?.prices?.[0]?.amount,
categories: product.categories?.map(cat => cat.name) || []
}));
return await this.searchService_.addDocuments(
this.productIndex,
searchDocuments,
"product"
);
}
async searchProducts(query, options = {}) {
const results = await this.searchService_.search(
this.productIndex,
query,
{
paginationOptions: {
limit: options.limit || 20,
offset: options.offset || 0
},
filter: options.categoryFilter ? `categories:${options.categoryFilter}` : null
}
);
return {
products: results.hits,
total: results.total,
page: Math.floor((options.offset || 0) / (options.limit || 20)) + 1
};
}
async updateProduct(productId, productData) {
await this.searchService_.replaceDocuments(
this.productIndex,
[{ id: productId, ...productData }],
"product"
);
}
async deleteProduct(productId) {
await this.searchService_.deleteDocument(this.productIndex, productId);
}
}All abstract methods throw descriptive errors when not implemented:
"createIndex must be overridden by a child class""getIndex must be overridden by a child class""addDocuments must be overridden by a child class""updateDocument must be overridden by a child class" (for replaceDocuments)"deleteDocument must be overridden by a child class""deleteAllDocuments must be overridden by a child class""search must be overridden by a child class""updateSettings must be overridden by a child class"