search
The provider-neutral part of Evolve search. It defines what a searchable product or content page looks like, and the two interfaces a search provider implements: one for writing documents into an index, one for querying it. It also has the service that keeps an index in step with the catalog, on events and on a full rebuild.
When to use
- A service fills a search index from the catalog: compose
SearchIndexerModulewith your engine's document source and your provider's index adapter. - You are adding support for a search provider: implement
SearchIndexAdapter,SearchQueryAdapter, or both, and run your index adapter againstdescribeSearchIndexAdapterContractfrom./testing. - You are adding support for a commerce backend: implement
DocumentSource. - A test needs an index or a catalog without a real provider: use
./testing.
Get started
Install the package with the peer dependencies it needs. It comes from the Evolve registry, which needs a one-time sign-in.
pnpm add @evolve-framework/search @evolve-framework/core fastifyRequirements4
@evolve-framework/core>= 1.0.0 < 2.0.0@types/node>=22optionalfastify>= 5.12.1 < 6.0.0vitest>= 4.0.0 < 6.0.0optional
For example:
import type { ProductDocument, SearchIndexAdapter } from "@evolve-framework/search";import { type DocumentSource, SearchIndexerModule } from "@evolve-framework/search/indexing";
// The source comes from an engine package (commercetools'// CommercetoolsCatalogSource, medusa's MedusaProductDocumentSource), the// adapter from a provider package (AlgoliaIndexAdapter,// MeilisearchIndexAdapter).export const createIndexer = ( source: DocumentSource, adapter: SearchIndexAdapter<ProductDocument>,) => new SearchIndexerModule({ source, adapter, secret: process.env.INDEXER_SECRET, });Modules
SearchIndexerModule
Hosts a catalog → search sync as a Domain Service.
The same service usually also consumes catalog events off a queue; use
SearchIndexerModule.handler for that, which is the platform-neutral
MessageHandler the cloud adapters wrap (createSQSHandler and friends).
A reindex is triggered rather than awaited: rebuilding a catalog takes far longer than a request, so the route returns immediately and the result is logged.
import { SearchIndexerModule } from "@evolve-framework/search/indexing";new SearchIndexerModule(options: SearchIndexerModuleOptions<TDoc>)| Option | Type | Description |
|---|---|---|
source | DocumentSource<TDoc> | Where documents come from: the commerce engine's catalog source. |
adapter | SearchIndexAdapter<TDoc> | Where documents go: the search provider's index adapter. |
routes? | boolean | Publish the operational routes ( |
secret? | string | Shared secret required on the operational routes. Callers must present
MoreRequired whenever the routes are published — see
|
allowUnauthenticatedRoutes? | boolean | Publish the operational routes with no secret at all. More
|
events? | CatalogEventRouting | Which event types trigger which action. Defaults to
|
CatalogEventRouting2The fields of events.
| Field | Type | Description |
|---|---|---|
upsert | string[] | Event types that mean "(re)build the documents for this entity". |
delete | string[] | Event types that mean "remove every document for this entity". |
| Method | Description |
|---|---|
handler(): MessageHandler<CatalogEvent> | Catalog events → sync. |
Extends AbstractModule<unknown>.
Adapters
InMemoryIndexAdapter
A SearchIndexAdapter that keeps documents in a Map, for testing the sync engine and any DocumentSource without a provider.
Records the batch sizes it was called with, so tests can assert that chunking
respects batchSize.
import { InMemoryIndexAdapter } from "@evolve-framework/search/testing";new InMemoryIndexAdapter(options?: InMemoryIndexAdapterOptions)| Option | Type | Description |
|---|---|---|
batchSize? | number | Maximum documents per upsert call. Defaults to 100. |
deleteBySource? | boolean | Advertise and implement |
fullReindex? | boolean | Advertise and implement |
| Method | Description |
|---|---|
init(): Promise<void> | One-shot, idempotent initialization (build SDK clients, etc.). |
upsertDocuments(scope: IndexScope, documents: TDoc[]): Promise<void> | Store each document under its id in the scope's target for its type, replacing one already there, and record the batch size in upsertBatches. |
deleteDocuments(
scope: IndexScope,
ids: string[],
documentTypes: string[],
): Promise<void> | Remove documents by id, from the target of every type in documentTypes. |
documents(scope: IndexScope, documentType?: string | undefined): TDoc[] | Every document currently in a scope's target. |
documentIds(
scope: IndexScope,
documentType?: string,
): string[] | The ids of InMemoryIndexAdapter.documents, in the same order. |
Testing
Test helpers in /testing. What they are for
Entry points
@evolve-framework/search | The document model (ProductDocument, ContentDocument, …), the provider ports (SearchIndexAdapter, SearchQueryAdapter, SearchCapabilities), supportsSort to check a sort against those capabilities, searchWithCorrection to search an adapter's suggestion when a term finds nothing, the query and facet types, and IndexScope57 exports |
@evolve-framework/search/indexing | SearchIndexerModule, the IndexSyncService it runs, the DocumentSource port and defaultEventRouting10 exports |
@evolve-framework/search/testing | InMemoryIndexAdapter and FakeCatalogSource, for testing an indexer or a mapper without a provider, and describeSearchIndexAdapterContract, the scenarios every index adapter meets5 exports |

