Adding a Search Engine
The API uses an engine abstraction that makes it straightforward to add new search backends. Four engines are currently implemented: Elasticsearch, OpenSearch, Meilisearch, and Typesense.
Architecture
Section titled “Architecture”Elasticsearch and OpenSearch share a base class (ElasticCompatEngine) since they use an identical query DSL. Subclasses only differ in client construction and response unwrapping. Meilisearch and Typesense implement SearchEngine directly because their API models are fundamentally different.
Directorysrc/engines/
- engine.ts SearchEngine interface
- elastic-compat.ts shared base class
- elasticsearch.ts
- opensearch.ts
- meilisearch.ts
- typesense.ts
- index.ts engine factory map
Adding a new engine
Section titled “Adding a new engine”-
Implement the
SearchEngineinterfaceCreate a new file in
src/engines/(e.g.typesense.ts). Your class must implement all five methods fromsrc/engines/engine.ts:import type {FacetSearchOptions,FacetValue,SearchOptions,SearchResult,} from "../types.ts";export interface SearchEngine {search(query: string, options: SearchOptions): Promise<SearchResult>;getDocument(id: string): Promise<Record<string, unknown> | null>;searchFacetValues(field: string,query: string,options?: FacetSearchOptions,): Promise<FacetValue[]>;getMapping(): Promise<Record<string, unknown>>;rawQuery(body: Record<string, unknown>): Promise<Record<string, unknown>>;}Method details
Section titled “Method details”Method Purpose searchFull-text search with pagination, facets, filters, boosts, highlights, histograms, and geo grid. Return a SearchResult.getDocumentFetch a single document by ID. Return nullif not found.searchFacetValuesSearch within a facet field’s values by name. Used for autocomplete-style facet filtering. getMappingReturn the index schema/mapping. Structure depends on the engine. rawQueryPass a raw query body to the engine. The body shape is engine-specific. Your constructor receives the full
IndexConfigand should extract what it needs (host, auth, index name).For engines with a completely different API model, implement
SearchEnginedirectly — seesrc/engines/meilisearch.ts. -
Register the engine
Add your engine to the factory map in
src/engines/index.ts:import { TypesenseEngine } from "./typesense.ts";const engineFactories: Record<string, (config: IndexConfig) => SearchEngine> = {elasticsearch: (config) => new ElasticsearchEngine(config),opensearch: (config) => new OpenSearchEngine(config),meilisearch: (config) => new MeilisearchEngine(config),typesense: (config) => new TypesenseEngine(config),}; -
Update the config schema
Add the new engine name as a valid literal in
src/config.ts:const IndexConfigSchema = t.Object({engine: t.Union([t.Literal("elasticsearch"),t.Literal("opensearch"),t.Literal("meilisearch"),t.Literal("typesense"),]),// ...});Also update
schemas/config.schema.jsonto include the new engine in theenum:"engine": {"type": "string","enum": ["elasticsearch", "opensearch", "meilisearch", "typesense"]}