From 9f41065dd03e50b848b2cf73c2b72d7accd2afda Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:36:52 +0300 Subject: [PATCH 01/15] Improve HttpRequests --- src/clients/client.ts | 50 +++-- src/http-requests.ts | 374 +++++++++++++++----------------------- src/indexes.ts | 412 ++++++++++++++++++++++++------------------ src/task.ts | 36 ++-- src/types/types.ts | 4 +- tests/client.test.ts | 46 +++-- tsconfig.json | 2 +- 7 files changed, 459 insertions(+), 465 deletions(-) diff --git a/src/clients/client.ts b/src/clients/client.ts index 0cef642a9..0d21e756a 100644 --- a/src/clients/client.ts +++ b/src/clients/client.ts @@ -117,9 +117,8 @@ class Client { parameters: IndexesQuery = {}, ): Promise> { const url = `indexes`; - return await this.httpRequest.get>( - url, - parameters, + return >( + await this.httpRequest.get({ relativeURL: url, params: parameters }) ); } @@ -188,7 +187,11 @@ class Client { */ async swapIndexes(params: SwapIndexesParams): Promise { const url = "/swap-indexes"; - return await this.httpRequest.post(url, params); + const taks = ( + await this.httpRequest.post({ relativeURL: url, body: params }) + ); + + return new EnqueuedTask(taks); } /// @@ -230,7 +233,9 @@ class Client { ): Promise | SearchResponse> { const url = `multi-search`; - return await this.httpRequest.post(url, queries, undefined, config); + return | SearchResponse>( + await this.httpRequest.post({ relativeURL: url, body: queries }) + ); } /// @@ -323,7 +328,9 @@ class Client { */ async getKeys(parameters: KeysQuery = {}): Promise { const url = `keys`; - const keys = await this.httpRequest.get(url, parameters); + const keys = ( + await this.httpRequest.get({ relativeURL: url, params: parameters }) + ); keys.results = keys.results.map((key) => ({ ...key, @@ -342,7 +349,7 @@ class Client { */ async getKey(keyOrUid: string): Promise { const url = `keys/${keyOrUid}`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -353,7 +360,9 @@ class Client { */ async createKey(options: KeyCreation): Promise { const url = `keys`; - return await this.httpRequest.post(url, options); + return ( + await this.httpRequest.post({ relativeURL: url, body: options }) + ); } /** @@ -365,7 +374,9 @@ class Client { */ async updateKey(keyOrUid: string, options: KeyUpdate): Promise { const url = `keys/${keyOrUid}`; - return await this.httpRequest.patch(url, options); + return ( + await this.httpRequest.patch({ relativeURL: url, body: options }) + ); } /** @@ -376,7 +387,7 @@ class Client { */ async deleteKey(keyOrUid: string): Promise { const url = `keys/${keyOrUid}`; - return await this.httpRequest.delete(url); + await this.httpRequest.delete({ relativeURL: url }); } /// @@ -390,7 +401,7 @@ class Client { */ async health(): Promise { const url = `health`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -400,9 +411,8 @@ class Client { */ async isHealthy(): Promise { try { - const url = `health`; - await this.httpRequest.get(url); - return true; + const { status } = await this.health(); + return status === "available"; } catch { return false; } @@ -419,7 +429,7 @@ class Client { */ async getStats(): Promise { const url = `stats`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /// @@ -433,7 +443,7 @@ class Client { */ async getVersion(): Promise { const url = `version`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /// @@ -447,8 +457,8 @@ class Client { */ async createDump(): Promise { const url = `dumps`; - const task = await this.httpRequest.post( - url, + const task = ( + await this.httpRequest.post({ relativeURL: url }) ); return new EnqueuedTask(task); } @@ -464,8 +474,8 @@ class Client { */ async createSnapshot(): Promise { const url = `snapshots`; - const task = await this.httpRequest.post( - url, + const task = ( + await this.httpRequest.post({ relativeURL: url }) ); return new EnqueuedTask(task); diff --git a/src/http-requests.ts b/src/http-requests.ts index 971c2df11..fcd099090 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -1,4 +1,4 @@ -import { Config, EnqueuedTaskObject } from "./types"; +import { Config } from "./types"; import { PACKAGE_VERSION } from "./package-version"; import { @@ -9,187 +9,132 @@ import { import { addTrailingSlash, addProtocolIfNotPresent } from "./utils"; -type queryParams = { [key in keyof T]: string }; - -function toQueryParams(parameters: T): queryParams { - const params = Object.keys(parameters) as Array; - - const queryParams = params.reduce>((acc, key) => { - const value = parameters[key]; - if (value === undefined) { - return acc; - } else if (Array.isArray(value)) { - return { ...acc, [key]: value.join(",") }; - } else if (value instanceof Date) { - return { ...acc, [key]: value.toISOString() }; +type URLSearchParamsRecord = Record< + string, + | string + | string[] + | Array + | number + | number[] + | boolean + | Date + | null + | undefined +>; + +function appendRecordToURLSearchParams( + searchParams: URLSearchParams, + recordToAppend: URLSearchParamsRecord, +): void { + for (const [key, val] of Object.entries(recordToAppend)) { + if (val != null) { + searchParams.set( + key, + Array.isArray(val) + ? val.join() + : val instanceof Date + ? val.toISOString() + : String(val), + ); } - return { ...acc, [key]: value }; - }, {} as queryParams); - return queryParams; -} - -function constructHostURL(host: string): string { - try { - host = addProtocolIfNotPresent(host); - host = addTrailingSlash(host); - return host; - } catch { - throw new MeiliSearchError("The provided host is not valid."); } } -function cloneAndParseHeaders(headers: HeadersInit): Record { - if (Array.isArray(headers)) { - return headers.reduce( - (acc, headerPair) => { - acc[headerPair[0]] = headerPair[1]; - return acc; - }, - {} as Record, - ); - } else if ("has" in headers) { - const clonedHeaders: Record = {}; - (headers as Headers).forEach((value, key) => (clonedHeaders[key] = value)); - return clonedHeaders; - } else { - return Object.assign({}, headers); - } -} - -function createHeaders(config: Config): Record { +function getHeaders(config: Config, headersInit?: HeadersInit): Headers { const agentHeader = "X-Meilisearch-Client"; const packageAgent = `Meilisearch JavaScript (v${PACKAGE_VERSION})`; const contentType = "Content-Type"; const authorization = "Authorization"; - const headers = cloneAndParseHeaders(config.requestConfig?.headers ?? {}); + + const headers = new Headers(headersInit); // do not override if user provided the header - if (config.apiKey && !headers[authorization]) { - headers[authorization] = `Bearer ${config.apiKey}`; + if (config.apiKey && !headers.has(authorization)) { + headers.set(authorization, `Bearer ${config.apiKey}`); } - if (!headers[contentType]) { - headers["Content-Type"] = "application/json"; + if (!headers.has(contentType)) { + headers.set("Content-Type", "application/json"); } // Creates the custom user agent with information on the package used. if (config.clientAgents && Array.isArray(config.clientAgents)) { const clients = config.clientAgents.concat(packageAgent); - headers[agentHeader] = clients.join(" ; "); + headers.set(agentHeader, clients.join(" ; ")); } else if (config.clientAgents && !Array.isArray(config.clientAgents)) { // If the header is defined but not an array throw new MeiliSearchError( `Meilisearch: The header "${agentHeader}" should be an array of string(s).\n`, ); } else { - headers[agentHeader] = packageAgent; + headers.set(agentHeader, packageAgent); } return headers; } -class HttpRequests { - headers: Record; +function constructHostURL(host: string): string { + host = addProtocolIfNotPresent(host); + host = addTrailingSlash(host); + return host; +} + +type RequestOptions = { + relativeURL: string; + method?: string; + params?: URLSearchParamsRecord; + headers?: HeadersInit; + body?: unknown; +}; + +export type MethodOptions = Omit; + +export class HttpRequests { url: URL; - requestConfig?: Config["requestConfig"]; - httpClient?: Required["httpClient"]; + requestInit: Omit, "headers"> & { + headers: Headers; + }; + httpClient: Config["httpClient"]; requestTimeout?: number; constructor(config: Config) { - this.headers = createHeaders(config); - this.requestConfig = config.requestConfig; - this.httpClient = config.httpClient; - this.requestTimeout = config.timeout; + const host = constructHostURL(config.host); try { - const host = constructHostURL(config.host); this.url = new URL(host); - } catch { - throw new MeiliSearchError("The provided host is not valid."); + } catch (error) { + throw new MeiliSearchError("The provided host is not valid", { + cause: error, + }); } - } - async request({ - method, - url, - params, - body, - config = {}, - }: { - method: string; - url: string; - params?: { [key: string]: any }; - body?: any; - config?: Record; - }) { - const constructURL = new URL(url, this.url); - if (params) { - const queryParams = new URLSearchParams(); - Object.keys(params) - .filter((x: string) => params[x] !== null) - .map((x: string) => queryParams.set(x, params[x])); - constructURL.search = queryParams.toString(); - } - - // in case a custom content-type is provided - // do not stringify body - if (!config.headers?.["Content-Type"]) { - body = JSON.stringify(body); - } - - const headers = { ...this.headers, ...config.headers }; - const responsePromise = this.fetchWithTimeout( - constructURL.toString(), - { - ...config, - ...this.requestConfig, - method, - body, - headers, - }, - this.requestTimeout, - ); - - const response = await responsePromise.catch((error: unknown) => { - throw new MeiliSearchRequestError(constructURL.toString(), error); - }); - - // When using a custom HTTP client, the response is returned to allow the user to parse/handle it as they see fit - if (this.httpClient !== undefined) { - return response; - } - - const responseBody = await response.text(); - const parsedResponse = - responseBody === "" ? undefined : JSON.parse(responseBody); - - if (!response.ok) { - throw new MeiliSearchApiError(response, parsedResponse); - } + this.requestInit = { + ...config.requestInit, + headers: getHeaders(config, config.requestInit?.headers), + }; - return parsedResponse; + this.httpClient = config.httpClient; + this.requestTimeout = config.timeout; } - async fetchWithTimeout( - url: string, - options: Record | RequestInit | undefined, - timeout: HttpRequests["requestTimeout"], + async #fetchWithTimeout( + ...fetchParams: Parameters ): Promise { return new Promise((resolve, reject) => { const fetchFn = this.httpClient ? this.httpClient : fetch; - const fetchPromise = fetchFn(url, options); + const fetchPromise = fetchFn(...fetchParams); const promises: Array> = [fetchPromise]; // TimeoutPromise will not run if undefined or zero let timeoutId: ReturnType; - if (timeout) { + if (this.requestTimeout) { const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error("Error: Request Timed Out")); - }, timeout); + }, this.requestTimeout); }); promises.push(timeoutPromise); @@ -204,116 +149,83 @@ class HttpRequests { }); } - async get( - url: string, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - - async get( - url: string, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - - async get( - url: string, - params?: { [key: string]: any }, - config?: Record, - ): Promise { - return await this.request({ - method: "GET", - url, - params, - config, + async #request({ + relativeURL, + method, + params, + headers, + body, + }: RequestOptions): Promise { + const url = new URL(relativeURL, this.url); + if (params !== undefined) { + appendRecordToURLSearchParams(url.searchParams, params); + } + + let isCustomContentTypeProvided: boolean; + + if (headers !== undefined) { + headers = new Headers(headers); + + isCustomContentTypeProvided = headers.has("Content-Type"); + + for (const [key, val] of this.requestInit.headers.entries()) { + if (!headers.has(key)) { + headers.set(key, val); + } + } + } else { + isCustomContentTypeProvided = false; + } + + const responsePromise = this.#fetchWithTimeout(url, { + method, + body: + // in case a custom content-type is provided do not stringify body + typeof body !== "string" || !isCustomContentTypeProvided + ? // this will throw an error for any value that is not serializable + JSON.stringify(body) + : body, + ...this.requestInit, + headers: headers ?? this.requestInit.headers, }); - } - async post( - url: string, - data?: T, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - - async post( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise { - return await this.request({ - method: "POST", - url, - body: data, - params, - config, + const response = await responsePromise.catch((error: unknown) => { + throw new MeiliSearchRequestError(url.toString(), error); }); + + // When using a custom HTTP client, the response is returned to allow the user to parse/handle it as they see fit + if (this.httpClient !== undefined) { + return response; + } + + const responseBody = await response.text(); + const parsedResponse = + responseBody === "" ? undefined : JSON.parse(responseBody); + + if (!response.ok) { + throw new MeiliSearchApiError(response, parsedResponse); + } + + return parsedResponse; } - async put( - url: string, - data?: T, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - - async put( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise { - return await this.request({ - method: "PUT", - url, - body: data, - params, - config, - }); + get(options: MethodOptions) { + return this.#request(options); } - async patch( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise { - return await this.request({ - method: "PATCH", - url, - body: data, - params, - config, - }); + post(options: MethodOptions) { + return this.#request({ ...options, method: "POST" }); } - async delete( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - async delete( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise; - async delete( - url: string, - data?: any, - params?: { [key: string]: any }, - config?: Record, - ): Promise { - return await this.request({ - method: "DELETE", - url, - body: data, - params, - config, - }); + put(options: MethodOptions) { + return this.#request({ ...options, method: "PUT" }); + } + + patch(options: MethodOptions) { + return this.#request({ ...options, method: "PATCH" }); } -} -export { HttpRequests, toQueryParams }; + delete(options: MethodOptions) { + return this.#request({ ...options, method: "DELETE" }); + } +} diff --git a/src/indexes.ts b/src/indexes.ts index f965706b9..1034a24cc 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -56,6 +56,7 @@ import { SearchSimilarDocumentsParams, LocalizedAttributes, UpdateDocumentsByFunctionOptions, + EnqueuedTaskObject, } from "./types"; import { removeUndefinedFromObject } from "./utils"; import { HttpRequests } from "./http-requests"; @@ -104,12 +105,10 @@ class Index = Record> { ): Promise> { const url = `indexes/${this.uid}/search`; - return await this.httpRequest.post( - url, - removeUndefinedFromObject({ q: query, ...options }), - undefined, - config, - ); + return >await this.httpRequest.post({ + relativeURL: url, + body: { q: query, ...options }, + }); } /** @@ -130,6 +129,7 @@ class Index = Record> { ): Promise> { const url = `indexes/${this.uid}/search`; + // @TODO: Make this a type thing instead of a runtime thing const parseFilter = (filter?: Filter): string | undefined => { if (typeof filter === "string") return filter; else if (Array.isArray(filter)) @@ -152,11 +152,10 @@ class Index = Record> { attributesToSearchOn: options?.attributesToSearchOn?.join(","), }; - return await this.httpRequest.get>( - url, - removeUndefinedFromObject(getParams), - config, - ); + return >await this.httpRequest.get({ + relativeURL: url, + params: getParams, + }); } /** @@ -172,11 +171,8 @@ class Index = Record> { ): Promise { const url = `indexes/${this.uid}/facet-search`; - return await this.httpRequest.post( - url, - removeUndefinedFromObject(params), - undefined, - config, + return ( + await this.httpRequest.post({ relativeURL: url, body: params }) ); } @@ -192,10 +188,8 @@ class Index = Record> { >(params: SearchSimilarDocumentsParams): Promise> { const url = `indexes/${this.uid}/similar`; - return await this.httpRequest.post( - url, - removeUndefinedFromObject(params), - undefined, + return >( + await this.httpRequest.post({ relativeURL: url, body: params }) ); } @@ -210,7 +204,7 @@ class Index = Record> { */ async getRawInfo(): Promise { const url = `indexes/${this.uid}`; - const res = await this.httpRequest.get(url); + const res = await this.httpRequest.get({ relativeURL: url }); this.primaryKey = res.primaryKey; this.updatedAt = new Date(res.updatedAt); this.createdAt = new Date(res.createdAt); @@ -252,7 +246,9 @@ class Index = Record> { ): Promise { const url = `indexes`; const req = new HttpRequests(config); - const task = await req.post(url, { ...options, uid }); + const task = ( + await req.post({ relativeURL: url, body: { ...options, uid } }) + ); return new EnqueuedTask(task); } @@ -265,7 +261,10 @@ class Index = Record> { */ async update(data: IndexOptions): Promise { const url = `indexes/${this.uid}`; - const task = await this.httpRequest.patch(url, data); + // @TODO: Something is not right + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: data }) + ); task.enqueuedAt = new Date(task.enqueuedAt); @@ -279,7 +278,9 @@ class Index = Record> { */ async delete(): Promise { const url = `indexes/${this.uid}`; - const task = await this.httpRequest.delete(url); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); return new EnqueuedTask(task); } @@ -353,7 +354,7 @@ class Index = Record> { */ async getStats(): Promise { const url = `indexes/${this.uid}/stats`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /// @@ -377,10 +378,9 @@ class Index = Record> { try { const url = `indexes/${this.uid}/documents/fetch`; - return await this.httpRequest.post< - DocumentsQuery, - Promise> - >(url, parameters); + return >( + await this.httpRequest.post({ relativeURL: url, body: parameters }) + ); } catch (e) { if (e instanceof MeiliSearchRequestError) { e.message = versionErrorHintMessage(e.message, "getDocuments"); @@ -390,18 +390,18 @@ class Index = Record> { throw e; } - // Else use `GET /documents` method } else { + // Else use `GET /documents` method const url = `indexes/${this.uid}/documents`; - - // Transform fields to query parameter string format - const fields = Array.isArray(parameters?.fields) - ? { fields: parameters?.fields?.join(",") } - : {}; - - return await this.httpRequest.get>>(url, { - ...parameters, - ...fields, + return >await this.httpRequest.get({ + relativeURL: url, + params: { + ...parameters, + // Transform fields to query parameter string format + fields: Array.isArray(parameters?.fields) + ? parameters.fields.join(",") + : undefined, + }, }); } } @@ -426,13 +426,13 @@ class Index = Record> { return undefined; })(); - return await this.httpRequest.get( - url, - removeUndefinedFromObject({ + return await this.httpRequest.get({ + relativeURL: url, + params: { ...parameters, fields, - }), - ); + }, + }); } /** @@ -447,7 +447,11 @@ class Index = Record> { options?: DocumentOptions, ): Promise { const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.post(url, documents, options); + const task = await this.httpRequest.post({ + relativeURL: url, + params: options, + body: documents, + }); return new EnqueuedTask(task); } @@ -469,10 +473,11 @@ class Index = Record> { ): Promise { const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.post(url, documents, queryParams, { - headers: { - "Content-Type": contentType, - }, + const task = await this.httpRequest.post({ + relativeURL: url, + body: documents, + params: queryParams, + headers: { "Content-Type": contentType }, }); return new EnqueuedTask(task); @@ -512,7 +517,11 @@ class Index = Record> { options?: DocumentOptions, ): Promise { const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.put(url, documents, options); + const task = await this.httpRequest.put({ + relativeURL: url, + params: options, + body: documents, + }); return new EnqueuedTask(task); } @@ -556,10 +565,11 @@ class Index = Record> { ): Promise { const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.put(url, documents, queryParams, { - headers: { - "Content-Type": contentType, - }, + const task = await this.httpRequest.put({ + relativeURL: url, + body: documents, + params: queryParams, + headers: { "Content-Type": contentType }, }); return new EnqueuedTask(task); @@ -573,11 +583,11 @@ class Index = Record> { */ async deleteDocument(documentId: string | number): Promise { const url = `indexes/${this.uid}/documents/${documentId}`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /** @@ -603,7 +613,10 @@ class Index = Record> { const url = `indexes/${this.uid}/${endpoint}`; try { - const task = await this.httpRequest.post(url, params); + const task = await this.httpRequest.post({ + relativeURL: url, + body: params, + }); return new EnqueuedTask(task); } catch (e) { @@ -624,11 +637,11 @@ class Index = Record> { */ async deleteAllDocuments(): Promise { const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /** @@ -647,7 +660,10 @@ class Index = Record> { options: UpdateDocumentsByFunctionOptions, ): Promise { const url = `indexes/${this.uid}/documents/edit`; - const task = await this.httpRequest.post(url, options); + const task = await this.httpRequest.post({ + relativeURL: url, + body: options, + }); return new EnqueuedTask(task); } @@ -663,7 +679,7 @@ class Index = Record> { */ async getSettings(): Promise { const url = `indexes/${this.uid}/settings`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -674,11 +690,11 @@ class Index = Record> { */ async updateSettings(settings: Settings): Promise { const url = `indexes/${this.uid}/settings`; - const task = await this.httpRequest.patch(url, settings); - - task.enqueued = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: settings }) + ); - return task; + return new EnqueuedTask(task); } /** @@ -688,11 +704,11 @@ class Index = Record> { */ async resetSettings(): Promise { const url = `indexes/${this.uid}/settings`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -706,7 +722,8 @@ class Index = Record> { */ async getPagination(): Promise { const url = `indexes/${this.uid}/settings/pagination`; - return await this.httpRequest.get(url); + // @TODO: I don't like this type + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -719,7 +736,9 @@ class Index = Record> { pagination: PaginationSettings, ): Promise { const url = `indexes/${this.uid}/settings/pagination`; - const task = await this.httpRequest.patch(url, pagination); + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: pagination }) + ); return new EnqueuedTask(task); } @@ -731,7 +750,9 @@ class Index = Record> { */ async resetPagination(): Promise { const url = `indexes/${this.uid}/settings/pagination`; - const task = await this.httpRequest.delete(url); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); return new EnqueuedTask(task); } @@ -747,7 +768,8 @@ class Index = Record> { */ async getSynonyms(): Promise { const url = `indexes/${this.uid}/settings/synonyms`; - return await this.httpRequest.get(url); + // @TODO: I don't like this type + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -758,7 +780,9 @@ class Index = Record> { */ async updateSynonyms(synonyms: Synonyms): Promise { const url = `indexes/${this.uid}/settings/synonyms`; - const task = await this.httpRequest.put(url, synonyms); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: synonyms }) + ); return new EnqueuedTask(task); } @@ -770,11 +794,11 @@ class Index = Record> { */ async resetSynonyms(): Promise { const url = `indexes/${this.uid}/settings/synonyms`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -788,7 +812,7 @@ class Index = Record> { */ async getStopWords(): Promise { const url = `indexes/${this.uid}/settings/stop-words`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -799,7 +823,9 @@ class Index = Record> { */ async updateStopWords(stopWords: StopWords): Promise { const url = `indexes/${this.uid}/settings/stop-words`; - const task = await this.httpRequest.put(url, stopWords); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: stopWords }) + ); return new EnqueuedTask(task); } @@ -811,11 +837,11 @@ class Index = Record> { */ async resetStopWords(): Promise { const url = `indexes/${this.uid}/settings/stop-words`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -829,7 +855,7 @@ class Index = Record> { */ async getRankingRules(): Promise { const url = `indexes/${this.uid}/settings/ranking-rules`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -841,7 +867,9 @@ class Index = Record> { */ async updateRankingRules(rankingRules: RankingRules): Promise { const url = `indexes/${this.uid}/settings/ranking-rules`; - const task = await this.httpRequest.put(url, rankingRules); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: rankingRules }) + ); return new EnqueuedTask(task); } @@ -853,11 +881,11 @@ class Index = Record> { */ async resetRankingRules(): Promise { const url = `indexes/${this.uid}/settings/ranking-rules`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -871,7 +899,7 @@ class Index = Record> { */ async getDistinctAttribute(): Promise { const url = `indexes/${this.uid}/settings/distinct-attribute`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -884,7 +912,9 @@ class Index = Record> { distinctAttribute: DistinctAttribute, ): Promise { const url = `indexes/${this.uid}/settings/distinct-attribute`; - const task = await this.httpRequest.put(url, distinctAttribute); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: distinctAttribute }) + ); return new EnqueuedTask(task); } @@ -896,11 +926,11 @@ class Index = Record> { */ async resetDistinctAttribute(): Promise { const url = `indexes/${this.uid}/settings/distinct-attribute`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -914,7 +944,7 @@ class Index = Record> { */ async getFilterableAttributes(): Promise { const url = `indexes/${this.uid}/settings/filterable-attributes`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -928,7 +958,10 @@ class Index = Record> { filterableAttributes: FilterableAttributes, ): Promise { const url = `indexes/${this.uid}/settings/filterable-attributes`; - const task = await this.httpRequest.put(url, filterableAttributes); + const task = await this.httpRequest.put({ + relativeURL: url, + body: filterableAttributes, + }); return new EnqueuedTask(task); } @@ -940,11 +973,11 @@ class Index = Record> { */ async resetFilterableAttributes(): Promise { const url = `indexes/${this.uid}/settings/filterable-attributes`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -958,7 +991,7 @@ class Index = Record> { */ async getSortableAttributes(): Promise { const url = `indexes/${this.uid}/settings/sortable-attributes`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -972,7 +1005,9 @@ class Index = Record> { sortableAttributes: SortableAttributes, ): Promise { const url = `indexes/${this.uid}/settings/sortable-attributes`; - const task = await this.httpRequest.put(url, sortableAttributes); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: sortableAttributes }) + ); return new EnqueuedTask(task); } @@ -984,11 +1019,11 @@ class Index = Record> { */ async resetSortableAttributes(): Promise { const url = `indexes/${this.uid}/settings/sortable-attributes`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1002,7 +1037,7 @@ class Index = Record> { */ async getSearchableAttributes(): Promise { const url = `indexes/${this.uid}/settings/searchable-attributes`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1016,7 +1051,10 @@ class Index = Record> { searchableAttributes: SearchableAttributes, ): Promise { const url = `indexes/${this.uid}/settings/searchable-attributes`; - const task = await this.httpRequest.put(url, searchableAttributes); + const task = await this.httpRequest.put({ + relativeURL: url, + body: searchableAttributes, + }); return new EnqueuedTask(task); } @@ -1028,11 +1066,11 @@ class Index = Record> { */ async resetSearchableAttributes(): Promise { const url = `indexes/${this.uid}/settings/searchable-attributes`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1046,7 +1084,7 @@ class Index = Record> { */ async getDisplayedAttributes(): Promise { const url = `indexes/${this.uid}/settings/displayed-attributes`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1060,7 +1098,10 @@ class Index = Record> { displayedAttributes: DisplayedAttributes, ): Promise { const url = `indexes/${this.uid}/settings/displayed-attributes`; - const task = await this.httpRequest.put(url, displayedAttributes); + const task = await this.httpRequest.put({ + relativeURL: url, + body: displayedAttributes, + }); return new EnqueuedTask(task); } @@ -1072,11 +1113,11 @@ class Index = Record> { */ async resetDisplayedAttributes(): Promise { const url = `indexes/${this.uid}/settings/displayed-attributes`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1090,7 +1131,7 @@ class Index = Record> { */ async getTypoTolerance(): Promise { const url = `indexes/${this.uid}/settings/typo-tolerance`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1104,11 +1145,11 @@ class Index = Record> { typoTolerance: TypoTolerance, ): Promise { const url = `indexes/${this.uid}/settings/typo-tolerance`; - const task = await this.httpRequest.patch(url, typoTolerance); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: typoTolerance }) + ); - return task; + return new EnqueuedTask(task); } /** @@ -1118,11 +1159,11 @@ class Index = Record> { */ async resetTypoTolerance(): Promise { const url = `indexes/${this.uid}/settings/typo-tolerance`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1136,7 +1177,7 @@ class Index = Record> { */ async getFaceting(): Promise { const url = `indexes/${this.uid}/settings/faceting`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1147,7 +1188,9 @@ class Index = Record> { */ async updateFaceting(faceting: Faceting): Promise { const url = `indexes/${this.uid}/settings/faceting`; - const task = await this.httpRequest.patch(url, faceting); + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: faceting }) + ); return new EnqueuedTask(task); } @@ -1159,7 +1202,9 @@ class Index = Record> { */ async resetFaceting(): Promise { const url = `indexes/${this.uid}/settings/faceting`; - const task = await this.httpRequest.delete(url); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); return new EnqueuedTask(task); } @@ -1175,7 +1220,7 @@ class Index = Record> { */ async getSeparatorTokens(): Promise { const url = `indexes/${this.uid}/settings/separator-tokens`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1188,7 +1233,9 @@ class Index = Record> { separatorTokens: SeparatorTokens, ): Promise { const url = `indexes/${this.uid}/settings/separator-tokens`; - const task = await this.httpRequest.put(url, separatorTokens); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: separatorTokens }) + ); return new EnqueuedTask(task); } @@ -1200,11 +1247,11 @@ class Index = Record> { */ async resetSeparatorTokens(): Promise { const url = `indexes/${this.uid}/settings/separator-tokens`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1218,7 +1265,7 @@ class Index = Record> { */ async getNonSeparatorTokens(): Promise { const url = `indexes/${this.uid}/settings/non-separator-tokens`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1231,7 +1278,9 @@ class Index = Record> { nonSeparatorTokens: NonSeparatorTokens, ): Promise { const url = `indexes/${this.uid}/settings/non-separator-tokens`; - const task = await this.httpRequest.put(url, nonSeparatorTokens); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: nonSeparatorTokens }) + ); return new EnqueuedTask(task); } @@ -1243,11 +1292,11 @@ class Index = Record> { */ async resetNonSeparatorTokens(): Promise { const url = `indexes/${this.uid}/settings/non-separator-tokens`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1261,7 +1310,7 @@ class Index = Record> { */ async getDictionary(): Promise { const url = `indexes/${this.uid}/settings/dictionary`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1272,7 +1321,10 @@ class Index = Record> { */ async updateDictionary(dictionary: Dictionary): Promise { const url = `indexes/${this.uid}/settings/dictionary`; - const task = await this.httpRequest.put(url, dictionary); + const task = await this.httpRequest.put({ + relativeURL: url, + body: dictionary, + }); return new EnqueuedTask(task); } @@ -1284,11 +1336,11 @@ class Index = Record> { */ async resetDictionary(): Promise { const url = `indexes/${this.uid}/settings/dictionary`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1302,7 +1354,7 @@ class Index = Record> { */ async getProximityPrecision(): Promise { const url = `indexes/${this.uid}/settings/proximity-precision`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1316,7 +1368,9 @@ class Index = Record> { proximityPrecision: ProximityPrecision, ): Promise { const url = `indexes/${this.uid}/settings/proximity-precision`; - const task = await this.httpRequest.put(url, proximityPrecision); + const task = ( + await this.httpRequest.put({ relativeURL: url, body: proximityPrecision }) + ); return new EnqueuedTask(task); } @@ -1328,11 +1382,11 @@ class Index = Record> { */ async resetProximityPrecision(): Promise { const url = `indexes/${this.uid}/settings/proximity-precision`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1346,7 +1400,7 @@ class Index = Record> { */ async getEmbedders(): Promise { const url = `indexes/${this.uid}/settings/embedders`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1357,7 +1411,9 @@ class Index = Record> { */ async updateEmbedders(embedders: Embedders): Promise { const url = `indexes/${this.uid}/settings/embedders`; - const task = await this.httpRequest.patch(url, embedders); + const task = ( + await this.httpRequest.patch({ relativeURL: url, body: embedders }) + ); return new EnqueuedTask(task); } @@ -1369,11 +1425,11 @@ class Index = Record> { */ async resetEmbedders(): Promise { const url = `indexes/${this.uid}/settings/embedders`; - const task = await this.httpRequest.delete(url); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); - return task; + return new EnqueuedTask(task); } /// @@ -1387,7 +1443,7 @@ class Index = Record> { */ async getSearchCutoffMs(): Promise { const url = `indexes/${this.uid}/settings/search-cutoff-ms`; - return await this.httpRequest.get(url); + return await this.httpRequest.get({ relativeURL: url }); } /** @@ -1400,7 +1456,10 @@ class Index = Record> { searchCutoffMs: SearchCutoffMs, ): Promise { const url = `indexes/${this.uid}/settings/search-cutoff-ms`; - const task = await this.httpRequest.put(url, searchCutoffMs); + const task = await this.httpRequest.put({ + relativeURL: url, + body: searchCutoffMs, + }); return new EnqueuedTask(task); } @@ -1412,7 +1471,9 @@ class Index = Record> { */ async resetSearchCutoffMs(): Promise { const url = `indexes/${this.uid}/settings/search-cutoff-ms`; - const task = await this.httpRequest.delete(url); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); return new EnqueuedTask(task); } @@ -1428,7 +1489,9 @@ class Index = Record> { */ async getLocalizedAttributes(): Promise { const url = `indexes/${this.uid}/settings/localized-attributes`; - return await this.httpRequest.get(url); + return ( + await this.httpRequest.get({ relativeURL: url }) + ); } /** @@ -1441,7 +1504,10 @@ class Index = Record> { localizedAttributes: LocalizedAttributes, ): Promise { const url = `indexes/${this.uid}/settings/localized-attributes`; - const task = await this.httpRequest.put(url, localizedAttributes); + const task = await this.httpRequest.put({ + relativeURL: url, + body: localizedAttributes, + }); return new EnqueuedTask(task); } @@ -1453,7 +1519,9 @@ class Index = Record> { */ async resetLocalizedAttributes(): Promise { const url = `indexes/${this.uid}/settings/localized-attributes`; - const task = await this.httpRequest.delete(url); + const task = ( + await this.httpRequest.delete({ relativeURL: url }) + ); return new EnqueuedTask(task); } diff --git a/src/task.ts b/src/task.ts index de3388add..38ecd2b2d 100644 --- a/src/task.ts +++ b/src/task.ts @@ -9,8 +9,9 @@ import { CancelTasksQuery, TasksResultsObject, DeleteTasksQuery, + EnqueuedTaskObject, } from "./types"; -import { HttpRequests, toQueryParams } from "./http-requests"; +import { HttpRequests } from "./http-requests"; import { sleep } from "./utils"; import { EnqueuedTask } from "./enqueued-task"; @@ -58,22 +59,23 @@ class TaskClient { */ async getTask(uid: number): Promise { const url = `tasks/${uid}`; - const taskItem = await this.httpRequest.get(url); + const taskItem = ( + await this.httpRequest.get({ relativeURL: url }) + ); return new Task(taskItem); } /** * Get tasks * - * @param parameters - Parameters to browse the tasks + * @param params - Parameters to browse the tasks * @returns Promise containing all tasks */ - async getTasks(parameters: TasksQuery = {}): Promise { + async getTasks(params: TasksQuery = {}): Promise { const url = `tasks`; - const tasks = await this.httpRequest.get>( - url, - toQueryParams(parameters), + const tasks = ( + await this.httpRequest.get({ relativeURL: url, params }) ); return { @@ -137,16 +139,14 @@ class TaskClient { /** * Cancel a list of enqueued or processing tasks. * - * @param parameters - Parameters to filter the tasks. + * @param params - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async cancelTasks(parameters: CancelTasksQuery = {}): Promise { + async cancelTasks(params: CancelTasksQuery = {}): Promise { const url = `tasks/cancel`; - const task = await this.httpRequest.post( - url, - {}, - toQueryParams(parameters), + const task = ( + await this.httpRequest.post({ relativeURL: url, params }) ); return new EnqueuedTask(task); @@ -155,16 +155,14 @@ class TaskClient { /** * Delete a list tasks. * - * @param parameters - Parameters to filter the tasks. + * @param params - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async deleteTasks(parameters: DeleteTasksQuery = {}): Promise { + async deleteTasks(params: DeleteTasksQuery = {}): Promise { const url = `tasks`; - const task = await this.httpRequest.delete( - url, - {}, - toQueryParams(parameters), + const task = ( + await this.httpRequest.delete({ relativeURL: url, params }) ); return new EnqueuedTask(task); } diff --git a/src/types/types.ts b/src/types/types.ts index b6feccdd6..dcd7b3d99 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -10,8 +10,8 @@ export type Config = { host: string; apiKey?: string; clientAgents?: string[]; - requestConfig?: Partial>; - httpClient?: (input: string, init?: RequestInit) => Promise; + requestInit?: Omit; + httpClient?: typeof fetch; timeout?: number; }; diff --git a/tests/client.test.ts b/tests/client.test.ts index e522c0813..be593ef62 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -55,13 +55,15 @@ describe.each([ const client = new MeiliSearch({ ...config, apiKey: key, - requestConfig: { + requestInit: { headers: { "Hello-There!": "General Kenobi", }, }, }); - expect(client.httpRequest.headers["Hello-There!"]).toBe("General Kenobi"); + expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( + "General Kenobi", + ); const health = await client.isHealthy(); expect(health).toBe(true); }); @@ -71,11 +73,13 @@ describe.each([ const client = new MeiliSearch({ ...config, apiKey: key, - requestConfig: { + requestInit: { headers: [["Hello-There!", "General Kenobi"]], }, }); - expect(client.httpRequest.headers["Hello-There!"]).toBe("General Kenobi"); + expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( + "General Kenobi", + ); const health = await client.isHealthy(); expect(health).toBe(true); }); @@ -83,15 +87,15 @@ describe.each([ test(`${permission} key: Create client with custom headers (Headers)`, async () => { const key = await getKey(permission); const headers = new Headers(); - headers.append("Hello-There!", "General Kenobi"); + headers.set("Hello-There!", "General Kenobi"); const client = new MeiliSearch({ ...config, apiKey: key, - requestConfig: { - headers, - }, + requestInit: { headers }, }); - expect(client.httpRequest.headers["hello-there!"]).toBe("General Kenobi"); + expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( + "General Kenobi", + ); const health = await client.isHealthy(); expect(health).toBe(true); }); @@ -186,7 +190,7 @@ describe.each([ test(`${permission} key: Empty string host should throw an error`, () => { expect(() => { new MeiliSearch({ host: "" }); - }).toThrow("The provided host is not valid."); + }).toThrow("The provided host is not valid"); }); }); @@ -202,13 +206,13 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( const client = new MeiliSearch({ ...config, apiKey: key, - requestConfig: { + requestInit: { headers: { "Hello-There!": "General Kenobi", }, }, }); - expect(client.config.requestConfig?.headers).toStrictEqual({ + expect(client.config.requestInit?.headers).toStrictEqual({ "Hello-There!": "General Kenobi", }); const health = await client.isHealthy(); @@ -260,14 +264,14 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( const client = new MeiliSearch({ ...config, apiKey: key, - requestConfig: { + requestInit: { headers: {}, }, }); - expect(client.httpRequest.headers["X-Meilisearch-Client"]).toStrictEqual( - `Meilisearch JavaScript (v${PACKAGE_VERSION})`, - ); + expect( + client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), + ).toStrictEqual(`Meilisearch JavaScript (v${PACKAGE_VERSION})`); }); test(`${permission} key: Create client with empty custom client agents`, async () => { @@ -278,9 +282,9 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( clientAgents: [], }); - expect(client.httpRequest.headers["X-Meilisearch-Client"]).toStrictEqual( - `Meilisearch JavaScript (v${PACKAGE_VERSION})`, - ); + expect( + client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), + ).toStrictEqual(`Meilisearch JavaScript (v${PACKAGE_VERSION})`); }); test(`${permission} key: Create client with custom client agents`, async () => { @@ -291,7 +295,9 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( clientAgents: ["random plugin 1", "random plugin 2"], }); - expect(client.httpRequest.headers["X-Meilisearch-Client"]).toStrictEqual( + expect( + client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), + ).toStrictEqual( `random plugin 1 ; random plugin 2 ; Meilisearch JavaScript (v${PACKAGE_VERSION})`, ); }); diff --git a/tsconfig.json b/tsconfig.json index 368aaf464..0152bf4ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,7 @@ // This matters for the generated CJS and ESM file, UMD file is down-leveled further to support IE11 // (only the syntax, URL, URLSearchParams, fetch is not IE11 compatible) with the help of Babel. "target": "es2022", - "lib": ["ESNext", "dom"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "strict": true, "noImplicitReturns": true }, From a32ed2d4a686d82ccfa5820826dee0c4414a067b Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:57:05 +0300 Subject: [PATCH 02/15] Convert props to private, refactor, adapt tests --- src/clients/client.ts | 59 ++- src/http-requests.ts | 46 ++- src/indexes.ts | 492 ++++++++++++-------------- src/task.ts | 21 +- src/types/types.ts | 8 +- src/utils.ts | 20 +- tests/client.test.ts | 225 ++++++++---- tests/unit.test.ts | 35 +- tests/utils/meilisearch-test-utils.ts | 2 +- 9 files changed, 455 insertions(+), 453 deletions(-) diff --git a/src/clients/client.ts b/src/clients/client.ts index 0d21e756a..bb1d7181b 100644 --- a/src/clients/client.ts +++ b/src/clients/client.ts @@ -98,7 +98,7 @@ class Client { * @returns Promise returning array of raw index information */ async getIndexes( - parameters: IndexesQuery = {}, + parameters?: IndexesQuery, ): Promise> { const rawIndexes = await this.getRawIndexes(parameters); const indexes: Index[] = rawIndexes.results.map( @@ -114,11 +114,10 @@ class Client { * @returns Promise returning array of raw index information */ async getRawIndexes( - parameters: IndexesQuery = {}, + parameters?: IndexesQuery, ): Promise> { - const url = `indexes`; return >( - await this.httpRequest.get({ relativeURL: url, params: parameters }) + await this.httpRequest.get({ relativeURL: "indexes", params: parameters }) ); } @@ -131,7 +130,7 @@ class Client { */ async createIndex( uid: string, - options: IndexOptions = {}, + options?: IndexOptions, ): Promise { return await Index.create(uid, options, this.config); } @@ -145,7 +144,7 @@ class Client { */ async updateIndex( uid: string, - options: IndexOptions = {}, + options?: IndexOptions, ): Promise { return await new Index(this.config, uid).update(options); } @@ -231,10 +230,11 @@ class Client { queries: MultiSearchParams | FederatedMultiSearchParams, config?: Partial, ): Promise | SearchResponse> { - const url = `multi-search`; - return | SearchResponse>( - await this.httpRequest.post({ relativeURL: url, body: queries }) + await this.httpRequest.post({ + relativeURL: "multi-search", + body: queries, + }) ); } @@ -248,7 +248,7 @@ class Client { * @param parameters - Parameters to browse the tasks * @returns Promise returning all tasks */ - async getTasks(parameters: TasksQuery = {}): Promise { + async getTasks(parameters?: TasksQuery): Promise { return await this.tasks.getTasks(parameters); } @@ -312,7 +312,7 @@ class Client { * @param parameters - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async deleteTasks(parameters: DeleteTasksQuery = {}): Promise { + async deleteTasks(parameters?: DeleteTasksQuery): Promise { return await this.tasks.deleteTasks(parameters); } @@ -326,10 +326,9 @@ class Client { * @param parameters - Parameters to browse the indexes * @returns Promise returning an object with keys */ - async getKeys(parameters: KeysQuery = {}): Promise { - const url = `keys`; + async getKeys(parameters?: KeysQuery): Promise { const keys = ( - await this.httpRequest.get({ relativeURL: url, params: parameters }) + await this.httpRequest.get({ relativeURL: "keys", params: parameters }) ); keys.results = keys.results.map((key) => ({ @@ -348,8 +347,7 @@ class Client { * @returns Promise returning a key */ async getKey(keyOrUid: string): Promise { - const url = `keys/${keyOrUid}`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ relativeURL: `keys/${keyOrUid}` }); } /** @@ -359,9 +357,8 @@ class Client { * @returns Promise returning a key */ async createKey(options: KeyCreation): Promise { - const url = `keys`; return ( - await this.httpRequest.post({ relativeURL: url, body: options }) + await this.httpRequest.post({ relativeURL: "keys", body: options }) ); } @@ -373,10 +370,10 @@ class Client { * @returns Promise returning a key */ async updateKey(keyOrUid: string, options: KeyUpdate): Promise { - const url = `keys/${keyOrUid}`; - return ( - await this.httpRequest.patch({ relativeURL: url, body: options }) - ); + return await this.httpRequest.patch({ + relativeURL: `keys/${keyOrUid}`, + body: options, + }); } /** @@ -386,8 +383,7 @@ class Client { * @returns */ async deleteKey(keyOrUid: string): Promise { - const url = `keys/${keyOrUid}`; - await this.httpRequest.delete({ relativeURL: url }); + await this.httpRequest.delete({ relativeURL: `keys/${keyOrUid}` }); } /// @@ -400,8 +396,7 @@ class Client { * @returns Promise returning an object with health details */ async health(): Promise { - const url = `health`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ relativeURL: "health" }); } /** @@ -428,8 +423,7 @@ class Client { * @returns Promise returning object of all the stats */ async getStats(): Promise { - const url = `stats`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ relativeURL: "stats" }); } /// @@ -442,8 +436,7 @@ class Client { * @returns Promise returning object with version details */ async getVersion(): Promise { - const url = `version`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ relativeURL: "version" }); } /// @@ -456,9 +449,8 @@ class Client { * @returns Promise returning object of the enqueued task */ async createDump(): Promise { - const url = `dumps`; const task = ( - await this.httpRequest.post({ relativeURL: url }) + await this.httpRequest.post({ relativeURL: "dumps" }) ); return new EnqueuedTask(task); } @@ -473,9 +465,8 @@ class Client { * @returns Promise returning object of the enqueued task */ async createSnapshot(): Promise { - const url = `snapshots`; const task = ( - await this.httpRequest.post({ relativeURL: url }) + await this.httpRequest.post({ relativeURL: "snapshots" }) ); return new EnqueuedTask(task); diff --git a/src/http-requests.ts b/src/http-requests.ts index fcd099090..0c4ef5254 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -7,7 +7,7 @@ import { MeiliSearchRequestError, } from "./errors"; -import { addTrailingSlash, addProtocolIfNotPresent } from "./utils"; +import { addProtocolIfNotPresent, addTrailingSlash } from "./utils"; type URLSearchParamsRecord = Record< string, @@ -74,12 +74,6 @@ function getHeaders(config: Config, headersInit?: HeadersInit): Headers { return headers; } -function constructHostURL(host: string): string { - host = addProtocolIfNotPresent(host); - host = addTrailingSlash(host); - return host; -} - type RequestOptions = { relativeURL: string; method?: string; @@ -91,50 +85,50 @@ type RequestOptions = { export type MethodOptions = Omit; export class HttpRequests { - url: URL; - requestInit: Omit, "headers"> & { + #url: URL; + #requestInit: Omit, "headers"> & { headers: Headers; }; - httpClient: Config["httpClient"]; - requestTimeout?: number; + #requestFn: NonNullable; + #isCustomRequestFnProvided: boolean; + #requestTimeout?: number; constructor(config: Config) { - const host = constructHostURL(config.host); + const host = addTrailingSlash(addProtocolIfNotPresent(config.host)); try { - this.url = new URL(host); + this.#url = new URL(host); } catch (error) { throw new MeiliSearchError("The provided host is not valid", { cause: error, }); } - this.requestInit = { + this.#requestInit = { ...config.requestInit, headers: getHeaders(config, config.requestInit?.headers), }; - this.httpClient = config.httpClient; - this.requestTimeout = config.timeout; + this.#requestFn = config.httpClient ?? fetch; + this.#isCustomRequestFnProvided = config.httpClient !== undefined; + this.#requestTimeout = config.timeout; } async #fetchWithTimeout( ...fetchParams: Parameters ): Promise { return new Promise((resolve, reject) => { - const fetchFn = this.httpClient ? this.httpClient : fetch; - - const fetchPromise = fetchFn(...fetchParams); + const fetchPromise = this.#requestFn(...fetchParams); const promises: Array> = [fetchPromise]; // TimeoutPromise will not run if undefined or zero let timeoutId: ReturnType; - if (this.requestTimeout) { + if (this.#requestTimeout) { const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error("Error: Request Timed Out")); - }, this.requestTimeout); + }, this.#requestTimeout); }); promises.push(timeoutPromise); @@ -156,7 +150,7 @@ export class HttpRequests { headers, body, }: RequestOptions): Promise { - const url = new URL(relativeURL, this.url); + const url = new URL(relativeURL, this.#url); if (params !== undefined) { appendRecordToURLSearchParams(url.searchParams, params); } @@ -168,7 +162,7 @@ export class HttpRequests { isCustomContentTypeProvided = headers.has("Content-Type"); - for (const [key, val] of this.requestInit.headers.entries()) { + for (const [key, val] of this.#requestInit.headers.entries()) { if (!headers.has(key)) { headers.set(key, val); } @@ -185,8 +179,8 @@ export class HttpRequests { ? // this will throw an error for any value that is not serializable JSON.stringify(body) : body, - ...this.requestInit, - headers: headers ?? this.requestInit.headers, + ...this.#requestInit, + headers: headers ?? this.#requestInit.headers, }); const response = await responsePromise.catch((error: unknown) => { @@ -194,7 +188,7 @@ export class HttpRequests { }); // When using a custom HTTP client, the response is returned to allow the user to parse/handle it as they see fit - if (this.httpClient !== undefined) { + if (this.#isCustomRequestFnProvided) { return response; } diff --git a/src/indexes.ts b/src/indexes.ts index 1034a24cc..baed2163b 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -58,7 +58,6 @@ import { UpdateDocumentsByFunctionOptions, EnqueuedTaskObject, } from "./types"; -import { removeUndefinedFromObject } from "./utils"; import { HttpRequests } from "./http-requests"; import { Task, TaskClient } from "./task"; import { EnqueuedTask } from "./enqueued-task"; @@ -103,10 +102,8 @@ class Index = Record> { options?: S, config?: Partial, ): Promise> { - const url = `indexes/${this.uid}/search`; - return >await this.httpRequest.post({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/search`, body: { q: query, ...options }, }); } @@ -127,8 +124,6 @@ class Index = Record> { options?: S, config?: Partial, ): Promise> { - const url = `indexes/${this.uid}/search`; - // @TODO: Make this a type thing instead of a runtime thing const parseFilter = (filter?: Filter): string | undefined => { if (typeof filter === "string") return filter; @@ -153,7 +148,7 @@ class Index = Record> { }; return >await this.httpRequest.get({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/search`, params: getParams, }); } @@ -169,11 +164,10 @@ class Index = Record> { params: SearchForFacetValuesParams, config?: Partial, ): Promise { - const url = `indexes/${this.uid}/facet-search`; - - return ( - await this.httpRequest.post({ relativeURL: url, body: params }) - ); + return await this.httpRequest.post({ + relativeURL: `indexes/${this.uid}/facet-search`, + body: params, + }); } /** @@ -186,11 +180,10 @@ class Index = Record> { D extends Record = T, S extends SearchParams = SearchParams, >(params: SearchSimilarDocumentsParams): Promise> { - const url = `indexes/${this.uid}/similar`; - - return >( - await this.httpRequest.post({ relativeURL: url, body: params }) - ); + return >await this.httpRequest.post({ + relativeURL: `indexes/${this.uid}/similar`, + body: params, + }); } /// @@ -203,8 +196,9 @@ class Index = Record> { * @returns Promise containing index information */ async getRawInfo(): Promise { - const url = `indexes/${this.uid}`; - const res = await this.httpRequest.get({ relativeURL: url }); + const res = ( + await this.httpRequest.get({ relativeURL: `indexes/${this.uid}` }) + ); this.primaryKey = res.primaryKey; this.updatedAt = new Date(res.updatedAt); this.createdAt = new Date(res.createdAt); @@ -244,10 +238,9 @@ class Index = Record> { options: IndexOptions = {}, config: Config, ): Promise { - const url = `indexes`; const req = new HttpRequests(config); const task = ( - await req.post({ relativeURL: url, body: { ...options, uid } }) + await req.post({ relativeURL: `indexes`, body: { ...options, uid } }) ); return new EnqueuedTask(task); @@ -260,15 +253,12 @@ class Index = Record> { * @returns Promise to the current Index object with updated information */ async update(data: IndexOptions): Promise { - const url = `indexes/${this.uid}`; - // @TODO: Something is not right - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: data }) - ); - - task.enqueuedAt = new Date(task.enqueuedAt); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}`, + body: data, + }); - return task; + return new EnqueuedTask(task); } /** @@ -277,9 +267,8 @@ class Index = Record> { * @returns Promise which resolves when index is deleted successfully */ async delete(): Promise { - const url = `indexes/${this.uid}`; const task = ( - await this.httpRequest.delete({ relativeURL: url }) + await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}` }) ); return new EnqueuedTask(task); @@ -295,7 +284,7 @@ class Index = Record> { * @param parameters - Parameters to browse the tasks * @returns Promise containing all tasks */ - async getTasks(parameters: TasksQuery = {}): Promise { + async getTasks(parameters?: TasksQuery): Promise { return await this.tasks.getTasks({ ...parameters, indexUids: [this.uid] }); } @@ -353,8 +342,9 @@ class Index = Record> { * @returns Promise containing object with stats of the index */ async getStats(): Promise { - const url = `indexes/${this.uid}/stats`; - return await this.httpRequest.get({ relativeURL: url }); + return ( + await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/stats` }) + ); } /// @@ -369,18 +359,17 @@ class Index = Record> { * @returns Promise containing the returned documents */ async getDocuments = T>( - parameters: DocumentsQuery = {}, + parameters?: DocumentsQuery, ): Promise> { - parameters = removeUndefinedFromObject(parameters); + const relativeBaseURL = `indexes/${this.uid}/documents`; // In case `filter` is provided, use `POST /documents/fetch` - if (parameters.filter !== undefined) { + if (parameters?.filter !== undefined) { try { - const url = `indexes/${this.uid}/documents/fetch`; - - return >( - await this.httpRequest.post({ relativeURL: url, body: parameters }) - ); + return >await this.httpRequest.post({ + relativeURL: `${relativeBaseURL}/fetch`, + body: parameters, + }); } catch (e) { if (e instanceof MeiliSearchRequestError) { e.message = versionErrorHintMessage(e.message, "getDocuments"); @@ -392,9 +381,8 @@ class Index = Record> { } } else { // Else use `GET /documents` method - const url = `indexes/${this.uid}/documents`; return >await this.httpRequest.get({ - relativeURL: url, + relativeURL: relativeBaseURL, params: { ...parameters, // Transform fields to query parameter string format @@ -417,8 +405,6 @@ class Index = Record> { documentId: string | number, parameters?: DocumentQuery, ): Promise { - const url = `indexes/${this.uid}/documents/${documentId}`; - const fields = (() => { if (Array.isArray(parameters?.fields)) { return parameters?.fields?.join(","); @@ -427,7 +413,7 @@ class Index = Record> { })(); return await this.httpRequest.get({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents/${documentId}`, params: { ...parameters, fields, @@ -446,9 +432,8 @@ class Index = Record> { documents: T[], options?: DocumentOptions, ): Promise { - const url = `indexes/${this.uid}/documents`; const task = await this.httpRequest.post({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents`, params: options, body: documents, }); @@ -471,10 +456,8 @@ class Index = Record> { contentType: ContentType, queryParams?: RawDocumentAdditionOptions, ): Promise { - const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.post({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents`, body: documents, params: queryParams, headers: { "Content-Type": contentType }, @@ -516,9 +499,8 @@ class Index = Record> { documents: Array>, options?: DocumentOptions, ): Promise { - const url = `indexes/${this.uid}/documents`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents`, params: options, body: documents, }); @@ -563,10 +545,8 @@ class Index = Record> { contentType: ContentType, queryParams?: RawDocumentAdditionOptions, ): Promise { - const url = `indexes/${this.uid}/documents`; - const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents`, body: documents, params: queryParams, headers: { "Content-Type": contentType }, @@ -582,10 +562,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async deleteDocument(documentId: string | number): Promise { - const url = `indexes/${this.uid}/documents/${documentId}`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/documents/${documentId}`, + }); return new EnqueuedTask(task); } @@ -610,11 +589,10 @@ class Index = Record> { const endpoint = isDocumentsDeletionQuery ? "documents/delete" : "documents/delete-batch"; - const url = `indexes/${this.uid}/${endpoint}`; try { const task = await this.httpRequest.post({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/${endpoint}`, body: params, }); @@ -636,10 +614,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async deleteAllDocuments(): Promise { - const url = `indexes/${this.uid}/documents`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/documents`, + }); return new EnqueuedTask(task); } @@ -659,9 +636,8 @@ class Index = Record> { async updateDocumentsByFunction( options: UpdateDocumentsByFunctionOptions, ): Promise { - const url = `indexes/${this.uid}/documents/edit`; const task = await this.httpRequest.post({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/documents/edit`, body: options, }); @@ -678,8 +654,9 @@ class Index = Record> { * @returns Promise containing Settings object */ async getSettings(): Promise { - const url = `indexes/${this.uid}/settings`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings`, + }); } /** @@ -689,10 +666,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateSettings(settings: Settings): Promise { - const url = `indexes/${this.uid}/settings`; - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: settings }) - ); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}/settings`, + body: settings, + }); return new EnqueuedTask(task); } @@ -703,10 +680,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSettings(): Promise { - const url = `indexes/${this.uid}/settings`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings`, + }); return new EnqueuedTask(task); } @@ -721,9 +697,9 @@ class Index = Record> { * @returns Promise containing object of pagination settings */ async getPagination(): Promise { - const url = `indexes/${this.uid}/settings/pagination`; - // @TODO: I don't like this type - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/pagination`, + }); } /** @@ -735,10 +711,10 @@ class Index = Record> { async updatePagination( pagination: PaginationSettings, ): Promise { - const url = `indexes/${this.uid}/settings/pagination`; - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: pagination }) - ); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}/settings/pagination`, + body: pagination, + }); return new EnqueuedTask(task); } @@ -749,10 +725,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetPagination(): Promise { - const url = `indexes/${this.uid}/settings/pagination`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/pagination`, + }); return new EnqueuedTask(task); } @@ -764,12 +739,12 @@ class Index = Record> { /** * Get the list of all synonyms * - * @returns Promise containing object of synonym mappings + * @returns Promise containing record of synonym mappings */ - async getSynonyms(): Promise { - const url = `indexes/${this.uid}/settings/synonyms`; - // @TODO: I don't like this type - return await this.httpRequest.get({ relativeURL: url }); + async getSynonyms(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/synonyms`, + }); } /** @@ -779,10 +754,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateSynonyms(synonyms: Synonyms): Promise { - const url = `indexes/${this.uid}/settings/synonyms`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: synonyms }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/synonyms`, + body: synonyms, + }); return new EnqueuedTask(task); } @@ -793,10 +768,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSynonyms(): Promise { - const url = `indexes/${this.uid}/settings/synonyms`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/synonyms`, + }); return new EnqueuedTask(task); } @@ -810,9 +784,10 @@ class Index = Record> { * * @returns Promise containing array of stop-words */ - async getStopWords(): Promise { - const url = `indexes/${this.uid}/settings/stop-words`; - return await this.httpRequest.get({ relativeURL: url }); + async getStopWords(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/stop-words`, + }); } /** @@ -822,10 +797,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateStopWords(stopWords: StopWords): Promise { - const url = `indexes/${this.uid}/settings/stop-words`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: stopWords }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/stop-words`, + body: stopWords, + }); return new EnqueuedTask(task); } @@ -836,10 +811,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetStopWords(): Promise { - const url = `indexes/${this.uid}/settings/stop-words`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/stop-words`, + }); return new EnqueuedTask(task); } @@ -853,9 +827,10 @@ class Index = Record> { * * @returns Promise containing array of ranking-rules */ - async getRankingRules(): Promise { - const url = `indexes/${this.uid}/settings/ranking-rules`; - return await this.httpRequest.get({ relativeURL: url }); + async getRankingRules(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/ranking-rules`, + }); } /** @@ -866,10 +841,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateRankingRules(rankingRules: RankingRules): Promise { - const url = `indexes/${this.uid}/settings/ranking-rules`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: rankingRules }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/ranking-rules`, + body: rankingRules, + }); return new EnqueuedTask(task); } @@ -880,10 +855,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetRankingRules(): Promise { - const url = `indexes/${this.uid}/settings/ranking-rules`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/ranking-rules`, + }); return new EnqueuedTask(task); } @@ -897,9 +871,10 @@ class Index = Record> { * * @returns Promise containing the distinct-attribute of the index */ - async getDistinctAttribute(): Promise { - const url = `indexes/${this.uid}/settings/distinct-attribute`; - return await this.httpRequest.get({ relativeURL: url }); + async getDistinctAttribute(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, + }); } /** @@ -911,10 +886,10 @@ class Index = Record> { async updateDistinctAttribute( distinctAttribute: DistinctAttribute, ): Promise { - const url = `indexes/${this.uid}/settings/distinct-attribute`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: distinctAttribute }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, + body: distinctAttribute, + }); return new EnqueuedTask(task); } @@ -925,10 +900,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDistinctAttribute(): Promise { - const url = `indexes/${this.uid}/settings/distinct-attribute`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, + }); return new EnqueuedTask(task); } @@ -942,9 +916,10 @@ class Index = Record> { * * @returns Promise containing an array of filterable-attributes */ - async getFilterableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/filterable-attributes`; - return await this.httpRequest.get({ relativeURL: url }); + async getFilterableAttributes(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, + }); } /** @@ -957,9 +932,8 @@ class Index = Record> { async updateFilterableAttributes( filterableAttributes: FilterableAttributes, ): Promise { - const url = `indexes/${this.uid}/settings/filterable-attributes`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, body: filterableAttributes, }); @@ -972,10 +946,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetFilterableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/filterable-attributes`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, + }); return new EnqueuedTask(task); } @@ -989,9 +962,10 @@ class Index = Record> { * * @returns Promise containing array of sortable-attributes */ - async getSortableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/sortable-attributes`; - return await this.httpRequest.get({ relativeURL: url }); + async getSortableAttributes(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, + }); } /** @@ -1004,10 +978,10 @@ class Index = Record> { async updateSortableAttributes( sortableAttributes: SortableAttributes, ): Promise { - const url = `indexes/${this.uid}/settings/sortable-attributes`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: sortableAttributes }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, + body: sortableAttributes, + }); return new EnqueuedTask(task); } @@ -1018,10 +992,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSortableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/sortable-attributes`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, + }); return new EnqueuedTask(task); } @@ -1035,9 +1008,10 @@ class Index = Record> { * * @returns Promise containing array of searchable-attributes */ - async getSearchableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/searchable-attributes`; - return await this.httpRequest.get({ relativeURL: url }); + async getSearchableAttributes(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, + }); } /** @@ -1050,9 +1024,8 @@ class Index = Record> { async updateSearchableAttributes( searchableAttributes: SearchableAttributes, ): Promise { - const url = `indexes/${this.uid}/settings/searchable-attributes`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, body: searchableAttributes, }); @@ -1065,10 +1038,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSearchableAttributes(): Promise { - const url = `indexes/${this.uid}/settings/searchable-attributes`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, + }); return new EnqueuedTask(task); } @@ -1082,9 +1054,10 @@ class Index = Record> { * * @returns Promise containing array of displayed-attributes */ - async getDisplayedAttributes(): Promise { - const url = `indexes/${this.uid}/settings/displayed-attributes`; - return await this.httpRequest.get({ relativeURL: url }); + async getDisplayedAttributes(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, + }); } /** @@ -1097,9 +1070,8 @@ class Index = Record> { async updateDisplayedAttributes( displayedAttributes: DisplayedAttributes, ): Promise { - const url = `indexes/${this.uid}/settings/displayed-attributes`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, body: displayedAttributes, }); @@ -1112,10 +1084,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDisplayedAttributes(): Promise { - const url = `indexes/${this.uid}/settings/displayed-attributes`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, + }); return new EnqueuedTask(task); } @@ -1130,8 +1101,9 @@ class Index = Record> { * @returns Promise containing the typo tolerance settings. */ async getTypoTolerance(): Promise { - const url = `indexes/${this.uid}/settings/typo-tolerance`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, + }); } /** @@ -1144,10 +1116,10 @@ class Index = Record> { async updateTypoTolerance( typoTolerance: TypoTolerance, ): Promise { - const url = `indexes/${this.uid}/settings/typo-tolerance`; - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: typoTolerance }) - ); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, + body: typoTolerance, + }); return new EnqueuedTask(task); } @@ -1158,10 +1130,9 @@ class Index = Record> { * @returns Promise containing object of the enqueued update */ async resetTypoTolerance(): Promise { - const url = `indexes/${this.uid}/settings/typo-tolerance`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, + }); return new EnqueuedTask(task); } @@ -1176,8 +1147,9 @@ class Index = Record> { * @returns Promise containing object of faceting index settings */ async getFaceting(): Promise { - const url = `indexes/${this.uid}/settings/faceting`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/faceting`, + }); } /** @@ -1187,10 +1159,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateFaceting(faceting: Faceting): Promise { - const url = `indexes/${this.uid}/settings/faceting`; - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: faceting }) - ); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}/settings/faceting`, + body: faceting, + }); return new EnqueuedTask(task); } @@ -1201,10 +1173,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetFaceting(): Promise { - const url = `indexes/${this.uid}/settings/faceting`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/faceting`, + }); return new EnqueuedTask(task); } @@ -1218,9 +1189,10 @@ class Index = Record> { * * @returns Promise containing array of separator tokens */ - async getSeparatorTokens(): Promise { - const url = `indexes/${this.uid}/settings/separator-tokens`; - return await this.httpRequest.get({ relativeURL: url }); + async getSeparatorTokens(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/separator-tokens`, + }); } /** @@ -1232,10 +1204,10 @@ class Index = Record> { async updateSeparatorTokens( separatorTokens: SeparatorTokens, ): Promise { - const url = `indexes/${this.uid}/settings/separator-tokens`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: separatorTokens }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/separator-tokens`, + body: separatorTokens, + }); return new EnqueuedTask(task); } @@ -1246,10 +1218,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSeparatorTokens(): Promise { - const url = `indexes/${this.uid}/settings/separator-tokens`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/separator-tokens`, + }); return new EnqueuedTask(task); } @@ -1263,9 +1234,10 @@ class Index = Record> { * * @returns Promise containing array of non-separator tokens */ - async getNonSeparatorTokens(): Promise { - const url = `indexes/${this.uid}/settings/non-separator-tokens`; - return await this.httpRequest.get({ relativeURL: url }); + async getNonSeparatorTokens(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, + }); } /** @@ -1277,10 +1249,10 @@ class Index = Record> { async updateNonSeparatorTokens( nonSeparatorTokens: NonSeparatorTokens, ): Promise { - const url = `indexes/${this.uid}/settings/non-separator-tokens`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: nonSeparatorTokens }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, + body: nonSeparatorTokens, + }); return new EnqueuedTask(task); } @@ -1291,10 +1263,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetNonSeparatorTokens(): Promise { - const url = `indexes/${this.uid}/settings/non-separator-tokens`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, + }); return new EnqueuedTask(task); } @@ -1308,9 +1279,10 @@ class Index = Record> { * * @returns Promise containing the dictionary settings */ - async getDictionary(): Promise { - const url = `indexes/${this.uid}/settings/dictionary`; - return await this.httpRequest.get({ relativeURL: url }); + async getDictionary(): Promise { + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/dictionary`, + }); } /** @@ -1320,9 +1292,8 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask or null */ async updateDictionary(dictionary: Dictionary): Promise { - const url = `indexes/${this.uid}/settings/dictionary`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/dictionary`, body: dictionary, }); @@ -1335,10 +1306,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDictionary(): Promise { - const url = `indexes/${this.uid}/settings/dictionary`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/dictionary`, + }); return new EnqueuedTask(task); } @@ -1353,8 +1323,9 @@ class Index = Record> { * @returns Promise containing the proximity precision settings */ async getProximityPrecision(): Promise { - const url = `indexes/${this.uid}/settings/proximity-precision`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/proximity-precision`, + }); } /** @@ -1367,10 +1338,10 @@ class Index = Record> { async updateProximityPrecision( proximityPrecision: ProximityPrecision, ): Promise { - const url = `indexes/${this.uid}/settings/proximity-precision`; - const task = ( - await this.httpRequest.put({ relativeURL: url, body: proximityPrecision }) - ); + const task = await this.httpRequest.put({ + relativeURL: `indexes/${this.uid}/settings/proximity-precision`, + body: proximityPrecision, + }); return new EnqueuedTask(task); } @@ -1381,10 +1352,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetProximityPrecision(): Promise { - const url = `indexes/${this.uid}/settings/proximity-precision`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/proximity-precision`, + }); return new EnqueuedTask(task); } @@ -1399,8 +1369,9 @@ class Index = Record> { * @returns Promise containing the embedders settings */ async getEmbedders(): Promise { - const url = `indexes/${this.uid}/settings/embedders`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/embedders`, + }); } /** @@ -1410,10 +1381,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask or null */ async updateEmbedders(embedders: Embedders): Promise { - const url = `indexes/${this.uid}/settings/embedders`; - const task = ( - await this.httpRequest.patch({ relativeURL: url, body: embedders }) - ); + const task = await this.httpRequest.patch({ + relativeURL: `indexes/${this.uid}/settings/embedders`, + body: embedders, + }); return new EnqueuedTask(task); } @@ -1424,10 +1395,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetEmbedders(): Promise { - const url = `indexes/${this.uid}/settings/embedders`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/embedders`, + }); return new EnqueuedTask(task); } @@ -1442,8 +1412,9 @@ class Index = Record> { * @returns Promise containing object of SearchCutoffMs settings */ async getSearchCutoffMs(): Promise { - const url = `indexes/${this.uid}/settings/search-cutoff-ms`; - return await this.httpRequest.get({ relativeURL: url }); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, + }); } /** @@ -1455,9 +1426,8 @@ class Index = Record> { async updateSearchCutoffMs( searchCutoffMs: SearchCutoffMs, ): Promise { - const url = `indexes/${this.uid}/settings/search-cutoff-ms`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, body: searchCutoffMs, }); @@ -1470,10 +1440,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSearchCutoffMs(): Promise { - const url = `indexes/${this.uid}/settings/search-cutoff-ms`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, + }); return new EnqueuedTask(task); } @@ -1488,10 +1457,9 @@ class Index = Record> { * @returns Promise containing object of localized attributes settings */ async getLocalizedAttributes(): Promise { - const url = `indexes/${this.uid}/settings/localized-attributes`; - return ( - await this.httpRequest.get({ relativeURL: url }) - ); + return await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/settings/localized-attributes`, + }); } /** @@ -1503,9 +1471,8 @@ class Index = Record> { async updateLocalizedAttributes( localizedAttributes: LocalizedAttributes, ): Promise { - const url = `indexes/${this.uid}/settings/localized-attributes`; const task = await this.httpRequest.put({ - relativeURL: url, + relativeURL: `indexes/${this.uid}/settings/localized-attributes`, body: localizedAttributes, }); @@ -1518,10 +1485,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetLocalizedAttributes(): Promise { - const url = `indexes/${this.uid}/settings/localized-attributes`; - const task = ( - await this.httpRequest.delete({ relativeURL: url }) - ); + const task = await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}/settings/localized-attributes`, + }); return new EnqueuedTask(task); } diff --git a/src/task.ts b/src/task.ts index 38ecd2b2d..2577d2ca1 100644 --- a/src/task.ts +++ b/src/task.ts @@ -58,9 +58,8 @@ class TaskClient { * @returns */ async getTask(uid: number): Promise { - const url = `tasks/${uid}`; const taskItem = ( - await this.httpRequest.get({ relativeURL: url }) + await this.httpRequest.get({ relativeURL: `tasks/${uid}` }) ); return new Task(taskItem); } @@ -71,11 +70,9 @@ class TaskClient { * @param params - Parameters to browse the tasks * @returns Promise containing all tasks */ - async getTasks(params: TasksQuery = {}): Promise { - const url = `tasks`; - + async getTasks(params?: TasksQuery): Promise { const tasks = ( - await this.httpRequest.get({ relativeURL: url, params }) + await this.httpRequest.get({ relativeURL: `tasks`, params }) ); return { @@ -142,11 +139,9 @@ class TaskClient { * @param params - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async cancelTasks(params: CancelTasksQuery = {}): Promise { - const url = `tasks/cancel`; - + async cancelTasks(params?: CancelTasksQuery): Promise { const task = ( - await this.httpRequest.post({ relativeURL: url, params }) + await this.httpRequest.post({ relativeURL: `tasks/cancel`, params }) ); return new EnqueuedTask(task); @@ -158,11 +153,9 @@ class TaskClient { * @param params - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async deleteTasks(params: DeleteTasksQuery = {}): Promise { - const url = `tasks`; - + async deleteTasks(params?: DeleteTasksQuery): Promise { const task = ( - await this.httpRequest.delete({ relativeURL: url, params }) + await this.httpRequest.delete({ relativeURL: `tasks`, params }) ); return new EnqueuedTask(task); } diff --git a/src/types/types.ts b/src/types/types.ts index dcd7b3d99..3ca3735be 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -359,9 +359,7 @@ export type SortableAttributes = string[] | null; export type DisplayedAttributes = string[] | null; export type RankingRules = string[] | null; export type StopWords = string[] | null; -export type Synonyms = { - [field: string]: string[]; -} | null; +export type Synonyms = Record | null; export type TypoTolerance = { enabled?: boolean | null; disableOnAttributes?: string[] | null; @@ -524,9 +522,9 @@ export type TasksQuery = { from?: number; }; -export type CancelTasksQuery = Omit & {}; +export type CancelTasksQuery = Omit; -export type DeleteTasksQuery = Omit & {}; +export type DeleteTasksQuery = Omit; export type EnqueuedTaskObject = { taskUid: number; diff --git a/src/utils.ts b/src/utils.ts index b5de6d680..7e8623faa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,15 +1,3 @@ -/** Removes undefined entries from object */ -function removeUndefinedFromObject(obj: Record): object { - return Object.entries(obj).reduce( - (acc, curEntry) => { - const [key, val] = curEntry; - if (val !== undefined) acc[key] = val; - return acc; - }, - {} as Record, - ); -} - async function sleep(ms: number): Promise { return await new Promise((resolve) => setTimeout(resolve, ms)); } @@ -34,10 +22,4 @@ function validateUuid4(uuid: string): boolean { return regexExp.test(uuid); } -export { - sleep, - removeUndefinedFromObject, - addProtocolIfNotPresent, - addTrailingSlash, - validateUuid4, -}; +export { sleep, addProtocolIfNotPresent, addTrailingSlash, validateUuid4 }; diff --git a/tests/client.test.ts b/tests/client.test.ts index be593ef62..b743c648b 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -1,4 +1,14 @@ -import { afterAll, expect, test, describe, beforeEach } from "vitest"; +import { + afterAll, + expect, + test, + describe, + beforeEach, + vi, + type MockInstance, + beforeAll, + assert, +} from "vitest"; import { ErrorStatusCode, Health, Version, Stats, TaskTypes } from "../src"; import { PACKAGE_VERSION } from "../src/package-version"; import { @@ -50,54 +60,85 @@ describe.each([ expect(health).toBe(true); }); - test(`${permission} key: Create client with custom headers (object)`, async () => { - const key = await getKey(permission); - const client = new MeiliSearch({ - ...config, - apiKey: key, - requestInit: { - headers: { - "Hello-There!": "General Kenobi", + describe("Header tests", () => { + let fetchSpy: MockInstance; + + beforeAll(() => { + fetchSpy = vi.spyOn(globalThis, "fetch"); + }); + + afterAll(() => fetchSpy.mockRestore()); + + test(`${permission} key: Create client with custom headers (object)`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + requestInit: { + headers: { + "Hello-There!": "General Kenobi", + }, }, - }, + }); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("Hello-There!"), + "General Kenobi", + ); }); - expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( - "General Kenobi", - ); - const health = await client.isHealthy(); - expect(health).toBe(true); - }); - test(`${permission} key: Create client with custom headers (array)`, async () => { - const key = await getKey(permission); - const client = new MeiliSearch({ - ...config, - apiKey: key, - requestInit: { - headers: [["Hello-There!", "General Kenobi"]], - }, + test(`${permission} key: Create client with custom headers (array)`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + requestInit: { + headers: [["Hello-There!", "General Kenobi"]], + }, + }); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("Hello-There!"), + "General Kenobi", + ); }); - expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( - "General Kenobi", - ); - const health = await client.isHealthy(); - expect(health).toBe(true); - }); - test(`${permission} key: Create client with custom headers (Headers)`, async () => { - const key = await getKey(permission); - const headers = new Headers(); - headers.set("Hello-There!", "General Kenobi"); - const client = new MeiliSearch({ - ...config, - apiKey: key, - requestInit: { headers }, + test(`${permission} key: Create client with custom headers (Headers)`, async () => { + const key = await getKey(permission); + const headers = new Headers(); + headers.set("Hello-There!", "General Kenobi"); + const client = new MeiliSearch({ + ...config, + apiKey: key, + requestInit: { headers }, + }); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("Hello-There!"), + "General Kenobi", + ); }); - expect(client.httpRequest.requestInit.headers.get("Hello-There!")).toBe( - "General Kenobi", - ); - const health = await client.isHealthy(); - expect(health).toBe(true); }); test(`${permission} key: No double slash when on host with domain and path and trailing slash`, async () => { @@ -259,47 +300,79 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( expect(documents.length).toBe(1); }); - test(`${permission} key: Create client with no custom client agents`, async () => { - const key = await getKey(permission); - const client = new MeiliSearch({ - ...config, - apiKey: key, - requestInit: { - headers: {}, - }, + describe("Header tests", () => { + let fetchSpy: MockInstance; + + beforeAll(() => { + fetchSpy = vi.spyOn(globalThis, "fetch"); }); - expect( - client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), - ).toStrictEqual(`Meilisearch JavaScript (v${PACKAGE_VERSION})`); - }); + afterAll(() => fetchSpy.mockRestore()); - test(`${permission} key: Create client with empty custom client agents`, async () => { - const key = await getKey(permission); - const client = new MeiliSearch({ - ...config, - apiKey: key, - clientAgents: [], + test(`${permission} key: Create client with no custom client agents`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + requestInit: { + headers: {}, + }, + }); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("X-Meilisearch-Client"), + `Meilisearch JavaScript (v${PACKAGE_VERSION})`, + ); }); - expect( - client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), - ).toStrictEqual(`Meilisearch JavaScript (v${PACKAGE_VERSION})`); - }); + test(`${permission} key: Create client with empty custom client agents`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + clientAgents: [], + }); - test(`${permission} key: Create client with custom client agents`, async () => { - const key = await getKey(permission); - const client = new MeiliSearch({ - ...config, - apiKey: key, - clientAgents: ["random plugin 1", "random plugin 2"], + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("X-Meilisearch-Client"), + `Meilisearch JavaScript (v${PACKAGE_VERSION})`, + ); }); - expect( - client.httpRequest.requestInit.headers.get("X-Meilisearch-Client"), - ).toStrictEqual( - `random plugin 1 ; random plugin 2 ; Meilisearch JavaScript (v${PACKAGE_VERSION})`, - ); + test(`${permission} key: Create client with custom client agents`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + clientAgents: ["random plugin 1", "random plugin 2"], + }); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [, requestInit] = fetchSpy.mock.lastCall!; + + assert.isDefined(requestInit?.headers); + assert.instanceOf(requestInit!.headers, Headers); + assert.strictEqual( + (requestInit!.headers! as Headers).get("X-Meilisearch-Client"), + `random plugin 1 ; random plugin 2 ; Meilisearch JavaScript (v${PACKAGE_VERSION})`, + ); + }); }); describe("Test on indexes methods", () => { diff --git a/tests/unit.test.ts b/tests/unit.test.ts index 3134974c5..bca78f807 100644 --- a/tests/unit.test.ts +++ b/tests/unit.test.ts @@ -1,27 +1,32 @@ -import { afterAll, expect, test } from "vitest"; +import { afterAll, assert, beforeAll, MockInstance, test, vi } from "vitest"; import { clearAllIndexes, config, MeiliSearch, } from "./utils/meilisearch-test-utils"; -afterAll(() => { - return clearAllIndexes(config); +let fetchSpy: MockInstance; + +beforeAll(() => { + fetchSpy = vi.spyOn(globalThis, "fetch"); }); -test(`Client handles host URL with domain and path`, () => { - const customHost = `${config.host}/api/`; - const client = new MeiliSearch({ - host: customHost, - }); - expect(client.config.host).toBe(customHost); - expect(client.httpRequest.url.href).toBe(customHost); +afterAll(async () => { + fetchSpy.mockRestore(); + await clearAllIndexes(config); }); -test(`Client handles host URL with domain and path and no trailing slash`, () => { +test(`Client handles host URL with domain and path, and adds trailing slash`, async () => { const customHost = `${config.host}/api`; - const client = new MeiliSearch({ - host: customHost, - }); - expect(client.httpRequest.url.href).toBe(customHost + "/"); + const client = new MeiliSearch({ host: customHost }); + + assert.strictEqual(client.config.host, customHost); + + assert.isTrue(await client.isHealthy()); + + assert.isDefined(fetchSpy.mock.lastCall); + const [input] = fetchSpy.mock.lastCall!; + + assert.instanceOf(input, URL); + assert.strictEqual((input as URL).href, `${customHost}/health`); }); diff --git a/tests/utils/meilisearch-test-utils.ts b/tests/utils/meilisearch-test-utils.ts index e2a9cb834..bfbab4258 100644 --- a/tests/utils/meilisearch-test-utils.ts +++ b/tests/utils/meilisearch-test-utils.ts @@ -78,7 +78,7 @@ const clearAllIndexes = async (config: Config): Promise => { const client = new MeiliSearch(config); const { results } = await client.getRawIndexes(); - const indexes = results.map((elem) => elem.uid); + const indexes = results.map(({ uid }) => uid); const taskIds: number[] = []; for (const indexUid of indexes) { From 118534da71249c48e3d1a093c78fa6a6250c56db Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:59:05 +0300 Subject: [PATCH 03/15] Fix type issue --- src/indexes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexes.ts b/src/indexes.ts index baed2163b..4e2640338 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -252,7 +252,7 @@ class Index = Record> { * @param data - Data to update * @returns Promise to the current Index object with updated information */ - async update(data: IndexOptions): Promise { + async update(data?: IndexOptions): Promise { const task = await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}`, body: data, From 942e889e36e8165d5180982a3547c2ef14b132a0 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:44:06 +0300 Subject: [PATCH 04/15] Improve timeout, refactor --- src/clients/client.ts | 116 ++++++++-------- src/http-requests.ts | 115 ++++++++++----- src/indexes.ts | 317 +++++++++++++++++++++--------------------- src/task.ts | 27 ++-- tests/search.test.ts | 2 +- tests/unit.test.ts | 2 +- 6 files changed, 315 insertions(+), 264 deletions(-) diff --git a/src/clients/client.ts b/src/clients/client.ts index bb1d7181b..0473b58dc 100644 --- a/src/clients/client.ts +++ b/src/clients/client.ts @@ -18,25 +18,20 @@ import { ErrorStatusCode, TokenSearchRules, TokenOptions, - TasksQuery, - WaitOptions, KeyUpdate, IndexesQuery, IndexesResults, KeysQuery, KeysResults, - TasksResults, EnqueuedTaskObject, SwapIndexesParams, - CancelTasksQuery, - DeleteTasksQuery, MultiSearchParams, MultiSearchResponse, SearchResponse, FederatedMultiSearchParams, } from "../types"; import { HttpRequests } from "../http-requests"; -import { TaskClient, Task } from "../task"; +import { TaskClient } from "../task"; import { EnqueuedTask } from "../enqueued-task"; class Client { @@ -116,9 +111,10 @@ class Client { async getRawIndexes( parameters?: IndexesQuery, ): Promise> { - return >( - await this.httpRequest.get({ relativeURL: "indexes", params: parameters }) - ); + return (await this.httpRequest.get({ + relativeURL: "indexes", + params: parameters, + })) as IndexesResults; } /** @@ -186,9 +182,10 @@ class Client { */ async swapIndexes(params: SwapIndexesParams): Promise { const url = "/swap-indexes"; - const taks = ( - await this.httpRequest.post({ relativeURL: url, body: params }) - ); + const taks = (await this.httpRequest.post({ + relativeURL: url, + body: params, + })) as EnqueuedTaskObject; return new EnqueuedTask(taks); } @@ -230,12 +227,10 @@ class Client { queries: MultiSearchParams | FederatedMultiSearchParams, config?: Partial, ): Promise | SearchResponse> { - return | SearchResponse>( - await this.httpRequest.post({ - relativeURL: "multi-search", - body: queries, - }) - ); + return (await this.httpRequest.post({ + relativeURL: "multi-search", + body: queries, + })) as MultiSearchResponse | SearchResponse; } /// @@ -248,8 +243,10 @@ class Client { * @param parameters - Parameters to browse the tasks * @returns Promise returning all tasks */ - async getTasks(parameters?: TasksQuery): Promise { - return await this.tasks.getTasks(parameters); + async getTasks( + ...params: Parameters + ): ReturnType { + return await this.tasks.getTasks(...params); } /** @@ -258,8 +255,10 @@ class Client { * @param taskUid - Task identifier * @returns Promise returning a task */ - async getTask(taskUid: number): Promise { - return await this.tasks.getTask(taskUid); + async getTask( + ...params: Parameters + ): ReturnType { + return await this.tasks.getTask(...params); } /** @@ -270,13 +269,9 @@ class Client { * @returns Promise returning an array of tasks */ async waitForTasks( - taskUids: number[], - { timeOutMs = 5000, intervalMs = 50 }: WaitOptions = {}, - ): Promise { - return await this.tasks.waitForTasks(taskUids, { - timeOutMs, - intervalMs, - }); + ...params: Parameters + ): ReturnType { + return await this.tasks.waitForTasks(...params); } /** @@ -287,13 +282,9 @@ class Client { * @returns Promise returning an array of tasks */ async waitForTask( - taskUid: number, - { timeOutMs = 5000, intervalMs = 50 }: WaitOptions = {}, - ): Promise { - return await this.tasks.waitForTask(taskUid, { - timeOutMs, - intervalMs, - }); + ...params: Parameters + ): ReturnType { + return await this.tasks.waitForTask(...params); } /** @@ -302,8 +293,10 @@ class Client { * @param parameters - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async cancelTasks(parameters: CancelTasksQuery): Promise { - return await this.tasks.cancelTasks(parameters); + async cancelTasks( + ...params: Parameters + ): ReturnType { + return await this.tasks.cancelTasks(...params); } /** @@ -312,8 +305,10 @@ class Client { * @param parameters - Parameters to filter the tasks. * @returns Promise containing an EnqueuedTask */ - async deleteTasks(parameters?: DeleteTasksQuery): Promise { - return await this.tasks.deleteTasks(parameters); + async deleteTasks( + ...params: Parameters + ): ReturnType { + return await this.tasks.deleteTasks(...params); } /// @@ -327,9 +322,10 @@ class Client { * @returns Promise returning an object with keys */ async getKeys(parameters?: KeysQuery): Promise { - const keys = ( - await this.httpRequest.get({ relativeURL: "keys", params: parameters }) - ); + const keys = (await this.httpRequest.get({ + relativeURL: "keys", + params: parameters, + })) as KeysResults; keys.results = keys.results.map((key) => ({ ...key, @@ -347,7 +343,9 @@ class Client { * @returns Promise returning a key */ async getKey(keyOrUid: string): Promise { - return await this.httpRequest.get({ relativeURL: `keys/${keyOrUid}` }); + return (await this.httpRequest.get({ + relativeURL: `keys/${keyOrUid}`, + })) as Key; } /** @@ -357,9 +355,10 @@ class Client { * @returns Promise returning a key */ async createKey(options: KeyCreation): Promise { - return ( - await this.httpRequest.post({ relativeURL: "keys", body: options }) - ); + return (await this.httpRequest.post({ + relativeURL: "keys", + body: options, + })) as Key; } /** @@ -370,10 +369,10 @@ class Client { * @returns Promise returning a key */ async updateKey(keyOrUid: string, options: KeyUpdate): Promise { - return await this.httpRequest.patch({ + return (await this.httpRequest.patch({ relativeURL: `keys/${keyOrUid}`, body: options, - }); + })) as Key; } /** @@ -396,7 +395,7 @@ class Client { * @returns Promise returning an object with health details */ async health(): Promise { - return await this.httpRequest.get({ relativeURL: "health" }); + return (await this.httpRequest.get({ relativeURL: "health" })) as Health; } /** @@ -423,7 +422,7 @@ class Client { * @returns Promise returning object of all the stats */ async getStats(): Promise { - return await this.httpRequest.get({ relativeURL: "stats" }); + return (await this.httpRequest.get({ relativeURL: "stats" })) as Stats; } /// @@ -436,7 +435,7 @@ class Client { * @returns Promise returning object with version details */ async getVersion(): Promise { - return await this.httpRequest.get({ relativeURL: "version" }); + return (await this.httpRequest.get({ relativeURL: "version" })) as Version; } /// @@ -449,9 +448,10 @@ class Client { * @returns Promise returning object of the enqueued task */ async createDump(): Promise { - const task = ( - await this.httpRequest.post({ relativeURL: "dumps" }) - ); + const task = (await this.httpRequest.post({ + relativeURL: "dumps", + })) as EnqueuedTaskObject; + return new EnqueuedTask(task); } @@ -465,9 +465,9 @@ class Client { * @returns Promise returning object of the enqueued task */ async createSnapshot(): Promise { - const task = ( - await this.httpRequest.post({ relativeURL: "snapshots" }) - ); + const task = (await this.httpRequest.post({ + relativeURL: "snapshots", + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } diff --git a/src/http-requests.ts b/src/http-requests.ts index 0c4ef5254..3ef98c057 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -84,6 +84,65 @@ type RequestOptions = { export type MethodOptions = Omit; +const TIMEOUT_SYMBOL = Symbol("Symbol indicating a timeout error"); + +// Attach a timeout signal to `requestInit` +// NOTE: This could be a short few straight forward lines using the following: +// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static +// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static +// But these aren't yet widely supported enough perhaps, nor polyfillable +function getTimeoutFn( + requestInit: RequestInit, + ms: number, +): () => (() => void) | void { + const { signal } = requestInit; + const ac = new AbortController(); + + if (signal != null) { + let acSignalFn: (() => void) | null = null; + + if (signal.aborted) { + ac.abort(signal.reason); + } else { + const fn = () => ac.abort(signal.reason); + + signal.addEventListener("abort", fn, { once: true }); + + acSignalFn = () => signal.removeEventListener("abort", fn); + ac.signal.addEventListener("abort", acSignalFn, { once: true }); + } + + return () => { + if (signal.aborted) { + return; + } + + const to = setTimeout(() => ac.abort(TIMEOUT_SYMBOL), ms); + const fn = () => { + clearTimeout(to); + + if (acSignalFn !== null) { + ac.signal.removeEventListener("abort", acSignalFn); + } + }; + + signal.addEventListener("abort", fn, { once: true }); + + return () => { + signal.removeEventListener("abort", fn); + fn(); + }; + }; + } + + requestInit.signal = ac.signal; + + return () => { + const to = setTimeout(() => ac.abort(TIMEOUT_SYMBOL), ms); + return () => clearTimeout(to); + }; +} + export class HttpRequests { #url: URL; #requestInit: Omit, "headers"> & { @@ -114,35 +173,6 @@ export class HttpRequests { this.#requestTimeout = config.timeout; } - async #fetchWithTimeout( - ...fetchParams: Parameters - ): Promise { - return new Promise((resolve, reject) => { - const fetchPromise = this.#requestFn(...fetchParams); - - const promises: Array> = [fetchPromise]; - - // TimeoutPromise will not run if undefined or zero - let timeoutId: ReturnType; - if (this.#requestTimeout) { - const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => { - reject(new Error("Error: Request Timed Out")); - }, this.#requestTimeout); - }); - - promises.push(timeoutPromise); - } - - Promise.race(promises) - .then(resolve) - .catch(reject) - .finally(() => { - clearTimeout(timeoutId); - }); - }); - } - async #request({ relativeURL, method, @@ -171,7 +201,7 @@ export class HttpRequests { isCustomContentTypeProvided = false; } - const responsePromise = this.#fetchWithTimeout(url, { + const requestInit: RequestInit = { method, body: // in case a custom content-type is provided do not stringify body @@ -181,11 +211,28 @@ export class HttpRequests { : body, ...this.#requestInit, headers: headers ?? this.#requestInit.headers, - }); + }; - const response = await responsePromise.catch((error: unknown) => { - throw new MeiliSearchRequestError(url.toString(), error); - }); + const startTimeout = + this.#requestTimeout !== undefined + ? getTimeoutFn(requestInit, this.#requestTimeout) + : null; + + const responsePromise = this.#requestFn(url, requestInit); + const stopTimeout = startTimeout?.(); + + const response = await responsePromise + .catch((error: unknown) => { + throw new MeiliSearchRequestError( + url.toString(), + error === TIMEOUT_SYMBOL + ? new Error(`request timed out after ${this.#requestTimeout}ms`, { + cause: requestInit, + }) + : error, + ); + }) + .finally(() => stopTimeout?.()); // When using a custom HTTP client, the response is returned to allow the user to parse/handle it as they see fit if (this.#isCustomRequestFnProvided) { diff --git a/src/indexes.ts b/src/indexes.ts index 4e2640338..f2867262a 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -102,10 +102,10 @@ class Index = Record> { options?: S, config?: Partial, ): Promise> { - return >await this.httpRequest.post({ + return (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/search`, body: { q: query, ...options }, - }); + })) as SearchResponse; } /** @@ -147,10 +147,10 @@ class Index = Record> { attributesToSearchOn: options?.attributesToSearchOn?.join(","), }; - return >await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/search`, params: getParams, - }); + })) as SearchResponse; } /** @@ -164,10 +164,10 @@ class Index = Record> { params: SearchForFacetValuesParams, config?: Partial, ): Promise { - return await this.httpRequest.post({ + return (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/facet-search`, body: params, - }); + })) as SearchForFacetValuesResponse; } /** @@ -180,10 +180,10 @@ class Index = Record> { D extends Record = T, S extends SearchParams = SearchParams, >(params: SearchSimilarDocumentsParams): Promise> { - return >await this.httpRequest.post({ + return (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/similar`, body: params, - }); + })) as SearchResponse; } /// @@ -196,9 +196,9 @@ class Index = Record> { * @returns Promise containing index information */ async getRawInfo(): Promise { - const res = ( - await this.httpRequest.get({ relativeURL: `indexes/${this.uid}` }) - ); + const res = (await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}`, + })) as IndexObject; this.primaryKey = res.primaryKey; this.updatedAt = new Date(res.updatedAt); this.createdAt = new Date(res.createdAt); @@ -239,9 +239,10 @@ class Index = Record> { config: Config, ): Promise { const req = new HttpRequests(config); - const task = ( - await req.post({ relativeURL: `indexes`, body: { ...options, uid } }) - ); + const task = (await req.post({ + relativeURL: `indexes`, + body: { ...options, uid }, + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -253,10 +254,10 @@ class Index = Record> { * @returns Promise to the current Index object with updated information */ async update(data?: IndexOptions): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}`, body: data, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -267,9 +268,9 @@ class Index = Record> { * @returns Promise which resolves when index is deleted successfully */ async delete(): Promise { - const task = ( - await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}` }) - ); + const task = (await this.httpRequest.delete({ + relativeURL: `indexes/${this.uid}`, + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -342,9 +343,9 @@ class Index = Record> { * @returns Promise containing object with stats of the index */ async getStats(): Promise { - return ( - await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/stats` }) - ); + return (await this.httpRequest.get({ + relativeURL: `indexes/${this.uid}/stats`, + })) as IndexStats; } /// @@ -366,10 +367,10 @@ class Index = Record> { // In case `filter` is provided, use `POST /documents/fetch` if (parameters?.filter !== undefined) { try { - return >await this.httpRequest.post({ + return (await this.httpRequest.post({ relativeURL: `${relativeBaseURL}/fetch`, body: parameters, - }); + })) as ResourceResults; } catch (e) { if (e instanceof MeiliSearchRequestError) { e.message = versionErrorHintMessage(e.message, "getDocuments"); @@ -381,7 +382,7 @@ class Index = Record> { } } else { // Else use `GET /documents` method - return >await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: relativeBaseURL, params: { ...parameters, @@ -390,7 +391,7 @@ class Index = Record> { ? parameters.fields.join(",") : undefined, }, - }); + })) as ResourceResults; } } @@ -412,13 +413,13 @@ class Index = Record> { return undefined; })(); - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/documents/${documentId}`, params: { ...parameters, fields, }, - }); + })) as D; } /** @@ -432,11 +433,11 @@ class Index = Record> { documents: T[], options?: DocumentOptions, ): Promise { - const task = await this.httpRequest.post({ + const task = (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/documents`, params: options, body: documents, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -456,12 +457,12 @@ class Index = Record> { contentType: ContentType, queryParams?: RawDocumentAdditionOptions, ): Promise { - const task = await this.httpRequest.post({ + const task = (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/documents`, body: documents, params: queryParams, headers: { "Content-Type": contentType }, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -499,11 +500,11 @@ class Index = Record> { documents: Array>, options?: DocumentOptions, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/documents`, params: options, body: documents, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -545,12 +546,12 @@ class Index = Record> { contentType: ContentType, queryParams?: RawDocumentAdditionOptions, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/documents`, body: documents, params: queryParams, headers: { "Content-Type": contentType }, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -562,9 +563,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async deleteDocument(documentId: string | number): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/documents/${documentId}`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -591,10 +592,10 @@ class Index = Record> { : "documents/delete-batch"; try { - const task = await this.httpRequest.post({ + const task = (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/${endpoint}`, body: params, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } catch (e) { @@ -614,9 +615,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async deleteAllDocuments(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/documents`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -636,10 +637,10 @@ class Index = Record> { async updateDocumentsByFunction( options: UpdateDocumentsByFunctionOptions, ): Promise { - const task = await this.httpRequest.post({ + const task = (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/documents/edit`, body: options, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -654,9 +655,9 @@ class Index = Record> { * @returns Promise containing Settings object */ async getSettings(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings`, - }); + })) as Settings; } /** @@ -666,10 +667,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateSettings(settings: Settings): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}/settings`, body: settings, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -680,9 +681,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSettings(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -697,9 +698,9 @@ class Index = Record> { * @returns Promise containing object of pagination settings */ async getPagination(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/pagination`, - }); + })) as PaginationSettings; } /** @@ -711,10 +712,10 @@ class Index = Record> { async updatePagination( pagination: PaginationSettings, ): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}/settings/pagination`, body: pagination, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -725,9 +726,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetPagination(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/pagination`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -742,9 +743,9 @@ class Index = Record> { * @returns Promise containing record of synonym mappings */ async getSynonyms(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/synonyms`, - }); + })) as Synonyms; } /** @@ -754,10 +755,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateSynonyms(synonyms: Synonyms): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/synonyms`, body: synonyms, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -768,9 +769,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSynonyms(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/synonyms`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -785,9 +786,9 @@ class Index = Record> { * @returns Promise containing array of stop-words */ async getStopWords(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/stop-words`, - }); + })) as StopWords; } /** @@ -797,10 +798,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateStopWords(stopWords: StopWords): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/stop-words`, body: stopWords, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -811,9 +812,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetStopWords(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/stop-words`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -828,9 +829,9 @@ class Index = Record> { * @returns Promise containing array of ranking-rules */ async getRankingRules(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/ranking-rules`, - }); + })) as RankingRules; } /** @@ -841,10 +842,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateRankingRules(rankingRules: RankingRules): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/ranking-rules`, body: rankingRules, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -855,9 +856,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetRankingRules(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/ranking-rules`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -872,9 +873,9 @@ class Index = Record> { * @returns Promise containing the distinct-attribute of the index */ async getDistinctAttribute(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, - }); + })) as DistinctAttribute; } /** @@ -886,10 +887,10 @@ class Index = Record> { async updateDistinctAttribute( distinctAttribute: DistinctAttribute, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, body: distinctAttribute, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -900,9 +901,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDistinctAttribute(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/distinct-attribute`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -917,9 +918,9 @@ class Index = Record> { * @returns Promise containing an array of filterable-attributes */ async getFilterableAttributes(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, - }); + })) as FilterableAttributes; } /** @@ -932,10 +933,10 @@ class Index = Record> { async updateFilterableAttributes( filterableAttributes: FilterableAttributes, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, body: filterableAttributes, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -946,9 +947,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetFilterableAttributes(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -963,9 +964,9 @@ class Index = Record> { * @returns Promise containing array of sortable-attributes */ async getSortableAttributes(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, - }); + })) as SortableAttributes; } /** @@ -978,10 +979,10 @@ class Index = Record> { async updateSortableAttributes( sortableAttributes: SortableAttributes, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, body: sortableAttributes, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -992,9 +993,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSortableAttributes(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1009,9 +1010,9 @@ class Index = Record> { * @returns Promise containing array of searchable-attributes */ async getSearchableAttributes(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, - }); + })) as SearchableAttributes; } /** @@ -1024,10 +1025,10 @@ class Index = Record> { async updateSearchableAttributes( searchableAttributes: SearchableAttributes, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, body: searchableAttributes, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1038,9 +1039,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSearchableAttributes(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1055,9 +1056,9 @@ class Index = Record> { * @returns Promise containing array of displayed-attributes */ async getDisplayedAttributes(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, - }); + })) as DisplayedAttributes; } /** @@ -1070,10 +1071,10 @@ class Index = Record> { async updateDisplayedAttributes( displayedAttributes: DisplayedAttributes, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, body: displayedAttributes, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1084,9 +1085,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDisplayedAttributes(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1101,9 +1102,9 @@ class Index = Record> { * @returns Promise containing the typo tolerance settings. */ async getTypoTolerance(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, - }); + })) as TypoTolerance; } /** @@ -1116,10 +1117,10 @@ class Index = Record> { async updateTypoTolerance( typoTolerance: TypoTolerance, ): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, body: typoTolerance, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1130,9 +1131,9 @@ class Index = Record> { * @returns Promise containing object of the enqueued update */ async resetTypoTolerance(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1147,9 +1148,9 @@ class Index = Record> { * @returns Promise containing object of faceting index settings */ async getFaceting(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/faceting`, - }); + })) as Faceting; } /** @@ -1159,10 +1160,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async updateFaceting(faceting: Faceting): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}/settings/faceting`, body: faceting, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1173,9 +1174,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetFaceting(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/faceting`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1190,9 +1191,9 @@ class Index = Record> { * @returns Promise containing array of separator tokens */ async getSeparatorTokens(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/separator-tokens`, - }); + })) as SeparatorTokens; } /** @@ -1204,10 +1205,10 @@ class Index = Record> { async updateSeparatorTokens( separatorTokens: SeparatorTokens, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/separator-tokens`, body: separatorTokens, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1218,9 +1219,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSeparatorTokens(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/separator-tokens`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1235,9 +1236,9 @@ class Index = Record> { * @returns Promise containing array of non-separator tokens */ async getNonSeparatorTokens(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, - }); + })) as NonSeparatorTokens; } /** @@ -1249,10 +1250,10 @@ class Index = Record> { async updateNonSeparatorTokens( nonSeparatorTokens: NonSeparatorTokens, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, body: nonSeparatorTokens, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1263,9 +1264,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetNonSeparatorTokens(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1280,9 +1281,9 @@ class Index = Record> { * @returns Promise containing the dictionary settings */ async getDictionary(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/dictionary`, - }); + })) as Dictionary; } /** @@ -1292,10 +1293,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask or null */ async updateDictionary(dictionary: Dictionary): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/dictionary`, body: dictionary, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1306,9 +1307,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetDictionary(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/dictionary`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1323,9 +1324,9 @@ class Index = Record> { * @returns Promise containing the proximity precision settings */ async getProximityPrecision(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/proximity-precision`, - }); + })) as ProximityPrecision; } /** @@ -1338,10 +1339,10 @@ class Index = Record> { async updateProximityPrecision( proximityPrecision: ProximityPrecision, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/proximity-precision`, body: proximityPrecision, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1352,9 +1353,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetProximityPrecision(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/proximity-precision`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1369,9 +1370,9 @@ class Index = Record> { * @returns Promise containing the embedders settings */ async getEmbedders(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/embedders`, - }); + })) as Embedders; } /** @@ -1381,10 +1382,10 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask or null */ async updateEmbedders(embedders: Embedders): Promise { - const task = await this.httpRequest.patch({ + const task = (await this.httpRequest.patch({ relativeURL: `indexes/${this.uid}/settings/embedders`, body: embedders, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1395,9 +1396,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetEmbedders(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/embedders`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1412,9 +1413,9 @@ class Index = Record> { * @returns Promise containing object of SearchCutoffMs settings */ async getSearchCutoffMs(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, - }); + })) as SearchCutoffMs; } /** @@ -1426,10 +1427,10 @@ class Index = Record> { async updateSearchCutoffMs( searchCutoffMs: SearchCutoffMs, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, body: searchCutoffMs, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1440,9 +1441,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetSearchCutoffMs(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/search-cutoff-ms`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1457,9 +1458,9 @@ class Index = Record> { * @returns Promise containing object of localized attributes settings */ async getLocalizedAttributes(): Promise { - return await this.httpRequest.get({ + return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/localized-attributes`, - }); + })) as LocalizedAttributes; } /** @@ -1471,10 +1472,10 @@ class Index = Record> { async updateLocalizedAttributes( localizedAttributes: LocalizedAttributes, ): Promise { - const task = await this.httpRequest.put({ + const task = (await this.httpRequest.put({ relativeURL: `indexes/${this.uid}/settings/localized-attributes`, body: localizedAttributes, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -1485,9 +1486,9 @@ class Index = Record> { * @returns Promise containing an EnqueuedTask */ async resetLocalizedAttributes(): Promise { - const task = await this.httpRequest.delete({ + const task = (await this.httpRequest.delete({ relativeURL: `indexes/${this.uid}/settings/localized-attributes`, - }); + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } diff --git a/src/task.ts b/src/task.ts index 2577d2ca1..522a96b88 100644 --- a/src/task.ts +++ b/src/task.ts @@ -58,9 +58,9 @@ class TaskClient { * @returns */ async getTask(uid: number): Promise { - const taskItem = ( - await this.httpRequest.get({ relativeURL: `tasks/${uid}` }) - ); + const taskItem = (await this.httpRequest.get({ + relativeURL: `tasks/${uid}`, + })) as TaskObject; return new Task(taskItem); } @@ -71,9 +71,10 @@ class TaskClient { * @returns Promise containing all tasks */ async getTasks(params?: TasksQuery): Promise { - const tasks = ( - await this.httpRequest.get({ relativeURL: `tasks`, params }) - ); + const tasks = (await this.httpRequest.get({ + relativeURL: `tasks`, + params, + })) as TasksResultsObject; return { ...tasks, @@ -140,9 +141,10 @@ class TaskClient { * @returns Promise containing an EnqueuedTask */ async cancelTasks(params?: CancelTasksQuery): Promise { - const task = ( - await this.httpRequest.post({ relativeURL: `tasks/cancel`, params }) - ); + const task = (await this.httpRequest.post({ + relativeURL: `tasks/cancel`, + params, + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } @@ -154,9 +156,10 @@ class TaskClient { * @returns Promise containing an EnqueuedTask */ async deleteTasks(params?: DeleteTasksQuery): Promise { - const task = ( - await this.httpRequest.delete({ relativeURL: `tasks`, params }) - ); + const task = (await this.httpRequest.delete({ + relativeURL: `tasks`, + params, + })) as EnqueuedTaskObject; return new EnqueuedTask(task); } } diff --git a/tests/search.test.ts b/tests/search.test.ts index f4cf6658b..f6e404687 100644 --- a/tests/search.test.ts +++ b/tests/search.test.ts @@ -1269,7 +1269,7 @@ describe.each([ try { await client.health(); } catch (e: any) { - expect(e.cause.message).toEqual("Error: Request Timed Out"); + expect(e.cause.message).toEqual("request timed out after 1ms"); expect(e.name).toEqual("MeiliSearchRequestError"); } }); diff --git a/tests/unit.test.ts b/tests/unit.test.ts index bca78f807..f014cdf6d 100644 --- a/tests/unit.test.ts +++ b/tests/unit.test.ts @@ -22,7 +22,7 @@ test(`Client handles host URL with domain and path, and adds trailing slash`, as assert.strictEqual(client.config.host, customHost); - assert.isTrue(await client.isHealthy()); + await client.isHealthy(); assert.isDefined(fetchSpy.mock.lastCall); const [input] = fetchSpy.mock.lastCall!; From 455d8c34410951f0e6623d6ca383274f90c5cadf Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:21:56 +0300 Subject: [PATCH 05/15] Misc --- src/http-requests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/http-requests.ts b/src/http-requests.ts index 3ef98c057..41dc56c7d 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -86,7 +86,8 @@ export type MethodOptions = Omit; const TIMEOUT_SYMBOL = Symbol("Symbol indicating a timeout error"); -// Attach a timeout signal to `requestInit` +// Attach a timeout signal to `requestInit`, +// while preserving original signal functionality // NOTE: This could be a short few straight forward lines using the following: // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static From d1b442b79e370ed5b7be8091372735acaddd1136 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:50:25 +0300 Subject: [PATCH 06/15] Fix browser env test --- package.json | 2 +- scripts/copy-umd-file.js | 16 ++++++++++++++++ src/http-requests.ts | 4 +++- tests/env/express/.gitignore | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 scripts/copy-umd-file.js diff --git a/package.json b/package.json index 465265ccc..09131b149 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test": "vitest run --coverage", "types:watch": "nodemon --config nodemon.json", "types": "yarn tsc", - "test:env:browser": "yarn build && yarn --cwd tests/env/express && yarn --cwd tests/env/express test", + "test:env:browser": "yarn build && node scripts/copy-umd-file.js --to ./tests/env/express/public && yarn --cwd tests/env/express && yarn --cwd tests/env/express test", "test:watch": "vitest watch", "test:coverage": "yarn test", "test:ci": "yarn test", diff --git a/scripts/copy-umd-file.js b/scripts/copy-umd-file.js new file mode 100644 index 000000000..7572c70c2 --- /dev/null +++ b/scripts/copy-umd-file.js @@ -0,0 +1,16 @@ +const { parseArgs } = require("node:util"); +const { resolve, join, basename } = require("node:path"); +const { copyFileSync } = require("node:fs"); +const pkg = require("../package.json"); + +const { + values: { to }, +} = parseArgs({ options: { to: { type: "string" } } }); + +if (to === undefined) { + throw new Error("required argument `to` missing"); +} + +const umdAbsolutePath = resolve(__dirname, join("..", pkg.jsdelivr)); + +copyFileSync(umdAbsolutePath, join(to, basename(pkg.jsdelivr))); diff --git a/src/http-requests.ts b/src/http-requests.ts index 41dc56c7d..a58c84777 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -169,7 +169,9 @@ export class HttpRequests { headers: getHeaders(config, config.requestInit?.headers), }; - this.#requestFn = config.httpClient ?? fetch; + this.#requestFn = + config.httpClient ?? + fetch.bind(typeof window !== "undefined" ? window : globalThis); this.#isCustomRequestFnProvided = config.httpClient !== undefined; this.#requestTimeout = config.timeout; } diff --git a/tests/env/express/.gitignore b/tests/env/express/.gitignore index 3c3629e64..e6252fa2d 100644 --- a/tests/env/express/.gitignore +++ b/tests/env/express/.gitignore @@ -1 +1,2 @@ node_modules +public/meilisearch.umd.js From c044319d03e6761395f2b09070064232da1bc540 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:08:44 +0300 Subject: [PATCH 07/15] Fix Node.js 18 fetch signal issue --- src/http-requests.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/http-requests.ts b/src/http-requests.ts index a58c84777..e7a933b3a 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -84,7 +84,9 @@ type RequestOptions = { export type MethodOptions = Omit; -const TIMEOUT_SYMBOL = Symbol("Symbol indicating a timeout error"); +// This could be a symbol, but Node.js 18 fetch doesn't support that yet +// https://github.com/nodejs/node/issues/49557 +const TIMEOUT_OBJECT = {}; // Attach a timeout signal to `requestInit`, // while preserving original signal functionality @@ -118,7 +120,7 @@ function getTimeoutFn( return; } - const to = setTimeout(() => ac.abort(TIMEOUT_SYMBOL), ms); + const to = setTimeout(() => ac.abort(TIMEOUT_OBJECT), ms); const fn = () => { clearTimeout(to); @@ -139,7 +141,7 @@ function getTimeoutFn( requestInit.signal = ac.signal; return () => { - const to = setTimeout(() => ac.abort(TIMEOUT_SYMBOL), ms); + const to = setTimeout(() => ac.abort(TIMEOUT_OBJECT), ms); return () => clearTimeout(to); }; } @@ -228,7 +230,7 @@ export class HttpRequests { .catch((error: unknown) => { throw new MeiliSearchRequestError( url.toString(), - error === TIMEOUT_SYMBOL + Object.is(error, TIMEOUT_OBJECT) ? new Error(`request timed out after ${this.#requestTimeout}ms`, { cause: requestInit, }) From e6236f67390c61e2d725e7eb6b3ebfb768fbc329 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:49:40 +0300 Subject: [PATCH 08/15] Add extra RequestInit for search --- src/clients/client.ts | 9 ++++--- src/http-requests.ts | 58 ++++++++++++++++++++++++++++++++----------- src/indexes.ts | 20 +++++++-------- src/task.ts | 6 ++--- src/types/types.ts | 2 +- tests/search.test.ts | 58 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 32 deletions(-) diff --git a/src/clients/client.ts b/src/clients/client.ts index 0473b58dc..ee6d82dd9 100644 --- a/src/clients/client.ts +++ b/src/clients/client.ts @@ -30,7 +30,7 @@ import { SearchResponse, FederatedMultiSearchParams, } from "../types"; -import { HttpRequests } from "../http-requests"; +import { ExtraRequestInit, HttpRequests } from "../http-requests"; import { TaskClient } from "../task"; import { EnqueuedTask } from "../enqueued-task"; @@ -217,19 +217,20 @@ class Client { */ multiSearch = Record>( queries: MultiSearchParams, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise>; multiSearch = Record>( queries: FederatedMultiSearchParams, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise>; async multiSearch = Record>( queries: MultiSearchParams | FederatedMultiSearchParams, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise | SearchResponse> { return (await this.httpRequest.post({ relativeURL: "multi-search", body: queries, + extraRequestInit, })) as MultiSearchResponse | SearchResponse; } diff --git a/src/http-requests.ts b/src/http-requests.ts index e7a933b3a..06565c30b 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -80,10 +80,13 @@ type RequestOptions = { params?: URLSearchParamsRecord; headers?: HeadersInit; body?: unknown; + extraRequestInit?: ExtraRequestInit; }; export type MethodOptions = Omit; +export type ExtraRequestInit = Omit; + // This could be a symbol, but Node.js 18 fetch doesn't support that yet // https://github.com/nodejs/node/issues/49557 const TIMEOUT_OBJECT = {}; @@ -178,22 +181,19 @@ export class HttpRequests { this.#requestTimeout = config.timeout; } - async #request({ - relativeURL, - method, - params, - headers, - body, - }: RequestOptions): Promise { - const url = new URL(relativeURL, this.#url); - if (params !== undefined) { - appendRecordToURLSearchParams(url.searchParams, params); - } - + // combine headers, with the following priority: + // 1. `headers` - primary headers provided by functions in Index and Client + // 2. `this.#requestInit.headers` - main headers of this class + // 3. `extraHeaders` - extra headers provided in search functions by users + #getHeaders( + headers?: HeadersInit, + extraHeaders?: HeadersInit, + ): { finalHeaders: Headers; isCustomContentTypeProvided: boolean } { let isCustomContentTypeProvided: boolean; - if (headers !== undefined) { + if (headers !== undefined || extraHeaders !== undefined) { headers = new Headers(headers); + extraHeaders = new Headers(extraHeaders); isCustomContentTypeProvided = headers.has("Content-Type"); @@ -202,10 +202,39 @@ export class HttpRequests { headers.set(key, val); } } + + for (const [key, val] of extraHeaders.entries()) { + if (!headers.has(key)) { + headers.set(key, val); + } + } } else { isCustomContentTypeProvided = false; } + const finalHeaders = headers ?? this.#requestInit.headers; + + return { finalHeaders, isCustomContentTypeProvided }; + } + + async #request({ + relativeURL, + method, + params, + headers, + body, + extraRequestInit, + }: RequestOptions): Promise { + const url = new URL(relativeURL, this.#url); + if (params !== undefined) { + appendRecordToURLSearchParams(url.searchParams, params); + } + + const { finalHeaders, isCustomContentTypeProvided } = this.#getHeaders( + headers, + extraRequestInit?.headers, + ); + const requestInit: RequestInit = { method, body: @@ -215,7 +244,8 @@ export class HttpRequests { JSON.stringify(body) : body, ...this.#requestInit, - headers: headers ?? this.#requestInit.headers, + ...extraRequestInit, + headers: finalHeaders, }; const startTimeout = diff --git a/src/indexes.ts b/src/indexes.ts index f2867262a..965cd9cc8 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -58,7 +58,7 @@ import { UpdateDocumentsByFunctionOptions, EnqueuedTaskObject, } from "./types"; -import { HttpRequests } from "./http-requests"; +import { ExtraRequestInit, HttpRequests } from "./http-requests"; import { Task, TaskClient } from "./task"; import { EnqueuedTask } from "./enqueued-task"; @@ -100,11 +100,12 @@ class Index = Record> { >( query?: string | null, options?: S, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise> { return (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/search`, body: { q: query, ...options }, + extraRequestInit, })) as SearchResponse; } @@ -122,7 +123,7 @@ class Index = Record> { >( query?: string | null, options?: S, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise> { // @TODO: Make this a type thing instead of a runtime thing const parseFilter = (filter?: Filter): string | undefined => { @@ -150,6 +151,7 @@ class Index = Record> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/search`, params: getParams, + extraRequestInit, })) as SearchResponse; } @@ -162,11 +164,12 @@ class Index = Record> { */ async searchForFacetValues( params: SearchForFacetValuesParams, - config?: Partial, + extraRequestInit?: ExtraRequestInit, ): Promise { return (await this.httpRequest.post({ relativeURL: `indexes/${this.uid}/facet-search`, body: params, + extraRequestInit, })) as SearchForFacetValuesResponse; } @@ -240,7 +243,7 @@ class Index = Record> { ): Promise { const req = new HttpRequests(config); const task = (await req.post({ - relativeURL: `indexes`, + relativeURL: "indexes", body: { ...options, uid }, })) as EnqueuedTaskObject; @@ -388,7 +391,7 @@ class Index = Record> { ...parameters, // Transform fields to query parameter string format fields: Array.isArray(parameters?.fields) - ? parameters.fields.join(",") + ? parameters.fields.join() : undefined, }, })) as ResourceResults; @@ -415,10 +418,7 @@ class Index = Record> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/documents/${documentId}`, - params: { - ...parameters, - fields, - }, + params: { ...parameters, fields }, })) as D; } diff --git a/src/task.ts b/src/task.ts index 522a96b88..c1804bf37 100644 --- a/src/task.ts +++ b/src/task.ts @@ -72,7 +72,7 @@ class TaskClient { */ async getTasks(params?: TasksQuery): Promise { const tasks = (await this.httpRequest.get({ - relativeURL: `tasks`, + relativeURL: "tasks", params, })) as TasksResultsObject; @@ -142,7 +142,7 @@ class TaskClient { */ async cancelTasks(params?: CancelTasksQuery): Promise { const task = (await this.httpRequest.post({ - relativeURL: `tasks/cancel`, + relativeURL: "tasks/cancel", params, })) as EnqueuedTaskObject; @@ -157,7 +157,7 @@ class TaskClient { */ async deleteTasks(params?: DeleteTasksQuery): Promise { const task = (await this.httpRequest.delete({ - relativeURL: `tasks`, + relativeURL: "tasks", params, })) as EnqueuedTaskObject; return new EnqueuedTask(task); diff --git a/src/types/types.ts b/src/types/types.ts index 3ca3735be..9db7f01d4 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -10,7 +10,7 @@ export type Config = { host: string; apiKey?: string; clientAgents?: string[]; - requestInit?: Omit; + requestInit?: Omit; httpClient?: typeof fetch; timeout?: number; }; diff --git a/tests/search.test.ts b/tests/search.test.ts index f6e404687..83b56fabd 100644 --- a/tests/search.test.ts +++ b/tests/search.test.ts @@ -5,6 +5,8 @@ import { beforeEach, afterAll, beforeAll, + assert, + vi, } from "vitest"; import { ErrorStatusCode, MatchingStrategies } from "../src/types"; import { EnqueuedTask } from "../src/enqueued-task"; @@ -1273,6 +1275,62 @@ describe.each([ expect(e.name).toEqual("MeiliSearchRequestError"); } }); + + test(`${permission} key: search should be aborted on already abort signal`, async () => { + const key = await getKey(permission); + const client = new MeiliSearch({ + ...config, + apiKey: key, + timeout: 1_000, + }); + const someErrorObj = {}; + + try { + const ac = new AbortController(); + ac.abort(someErrorObj); + + await client.multiSearch( + { queries: [{ indexUid: "doesn't matter" }] }, + { signal: ac.signal }, + ); + } catch (e: any) { + assert.strictEqual(e.cause, someErrorObj); + assert.strictEqual(e.name, "MeiliSearchRequestError"); + } + + vi.stubGlobal("fetch", (_, requestInit?: RequestInit) => { + return new Promise((_, reject) => { + setInterval(() => { + if (requestInit?.signal?.aborted) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(requestInit.signal.reason); + } + }, 5); + }); + }); + + const clientWithStubbedFetch = new MeiliSearch({ + ...config, + apiKey: key, + timeout: 1_000, + }); + + try { + const ac = new AbortController(); + + const promise = clientWithStubbedFetch.multiSearch( + { queries: [{ indexUid: "doesn't matter" }] }, + { signal: ac.signal }, + ); + setTimeout(() => ac.abort(someErrorObj), 1); + await promise; + } catch (e: any) { + assert.strictEqual(e.cause, someErrorObj); + assert.strictEqual(e.name, "MeiliSearchRequestError"); + } finally { + vi.unstubAllGlobals(); + } + }); }); describe.each([ From 4a497905d2bfa8100e021914d67dc369f2d9b886 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:15:51 +0300 Subject: [PATCH 09/15] Refactor --- src/http-requests.ts | 41 ++++++++++------------------------------- src/types/types.ts | 32 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/http-requests.ts b/src/http-requests.ts index 06565c30b..2f8eeef5c 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -1,4 +1,10 @@ -import { Config } from "./types"; +import type { + Config, + HttpRequestsRequestInit, + MethodOptions, + RequestOptions, + URLSearchParamsRecord, +} from "./types"; import { PACKAGE_VERSION } from "./package-version"; import { @@ -9,19 +15,6 @@ import { import { addProtocolIfNotPresent, addTrailingSlash } from "./utils"; -type URLSearchParamsRecord = Record< - string, - | string - | string[] - | Array - | number - | number[] - | boolean - | Date - | null - | undefined ->; - function appendRecordToURLSearchParams( searchParams: URLSearchParams, recordToAppend: URLSearchParamsRecord, @@ -74,19 +67,6 @@ function getHeaders(config: Config, headersInit?: HeadersInit): Headers { return headers; } -type RequestOptions = { - relativeURL: string; - method?: string; - params?: URLSearchParamsRecord; - headers?: HeadersInit; - body?: unknown; - extraRequestInit?: ExtraRequestInit; -}; - -export type MethodOptions = Omit; - -export type ExtraRequestInit = Omit; - // This could be a symbol, but Node.js 18 fetch doesn't support that yet // https://github.com/nodejs/node/issues/49557 const TIMEOUT_OBJECT = {}; @@ -151,10 +131,8 @@ function getTimeoutFn( export class HttpRequests { #url: URL; - #requestInit: Omit, "headers"> & { - headers: Headers; - }; - #requestFn: NonNullable; + #requestInit: HttpRequestsRequestInit; + #requestFn: typeof fetch; #isCustomRequestFnProvided: boolean; #requestTimeout?: number; @@ -176,6 +154,7 @@ export class HttpRequests { this.#requestFn = config.httpClient ?? + // in browsers `fetch` can only be called with a `this` pointing to `window` fetch.bind(typeof window !== "undefined" ? window : globalThis); this.#isCustomRequestFnProvided = config.httpClient !== undefined; this.#requestTimeout = config.timeout; diff --git a/src/types/types.ts b/src/types/types.ts index 9db7f01d4..578878e9d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -6,15 +6,45 @@ import { Task } from "../task"; +export type URLSearchParamsRecord = Record< + string, + | string + | string[] + | Array + | number + | number[] + | boolean + | Date + | null + | undefined +>; + +export type ExtraRequestInit = Omit; +export type BaseRequestInit = Omit; +export type HttpRequestsRequestInit = Omit & { + headers: Headers; +}; + export type Config = { host: string; apiKey?: string; clientAgents?: string[]; - requestInit?: Omit; + requestInit?: BaseRequestInit; httpClient?: typeof fetch; timeout?: number; }; +export type RequestOptions = { + relativeURL: string; + method?: string; + params?: URLSearchParamsRecord; + headers?: HeadersInit; + body?: unknown; + extraRequestInit?: ExtraRequestInit; +}; + +export type MethodOptions = Omit; + /// /// Resources /// From 0e41c86dd1d95b8b224fa1169830c6d28bc1f47e Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:17:38 +0300 Subject: [PATCH 10/15] Fix type issues --- src/clients/client.ts | 3 ++- src/indexes.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clients/client.ts b/src/clients/client.ts index ee6d82dd9..053bfd941 100644 --- a/src/clients/client.ts +++ b/src/clients/client.ts @@ -29,8 +29,9 @@ import { MultiSearchResponse, SearchResponse, FederatedMultiSearchParams, + ExtraRequestInit, } from "../types"; -import { ExtraRequestInit, HttpRequests } from "../http-requests"; +import { HttpRequests } from "../http-requests"; import { TaskClient } from "../task"; import { EnqueuedTask } from "../enqueued-task"; diff --git a/src/indexes.ts b/src/indexes.ts index 965cd9cc8..3464f0e4f 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -57,8 +57,9 @@ import { LocalizedAttributes, UpdateDocumentsByFunctionOptions, EnqueuedTaskObject, + ExtraRequestInit, } from "./types"; -import { ExtraRequestInit, HttpRequests } from "./http-requests"; +import { HttpRequests } from "./http-requests"; import { Task, TaskClient } from "./task"; import { EnqueuedTask } from "./enqueued-task"; From 1949134a5ccb5eb11d6cdf6539e4802cb5df0119 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:36:41 +0300 Subject: [PATCH 11/15] Fix some types --- src/indexes.ts | 16 ++++++++-------- tests/dictionary.test.ts | 2 +- tests/stop_words.test.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/indexes.ts b/src/indexes.ts index 3464f0e4f..7f4fc1aa9 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -786,10 +786,10 @@ class Index = Record> { * * @returns Promise containing array of stop-words */ - async getStopWords(): Promise { + async getStopWords(): Promise> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/stop-words`, - })) as StopWords; + })) as NonNullable; } /** @@ -1056,10 +1056,10 @@ class Index = Record> { * * @returns Promise containing array of displayed-attributes */ - async getDisplayedAttributes(): Promise { + async getDisplayedAttributes(): Promise> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, - })) as DisplayedAttributes; + })) as NonNullable; } /** @@ -1102,10 +1102,10 @@ class Index = Record> { * * @returns Promise containing the typo tolerance settings. */ - async getTypoTolerance(): Promise { + async getTypoTolerance(): Promise> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, - })) as TypoTolerance; + })) as NonNullable; } /** @@ -1281,10 +1281,10 @@ class Index = Record> { * * @returns Promise containing the dictionary settings */ - async getDictionary(): Promise { + async getDictionary(): Promise> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/dictionary`, - })) as Dictionary; + })) as NonNullable; } /** diff --git a/tests/dictionary.test.ts b/tests/dictionary.test.ts index 5c352347a..c5440306e 100644 --- a/tests/dictionary.test.ts +++ b/tests/dictionary.test.ts @@ -28,7 +28,7 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( test(`${permission} key: Get default dictionary`, async () => { const client = await getClient(permission); - const response: string[] = await client.index(index.uid).getDictionary(); + const response = await client.index(index.uid).getDictionary(); expect(response).toEqual([]); }); diff --git a/tests/stop_words.test.ts b/tests/stop_words.test.ts index 0980a836a..f7a321541 100644 --- a/tests/stop_words.test.ts +++ b/tests/stop_words.test.ts @@ -29,7 +29,7 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( test(`${permission} key: Get default stop words`, async () => { const client = await getClient(permission); - const response: string[] = await client.index(index.uid).getStopWords(); + const response = await client.index(index.uid).getStopWords(); expect(response).toEqual([]); }); From ace3412419ba0284a2a277ddca746fcff801da52 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:59:16 +0300 Subject: [PATCH 12/15] Make extraRequestInit function as it originally did --- src/http-requests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http-requests.ts b/src/http-requests.ts index 2f8eeef5c..dfec54a68 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -222,8 +222,8 @@ export class HttpRequests { ? // this will throw an error for any value that is not serializable JSON.stringify(body) : body, - ...this.#requestInit, ...extraRequestInit, + ...this.#requestInit, headers: finalHeaders, }; From 3854e13746acb8ff4fd56fc851ba05beb228c4e7 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:07:22 +0300 Subject: [PATCH 13/15] Remove unnecessary transformation --- src/indexes.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/indexes.ts b/src/indexes.ts index 7f4fc1aa9..df9ca0c6b 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -359,21 +359,21 @@ class Index = Record> { /** * Get documents of an index. * - * @param parameters - Parameters to browse the documents. Parameters can - * contain the `filter` field only available in Meilisearch v1.2 and newer + * @param params - Parameters to browse the documents. Parameters can contain + * the `filter` field only available in Meilisearch v1.2 and newer * @returns Promise containing the returned documents */ async getDocuments = T>( - parameters?: DocumentsQuery, + params?: DocumentsQuery, ): Promise> { const relativeBaseURL = `indexes/${this.uid}/documents`; // In case `filter` is provided, use `POST /documents/fetch` - if (parameters?.filter !== undefined) { + if (params?.filter !== undefined) { try { return (await this.httpRequest.post({ relativeURL: `${relativeBaseURL}/fetch`, - body: parameters, + body: params, })) as ResourceResults; } catch (e) { if (e instanceof MeiliSearchRequestError) { @@ -388,13 +388,7 @@ class Index = Record> { // Else use `GET /documents` method return (await this.httpRequest.get({ relativeURL: relativeBaseURL, - params: { - ...parameters, - // Transform fields to query parameter string format - fields: Array.isArray(parameters?.fields) - ? parameters.fields.join() - : undefined, - }, + params, })) as ResourceResults; } } From d73d58d359b43a36cc000d19cd0aca83c533df4a Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:53:50 +0300 Subject: [PATCH 14/15] Revert some type changes --- src/indexes.ts | 44 +++++++++++++++++++++--------------------- tests/synonyms.test.ts | 2 +- tests/task.test.ts | 5 +---- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/indexes.ts b/src/indexes.ts index df9ca0c6b..cf0941ced 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -737,10 +737,10 @@ class Index = Record> { * * @returns Promise containing record of synonym mappings */ - async getSynonyms(): Promise { + async getSynonyms(): Promise> { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/synonyms`, - })) as Synonyms; + })) as Record; } /** @@ -780,10 +780,10 @@ class Index = Record> { * * @returns Promise containing array of stop-words */ - async getStopWords(): Promise> { + async getStopWords(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/stop-words`, - })) as NonNullable; + })) as string[]; } /** @@ -823,10 +823,10 @@ class Index = Record> { * * @returns Promise containing array of ranking-rules */ - async getRankingRules(): Promise { + async getRankingRules(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/ranking-rules`, - })) as RankingRules; + })) as string[]; } /** @@ -912,10 +912,10 @@ class Index = Record> { * * @returns Promise containing an array of filterable-attributes */ - async getFilterableAttributes(): Promise { + async getFilterableAttributes(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/filterable-attributes`, - })) as FilterableAttributes; + })) as string[]; } /** @@ -958,10 +958,10 @@ class Index = Record> { * * @returns Promise containing array of sortable-attributes */ - async getSortableAttributes(): Promise { + async getSortableAttributes(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/sortable-attributes`, - })) as SortableAttributes; + })) as string[]; } /** @@ -1004,10 +1004,10 @@ class Index = Record> { * * @returns Promise containing array of searchable-attributes */ - async getSearchableAttributes(): Promise { + async getSearchableAttributes(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/searchable-attributes`, - })) as SearchableAttributes; + })) as string[]; } /** @@ -1050,10 +1050,10 @@ class Index = Record> { * * @returns Promise containing array of displayed-attributes */ - async getDisplayedAttributes(): Promise> { + async getDisplayedAttributes(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/displayed-attributes`, - })) as NonNullable; + })) as string[]; } /** @@ -1096,10 +1096,10 @@ class Index = Record> { * * @returns Promise containing the typo tolerance settings. */ - async getTypoTolerance(): Promise> { + async getTypoTolerance(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/typo-tolerance`, - })) as NonNullable; + })) as TypoTolerance; } /** @@ -1185,10 +1185,10 @@ class Index = Record> { * * @returns Promise containing array of separator tokens */ - async getSeparatorTokens(): Promise { + async getSeparatorTokens(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/separator-tokens`, - })) as SeparatorTokens; + })) as string[]; } /** @@ -1230,10 +1230,10 @@ class Index = Record> { * * @returns Promise containing array of non-separator tokens */ - async getNonSeparatorTokens(): Promise { + async getNonSeparatorTokens(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/non-separator-tokens`, - })) as NonSeparatorTokens; + })) as string[]; } /** @@ -1275,10 +1275,10 @@ class Index = Record> { * * @returns Promise containing the dictionary settings */ - async getDictionary(): Promise> { + async getDictionary(): Promise { return (await this.httpRequest.get({ relativeURL: `indexes/${this.uid}/settings/dictionary`, - })) as NonNullable; + })) as string[]; } /** diff --git a/tests/synonyms.test.ts b/tests/synonyms.test.ts index 110439e67..5c98ec4ae 100644 --- a/tests/synonyms.test.ts +++ b/tests/synonyms.test.ts @@ -28,7 +28,7 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( test(`${permission} key: Get default synonyms`, async () => { const client = await getClient(permission); - const response: object = await client.index(index.uid).getSynonyms(); + const response = await client.index(index.uid).getSynonyms(); expect(response).toEqual({}); }); diff --git a/tests/task.test.ts b/tests/task.test.ts index d36fa677b..ed3dd5f83 100644 --- a/tests/task.test.ts +++ b/tests/task.test.ts @@ -589,10 +589,7 @@ describe.each([{ permission: "Master" }, { permission: "Admin" }])( test(`${permission} key: Try to cancel without filters and fail`, async () => { const client = await getClient(permission); - await expect( - // @ts-expect-error testing wrong argument type - client.cancelTasks(), - ).rejects.toHaveProperty( + await expect(client.cancelTasks()).rejects.toHaveProperty( "cause.code", ErrorStatusCode.MISSING_TASK_FILTERS, ); From 2d4849db9c45d73ce3f90cda3b1d5d0f44c59056 Mon Sep 17 00:00:00 2001 From: "F. Levi" <55688616+flevi29@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:10:48 +0300 Subject: [PATCH 15/15] Optimize --- src/http-requests.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http-requests.ts b/src/http-requests.ts index dfec54a68..fcb94c639 100644 --- a/src/http-requests.ts +++ b/src/http-requests.ts @@ -172,8 +172,6 @@ export class HttpRequests { if (headers !== undefined || extraHeaders !== undefined) { headers = new Headers(headers); - extraHeaders = new Headers(extraHeaders); - isCustomContentTypeProvided = headers.has("Content-Type"); for (const [key, val] of this.#requestInit.headers.entries()) { @@ -182,9 +180,11 @@ export class HttpRequests { } } - for (const [key, val] of extraHeaders.entries()) { - if (!headers.has(key)) { - headers.set(key, val); + if (extraHeaders !== undefined) { + for (const [key, val] of new Headers(extraHeaders).entries()) { + if (!headers.has(key)) { + headers.set(key, val); + } } } } else {