← All @molecule/* packages · App templates
@molecule/api-searchCore interface · search · API (Node) · v1.0.1 · Apache-2.0
Full-text search core interface for molecule.dev
npm install @molecule/api-search@molecule/api-search is the search core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 4 providers: @molecule/api-search-elasticsearch, @molecule/api-search-meilisearch, @molecule/api-search-postgres, @molecule/api-search-typesense.
import { setProvider, search, index, suggest } from '@molecule/api-search'
import { provider as elasticsearch } from '@molecule/api-search-elasticsearch'
setProvider(elasticsearch)
await index('products', '1', { name: 'Widget', price: 9.99 })
const results = await search('products', { text: 'widget', highlight: true })
const suggestions = await suggest('products', 'wid', { limit: 5 })Providers (4): @molecule/api-search-elasticsearch, @molecule/api-search-meilisearch, @molecule/api-search-postgres, @molecule/api-search-typesense
Works with: @molecule/api-bond, @molecule/api-i18n
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Provider-agnostic full-text search interface for molecule.dev.
Defines the SearchProvider interface for indexing, querying, and
autocomplete suggestions. Bond packages (Elasticsearch, Meilisearch,
Typesense, PostgreSQL, etc.) implement this interface. Application code
uses the convenience functions (search, index, suggest, etc.) which
delegate to the bonded provider.
import { setProvider, search, index, suggest } from '@molecule/api-search'
import { provider as elasticsearch } from '@molecule/api-search-elasticsearch'
setProvider(elasticsearch)
await index('products', '1', { name: 'Widget', price: 9.99 })
const results = await search('products', { text: 'widget', highlight: true })
const suggestions = await suggest('products', 'wid', { limit: 5 })
core
npm install @molecule/api-search @molecule/api-bond @molecule/api-i18n
BulkIndexResultResult of a bulk index operation.
interface BulkIndexResult {
/**
* Number of documents successfully indexed.
*/
indexed: number
/**
* Number of documents that failed to index.
*/
failed: number
/**
* Errors encountered during bulk indexing, keyed by document id.
*/
errors: Record<string, string>
}
FacetCountA single facet count entry.
interface FacetCount {
/**
* The facet value.
*/
value: string
/**
* Number of documents matching this facet value.
*/
count: number
}
IndexDocumentA document to be indexed in a bulk operation.
interface IndexDocument {
/**
* Unique identifier for the document.
*/
id: string
/**
* The document fields and values.
*/
document: Record<string, unknown>
}
IndexSchemaSchema definition for a search index, describing the fields and their roles.
interface IndexSchema {
/**
* Map of field names to their types.
*/
fields: Record<string, FieldType>
/**
* Fields that are searchable via full-text queries.
*/
searchableFields?: string[]
/**
* Fields that can be used in filter expressions.
*/
filterableFields?: string[]
/**
* Fields that can be used for sorting results.
*/
sortableFields?: string[]
}
SearchHitA single search result hit.
interface SearchHit {
/**
* Document identifier.
*/
id: string
/**
* Relevance score.
*/
score: number
/**
* The matched document fields.
*/
document: Record<string, unknown>
/**
* Highlighted field snippets, keyed by field name.
*/
highlights?: Record<string, string[]>
}
SearchProviderSearch provider interface.
All search providers must implement this interface to provide full-text search, indexing, and suggestion capabilities.
interface SearchProvider {
/**
* Creates a search index with an optional schema.
*
* @param name - Index name.
* @param schema - Optional schema describing field types and roles.
*/
createIndex(name: string, schema?: IndexSchema): Promise<void>
/**
* Deletes a search index and all its documents.
*
* @param name - Index name to delete.
*/
deleteIndex(name: string): Promise<void>
/**
* Indexes a single document.
*
* @param indexName - Target index name.
* @param id - Unique document identifier.
* @param document - The document fields and values.
*/
index(indexName: string, id: string, document: Record<string, unknown>): Promise<void>
/**
* Indexes multiple documents in a single operation.
*
* @param indexName - Target index name.
* @param documents - Array of documents to index.
* @returns Result with indexed/failed counts and errors.
*/
bulkIndex(indexName: string, documents: IndexDocument[]): Promise<BulkIndexResult>
/**
* Executes a full-text search query against an index.
*
* @param indexName - Index to search.
* @param query - Search query with text, filters, pagination, etc.
* @returns Search results with hits, total count, facets, and timing.
*/
search(indexName: string, query: SearchQuery): Promise<SearchResult>
/**
* Deletes a document from an index by id.
*
* @param indexName - Index containing the document.
* @param id - Document identifier to delete.
*/
delete(indexName: string, id: string): Promise<void>
/**
* Returns typeahead/autocomplete suggestions for a partial query.
*
* @param indexName - Index to generate suggestions from.
* @param query - Partial text to complete.
* @param options - Suggestion options (limit, fields, fuzzy).
* @returns Array of suggestions sorted by relevance.
*/
suggest(indexName: string, query: string, options?: SuggestOptions): Promise<Suggestion[]>
/**
* Retrieves a single document from an index by id.
*
* @param indexName - Index containing the document.
* @param id - Document identifier.
* @returns The document fields, or `null` if not found.
*/
getDocument(indexName: string, id: string): Promise<Record<string, unknown> | null>
}
SearchQueryA full-text search query with optional filters, facets, sorting, and pagination.
interface SearchQuery {
/**
* The search text.
*
* Empty or whitespace-only text is "browse" mode: bond implementations
* MUST match ALL documents (subject to `filters`, `sort`, and pagination)
* rather than erroring or returning zero hits. Every bundled bond
* (Elasticsearch, Meilisearch, Typesense, PostgreSQL) follows this
* contract, so swapping providers doesn't silently change what an empty
* search box shows.
*/
text: string
/**
* Filter expressions to narrow results.
*/
filters?: Record<string, unknown>
/**
* Fields to compute facet counts for.
*/
facets?: string[]
/**
* Sort fields and directions.
*/
sort?: SortField[]
/**
* Page number (1-based).
*/
page?: number
/**
* Number of results per page.
*/
perPage?: number
/**
* Whether to include highlighted snippets in results.
*/
highlight?: boolean
}
SearchResultThe result of a search query, including hits, pagination, facets, and timing.
interface SearchResult {
/**
* Matched documents.
*/
hits: SearchHit[]
/**
* Total number of matching documents.
*/
total: number
/**
* Current page number.
*/
page: number
/**
* Number of results per page.
*/
perPage: number
/**
* Facet counts keyed by field name.
*/
facets?: Record<string, FacetCount[]>
/**
* Time taken to process the query in milliseconds.
*/
processingTimeMs: number
}
SortFieldA field to sort search results by.
interface SortField {
/**
* The field name to sort on.
*/
field: string
/**
* Sort direction.
*/
direction: SortDirection
}
SuggestionA single autocomplete suggestion.
interface Suggestion {
/**
* The suggested text.
*/
text: string
/**
* Relevance score for ranking suggestions.
*/
score: number
/**
* Optional highlighted version of the suggestion.
*/
highlighted?: string
}
SuggestOptionsOptions for typeahead / autocomplete suggestions.
interface SuggestOptions {
/**
* Maximum number of suggestions to return.
*/
limit?: number
/**
* Fields to generate suggestions from.
*/
fields?: string[]
/**
* Whether to apply fuzzy matching.
*/
fuzzy?: boolean
}
FieldTypeField type for index schema definitions.
type FieldType = 'text' | 'keyword' | 'number' | 'boolean' | 'date' | 'geo'
SortDirectionSort direction for search results.
type SortDirection = 'asc' | 'desc'
bulkIndex(indexName, documents)Indexes multiple documents in a single operation.
function bulkIndex(indexName: string, documents: IndexDocument[]): Promise<BulkIndexResult>
indexName — Target index name.documents — Array of documents to index.Returns: Result with indexed/failed counts and errors.
createIndex(name, schema)Creates a search index with an optional schema.
function createIndex(name: string, schema?: IndexSchema): Promise<void>
name — Index name.schema — Optional schema describing field types and roles.Returns: A promise that resolves when the index has been created.
deleteDocument(indexName, id)Deletes a document from an index by id.
function deleteDocument(indexName: string, id: string): Promise<void>
indexName — Index containing the document.id — Document identifier to delete.Returns: A promise that resolves when the document has been deleted.
deleteIndex(name)Deletes a search index and all its documents.
function deleteIndex(name: string): Promise<void>
name — Index name to delete.Returns: A promise that resolves when the index has been deleted.
getDocument(indexName, id)Retrieves a single document from an index by id.
function getDocument(indexName: string, id: string): Promise<Record<string, unknown> | null>
indexName — Index containing the document.id — Document identifier.Returns: The document fields, or null if not found.
getProvider()Retrieves the bonded search provider, throwing if none is configured.
function getProvider(): SearchProvider
Returns: The bonded search provider.
hasProvider()Checks whether a search provider is currently bonded.
function hasProvider(): boolean
Returns: true if a search provider is bonded.
index(indexName, id, document)Indexes a single document.
function index(indexName: string, id: string, document: Record<string, unknown>): Promise<void>
indexName — Target index name.id — Unique document identifier.document — The document fields and values.Returns: A promise that resolves when the document has been indexed.
search(indexName, query)Executes a full-text search query against an index.
function search(indexName: string, query: SearchQuery): Promise<SearchResult>
indexName — Index to search.query — Search query with text, filters, pagination, etc.Returns: Search results with hits, total count, facets, and timing.
setProvider(provider)Registers a search provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: SearchProvider): void
provider — The search provider implementation to bond.suggest(indexName, query, options)Returns typeahead/autocomplete suggestions for a partial query.
function suggest(indexName: string, query: string, options?: SuggestOptions): Promise<Suggestion[]>
indexName — Index to generate suggestions from.query — Partial text to complete.options — Suggestion options (limit, fields, fuzzy).Returns: Array of suggestions sorted by relevance.
| Provider | Package |
|---|---|
| Search | @molecule/api-search-elasticsearch |
| Search | @molecule/api-search-meilisearch |
| Search | @molecule/api-search-postgres |
| Search | @molecule/api-search-typesense |
Peer dependencies:
@molecule/api-bond ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-bond
@molecule/api-i18n
A search() route needs BOTH halves wired, or it ships dead. (1) The
index starts EMPTY — you must index(collection, id, doc) each record on
create/update (and deleteDocument() on delete), or a query returns zero
hits forever (nothing was ever indexed). (2) The engine can be ABSENT —
unbonded, or no service URL in dev/CI/sandboxes — so a bare search()
throws and the endpoint 500s. Guard and degrade: call search() only when
hasProvider() is true, wrap it in try/catch, and on absence or failure
fall back to a DataStore query — findMany(coll, { where: [{ field, operator: 'ilike', value: %${text}% }] }) — so the feature works WITH or
WITHOUT the engine. For a small, already-loaded list, filtering client-side
is fine — just don't ship a search() route no page calls and no writer
indexes.
Empty/whitespace-only SearchQuery.text is "browse" mode — every
bundled bond matches ALL documents (filters/sort/pagination still
apply) rather than erroring or returning zero hits. Build an initial
"show everything" view with search('products', { text: '' }) instead
of special-casing an empty search box in application code.
Bonds diverge on details the core contract does NOT standardize:
facet support (PostgreSQL supports it via an extra GROUP BY query per
field; the engine-backed bonds use native aggregations), highlight
result shape (per-field for Elasticsearch/Meilisearch/Typesense vs. a
single _content key for PostgreSQL), and filter semantics (exact
term/= matching everywhere — declare filterable string fields as
keyword for Elasticsearch). Check the bond's own module @remarks
before debugging a result that looks wrong only on one provider.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
text matches
ALL documents by contract) — not zero results and not an error.