reduct-js
Version:
ReductStore Client SDK for Javascript/NodeJS/Typescript
167 lines (166 loc) • 6.33 kB
JavaScript
import { __require } from "../_virtual/_rolldown/runtime.js";
import { APIError } from "../APIError.js";
import { isBrowser } from "../utils/env.js";
import { PACKAGE_VERSION } from "../version.js";
import { Buffer } from "buffer";
import JSONbig from "json-bigint";
//#region src/http/HttpClient.ts
var bigJson = JSONbig({
alwaysParseAsBig: false,
useNativeBigInt: true
});
var undiciAgent = null;
var undiciFetchImpl;
if (!isBrowser) try {
const { Agent, fetch } = __require("undici");
undiciFetchImpl = fetch;
undiciAgent = new Agent({ connect: { rejectUnauthorized: false } });
} catch {}
var InMemoryCookieJar = class {
constructor() {
this.cookies = /* @__PURE__ */ new Map();
}
getCookieHeader() {
if (this.cookies.size === 0) return;
return Array.from(this.cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
}
setCookies(setCookieHeaders) {
for (const header of setCookieHeaders) {
const firstPair = header.split(";")[0]?.trim();
if (!firstPair) continue;
const equalPos = firstPair.indexOf("=");
if (equalPos <= 0) continue;
const name = firstPair.slice(0, equalPos).trim();
const value = firstPair.slice(equalPos + 1).trim();
if (!name) continue;
this.cookies.set(name, value);
}
}
};
var getSetCookieHeaders = (headers) => {
const maybeUndiciHeaders = headers;
if (typeof maybeUndiciHeaders.getSetCookie === "function") return maybeUndiciHeaders.getSetCookie();
if (typeof maybeUndiciHeaders.raw === "function") return maybeUndiciHeaders.raw()["set-cookie"] ?? [];
const single = headers.get("set-cookie");
return single ? [single] : [];
};
var HttpClient = class {
constructor(url, options = {}) {
this.baseURL = `${url}/api/v1`;
this.timeout = options.timeout;
this.keepAlive = options.keepAlive ?? false;
this.stickySessions = options.stickySessions ?? !isBrowser;
this.cookieJar = this.stickySessions ? options.cookieJar ?? new InMemoryCookieJar() : void 0;
this.headers = {
Authorization: `Bearer ${options.apiToken}`,
...!isBrowser && !this.keepAlive ? { Connection: "close" } : {}
};
if (!isBrowser && options.verifySSL === false) this.dispatcher = undiciAgent;
this.fetchImpl = this.dispatcher && undiciFetchImpl ? undiciFetchImpl : globalThis.fetch.bind(globalThis);
}
async close() {
if (isBrowser) return;
if (this.dispatcher?.close) {
await this.dispatcher.close();
return;
}
const dispatcher = (await import(
/* webpackIgnore: true */
"undici"
)).getGlobalDispatcher?.();
if (dispatcher?.destroy) {
dispatcher.destroy();
return;
}
if (dispatcher?.close) await dispatcher.close();
}
async request(method, url, body, headers) {
const controller = new AbortController();
const { signal } = controller;
const { timeout } = this;
let abortedByTimeout = false;
if (timeout) setTimeout(() => {
abortedByTimeout = true;
controller.abort();
}, timeout);
const encodedBody = this.encodeBody(body);
const hasReadableStream = typeof ReadableStream !== "undefined" && encodedBody instanceof ReadableStream;
const requestHeaders = {
...this.headers,
...headers
};
if (this.stickySessions) {
const cookieHeader = this.cookieJar?.getCookieHeader();
if (cookieHeader) requestHeaders["Cookie"] = cookieHeader;
}
const init = {
method,
headers: requestHeaders,
signal
};
if (isBrowser && this.stickySessions) init.credentials = "include";
if (encodedBody !== void 0) init.body = encodedBody;
if (this.dispatcher) init.dispatcher = this.dispatcher;
if (hasReadableStream) init.duplex = "half";
const response = await this.fetchImpl(`${this.baseURL}${url}`, init).catch((err) => {
if (abortedByTimeout) throw new APIError(`timeout of ${this.timeout}ms exceeded`, void 0, err);
if (signal.aborted) throw new APIError("Request aborted", void 0, err);
throw new APIError(err.message, void 0, err);
});
if (this.stickySessions) {
const setCookieHeaders = getSetCookieHeaders(response.headers);
if (setCookieHeaders.length > 0) this.cookieJar?.setCookies(setCookieHeaders);
}
const apiVersionHeader = response.headers.get("x-reduct-api");
if (!apiVersionHeader) throw new APIError("Server did not provide API version", void 0, { response });
this.apiVersion = checkServeApiVersion(apiVersionHeader);
if (!response.ok) throw new APIError(response.headers.get("x-reduct-error") || response.statusText, response.status, { response });
return {
data: await this.parseResponse(response),
headers: response.headers,
status: response.status
};
}
encodeBody(data) {
if (data === void 0 || typeof data === "string" || Buffer.isBuffer(data) || data instanceof Uint8Array || data instanceof ArrayBuffer || data instanceof Blob || data instanceof ReadableStream) return data;
return bigJson.stringify(data);
}
async parseResponse(res) {
if (res.status === 204) return {};
const ct = res.headers.get("content-type") ?? "";
if (!res.body) return {};
if (ct.startsWith("application/json")) {
const text = await res.text();
return text ? bigJson.parse(text) : {};
}
if (ct.startsWith("text/")) return res.text();
return res.body;
}
get(url, headers) {
return this.request("GET", url, void 0, headers);
}
post(url, data, headers) {
return this.request("POST", url, data, headers);
}
put(url, data, headers) {
return this.request("PUT", url, data, headers);
}
patch(url, data, headers) {
return this.request("PATCH", url, data, headers);
}
delete(url, headers) {
return this.request("DELETE", url, void 0, headers);
}
head(url, headers) {
return this.request("HEAD", url, void 0, headers);
}
};
var checkServeApiVersion = (serverApiVersion) => {
const [server_major, server_minor] = serverApiVersion.split(".").map((v) => parseInt(v));
const [client_major, client_minor] = PACKAGE_VERSION.split(".").map((v) => parseInt(v));
if (server_major !== client_major) throw new APIError(`Incompatible server API version: ${serverApiVersion}. Client version: ${PACKAGE_VERSION}. Please update your client.`);
if (server_minor + 2 < client_minor) console.error(`Server API version ${serverApiVersion} is too old for this client version ${PACKAGE_VERSION}. Please update your server.`);
return [server_major, server_minor];
};
//#endregion
export { HttpClient };