Skip to content

search

@evolve-framework/search · v1.0.0

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 SearchIndexerModule with 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 against describeSearchIndexAdapterContract from ./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

  1. 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 fastify
    Requirements4
    @evolve-framework/core>= 1.0.0 < 2.0.0
    @types/node>=22optional
    fastify>= 5.12.1 < 6.0.0
    vitest>= 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>)
OptionTypeDescription
sourceDocumentSource<TDoc>

Where documents come from: the commerce engine's catalog source.

adapterSearchIndexAdapter<TDoc>

Where documents go: the search provider's index adapter.

routes?boolean

Publish the operational routes (/sync/:id, /reindex). Disable for a queue-only deployment that should expose no HTTP surface of its own. Defaults to true.

secret?string

Shared secret required on the operational routes. Callers must present authorization: Bearer <secret>.

More

Required whenever the routes are published — see SearchIndexerModuleOptions.allowUnauthenticatedRoutes.

allowUnauthenticatedRoutes?boolean

Publish the operational routes with no secret at all.

More

/reindex rebuilds an entire catalog, so leaving it open to anyone who can reach the service is a denial-of-service switch and a way to force arbitrary load onto the commerce backend. Opt in explicitly, and only when something else authenticates the caller — a private network, a service mesh, or an ingress that authorizes before forwarding.

events?CatalogEventRouting

Which event types trigger which action. Defaults to defaultEventRouting, whose names are commercetools product events — so a source over anything else has to say what its own events are called.

CatalogEventRouting2The fields of events.
FieldTypeDescription
upsertstring[]

Event types that mean "(re)build the documents for this entity".

deletestring[]

Event types that mean "remove every document for this entity".

MethodDescription
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)
OptionTypeDescription
batchSize?number

Maximum documents per upsert call. Defaults to 100.

deleteBySource?boolean

Advertise and implement deleteBySource. Defaults to true.

fullReindex?boolean

Advertise and implement beginFullReindex. Defaults to true.

MethodDescription
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/searchThe 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/indexingSearchIndexerModule, the IndexSyncService it runs, the DocumentSource port and defaultEventRouting10 exports
@evolve-framework/search/testingInMemoryIndexAdapter and FakeCatalogSource, for testing an indexer or a mapper without a provider, and describeSearchIndexAdapterContract, the scenarios every index adapter meets5 exports