search-algolia API
Everything the package exports, by the path you import it from. Open an export for its signature and options.
@evolve-framework/search-algolia
Both adapters, the index naming (defaultIndexName, localeIndexName), and the record mappers (toAlgoliaRecord and toProductDocument, toVariantRecord and fromVariantRecord)
Classes
AlgoliaIndexAdapterWrites documents to Algolia.
import { AlgoliaIndexAdapter } from "@evolve-framework/search-algolia";Every method throws on failure rather than logging and returning: a swallowed error acks the queue message that triggered the write, losing the update with no trace.
new AlgoliaIndexAdapter(options: AlgoliaIndexAdapterOptions<TRecord>)| Option | Type | Description |
|---|---|---|
appId | string | The Algolia application. |
writeApiKey | string | A key allowed to write and configure indices. Never give it a browser. |
resolveIndexName? | IndexNameResolver | Override index naming to match an existing convention. Defaults to
|
toRecord? | ((document: ProductDocument, scope: IndexScope) => TRecord | TRecord[]) | Override how a document becomes stored records. Returning several records
fans one document out; the sync engine's MoreThe scope is passed so a record can be written for the target it is going to — flattening localized values to the index's language, say. |
batchSize? | number | Algolia accepts up to 1000 objects per batch. |
atomicReindex? | boolean | Rebuild through a temporary index that is moved into place. Defaults to true, and there is rarely a reason to turn it off — a move to a destination that has replicas is supported and propagates the new data to them. MoreSetting it to |
| 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 ids from every declared type's index. |
deleteBySource(
scope: IndexScope,
sourceIds: string[],
options: DeleteBySourceOptions,
): Promise<void> | Remove every record a product owns, except the ids just written, across every declared type's index. |
applySettings(scope: IndexScope, settings: unknown): Promise<void> | Push provider index configuration (Algolia settings and replicas, …). |
AlgoliaQueryAdapterReads documents back out of Algolia.
import { AlgoliaQueryAdapter } from "@evolve-framework/search-algolia";Separate from the index adapter so an API deployment holds only a search key.
new AlgoliaQueryAdapter(options: AlgoliaQueryAdapterOptions<TRecord>)| Option | Type | Description |
|---|---|---|
appId | string | The Algolia application. |
searchApiKey | string | A search-only key: this adapter never writes. |
resolveIndexName? | IndexNameResolver | Defaults to |
toDocument? | ((record: TRecord, locale: string) => ProductDocument) | Inverse of the index adapter's MoreThe request's locale is passed so a record whose localized values were flattened for its index can be read back under the locale being asked for. |
buildQuery? | ((queries: SearchForHits[], query: ProductSearchQuery) => SearchForHits[]) | Adjust the queries before they are sent — rule contexts, optional
filters, |
distinct? | number | boolean | Deduplicate hits by the index's MoreRequired by the variant-record model: with a record per variant, a search
without this returns every variant of a product as its own row. Set it to
|
scopeGranularity? | "language" | "locale" | "store" | Must match the More
|
paginationLimitedTo? | number | The index's |
clickAnalytics? | boolean | Ask Algolia for a query id on every search, which click and conversion events send back so Insights attributes them to the search. Off by default, as Algolia only issues one when asked. |
querySuggestions? | boolean | Read suggestions from the index's Algolia Query Suggestions index, named
by |
| Method | Description |
|---|---|
init(): Promise<void> | Create the Algolia client from appId and searchApiKey. |
search(
query: ProductSearchQuery,
): Promise<SearchResponse<ProductDocument>> | Run a query and return the documents that matched. |
suggest(query: SuggestQuery): Promise<SuggestResponse> | Implement when capabilities.suggestions is true. |
Functions
createCategoryPathTurn a category path into Algolia's breadcrumb strings.
import { createCategoryPath } from "@evolve-framework/search-algolia";Each level repeats its ancestors, which is what Algolia's hierarchical menu
widget expects:
["clothes", "clothes > men", "clothes > men > pants"].
function createCategoryPath(path: CategoryPath): string[]Returns string[]
defaultIndexName<what>-<store>[-<language>][-<sort>], e.g.
import { defaultIndexName } from "@evolve-framework/search-algolia";The segment is the language, which is what Algolia's own language settings
are keyed on (indexLanguages takes ISO 639-1). It appears when every locale
in the scope shares one language — so a scope of ["nl-NL", "nl-BE"] is
products-<store>-nl, and a scope spanning several languages gets no segment
at all.
That rule is what keeps the halves in step: a per-language deployment names
the language, a per-store one does not, and neither needs a second setting
that could disagree with the first. Pair localeIndexName with per-locale
scopes when regional variants need indices of their own.
The leading segment names what the index holds, which is also the document
type — a second index of bundles is bundles-evolve-nl. The primary index
carries no sort segment; only replicas do.
Algolia has no per-request sort — each order is a replica index of its own — so the sort segment is the read side's business. Writes always go to the primary and Algolia propagates them to the replicas.
function defaultIndexName( scope: { storeKey: string; locales?: string[] | undefined; }, context: IndexNameContext,): string| Option | Type | Description |
|---|---|---|
storeKey | string | |
documentType | string | |
locales? | string[] | |
sort? | SortSpec | Set on the read path; picks a sort replica. |
suggestions? | boolean | Set when querying the query-suggestions index. |
Returns string
expandTextPut a flattened value back under the locale it is being read for, so the read side sees the same shape whichever granularity wrote the record.
import { expandText } from "@evolve-framework/search-algolia";function expandText( value: RecordText | undefined, locale: string,): LocalizedText | undefinedReturns LocalizedText | undefined
flattenTextReduce a localized value to the one string a per-language index needs.
import { flattenText } from "@evolve-framework/search-algolia";Worth doing rather than storing the map: Algolia caps record size (10KB on
some plans) and its guidance is to send only what search and display use, and
a flattened record also lets facets and searchable attributes be declared as
attributes.color instead of one path per locale.
function flattenText( value: LocalizedText | undefined, targets: string[] | undefined,): RecordText | undefinedReturns RecordText | undefined
fromVariantRecordRebuild the document.
import { fromVariantRecord } from "@evolve-framework/search-algolia";Flattened text goes back under locale, so the read side sees the same shape
whichever granularity wrote the record. Pass the locale the query was made
for — with per-language indices a nl-BE request reads a record flattened
from nl-NL, and it is the request's locale the GraphQL layer looks up.
function fromVariantRecord( record: AlgoliaVariantRecord, locale?: string,): ProductDocument| Option | Type | Description |
|---|---|---|
objectID | string | |
sourceId | string | The product this variant belongs to; the deduplication key. |
type | string | |
name | RecordText | |
slug | RecordText | |
categories | RecordCategoryPath[] | |
hierarchicalCategories | ProductCategoryHierarchy | Algolia's hierarchical-menu shape, derived from |
categoryPageId | string[] | Breadcrumb strings, for the hierarchical menu and category facets. |
categoryIds | string[] | Every category the product is in, ancestors included. MoreAncestors come for free because a document's category paths run root →
leaf, and including them is what makes a category page behave like
commercetools' |
variantId | string | |
sku | string | |
images | string[] | |
prices | PriceDocument[] | The representative variant's own prices. |
startingPrice | PriceDocument[] | Lowest price across the group, one entry per currency/country. MoreStored in its own attribute because it cannot be recovered from the rest of
the record: the mapper computes it across every variant a group may span,
including variants of related products, so reconstructing it from
|
attributes | RecordAttributeMap | |
variants | { id: string; sku: string; images: string[]; prices: PriceDocument[]; attributes: RecordAttributeMap; dimensions?: RecordDimension[] | undefined; availability?:… | Every variant in the group, including the representative one. |
brand? | string | |
meta? | { title?: RecordText | undefined; description?: RecordText | undefined; } | |
primaryCategory? | RecordCategoryPath | |
variantDimensions? | RecordVariantDimension[] | The full picker matrix; a deduplicated hit still renders every option. |
image? | string | |
price? | PriceDocument | The group's lowest price, for ranking. Taken from the document's
|
rating? | number | Average review rating, the attribute a |
dimensions? | RecordDimension[] | |
availability? | RecordAvailability | |
ribbon? | RecordRibbon | |
quantityRule? | QuantityRuleDocument | |
localizedAttributes? | string[] | Attribute keys whose values were localized before flattening. MoreA flattened |
extraKeys? | string[] | Which top-level keys came from MoreConsumer fields are spread flat so they are directly filterable, which also
makes them indistinguishable from the adapter's own on the way back.
Recording the key names is what lets the read side rebuild |
Returns ProductDocument
localeIndexNameThe full locale rather than its language subtag: products-evolve-nl-be.
import { localeIndexName } from "@evolve-framework/search-algolia";Necessary when a store serves several variants of one language, where
defaultIndexName would resolve them to the same index.
function localeIndexName( scope: { storeKey: string; locales?: string[] | undefined; }, context: IndexNameContext,): string| Option | Type | Description |
|---|---|---|
storeKey | string | |
documentType | string | |
locales? | string[] | |
sort? | SortSpec | Set on the read path; picks a sort replica. |
suggestions? | boolean | Set when querying the query-suggestions index. |
Returns string
localeTargetsThe locales to try, in order, when flattening a localized value for a scope — or undefined when the scope spans several languages and the maps have to be kept.
import { localeTargets } from "@evolve-framework/search-algolia";The bare language comes first, then the scope's locales in order: a Dutch
index built from ["nl-NL", "nl-BE"] prefers a nl value and falls back to
nl-NL, then nl-BE. Commercetools projects are usually keyed by locale
rather than language, so the fallback is what actually supplies the text.
function localeTargets(locales: string[] | undefined): string[] | undefinedReturns string[] | undefined
toAlgoliaRecordMap a document to the stored record.
import { toAlgoliaRecord } from "@evolve-framework/search-algolia";centAmount comes from the gross amount. commercetools stores one amount per
price and a flag saying whether tax is included; with no tax configured the
document's net and gross are both that stored amount, so this reproduces what
the previous mapper wrote. Once tax is configured, gross is the
shopper-facing figure.
function toAlgoliaRecord(document: ProductDocument): AlgoliaProductRecordReturns AlgoliaProductRecord
toProductDocumentMap a stored record back to a document, for the read path.
import { toProductDocument } from "@evolve-framework/search-algolia";Lossy by construction: the record was never designed to round-trip. Fields it does not carry (per-locale category names, net prices, variant dimensions) come back absent rather than empty, so the GraphQL layer hydrates them instead of rendering a stub.
function toProductDocument(record: StoredAlgoliaProductRecord): ProductDocument| Option | Type | Description |
|---|---|---|
image | string | undefined | |
objectID | string | |
name | LocalizedText | |
slug | LocalizedText | |
description | LocalizedText | |
variants | AlgoliaProductVariant[] | |
categoryPageId | string[] | |
categoryIds | string[] | Every category the product is in, ancestors included. |
hierarchicalCategories | ProductCategoryHierarchy | |
startingPrice | AlgoliaProductPrice | |
rating? | number | Average review rating, the attribute a |
type? | string | Document type, for indices holding more than products. |
_doc? | ProductDocument | The canonical document, stored verbatim. MoreThe fields above are the shape this integration has always written, and the storefront's InstantSearch code reads them directly — so they stay. But that shape cannot express everything a document carries (localized category paths, the variant dimension matrix, ribbons), and a field the record drops is a field the read side has to fetch from the commerce backend. Keeping the document itself makes the round trip lossless. Absent on records written before this existed; those fall back to reconstructing what the legacy fields can express. Costs record size — roughly double. Algolia caps a record at 10KB on some plans; a catalog with very large products should check. |
sourceId? | string |
Returns ProductDocument
toVariantRecordMap a document to its record.
import { toVariantRecord } from "@evolve-framework/search-algolia";VariantDocumentMapper produces one document per variant, or per group when
given a groupBy. Either way the first variant is the representative one
carrying the card's sku, image and price, while the group as a whole is kept
in variants.
The scope decides how localized values are stored: a per-language index gets plain strings, an index holding several languages keeps the maps.
function toVariantRecord( document: ProductDocument, scope?: Pick<IndexScope, "locales">,): AlgoliaVariantRecord| Option | Type | Description |
|---|---|---|
locales | string[] | Locales to index content for. |
Returns AlgoliaVariantRecord
Types
AlgoliaAttributeValue
import { type AlgoliaAttributeValue } from "@evolve-framework/search-algolia";export type AlgoliaAttributeValue = string | boolean | Record<string, string>;AlgoliaIndexAdapterOptions
import { type AlgoliaIndexAdapterOptions } from "@evolve-framework/search-algolia";export type AlgoliaIndexAdapterOptions<TRecord extends AlgoliaRecordBase = AlgoliaProductRecord> = { appId: string; writeApiKey: string; resolveIndexName?: IndexNameResolver; toRecord?: (document: ProductDocument, scope: IndexScope) => TRecord | TRecord[]; batchSize?: number; atomicReindex?: boolean;};AlgoliaProductPrice
import { type AlgoliaProductPrice } from "@evolve-framework/search-algolia";export type AlgoliaProductPrice = { currency: string; centAmount: number;};AlgoliaProductRecordThe record shape stored in Algolia.
import { type AlgoliaProductRecord } from "@evolve-framework/search-algolia";Deliberately unchanged from what @evolve-framework/commercetools/algolia
has always written: live indices and the InstantSearch code reading them stay
untouched by this refactor. The canonical ProductDocument is the
contract between framework and adapter, not the storage format — this
module maps between the two in both directions.
export type AlgoliaProductRecord = { objectID: string; name: LocalizedText; slug: LocalizedText; description: LocalizedText; variants: AlgoliaProductVariant[]; categoryPageId: string[]; categoryIds: string[]; hierarchicalCategories: ProductCategoryHierarchy; startingPrice: AlgoliaProductPrice; rating?: number; image: string | undefined; sourceId: string; type?: string; _doc?: ProductDocument;};AlgoliaProductVariant
import { type AlgoliaProductVariant } from "@evolve-framework/search-algolia";export type AlgoliaProductVariant = { id: string; sku: string; image: string | undefined; price: AlgoliaProductPrice; attributes: Record<string, AlgoliaAttributeValue>;};AlgoliaQueryAdapterOptions
import { type AlgoliaQueryAdapterOptions } from "@evolve-framework/search-algolia";export type AlgoliaQueryAdapterOptions<TRecord = AlgoliaProductRecord> = { appId: string; searchApiKey: string; resolveIndexName?: IndexNameResolver; toDocument?: (record: TRecord, locale: string) => ProductDocument; buildQuery?: (queries: SearchForHits[], query: ProductSearchQuery) => SearchForHits[]; distinct?: boolean | number; scopeGranularity?: "language" | "locale" | "store"; paginationLimitedTo?: number; clickAnalytics?: boolean; querySuggestions?: boolean;};AlgoliaVariantRecordA record per variant, or per group of variants.
import { type AlgoliaVariantRecord } from "@evolve-framework/search-algolia";Flat and explicit rather than a copy of the document: Algolia's guidance is to send only what search and display need, since record size is capped (10KB on some plans). Every field here is searched, filtered, or rendered on a card.
Shared attributes sit at attributes.* rather than nested under variants.
That is the point of the model: a facet filter applies to the record, so
"green AND size 40" cannot match a product that is green in 38 and black in
40 — which is what the product-level model does unless the frontend
post-processes the hits.
sourceId is the product, and doubles as attributeForDistinct: with
distinct enabled a search returns the best-matching record per product.
export type AlgoliaVariantRecord = { objectID: string; sourceId: string; type: string; name: RecordText; slug: RecordText; brand?: string; meta?: { title?: RecordText; description?: RecordText; }; categories: RecordCategoryPath[]; primaryCategory?: RecordCategoryPath; hierarchicalCategories: ProductCategoryHierarchy; categoryPageId: string[]; categoryIds: string[]; variantDimensions?: RecordVariantDimension[]; variantId: string; sku: string; image?: string; images: string[]; price?: PriceDocument; prices: PriceDocument[]; startingPrice: PriceDocument[]; rating?: number; attributes: RecordAttributeMap; dimensions?: RecordDimension[]; availability?: RecordAvailability; ribbon?: RecordRibbon; quantityRule?: QuantityRuleDocument; // …IndexNameContext
import { type IndexNameContext } from "@evolve-framework/search-algolia";export type IndexNameContext = { documentType: string; sort?: SortSpec; suggestions?: boolean;};IndexNameResolver
import { type IndexNameResolver } from "@evolve-framework/search-algolia";export type IndexNameResolver = (scope: { storeKey: string; locales?: string[];}, context: IndexNameContext) => string;ProductCategoryHierarchy
import { type ProductCategoryHierarchy } from "@evolve-framework/search-algolia";export type ProductCategoryHierarchy = { lvl0: string | undefined; lvl1: string | undefined; lvl2: string | undefined; lvl3: string | undefined;};RecordAttributeMap
import { type RecordAttributeMap } from "@evolve-framework/search-algolia";export type RecordAttributeMap = Record<string, RecordAttributeValue>;RecordAvailability
import { type RecordAvailability } from "@evolve-framework/search-algolia";export type RecordAvailability = { inStock: boolean; quantity?: number; deliveryTime?: RecordText;};RecordCategoryLocalized values are reduced to a single string on a per-language index, and kept as maps when one index holds several languages — so every text field is either.
import { type RecordCategory } from "@evolve-framework/search-algolia";export type RecordCategory = { id: string; key: string; name: RecordText; slug: RecordText; description?: RecordText;};RecordCategoryPath
import { type RecordCategoryPath } from "@evolve-framework/search-algolia";export type RecordCategoryPath = RecordCategory[];RecordDimension
import { type RecordDimension } from "@evolve-framework/search-algolia";export type RecordDimension = { key: string; label: RecordText; value: string; valueLabel: RecordText;};RecordRibbon
import { type RecordRibbon } from "@evolve-framework/search-algolia";export type RecordRibbon = { kind: string; label: RecordText;};RecordTextA text field in a stored record: a plain string on a per-language index, or the locale map when one index holds several languages.
import { type RecordText } from "@evolve-framework/search-algolia";export type RecordText = string | LocalizedText;RecordVariantDimension
import { type RecordVariantDimension } from "@evolve-framework/search-algolia";export type RecordVariantDimension = { name: string; label: RecordText; type: "text" | "color" | "image"; values: { value: string; label: RecordText; color?: string; variants: VariantRef[]; }[];};@evolve-framework/search-algolia/indexing
AlgoliaIndexAdapter
Classes
AlgoliaIndexAdapterWrites documents to Algolia.
import { AlgoliaIndexAdapter } from "@evolve-framework/search-algolia/indexing";Every method throws on failure rather than logging and returning: a swallowed error acks the queue message that triggered the write, losing the update with no trace.
new AlgoliaIndexAdapter(options: AlgoliaIndexAdapterOptions<TRecord>)| Option | Type | Description |
|---|---|---|
appId | string | The Algolia application. |
writeApiKey | string | A key allowed to write and configure indices. Never give it a browser. |
resolveIndexName? | IndexNameResolver | Override index naming to match an existing convention. Defaults to
|
toRecord? | ((document: ProductDocument, scope: IndexScope) => TRecord | TRecord[]) | Override how a document becomes stored records. Returning several records
fans one document out; the sync engine's MoreThe scope is passed so a record can be written for the target it is going to — flattening localized values to the index's language, say. |
batchSize? | number | Algolia accepts up to 1000 objects per batch. |
atomicReindex? | boolean | Rebuild through a temporary index that is moved into place. Defaults to true, and there is rarely a reason to turn it off — a move to a destination that has replicas is supported and propagates the new data to them. MoreSetting it to |
| 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 ids from every declared type's index. |
deleteBySource(
scope: IndexScope,
sourceIds: string[],
options: DeleteBySourceOptions,
): Promise<void> | Remove every record a product owns, except the ids just written, across every declared type's index. |
applySettings(scope: IndexScope, settings: unknown): Promise<void> | Push provider index configuration (Algolia settings and replicas, …). |
Types
AlgoliaIndexAdapterOptions
import { type AlgoliaIndexAdapterOptions } from "@evolve-framework/search-algolia/indexing";export type AlgoliaIndexAdapterOptions<TRecord extends AlgoliaRecordBase = AlgoliaProductRecord> = { appId: string; writeApiKey: string; resolveIndexName?: IndexNameResolver; toRecord?: (document: ProductDocument, scope: IndexScope) => TRecord | TRecord[]; batchSize?: number; atomicReindex?: boolean;};@evolve-framework/search-algolia/query
AlgoliaQueryAdapter, plus buildQueries and mapFacets for a query adapter of your own
Classes
AlgoliaQueryAdapterReads documents back out of Algolia.
import { AlgoliaQueryAdapter } from "@evolve-framework/search-algolia/query";Separate from the index adapter so an API deployment holds only a search key.
new AlgoliaQueryAdapter(options: AlgoliaQueryAdapterOptions<TRecord>)| Option | Type | Description |
|---|---|---|
appId | string | The Algolia application. |
searchApiKey | string | A search-only key: this adapter never writes. |
resolveIndexName? | IndexNameResolver | Defaults to |
toDocument? | ((record: TRecord, locale: string) => ProductDocument) | Inverse of the index adapter's MoreThe request's locale is passed so a record whose localized values were flattened for its index can be read back under the locale being asked for. |
buildQuery? | ((queries: SearchForHits[], query: ProductSearchQuery) => SearchForHits[]) | Adjust the queries before they are sent — rule contexts, optional
filters, |
distinct? | number | boolean | Deduplicate hits by the index's MoreRequired by the variant-record model: with a record per variant, a search
without this returns every variant of a product as its own row. Set it to
|
scopeGranularity? | "language" | "locale" | "store" | Must match the More
|
paginationLimitedTo? | number | The index's |
clickAnalytics? | boolean | Ask Algolia for a query id on every search, which click and conversion events send back so Insights attributes them to the search. Off by default, as Algolia only issues one when asked. |
querySuggestions? | boolean | Read suggestions from the index's Algolia Query Suggestions index, named
by |
| Method | Description |
|---|---|
init(): Promise<void> | Create the Algolia client from appId and searchApiKey. |
search(
query: ProductSearchQuery,
): Promise<SearchResponse<ProductDocument>> | Run a query and return the documents that matched. |
suggest(query: SuggestQuery): Promise<SuggestResponse> | Implement when capabilities.suggestions is true. |
Functions
buildQueriesBuild the Algolia queries for one product search.
import { buildQueries } from "@evolve-framework/search-algolia/query";More than one query is sent: the first returns the hits, and each active facet filter gets a companion query with that filter removed. Without them a facet would only ever report the options still matching the current selection, so a shopper could never widen a choice they had already narrowed.
function buildQueries( indexName: string, query: ProductSearchQuery,): SearchForHits[]Returns SearchForHits[]
mapFacetsTurn Algolia's facet payload into the normalized results.
import { mapFacets } from "@evolve-framework/search-algolia/query";Algolia has no notion of a facet kind — every facet comes back as a value
histogram, plus a facets_stats entry when the values are numeric. Which of
those is meant to render as a range slider is a presentation decision, and
Algolia keeps it in the dashboard's renderingContent. So the kind is
inferred here rather than being declared anywhere in the document.
renderingContent selects and orders the facets when it is set. When it is
not, every facet Algolia returned is surfaced and the kind is inferred from
whether the values are numeric — an unconfigured index shows its facets with
raw attribute keys for labels, rather than showing none.
function mapFacets( hits: SearchResponse<unknown> | undefined, facetResponses: SearchResponse<unknown>[], filters: FacetFilter[], currency?: string,): FacetResult[]Returns FacetResult[]
Types
AlgoliaQueryAdapterOptions
import { type AlgoliaQueryAdapterOptions } from "@evolve-framework/search-algolia/query";export type AlgoliaQueryAdapterOptions<TRecord = AlgoliaProductRecord> = { appId: string; searchApiKey: string; resolveIndexName?: IndexNameResolver; toDocument?: (record: TRecord, locale: string) => ProductDocument; buildQuery?: (queries: SearchForHits[], query: ProductSearchQuery) => SearchForHits[]; distinct?: boolean | number; scopeGranularity?: "language" | "locale" | "store"; paginationLimitedTo?: number; clickAnalytics?: boolean; querySuggestions?: boolean;};FacetStats
import { type FacetStats } from "@evolve-framework/search-algolia/query";export type FacetStats = { min?: number; max?: number;};
