meilisearch
Version:
The Meilisearch JS client for Node.js and the browser.
198 lines • 6.72 kB
JavaScript
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