UNPKG

meilisearch

Version:

The Meilisearch JS client for Node.js and the browser.

198 lines 6.72 kB
import { PACKAGE_VERSION } from "./package-version.js"; import { MeiliSearchError, MeiliSearchApiError, MeiliSearchRequestError, } from "./errors/index.js"; import { addTrailingSlash, addProtocolIfNotPresent } from "./utils.js"; function toQueryParams(parameters) { const params = Object.keys(parameters); 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() }; } return { ...acc, [key]: value }; }, {}); return queryParams; } function constructHostURL(host) { try { host = addProtocolIfNotPresent(host); host = addTrailingSlash(host); return host; } catch { throw new MeiliSearchError("The provided host is not valid."); } } function cloneAndParseHeaders(headers) { if (Array.isArray(headers)) { return headers.reduce((acc, headerPair) => { acc[headerPair[0]] = headerPair[1]; return acc; }, {}); } else if ("has" in headers) { const clonedHeaders = {}; headers.forEach((value, key) => (clonedHeaders[key] = value)); return clonedHeaders; } else { return Object.assign({}, headers); } } function createHeaders(config) { 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 ?? {}); // do not override if user provided the header if (config.apiKey && !headers[authorization]) { headers[authorization] = `Bearer ${config.apiKey}`; } if (!headers[contentType]) { headers["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(" ; "); } 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; } return headers; } class HttpRequests { headers; url; requestConfig; httpClient; requestTimeout; constructor(config) { this.headers = createHeaders(config); this.requestConfig = config.requestConfig; this.httpClient = config.httpClient; this.requestTimeout = config.timeout; try { const host = constructHostURL(config.host); this.url = new URL(host); } catch { throw new MeiliSearchError("The provided host is not valid."); } } async request({ method, url, params, body, config = {}, }) { const constructURL = new URL(url, this.url); if (params) { const queryParams = new URLSearchParams(); Object.keys(params) .filter((x) => params[x] !== null) .map((x) => 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) => { 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); } return parsedResponse; } async fetchWithTimeout(url, options, timeout) { return new Promise((resolve, reject) => { const fetchFn = this.httpClient ? this.httpClient : fetch; const fetchPromise = fetchFn(url, options); const promises = [fetchPromise]; // TimeoutPromise will not run if undefined or zero let timeoutId; if (timeout) { const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error("Error: Request Timed Out")); }, timeout); }); promises.push(timeoutPromise); } Promise.race(promises) .then(resolve) .catch(reject) .finally(() => { clearTimeout(timeoutId); }); }); } async get(url, params, config) { return await this.request({ method: "GET", url, params, config, }); } async post(url, data, params, config) { return await this.request({ method: "POST", url, body: data, params, config, }); } async put(url, data, params, config) { return await this.request({ method: "PUT", url, body: data, params, config, }); } async patch(url, data, params, config) { return await this.request({ method: "PATCH", url, body: data, params, config, }); } async delete(url, data, params, config) { return await this.request({ method: "DELETE", url, body: data, params, config, }); } } export { HttpRequests, toQueryParams }; //# sourceMappingURL=http-requests.js.map