npm package: @rohanadnan1/search-service
Description: Plug-and-play NestJS search service with fuzzy, autocomplete, hybrid, and semantic vector search for MongoDB Atlas.
Latest version: 0.0.2

# @rohanadnan1/search-service

> Plug-and-play NestJS search service with **fuzzy**, **autocomplete**, **hybrid**, and **semantic vector** search for MongoDB Atlas.

[![npm version](https://img.shields.io/npm/v/@rohanadnan1/search-service.svg)](https://www.npmjs.com/package/@rohanadnan1/search-service)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

---

## Features

- 🔍 **Text Search** — Full-text search using MongoDB `$text` operator
- 🪄 **Fuzzy Search** — Typo-tolerant search via MongoDB Atlas Search
- ⚡ **Autocomplete** — Prefix-based suggestions as the user types
- 🔗 **Hybrid Search** — Combines fuzzy + text with automatic fallback
- 🧠 **Vector / Semantic Search** — AI-powered meaning-based search using Cohere embeddings
- 📦 **Plug-and-Play** — One import, one line of config
- 🏗️ **Multi-Tenant Ready** — API key-based collection isolation

---

## Quick Start (3 Steps)

### 1. Install

```bash
npm install @rohanadnan1/search-service
```

> **Peer dependencies** — your NestJS project should already have these installed. If not:
>
> ```bash
> npm install @nestjs/common @nestjs/core @nestjs/config @nestjs/mongoose mongoose reflect-metadata rxjs
> ```
>
> For **vector/semantic search**, also install:
>
> ```bash
> npm install cohere-ai
> ```

### 2. Import the Module

In your `app.module.ts` (or any NestJS module):

```typescript
import { Module } from '@nestjs/common';
import { SearchServiceModule } from '@rohanadnan1/search-service';

@Module({
  imports: [
    SearchServiceModule.forRoot({
      mongoUri: 'mongodb+srv://user:pass@cluster.mongodb.net/mydb',
      cohereApiKey: 'your-cohere-api-key', // optional — only needed for vector search
    }),
  ],
})
export class AppModule {}
```

**That's it.** All search services, repositories, and controllers are now available in your app.

### 3. Use the Services

Inject any service you need:

```typescript
import { Injectable } from '@nestjs/common';
import {
  SearchOrchestratorService,
  CollectionsService,
  EmbeddingsService,
  SearchType,
} from '@rohanadnan1/search-service';

@Injectable()
export class ProductSearchService {
  constructor(
    private readonly search: SearchOrchestratorService,
    private readonly collections: CollectionsService,
    private readonly embeddings: EmbeddingsService, // optional
  ) {}

  // Fuzzy search with typo tolerance
  async searchProducts(query: string) {
    return this.search.search({
      collectionName: 'products',
      query,
      searchType: SearchType.FUZZY,
      pagination: { limit: 20, page: 1 },
    });
  }

  // Autocomplete suggestions
  async autocomplete(query: string) {
    return this.search.search({
      collectionName: 'products',
      query,
      searchType: SearchType.AUTOCOMPLETE,
      pagination: { limit: 5 },
    });
  }
}
```

---

## Configuration Options

### `SearchServiceModule.forRoot(options)`

| Option           | Type     | Required | Default                  | Description                              |
| ---------------- | -------- | -------- | ------------------------ | ---------------------------------------- |
| `mongoUri`       | `string` | ✅       | —                        | MongoDB connection URI                   |
| `dbName`         | `string` | ❌       | URI default              | Database name                            |
| `cohereApiKey`   | `string` | ❌       | —                        | Cohere API key (for vector search)       |
| `embeddingModel` | `string` | ❌       | `embed-english-v3.0`    | Cohere embedding model                   |
| `isGlobalConfig` | `boolean`| ❌       | `true`                   | Register ConfigModule globally           |

### `SearchServiceModule.forRootFromEnv()`

Reads config from environment variables — no options needed:

```typescript
SearchServiceModule.forRootFromEnv()
```

| Env Variable             | Required | Description                 |
| ------------------------ | -------- | --------------------------- |
| `MONGO_URI`              | ✅       | MongoDB connection URI      |
| `DB_NAME`                | ❌       | Database name               |
| `COHERE_API_KEY`         | ❌       | Cohere API key              |
| `EMBEDDING_MODEL`        | ❌       | Cohere model name           |
| `EMBEDDINGS_CACHE_MAX_SIZE` | ❌    | Max cache entries (default: 10000) |

---

## Registering a Collection

Before you can search, register your MongoDB collection so the service knows which fields are searchable:

```typescript
// Register via CollectionsService (programmatically)
const result = await collectionsService.registerCollection({
  collectionName: 'products',
  searchableFields: {
    text: ['name', 'description', 'brand'],     // required — at least one field
    autocomplete: ['name', 'brand'],             // optional
    filters: ['category', 'price', 'inStock'],   // optional
    sortable: ['price', 'rating', 'createdAt'],   // optional
  },
  searchDefaults: {
    fuzzyMaxEdits: 2,     // 0, 1, or 2 — typo tolerance
    limit: 20,            // default results per page
    minScore: 0.5,        // minimum relevance threshold
  },
});

// The response includes Atlas Search index templates
// Create these indexes in MongoDB Atlas Dashboard, then:
await collectionsService.markIndexesCreated('products');
```

Or use the **REST API** (auto-registered at `/api/v1/collections`):

```bash
# Register collection
curl -X POST http://localhost:3000/api/v1/collections \
  -H "Content-Type: application/json" \
  -d '{
    "collectionName": "products",
    "searchableFields": {
      "text": ["name", "description"],
      "autocomplete": ["name"]
    }
  }'

# Get index templates to create in Atlas
curl http://localhost:3000/api/v1/collections/products/templates

# Mark indexes as created
curl -X POST http://localhost:3000/api/v1/collections/products/indexes-created
```

---

## Search Types

### Text Search

Basic full-text search using MongoDB `$text` operator. Requires a text index on the collection.

```typescript
const results = await search.search({
  collectionName: 'products',
  query: 'wireless headphones',
  searchType: SearchType.TEXT,
});
```

### Fuzzy Search

Typo-tolerant search via MongoDB Atlas Search. Handles misspellings gracefully.

```typescript
const results = await search.search({
  collectionName: 'products',
  query: 'wireles headfones',  // typos are OK!
  searchType: SearchType.FUZZY,
  fuzzyMaxEdits: 2,
});
```

### Autocomplete

Prefix-based suggestions as the user types. Returns max 10 results.

```typescript
const results = await search.search({
  collectionName: 'products',
  query: 'iph',  // matches "iPhone", "iPhone case", etc.
  searchType: SearchType.AUTOCOMPLETE,
  pagination: { limit: 5 },
});
```

### Hybrid Search

Combines fuzzy search with text search fallback. Tries fuzzy first, falls back to text if Atlas Search is unavailable.

```typescript
const results = await search.search({
  collectionName: 'products',
  query: 'laptop',
  searchType: SearchType.HYBRID,
});
```

### Vector / Semantic Search

AI-powered meaning-based search. Finds results by *meaning*, not just keyword matching.

**Prerequisites:**
1. Set `cohereApiKey` in module config
2. Generate embeddings for your documents (see below)
3. Create a vector search index in MongoDB Atlas

```typescript
// Search by meaning
const results = await search.search({
  collectionName: 'products',
  query: 'something to listen to music wirelessly',
  searchType: SearchType.VECTOR,
});

// Generate embeddings for all documents in a collection
// POST /api/v1/embeddings/generate-for-collection
// { "collectionName": "products", "nameField": "name", "descriptionField": "description" }
```

---

## Available Services

Everything is importable from the top-level package:

```typescript
import {
  // Module
  SearchServiceModule,

  // Search
  SearchOrchestratorService,  // Main search orchestrator
  StrategyFactoryService,     // Get search strategy by type
  SearchType,                 // Enum: TEXT, FUZZY, AUTOCOMPLETE, VECTOR, HYBRID
  SearchRequestDto,           // Search request structure
  SearchResponseDto,          // Search response structure

  // Collections
  CollectionsService,         // Register/manage collections
  RegisterCollectionDto,      // Registration request structure
  UpdateCollectionDto,        // Update request structure

  // Database
  DatabaseService,            // Connection management & health checks
  BaseRepository,             // Generic CRUD for any collection
  SearchRepository,           // Search-specific operations
  AggregationRepository,      // Complex aggregations

  // Embeddings
  EmbeddingsService,          // Generate AI embeddings
  EmbeddingsCache,            // Embedding cache management
  CohereProvider,             // Cohere AI provider

  // Strategies (for advanced use)
  TextSearchStrategy,
  FuzzySearchStrategy,
  AutocompleteStrategy,
  HybridSearchStrategy,
  VectorSearchStrategy,
} from '@rohanadnan1/search-service';
```

---

## REST API Endpoints

When you import `SearchServiceModule`, these endpoints are automatically available:

### Search

| Method | Endpoint                      | Description              |
| ------ | ----------------------------- | ------------------------ |
| POST   | `/api/v1/search`              | Execute a search         |
| POST   | `/api/v1/search/autocomplete` | Autocomplete suggestions |

### Collections

| Method | Endpoint                                      | Description                |
| ------ | --------------------------------------------- | -------------------------- |
| POST   | `/api/v1/collections`                         | Register a collection      |
| GET    | `/api/v1/collections`                         | List all collections       |
| GET    | `/api/v1/collections/:configId`               | Get collection config      |
| PATCH  | `/api/v1/collections/:configId`               | Update collection config   |
| DELETE | `/api/v1/collections/:configId`               | Delete collection config   |
| POST   | `/api/v1/collections/:configId/indexes-created` | Mark indexes as created  |
| GET    | `/api/v1/collections/:configId/templates`     | Get Atlas index templates  |

### Embeddings

| Method | Endpoint                                          | Description                |
| ------ | ------------------------------------------------- | -------------------------- |
| POST   | `/api/v1/embeddings/generate`                     | Generate single embedding  |
| POST   | `/api/v1/embeddings/batch`                        | Generate batch embeddings  |
| POST   | `/api/v1/embeddings/generate-for-collection`      | Embed all docs in collection |
| GET    | `/api/v1/embeddings/info`                         | Provider info & stats      |
| GET    | `/api/v1/embeddings/health`                       | Check Cohere availability  |
| DELETE | `/api/v1/embeddings/cache`                        | Clear embedding cache      |

---

## Sub-Module Imports

You can also import individual modules if you only need specific functionality:

```typescript
// Only database operations
import { DatabaseModule, DatabaseService, BaseRepository } from '@rohanadnan1/search-service/database';

// Only collection management
import { CollectionsModule, CollectionsService } from '@rohanadnan1/search-service/collections';

// Only embeddings
import { EmbeddingsModule, EmbeddingsService } from '@rohanadnan1/search-service/embeddings';

// Only search
import { SearchModule, SearchOrchestratorService, SearchType } from '@rohanadnan1/search-service/search';
```

---

## Full Integration Example

Here's a complete example of integrating into an existing NestJS e-commerce backend:

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { SearchServiceModule } from '@rohanadnan1/search-service';
import { ProductsModule } from './products/products.module';

@Module({
  imports: [
    SearchServiceModule.forRoot({
      mongoUri: process.env.MONGO_URI!,
      cohereApiKey: process.env.COHERE_API_KEY, // optional
    }),
    ProductsModule,
  ],
})
export class AppModule {}
```

```typescript
// products/products.service.ts
import { Injectable } from '@nestjs/common';
import {
  SearchOrchestratorService,
  CollectionsService,
  SearchType,
  BaseRepository,
} from '@rohanadnan1/search-service';

@Injectable()
export class ProductsService {
  constructor(
    private readonly search: SearchOrchestratorService,
    private readonly collections: CollectionsService,
    private readonly repo: BaseRepository,
  ) {}

  /**
   * One-time setup: register your collection
   * Call this once during app initialization
   */
  async setupSearch() {
    try {
      await this.collections.registerCollection({
        collectionName: 'products',
        searchableFields: {
          text: ['name', 'description', 'brand'],
          autocomplete: ['name'],
          filters: ['category', 'price'],
          sortable: ['price', 'rating'],
        },
      });
    } catch (error) {
      // Collection already registered — that's fine
    }
  }

  async searchProducts(query: string, page = 1) {
    return this.search.search({
      collectionName: 'products',
      query,
      searchType: SearchType.FUZZY,
      pagination: { limit: 20, page },
      filterOptions: {
        filters: { category: 'electronics' },
        sort: { price: 1 },
      },
    });
  }

  async getSuggestions(query: string) {
    return this.search.search({
      collectionName: 'products',
      query,
      searchType: SearchType.AUTOCOMPLETE,
      pagination: { limit: 5 },
    });
  }

  async findProduct(id: string) {
    return this.repo.findById('products', id);
  }
}
```

```typescript
// products/products.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ProductsService } from './products.service';

@Controller('products')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Get('search')
  async search(
    @Query('q') query: string,
    @Query('page') page?: string,
  ) {
    return this.productsService.searchProducts(query, Number(page) || 1);
  }

  @Get('suggest')
  async suggest(@Query('q') query: string) {
    return this.productsService.getSuggestions(query);
  }
}
```

---

## MongoDB Atlas Setup

For **fuzzy search** and **autocomplete**, you need MongoDB Atlas Search indexes:

1. Go to your [MongoDB Atlas Dashboard](https://cloud.mongodb.com/)
2. Navigate to **Database → Search**
3. Click **Create Search Index**
4. Use the templates from `GET /api/v1/collections/:configId/templates`
5. After creating, call `POST /api/v1/collections/:configId/indexes-created`

For **vector search**, also create a Vector Search index:

```json
{
  "type": "vectorSearch",
  "fields": [
    {
      "type": "vector",
      "path": "embedding",
      "numDimensions": 1024,
      "similarity": "cosine"
    }
  ]
}
```

---

## License

MIT © Rohan Adnan