UNPKG

@meilisearch/instant-meilisearch

Version:
167 lines (148 loc) 4.99 kB
import type { Filter, SearchContext } from '../../types/index.js' const filterEscapeRegExp = /([\\"])/g function getValueWithEscapedBackslashesAndQuotes(value: string): string { return value.replace(filterEscapeRegExp, '\\$1') } /** * Transform InstantSearch [facet * filter](https://www.algolia.com/doc/api-reference/api-parameters/facetFilters/) * to Meilisearch compatible filter format. Change sign from `:` to `=` * "facet:facetValue" becomes "facet=facetValue" * * Wrap both the facet and its facet value between quotes. This avoids * formatting issues on facets containing multiple words. Escape backslash \ and * quote " characters. * * 'My facet:My facet value' becomes '"My facet":"My facet value"' * * @param {string} filter * @returns {string} */ function transformFacetFilter(filter: string): string { const escapedFilter = getValueWithEscapedBackslashesAndQuotes(filter) const colonIndex = escapedFilter.indexOf(':') const attribute = escapedFilter.slice(0, colonIndex) const value = escapedFilter.slice(colonIndex + 1) return `"${attribute}"="${value}"` } /** * Transform InstantSearch [numeric * filter](https://www.algolia.com/doc/api-reference/api-parameters/numericFilters/) * to Meilisearch compatible filter format. * * 'price:5.99 TO 100' becomes '"price" 5.99 TO 100' * * 'price = 5.99' becomes '"price"=5.99' * * Wrap the attribute between quotes. Escape backslash \ and quote " characters. * * @param {string} filter * @returns {string} */ function transformNumericFilter(filter: string): string { const splitNumericFilter = (): [string, string, string] => { const attributeMatch = filter.match(/^([^<!>:=]*)([<!>:=]+)(.*)$/) if (attributeMatch) { const [attribute, dirtyOperator, valueEnd] = attributeMatch.slice(1) const operatorMatch = dirtyOperator.match(/^([<!>]?=|<|>|:){1}(.*)/) || [ '', '', ] const [operator, valueStart] = operatorMatch.slice(1) const cleanedValue = valueStart + valueEnd return [attribute, operator, cleanedValue] } return [filter, '', ''] } const [attribute, operator, value] = splitNumericFilter() const escapedAttribute = getValueWithEscapedBackslashesAndQuotes(attribute) return `"${escapedAttribute.trim()}"${ operator === ':' ? ' ' : operator }${value.trim()}` } /** * Iterate over all filters. Return the filters in a Meilisearch compatible * format. * * @param {(filter: string) => string} transformCallback * @param {SearchContext['facetFilters']} filters * @returns {Filter} */ function transformFilters( transformCallback: (filter: string) => string, filters: NonNullable<SearchContext['facetFilters']> ): Filter { return typeof filters === 'string' ? transformCallback(filters) : filters.map((filter) => typeof filter === 'string' ? transformCallback(filter) : filter.map((nestedFilter) => transformCallback(nestedFilter)) ) } /** * Return the filter in an array if it is a string If filter is array, return * without change. * * @param {Filter} [filter] * @returns {Array | undefined} */ function filterToArray(filter?: Filter): Array<string | string[]> | undefined { return typeof filter === 'string' ? [filter] : filter } /** * Merge filters, transformedNumericFilters and transformedFacetFilters * together. * * @param {string} filters * @param {Filter} transformedNumericFilters * @param {Filter} transformedFacetFilters * @returns {Filter} */ function mergeFilters( filters?: string, transformedNumericFilters?: Filter, transformedFacetFilters?: Filter ): Filter { const adaptedNumericFilters = filterToArray(transformedNumericFilters) const adaptedFacetFilters = filterToArray(transformedFacetFilters) const adaptedFilters: Filter = [] if (filters !== undefined) { adaptedFilters.push(filters) } if (adaptedNumericFilters !== undefined) { adaptedFilters.push(...adaptedNumericFilters) } if (adaptedFacetFilters !== undefined) { adaptedFilters.push(...adaptedFacetFilters) } return adaptedFilters } /** * Adapt instantsearch.js filters to Meilisearch filters by combining and * transforming all provided filters. * * @param {string | undefined} filters * @param {SearchContext['numericFilters']} numericFilters * @param {SearchContext['facetFilters']} facetFilters * @returns {Filter} */ export function adaptFilters( filters: string | undefined, numericFilters: SearchContext['numericFilters'], facetFilters: SearchContext['facetFilters'] ): Filter { const transformedNumericFilters = numericFilters !== undefined ? transformFilters(transformNumericFilter, numericFilters) : numericFilters const transformedFacetFilters = facetFilters !== undefined ? transformFilters(transformFacetFilter, facetFilters) : facetFilters return mergeFilters( filters, transformedNumericFilters, transformedFacetFilters ) }