import type { MultipleQueriesQuery, RequestOptions } from 'searchkit' import type Searchkit from 'searchkit' interface InstantSearchElasticsearchAdapterConfig { url: string headers?: Record | (() => Record) onError?: (error: Error) => boolean | void } function createEmptyResult(request: MultipleQueriesQuery) { return { hits: [], nbHits: 0, nbPages: 0, page: 0, processingTimeMS: 0, hitsPerPage: request.params?.hitsPerPage ?? 20, exhaustiveNbHits: true, query: request.params?.query ?? '', params: '', index: request.indexName } } type Config = InstantSearchElasticsearchAdapterConfig | Searchkit function isSearchkit(config: Config): config is Searchkit { return (config as Searchkit).handleInstantSearchRequests !== undefined } class InstantSearchElasticsearchAdapter { private cache: Record constructor(private config: Config, private requestOptions?: RequestOptions) { this.cache = [] if (!isSearchkit(this.config) && !this.config.url) { throw new Error('Searchkit Instantsearch Client: url is required') } if (!isSearchkit(this.config) && this.requestOptions) { throw new Error( 'Searchkit Instantsearch Client: requestOptions is not supported when used with url. Add the request options to @searchkit/api instead.' ) } } public clearCache(): Promise { this.cache = [] return Promise.resolve(undefined) } private getHeaders(): Record { let headers = {} if (!isSearchkit(this.config) && this.config.headers) { headers = typeof this.config.headers === 'function' ? this.config.headers() : this.config.headers } return headers } private handleError(error: Error, requests: readonly MultipleQueriesQuery[]) { const suppressLog = this.config && !isSearchkit(this.config) && this.config.onError?.(error) if (!suppressLog) { console.error('Searchkit InstantSearch Client error:', error.message) } return { results: requests.map((request) => createEmptyResult(request)) } } public async search(instantsearchRequests: readonly MultipleQueriesQuery[]): Promise { const key = JSON.stringify(instantsearchRequests) const cacheValue = this.cache[key] if (cacheValue) { return cacheValue } try { if (isSearchkit(this.config)) { const results = await this.config.handleInstantSearchRequests( instantsearchRequests, this.requestOptions ) this.cache[key] = results return results } const response = await fetch(this.config.url, { body: JSON.stringify(instantsearchRequests), headers: { 'Content-Type': 'application/json', ...this.getHeaders() }, method: 'POST' }) if (!response.ok) { const errorResult = this.handleError( new Error(`Search request failed with status ${response.status}: ${response.statusText}`), instantsearchRequests ) this.cache[key] = errorResult return errorResult } const results = await response.json() this.cache[key] = results return results } catch (error) { const errorResult = this.handleError( error instanceof Error ? error : new Error(String(error)), instantsearchRequests ) this.cache[key] = errorResult return errorResult } } public async searchForFacetValues( instantsearchRequests: readonly MultipleQueriesQuery[] ): Promise { const isr = instantsearchRequests.map((request) => { return { ...request, params: { ...request.params, hitsPerPage: 0 } } }) try { if (isSearchkit(this.config)) { const results = await this.config.handleInstantSearchRequests(isr) return results.results } const response = await fetch(this.config.url, { body: JSON.stringify(isr), headers: { 'Content-Type': 'application/json', ...this.getHeaders() }, method: 'POST' }) if (!response.ok) { const error = new Error(`Search request failed with status ${response.status}: ${response.statusText}`) const suppressLog = this.config && !isSearchkit(this.config) && this.config.onError?.(error) if (!suppressLog) { console.error('Searchkit InstantSearch Client error:', error.message) } return [] } const results = await response.json() return results.results } catch (error) { const err = error instanceof Error ? error : new Error(String(error)) const suppressLog = this.config && !isSearchkit(this.config) && (this.config as InstantSearchElasticsearchAdapterConfig).onError?.(err) if (!suppressLog) { console.error('Searchkit InstantSearch Client error:', err.message) } return [] } } } const createClient = (config: Config, requestOptions?: RequestOptions) : any => new InstantSearchElasticsearchAdapter(config, requestOptions) export default createClient