or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

base-service.mdfile-service.mdfulfillment-service.mdindex.mdnotification-service.mdoauth-service.mdpayment-service.mdsearch-service.md

search-service.mddocs/

0

# Search Service

1

2

Interface for search service implementations providing full-text search capabilities including index management, document operations, and search queries across different search engines.

3

4

**Deprecation Notice**: Use `AbstractSearchService` from @medusajs/utils instead.

5

6

## Capabilities

7

8

### Static Methods

9

10

Type checking and identification methods for search services.

11

12

```javascript { .api }

13

/**

14

* Static property identifying this as a search service

15

*/

16

static _isSearchService: boolean;

17

18

/**

19

* Checks if an object is a search service

20

* @param {object} obj - Object to check

21

* @returns {boolean} True if obj is a search service

22

*/

23

static isSearchService(obj: object): boolean;

24

```

25

26

### Configuration

27

28

Access to service configuration options.

29

30

```javascript { .api }

31

/**

32

* Getter for service options configuration

33

* @returns {object} Service options or empty object

34

*/

35

get options(): object;

36

```

37

38

### Index Management

39

40

Methods for creating, retrieving, and configuring search indexes.

41

42

```javascript { .api }

43

/**

44

* Creates a search index with optional configuration

45

* @param {string} indexName - The name of the index to create

46

* @param {object} options - Optional index configuration

47

* @returns {Promise<object>} Response from search engine provider

48

* @throws {Error} If not overridden by child class

49

*/

50

createIndex(indexName: string, options?: object): Promise<object>;

51

52

/**

53

* Retrieves information about an existing index

54

* @param {string} indexName - The name of the index to retrieve

55

* @returns {Promise<object>} Response from search engine provider

56

* @throws {Error} If not overridden by child class

57

*/

58

getIndex(indexName: string): Promise<object>;

59

60

/**

61

* Updates the settings of an existing index

62

* @param {string} indexName - The name of the index to update

63

* @param {object} settings - Settings object for index configuration

64

* @returns {Promise<object>} Response from search engine provider

65

* @throws {Error} If not overridden by child class

66

*/

67

updateSettings(indexName: string, settings: object): Promise<object>;

68

```

69

70

### Document Operations

71

72

Methods for adding, replacing, and deleting documents in search indexes.

73

74

```javascript { .api }

75

/**

76

* Adds documents to a search index

77

* @param {string} indexName - The name of the index

78

* @param {array} documents - Array of document objects to be indexed

79

* @param {string} type - Type of documents (e.g: products, regions, orders)

80

* @returns {Promise<object>} Response from search engine provider

81

* @throws {Error} If not overridden by child class

82

*/

83

addDocuments(indexName: string, documents: array, type: string): Promise<object>;

84

85

/**

86

* Replaces documents in a search index

87

* @param {string} indexName - The name of the index

88

* @param {array} documents - Array of document objects to replace existing documents

89

* @param {string} type - Type of documents to be replaced

90

* @returns {Promise<object>} Response from search engine provider

91

* @throws {Error} If not overridden by child class

92

*/

93

replaceDocuments(indexName: string, documents: array, type: string): Promise<object>;

94

95

/**

96

* Deletes a single document from the index

97

* @param {string} indexName - The name of the index

98

* @param {string} document_id - The ID of the document to delete

99

* @returns {Promise<object>} Response from search engine provider

100

* @throws {Error} If not overridden by child class

101

*/

102

deleteDocument(indexName: string, document_id: string): Promise<object>;

103

104

/**

105

* Deletes all documents from an index

106

* @param {string} indexName - The name of the index

107

* @returns {Promise<object>} Response from search engine provider

108

* @throws {Error} If not overridden by child class

109

*/

110

deleteAllDocuments(indexName: string): Promise<object>;

111

```

112

113

### Search Operations

114

115

Methods for performing search queries on indexed documents.

116

117

```javascript { .api }

118

/**

119

* Searches for documents in an index

120

* @param {string} indexName - The name of the index to search

121

* @param {string} query - The search query string

122

* @param {object} options - Search options including pagination, filters, and provider-specific options

123

* @returns {Promise<object>} Search results with hits array and metadata

124

* @throws {Error} If not overridden by child class

125

*/

126

search(indexName: string, query: string, options?: SearchOptions): Promise<SearchResult>;

127

```

128

129

## Types

130

131

```javascript { .api }

132

/**

133

* Search options configuration

134

*/

135

interface SearchOptions {

136

paginationOptions?: {

137

limit: number;

138

offset: number;

139

};

140

filter?: any;

141

additionalOptions?: any;

142

}

143

144

/**

145

* Search result structure

146

*/

147

interface SearchResult {

148

hits: any[];

149

[key: string]: any;

150

}

151

```

