search-meilisearch API
Everything the package exports, by the path you import it from. Open an export for its signature and options.
@evolve-framework/search-meilisearch
Both adapters, the record shape, defaultIndexName, and the mappers toRecord and toProductDocument
Classes
MeilisearchIndexAdapterMeilisearch as an index adapter.
import { MeilisearchIndexAdapter } from "@evolve-framework/search-meilisearch";One index per store and locale. beginFullReindex builds into a temporary
index and swaps it in, which Meilisearch does atomically — so a rebuild never
serves a half-filled index, and a failed rebuild leaves the live one
untouched. That is the capability Algolia needs a temporary index and a move
for, and it is the reason a full resync here is safe to run against
production.
new MeilisearchIndexAdapter(options: MeilisearchIndexAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
batchSize? | number | Documents per request. Meilisearch has no hard cap; this bounds memory. |
toRecord? | ((document: ProductDocument, locale: string) => MeilisearchProductRecord) | Inverse of the query adapter's reader. Defaults to |
filterableAttributes? | string[] | Attributes that can be filtered and facetted on, beyond the ones every product document has. A project's own attribute names go here. |
facetableAttributes? | string[] | Which product attributes to offer as facets, by their own names —
MoreThis is the same lever Algolia calls Curated rather than open. A wildcard works — and a real catalogue then
returns 224 attribute facets on one page, including a raw id, a
merchandiser's duplicate named Index-wide, because that is as far as Meilisearch's settings reach. Per category needs a layer above this one — Algolia does that with Rules, and Meilisearch has no equivalent. |
sortableAttributes? | string[] | Attributes that can be sorted on, beyond the price fields. |
swapTimeoutMs? | number | How long to wait for the index swap that ends a full rebuild. MoreThe client's own default is five seconds, which is a rebuild of a small catalogue. A swap covering eight locale indexes of several thousand documents takes longer, and the wait then times out on work that goes on to succeed — so a reindex that worked reports failure, and a scheduled one alerts on nothing. |
| Method | Description |
|---|---|
init(): Promise<void> | One-shot, idempotent initialization (build SDK clients, etc.). |
upsertDocuments(
scope: IndexScope,
documents: ProductDocument[],
): Promise<void> | Write documents into the scope's target for each one's type, replacing any stored document with the same id as a whole. |
deleteDocuments(
scope: IndexScope,
ids: string[],
documentTypes: string[],
): Promise<void> | Remove documents by id, from the target of every type in documentTypes. |
deleteBySource(
scope: IndexScope,
sourceIds: string[],
options: { documentTypes: string[]; keep?: string[] | undefined; },
): Promise<void> | Remove everything that came from a source, except what is still current. |
MeilisearchQueryAdapterAnswers product search from Meilisearch, with facets, filters and sorting by relevance, price, name, review rating or newest, reading the index per store and locale that MeilisearchIndexAdapter fills.
import { MeilisearchQueryAdapter } from "@evolve-framework/search-meilisearch";new MeilisearchQueryAdapter(options: MeilisearchQueryAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
facets? | string[] | Attributes to return facet counts for. |
distinct? | string | null | The field results are grouped by, one hit per distinct value. Defaults to
|
toDocument? | ((record: MeilisearchProductRecord, locale: string) => ProductDocument) | Inverse of the index adapter's |
maxTotalHits? | number | The index's |
| Method | Description |
|---|---|
init(): Promise<void> | Create the Meilisearch client from host and apiKey. |
search(
query: ProductSearchQuery,
): Promise<SearchResponse<ProductDocument>> | Run a query and return the documents that matched. |
Functions
defaultIndexNameproducts-evolve-nl-nl.
import { defaultIndexName } from "@evolve-framework/search-meilisearch";Lower-cased and dash-joined: Meilisearch index uids accept only letters, digits, hyphens and underscores, so a locale's underscore-free form is the one that survives.
function defaultIndexName( scope: { storeKey: string; locale: string; }, context: IndexNameContext,): string| Option | Type | Description |
|---|---|---|
storeKey | string | |
locale | string | |
documentType | string |
Returns string
toProductDocumentA record back as a product document.
import { toProductDocument } from "@evolve-framework/search-meilisearch";The record holds one locale's values, so the localized maps are rebuilt with
a single entry. Anything the record does not carry is left absent rather than
defaulted: a caller reading variants off a search hit should find nothing
there and go to the catalogue, not find an empty list and believe it.
function toProductDocument( record: MeilisearchProductRecord, locale: string,): ProductDocument| Option | Type | Description |
|---|---|---|
id | string | |
type | string | |
sourceId | string | |
categories | string[] | Facet values: the category each path ends in, never its ancestors. MoreAn ancestor is true of every product already on the page it would appear on — a shopper in "Boeken > Sport & Outdoor > Buitensport" is offered "Boeken (4)" beside "Hardlopen (2)", and the one that looks like the biggest refinement is the one that refines nothing. Filtering wants the ancestors; faceting wants only what narrows. |
categoryIds | string[] | Filter values: every node of every path, so a branch page matches. |
skus | string[] | |
name? | string | |
slug? | string | |
description? | string | |
brand? | string | |
primaryCategory? | string | |
rating? | number | Average review rating; sortable, so it ranks the rating sort. |
createdAt? | number | Creation time in epoch milliseconds, for the newest sort. A number rather than the ISO string, which sorts by its characters and so misorders two dates written with different offsets or precision. |
image? | string | |
quantityRule? | QuantityRuleDocument | The quantity rule of the variant |
Returns ProductDocument
toRecord
import { toRecord } from "@evolve-framework/search-meilisearch";function toRecord( document: ProductDocument, locale: string,): MeilisearchProductRecordReturns MeilisearchProductRecord
Types
IndexNameContextIndex naming.
import { type IndexNameContext } from "@evolve-framework/search-meilisearch";One index per store and locale, rather than one index holding every language. Meilisearch tokenises and stems per index, so a single index would have to pick one language's rules for all of them — which is what makes a Dutch search for "broeken" miss "broek" while the English stemmer is in charge.
Unlike Algolia there are no sort replicas: Meilisearch sorts any index by any
sortableAttribute, so a sort is a query parameter rather than a separate
index to route to.
export type IndexNameContext = { documentType: string;};IndexNameResolver
import { type IndexNameResolver } from "@evolve-framework/search-meilisearch";export type IndexNameResolver = (scope: { storeKey: string; locale: string;}, context: IndexNameContext) => string;MeilisearchProductRecordThe shared product document as a Meilisearch document.
import { type MeilisearchProductRecord } from "@evolve-framework/search-meilisearch";Localized values are resolved to the index's own locale rather than kept as
a map. An index serves exactly one locale (see index-name), so carrying
every language would make the searchable text of a Dutch index include German
and French — which is how a search for "short" starts matching trousers.
Prices are flattened to one field per currency (price_eur). Meilisearch
sorts and filters on scalar attributes, not on objects inside an array, so a
nested price list can be returned but never sorted by.
export type MeilisearchProductRecord = { id: string; type: string; sourceId: string; name?: string; slug?: string; description?: string; brand?: string; categories: string[]; categoryIds: string[]; primaryCategory?: string; skus: string[]; rating?: number; createdAt?: number; image?: string; quantityRule?: QuantityRuleDocument; [key: string]: unknown;};@evolve-framework/search-meilisearch/indexing
MeilisearchIndexAdapter
Classes
MeilisearchIndexAdapterMeilisearch as an index adapter.
import { MeilisearchIndexAdapter } from "@evolve-framework/search-meilisearch/indexing";One index per store and locale. beginFullReindex builds into a temporary
index and swaps it in, which Meilisearch does atomically — so a rebuild never
serves a half-filled index, and a failed rebuild leaves the live one
untouched. That is the capability Algolia needs a temporary index and a move
for, and it is the reason a full resync here is safe to run against
production.
new MeilisearchIndexAdapter(options: MeilisearchIndexAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
batchSize? | number | Documents per request. Meilisearch has no hard cap; this bounds memory. |
toRecord? | ((document: ProductDocument, locale: string) => MeilisearchProductRecord) | Inverse of the query adapter's reader. Defaults to |
filterableAttributes? | string[] | Attributes that can be filtered and facetted on, beyond the ones every product document has. A project's own attribute names go here. |
facetableAttributes? | string[] | Which product attributes to offer as facets, by their own names —
MoreThis is the same lever Algolia calls Curated rather than open. A wildcard works — and a real catalogue then
returns 224 attribute facets on one page, including a raw id, a
merchandiser's duplicate named Index-wide, because that is as far as Meilisearch's settings reach. Per category needs a layer above this one — Algolia does that with Rules, and Meilisearch has no equivalent. |
sortableAttributes? | string[] | Attributes that can be sorted on, beyond the price fields. |
swapTimeoutMs? | number | How long to wait for the index swap that ends a full rebuild. MoreThe client's own default is five seconds, which is a rebuild of a small catalogue. A swap covering eight locale indexes of several thousand documents takes longer, and the wait then times out on work that goes on to succeed — so a reindex that worked reports failure, and a scheduled one alerts on nothing. |
| Method | Description |
|---|---|
init(): Promise<void> | One-shot, idempotent initialization (build SDK clients, etc.). |
upsertDocuments(
scope: IndexScope,
documents: ProductDocument[],
): Promise<void> | Write documents into the scope's target for each one's type, replacing any stored document with the same id as a whole. |
deleteDocuments(
scope: IndexScope,
ids: string[],
documentTypes: string[],
): Promise<void> | Remove documents by id, from the target of every type in documentTypes. |
deleteBySource(
scope: IndexScope,
sourceIds: string[],
options: { documentTypes: string[]; keep?: string[] | undefined; },
): Promise<void> | Remove everything that came from a source, except what is still current. |
@evolve-framework/search-meilisearch/query
MeilisearchQueryAdapter, plus buildFilter and mapFacets for a query adapter of your own
Classes
MeilisearchQueryAdapterAnswers product search from Meilisearch, with facets, filters and sorting by relevance, price, name, review rating or newest, reading the index per store and locale that MeilisearchIndexAdapter fills.
import { MeilisearchQueryAdapter } from "@evolve-framework/search-meilisearch/query";new MeilisearchQueryAdapter(options: MeilisearchQueryAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
facets? | string[] | Attributes to return facet counts for. |
distinct? | string | null | The field results are grouped by, one hit per distinct value. Defaults to
|
toDocument? | ((record: MeilisearchProductRecord, locale: string) => ProductDocument) | Inverse of the index adapter's |
maxTotalHits? | number | The index's |
| Method | Description |
|---|---|
init(): Promise<void> | Create the Meilisearch client from host and apiKey. |
search(
query: ProductSearchQuery,
): Promise<SearchResponse<ProductDocument>> | Run a query and return the documents that matched. |
Functions
buildFilterEverything that narrows a query, as one expression.
import { buildFilter } from "@evolve-framework/search-meilisearch/query";Facet selections and prefilters are joined with AND, so a prefilter a project sets can never be widened by what a shopper picks. A prefilter matches any of its values.
function buildFilter( options: { filters?: FacetFilter[] | undefined; prefilters?: FilterCondition[] | undefined; documentTypes?: string[] | undefined; categoryId?: string | undefined; skus?: string[] | undefined; },): string | undefined| Option | Type | Description |
|---|---|---|
filters? | FacetFilter[] | |
prefilters? | FilterCondition[] | |
documentTypes? | string[] | |
categoryId? | string | |
skus? | string[] |
Returns string | undefined
mapFacets
import { mapFacets } from "@evolve-framework/search-meilisearch/query";function mapFacets( distribution: FacetDistribution | undefined, stats: FacetStats | undefined, selected: FacetFilter[] | undefined,): FacetResult[]Returns FacetResult[]
toProductDocumentA record back as a product document.
import { toProductDocument } from "@evolve-framework/search-meilisearch/query";The record holds one locale's values, so the localized maps are rebuilt with
a single entry. Anything the record does not carry is left absent rather than
defaulted: a caller reading variants off a search hit should find nothing
there and go to the catalogue, not find an empty list and believe it.
function toProductDocument( record: MeilisearchProductRecord, locale: string,): ProductDocument| Option | Type | Description |
|---|---|---|
id | string | |
type | string | |
sourceId | string | |
categories | string[] | Facet values: the category each path ends in, never its ancestors. MoreAn ancestor is true of every product already on the page it would appear on — a shopper in "Boeken > Sport & Outdoor > Buitensport" is offered "Boeken (4)" beside "Hardlopen (2)", and the one that looks like the biggest refinement is the one that refines nothing. Filtering wants the ancestors; faceting wants only what narrows. |
categoryIds | string[] | Filter values: every node of every path, so a branch page matches. |
skus | string[] | |
name? | string | |
slug? | string | |
description? | string | |
brand? | string | |
primaryCategory? | string | |
rating? | number | Average review rating; sortable, so it ranks the rating sort. |
createdAt? | number | Creation time in epoch milliseconds, for the newest sort. A number rather than the ISO string, which sorts by its characters and so misorders two dates written with different offsets or precision. |
image? | string | |
quantityRule? | QuantityRuleDocument | The quantity rule of the variant |
Returns ProductDocument
Types
FacetDistributionMeilisearch's facet distribution as the contract's facet results.
import { type FacetDistribution } from "@evolve-framework/search-meilisearch/query";Meilisearch reports counts per value and, for numeric attributes, the observed minimum and maximum. It does not say what kind of facet an attribute is, so the kind is inferred: a numeric stat is a range, two boolean values are a toggle, anything else is a list of options.
Inferring beats configuring here because the alternative is a second place that has to agree with the index settings — and when they disagree the facet silently renders as the wrong control.
export type FacetDistribution = Record<string, Record<string, number>>;FacetStats
import { type FacetStats } from "@evolve-framework/search-meilisearch/query";export type FacetStats = Record<string, { min: number; max: number;}>;Not listed: 4 options types. They appear as the tables under the constructors and functions they type.

