Skip to content

Search Domain

The search domain provides full-text search for TypeScript backends. It offers a provider-agnostic port interface that allows you to plug in Meilisearch, Algolia, Typesense, Elasticsearch, or a database-native solution.

Terminal window
npx @backcap/cli add search

The SearchIndex entity represents a named search index and tracks its document count. Immutable — mutations return new instances.

import { SearchIndex } from "./domains/search/domain/entities/search-index.entity";
const result = SearchIndex.create({
id: crypto.randomUUID(),
name: "posts",
});
if (result.isOk()) {
const index = result.unwrap();
console.log(index.documentCount); // 0
const updated = index.incrementCount();
console.log(updated.documentCount); // 1
}
FieldTypeDescription
idstringUnique identifier
namestringIndex name (trimmed, non-empty)
documentCountnumberNumber of documents (>= 0)
createdAtDateCreation timestamp
updatedAtDateLast modification timestamp

Methods: incrementCount() and decrementCount() return new instances with updated counts and timestamps.

import { SearchQuery } from "./domains/search/domain/value-objects/search-query.vo";
const result = SearchQuery.create({
query: "typescript backend",
filters: { status: "published" },
page: 1,
pageSize: 20,
});
// Result<SearchQuery, InvalidQuery>
PropertyTypeDescription
querystringTrimmed, non-empty search term
filtersRecord<string, string | string[]> | undefinedOptional field-level filters
pagination{ page: number, pageSize: number }Defaults: page 1, pageSize 10, max 100

Validation: query must be non-empty, page >= 1, pageSize capped at 100.

Error ClassConditionMessage
IndexNotFoundIndex does not existSearch index not found: "<name>"
InvalidQueryQuery is empty or invalidInvalid search query: <reason>
DocumentNotFoundDocument not in indexDocument not found with id: "<id>"
EventPayload
IndexUpdatedindexId, indexName, documentCount, occurredAt

Adds or updates a document in a search index. If the index does not exist, the search engine creates it automatically.

import { IndexDocument } from "./domains/search/application/use-cases/index-document.use-case";
const indexDocument = new IndexDocument(searchEngine);
const result = await indexDocument.execute({
indexName: "posts",
documentId: "post-123",
document: { title: "My Post", content: "Hello world", authorId: "user-1" },
});
// Result<{ documentId, indexedAt }, Error>

Possible failures: IndexNotFound

Performs a full-text search with pagination and filters.

const result = await searchDocuments.execute({
indexName: "posts",
query: "typescript",
filters: { status: "published" },
page: 1,
pageSize: 20,
});
// Result<{ hits: SearchHit[], total, page, pageSize }, Error>

Each SearchHit contains id, score, and document.

Possible failures: IndexNotFound, InvalidQuery

Removes a document from a search index.

const result = await removeFromIndex.execute({
indexName: "posts",
documentId: "post-123",
});
// Result<{ documentId, removedAt }, Error>

Possible failures: IndexNotFound, DocumentNotFound

export interface ISearchEngine {
indexDocument(indexName: string, documentId: string, document: Record<string, unknown>): Promise<void>;
search(params: {
indexName: string;
query: string;
filters?: Record<string, string | string[]>;
page: number;
pageSize: number;
}): Promise<{ hits: SearchHit[]; total: number }>;
removeDocument(indexName: string, documentId: string): Promise<boolean>;
indexExists(indexName: string): Promise<boolean>;
documentExists(indexName: string, documentId: string): Promise<boolean>;
}

Implement this port for your search provider (Meilisearch, Algolia, Typesense, PostgreSQL full-text, etc.).

import { createSearchService, ISearchService } from "./domains/search/contracts";
const searchService: ISearchService = createSearchService({
searchEngine,
});
// ISearchService interface:
// indexDocument(input): Promise<Result<IndexDocumentOutput, Error>>
// searchDocuments(input): Promise<Result<SearchDocumentsOutput, Error>>
// removeFromIndex(input): Promise<Result<RemoveFromIndexOutput, Error>>
domains/search/
domain/
entities/search-index.entity.ts
value-objects/search-query.vo.ts
events/index-updated.event.ts
errors/index-not-found.error.ts
errors/invalid-query.error.ts
errors/document-not-found.error.ts
application/
use-cases/index-document.use-case.ts
use-cases/search-documents.use-case.ts
use-cases/remove-from-index.use-case.ts
ports/search-engine.port.ts
dto/index-document.dto.ts
dto/search-documents.dto.ts
dto/remove-from-index.dto.ts
contracts/
search.contract.ts
search.factory.ts
index.ts
shared/
result.ts