152

153

## Implementation Example

154

155

```javascript

156

import { SearchService } from "medusa-interfaces";

157

158

// Elasticsearch implementation

159

class ElasticsearchService extends SearchService {

160

constructor(options) {

161

super();

162

this.client = options.client;

163

this.options_ = options;

164

}

165

166

async createIndex(indexName, options = {}) {

167

const indexConfig = {

168

index: indexName,

169

body: {

170

settings: {

171

number_of_shards: options.shards || 1,

172

number_of_replicas: options.replicas || 0,

173

...options.settings

174

},

175

mappings: options.mappings || {}

176

}

177

};

178

179

try {

180

const response = await this.client.indices.create(indexConfig);

181

return {

182

acknowledged: response.acknowledged,

183

index: indexName,

184

shards_acknowledged: response.shards_acknowledged

185

};

186

} catch (error) {

187

throw new Error(`Failed to create index: ${error.message}`);

188

}

189

}

190

191

async getIndex(indexName) {

192

try {

193

const response = await this.client.indices.get({ index: indexName });

194

return response[indexName];

195

} catch (error) {

196

throw new Error(`Failed to get index: ${error.message}`);

197

}

198

}

199

200

async updateSettings(indexName, settings) {

201

try {

202

const response = await this.client.indices.putSettings({

203

index: indexName,

204

body: { settings }

205

});

206

return { acknowledged: response.acknowledged };

207

} catch (error) {

208

throw new Error(`Failed to update settings: ${error.message}`);

209

}

210

}

211

212

async addDocuments(indexName, documents, type) {

213

const body = documents.flatMap(doc => [

214

{ index: { _index: indexName, _type: type, _id: doc.id } },

215

doc

216

]);

217

218

try {

219

const response = await this.client.bulk({ body });

220

return {

221

took: response.took,

222

errors: response.errors,

223

items: response.items

224

};

225

} catch (error) {

226

throw new Error(`Failed to add documents: ${error.message}`);

227

}

228

}

229

230

async replaceDocuments(indexName, documents, type) {

231

// Delete all documents of the type first

232

await this.client.deleteByQuery({

233

index: indexName,

234

body: {

235

query: { term: { _type: type } }

236

}

237

});

238

239

// Add new documents

240

return await this.addDocuments(indexName, documents, type);

241

}

242

243

async deleteDocument(indexName, document_id) {

244

try {

245

const response = await this.client.delete({

246

index: indexName,

247

id: document_id

248

});

249

return {

250

result: response.result,

251

version: response._version

252

};

253

} catch (error) {

254

throw new Error(`Failed to delete document: ${error.message}`);

255

}

256

}

257

258

async deleteAllDocuments(indexName) {

259

try {

260

const response = await this.client.deleteByQuery({

261

index: indexName,

262

body: { query: { match_all: {} } }

263

});

264

return {

265

deleted: response.deleted,

266

took: response.took

267

};

268

} catch (error) {

269

throw new Error(`Failed to delete all documents: ${error.message}`);

270

}

271

}

272

273

async search(indexName, query, options = {}) {

274

const searchBody = {

275

query: {

276

multi_match: {

277

query: query,

278

fields: ["*"]

279

}

280

}

281

};

282

283

// Add filters if provided

284

if (options.filter) {

285

searchBody.query = {

286

bool: {

287

must: searchBody.query,

288

filter: options.filter

289

}

290

};

291

}

292

293

const searchParams = {

294

index: indexName,

295

body: searchBody,

296

from: options.paginationOptions?.offset || 0,

297

size: options.paginationOptions?.limit || 20

298

};

299

300

// Add any additional provider-specific options

301

if (options.additionalOptions) {

302

Object.assign(searchParams, options.additionalOptions);

303

}

304

305

try {

306

const response = await this.client.search(searchParams);

307

return {

308

hits: response.hits.hits.map(hit => ({

309

id: hit._id,

310

score: hit._score,

311

source: hit._source

312

})),

313

total: response.hits.total.value,

314

took: response.took,

315

max_score: response.hits.max_score

316

};

317

} catch (error) {

318

throw new Error(`Search failed: ${error.message}`);

319

}

320

}

321

}

322

323

// Algolia implementation example

324

class AlgoliaSearchService extends SearchService {

325

constructor(options) {

326

super();

327

this.client = options.client;

328

this.options_ = options;

329

}

330

331

async createIndex(indexName, options = {}) {

332

const index = this.client.initIndex(indexName);

333

334

if (options.settings) {

335

await index.setSettings(options.settings);

336

}

337

338

return {

339

acknowledged: true,

340

index: indexName

341

};

342

}

343

344

async getIndex(indexName) {

345

const index = this.client.initIndex(indexName);

346

const settings = await index.getSettings();

347

return { settings };

348

}

349

350

async updateSettings(indexName, settings) {

351

const index = this.client.initIndex(indexName);

352

await index.setSettings(settings);

353

return { acknowledged: true };

354

}

355

356

async addDocuments(indexName, documents, type) {

357

const index = this.client.initIndex(indexName);

358

359

// Add type field to documents

360

const typedDocuments = documents.map(doc => ({ ...doc, _type: type }));

361

362

const response = await index.saveObjects(typedDocuments);

363

return {

364

objectIDs: response.objectIDs,

365

taskID: response.taskID

366

};

367

}

368

369

async replaceDocuments(indexName, documents, type) {

370

const index = this.client.initIndex(indexName);

371

372

// Clear existing documents of this type

373

await index.deleteBy({ filters: `_type:${type}` });

374

375

// Add new documents

376

return await this.addDocuments(indexName, documents, type);

377

}

378

379

async deleteDocument(indexName, document_id) {

380

const index = this.client.initIndex(indexName);

381

const response = await index.deleteObject(document_id);

382

return { taskID: response.taskID };

383

}

384

385

async deleteAllDocuments(indexName) {

386

const index = this.client.initIndex(indexName);

387

const response = await index.clearObjects();

388

return { taskID: response.taskID };

389

}

390

391

async search(indexName, query, options = {}) {

392

const index = this.client.initIndex(indexName);

393

394

const searchOptions = {

395

offset: options.paginationOptions?.offset || 0,

396

length: options.paginationOptions?.limit || 20,

397

filters: options.filter || "",

398

...options.additionalOptions

399

};

400

401

const response = await index.search(query, searchOptions);

402

403

return {

404

hits: response.hits.map(hit => ({

405

id: hit.objectID,

406

score: hit._rankingInfo?.nbTypos || 0,

407

source: hit

408

})),

409

total: response.nbHits,

410

took: response.processingTimeMs,

411

page: response.page

412

};

413

}

414

}

415

```

416

417

## Usage in Medusa

418

419

Search services are typically used for:

420

421

- **Product Search**: Full-text search across product catalog

422

- **Order Search**: Finding orders by customer, product, or status

423

- **Customer Search**: Admin panel customer lookup

424

- **Content Search**: CMS content and documentation search

425

426

**Basic Usage Pattern:**

427

428

```javascript

429

// In a Medusa service

430

class ProductSearchService {

431

constructor({ searchService }) {

432

this.searchService_ = searchService;

433

this.productIndex = "products";

434

}

435

436

async indexProducts(products) {

437

// Transform products for search indexing

438

const searchDocuments = products.map(product => ({

439

id: product.id,

440

title: product.title,

441

description: product.description,

442

tags: product.tags?.map(tag => tag.value) || [],

443

price: product.variants?.[0]?.prices?.[0]?.amount,

444

categories: product.categories?.map(cat => cat.name) || []

445

}));

446

447

return await this.searchService_.addDocuments(

448

this.productIndex,

449

searchDocuments,

450

"product"

451

);

452

}

453

454

async searchProducts(query, options = {}) {

455

const results = await this.searchService_.search(

456

this.productIndex,

457

query,

458

{

459

paginationOptions: {

460

limit: options.limit || 20,

461

offset: options.offset || 0

462

},

463

filter: options.categoryFilter ? `categories:${options.categoryFilter}` : null

464

}

465

);

466

467

return {

468

products: results.hits,

469

total: results.total,

470

page: Math.floor((options.offset || 0) / (options.limit || 20)) + 1

471

};

472

}

473

474

async updateProduct(productId, productData) {

475

await this.searchService_.replaceDocuments(

476

this.productIndex,

477

[{ id: productId, ...productData }],

478

"product"

479

);

480

}

481

482

async deleteProduct(productId) {

483

await this.searchService_.deleteDocument(this.productIndex, productId);

484

}

485

}

486

```

487

488

## Error Handling

489

490

All abstract methods throw descriptive errors when not implemented:

491

492

- `"createIndex must be overridden by a child class"`

493

- `"getIndex must be overridden by a child class"`

494

- `"addDocuments must be overridden by a child class"`

495

- `"updateDocument must be overridden by a child class"` (for replaceDocuments)

496

- `"deleteDocument must be overridden by a child class"`

497

- `"deleteAllDocuments must be overridden by a child class"`

498

- `"search must be overridden by a child class"`

499

- `"updateSettings must be overridden by a child class"`