fexios
Version:
Fetch based HTTP client with similar API to axios for browser and Node.js
631 lines (624 loc) • 20.4 kB
JavaScript
;
const isPlainObject = require('../shared/fexios.C6EYiGQl.cjs');
var FexiosErrorCodes = /* @__PURE__ */ ((FexiosErrorCodes2) => {
FexiosErrorCodes2["BODY_USED"] = "BODY_USED";
FexiosErrorCodes2["NO_BODY_READER"] = "NO_BODY_READER";
FexiosErrorCodes2["TIMEOUT"] = "TIMEOUT";
FexiosErrorCodes2["NETWORK_ERROR"] = "NETWORK_ERROR";
FexiosErrorCodes2["BODY_NOT_ALLOWED"] = "BODY_NOT_ALLOWED";
FexiosErrorCodes2["HOOK_CONTEXT_CHANGED"] = "HOOK_CONTEXT_CHANGED";
FexiosErrorCodes2["ABORTED_BY_HOOK"] = "ABORTED_BY_HOOK";
FexiosErrorCodes2["INVALID_HOOK_CALLBACK"] = "INVALID_HOOK_CALLBACK";
FexiosErrorCodes2["UNEXPECTED_HOOK_RETURN"] = "UNEXPECTED_HOOK_RETURN";
FexiosErrorCodes2["UNSUPPORTED_RESPONSE_TYPE"] = "UNSUPPORTED_RESPONSE_TYPE";
FexiosErrorCodes2["BODY_TRANSFORM_ERROR"] = "BODY_TRANSFORM_ERROR";
return FexiosErrorCodes2;
})(FexiosErrorCodes || {});
class FexiosError extends Error {
constructor(code, message, context, options) {
super(message, options);
this.code = code;
this.context = context;
}
name = "FexiosError";
static is(e, code) {
if (!(e instanceof FexiosError) || e instanceof FexiosResponseError) {
return false;
}
return code ? e.code === code : true;
}
}
class FexiosResponseError extends FexiosError {
constructor(message, response, options) {
super(response.statusText, message, void 0, options);
this.response = response;
}
name = "FexiosResponseError";
static is(e) {
return e instanceof FexiosResponseError;
}
}
const isFexiosError = (e) => {
return FexiosError.is(e);
};
exports.FexiosHeaderBuilder = void 0;
((FexiosHeaderBuilder2) => {
FexiosHeaderBuilder2.makeHeaders = (input) => {
if (!input) return new Headers();
if (input instanceof Headers) return new Headers(input);
const h = new Headers();
if (input instanceof Map) {
for (const [k, v] of input.entries()) {
if (v == null) continue;
if (Array.isArray(v)) {
for (const item of v) {
if (item == null) continue;
h.append(k, String(item));
}
} else {
h.append(k, String(v));
}
}
return h;
}
if (isPlainObject.isPlainObject(input)) {
for (const [k, v] of Object.entries(input)) {
if (v == null) continue;
if (Array.isArray(v)) {
for (const item of v) {
if (item == null) continue;
h.append(k, String(item));
}
} else {
h.append(k, String(v));
}
}
return h;
}
throw new TypeError(
"only plain object, Map/ReadonlyMap, or Headers is supported"
);
};
FexiosHeaderBuilder2.toHeaderRecord = (input) => {
if (input instanceof Headers) {
const out = {};
input.forEach((value, key) => {
out[key] = out[key] ? [...out[key], value] : [value];
});
return out;
}
if (input instanceof Map) {
const output = {};
for (const [key, raw] of input.entries()) {
if (raw == null) continue;
if (Array.isArray(raw)) {
const arr = raw.filter((v) => v != null).map((v) => String(v));
if (arr.length) output[key] = (output[key] ?? []).concat(arr);
} else {
const v = String(raw);
output[key] = output[key] ? [...output[key], v] : [v];
}
}
return output;
}
throw new TypeError(
`unsupported type transformation, got: ${Object.prototype.toString.call(
input
)}`
);
};
FexiosHeaderBuilder2.mergeHeaders = (...incomes) => {
const output = new Headers();
const mergeOneFromObject = (patch) => {
for (const [k, v] of Object.entries(patch)) {
if (v === void 0) continue;
if (v === null) {
output.delete(k);
continue;
}
if (Array.isArray(v)) {
output.delete(k);
for (const item of v) {
if (item == null) continue;
output.append(k, String(item));
}
} else {
output.set(k, String(v));
}
}
};
for (const input of incomes) {
if (input == null) continue;
if (input instanceof Headers) {
input.forEach((value, key) => {
output.set(key, value);
});
continue;
}
if (isPlainObject.isPlainObject(input)) {
mergeOneFromObject(input);
continue;
}
const rec = (0, FexiosHeaderBuilder2.toHeaderRecord)(input);
for (const [key, arr] of Object.entries(rec)) {
output.delete(key);
for (const v of arr) output.append(key, v);
}
}
return output;
};
})(exports.FexiosHeaderBuilder || (exports.FexiosHeaderBuilder = {}));
exports.FexiosQueryBuilder = void 0;
((FexiosQueryBuilder2) => {
FexiosQueryBuilder2.makeSearchParams = (params) => {
if (!params) {
return new URLSearchParams();
}
if (params instanceof URLSearchParams) {
return params;
}
if (typeof params !== "object" || params?.constructor !== Object) {
throw new TypeError("only plain object is supported");
}
const sp = new URLSearchParams();
const appendValue = (k, v) => {
if (v === void 0 || v === null) return;
sp.append(k, v);
};
const setValue = (k, v) => {
if (v === void 0 || v === null) return;
sp.set(k, v);
};
const handleNested = (prefix, val) => {
if (val === void 0 || val === null) return;
if (Array.isArray(val)) {
for (const item of val) appendValue(prefix, item?.toString());
return;
}
if (typeof val === "object" && val.constructor === Object) {
for (const [k, v] of Object.entries(val)) {
if (v === void 0 || v === null) continue;
const isBracketArrayKey = k.endsWith("[]");
const cleanKey = isBracketArrayKey ? k.slice(0, -2) : k;
const nextPrefix = `${prefix}[${cleanKey}]`;
if (isBracketArrayKey) {
const arrayKey = `${nextPrefix}[]`;
if (Array.isArray(v)) {
for (const item of v) appendValue(arrayKey, item?.toString());
} else if (typeof v === "object" && v !== null && v.constructor === Object) {
handleNested(`${nextPrefix}[]`, v);
} else {
appendValue(arrayKey, v?.toString());
}
} else {
if (Array.isArray(v)) {
for (const item of v) appendValue(nextPrefix, item?.toString());
} else if (typeof v === "object" && v !== null && v.constructor === Object) {
handleNested(nextPrefix, v);
} else {
setValue(nextPrefix, v?.toString());
}
}
}
return;
}
setValue(prefix, val?.toString());
};
for (const [key, value] of Object.entries(params)) {
handleNested(key, value);
}
return sp;
};
FexiosQueryBuilder2.makeQueryString = (query) => {
return (0, FexiosQueryBuilder2.makeSearchParams)(query).toString();
};
FexiosQueryBuilder2.makeURL = (url, params, hash, base) => {
const fallbackBase = typeof window !== "undefined" && window.location?.origin || "http://localhost";
const u = typeof url === "string" ? new URL(url, base ?? fallbackBase) : new URL(url);
const existingParams = (0, FexiosQueryBuilder2.toQueryRecord)(u.searchParams);
const mergedRecord = (0, FexiosQueryBuilder2.mergeQueries)(existingParams, params || {});
const mergedParams = (0, FexiosQueryBuilder2.makeSearchParams)(mergedRecord);
u.search = mergedParams.toString();
if (typeof hash !== "undefined") u.hash = hash;
return u;
};
FexiosQueryBuilder2.toQueryRecord = (input) => {
if (typeof input === "string") {
input = (0, FexiosQueryBuilder2.fromString)(input);
}
const output = {};
const parseKey = (key) => {
if (!key.includes("[")) return { path: [key], forceArray: false };
const base = key.slice(0, key.indexOf("["));
const parts = [base];
const re = /\[([^\]]*)\]/g;
let m;
let forceArray = false;
let lastWasEmpty = false;
while (m = re.exec(key)) {
if (m[1] === "") {
forceArray = true;
lastWasEmpty = true;
} else {
parts.push(m[1]);
lastWasEmpty = false;
}
}
if (forceArray && lastWasEmpty) {
parts[parts.length - 1] = parts[parts.length - 1] + "[]";
}
return { path: parts, forceArray };
};
const setDeep = (obj, path, value, forceArray) => {
let cur = obj;
for (let i = 0; i < path.length; i++) {
const k = path[i];
const last = i === path.length - 1;
if (last) {
if (forceArray) {
if (cur[k] === void 0) cur[k] = [value];
else if (Array.isArray(cur[k])) cur[k].push(value);
else cur[k] = [cur[k], value];
} else {
if (cur[k] === void 0) cur[k] = value;
else if (Array.isArray(cur[k])) cur[k].push(value);
else cur[k] = [cur[k], value];
}
} else {
if (cur[k] === void 0 || typeof cur[k] !== "object" || Array.isArray(cur[k])) {
cur[k] = {};
}
cur = cur[k];
}
}
};
for (const [rawKey, val] of input.entries()) {
const { path, forceArray } = parseKey(String(rawKey));
setDeep(output, path, val?.toString(), forceArray);
}
return output;
};
FexiosQueryBuilder2.fromString = (s) => {
const t = s.trim();
if (!t) return new URLSearchParams();
if (t.startsWith("?")) return new URLSearchParams(t.slice(1));
const qIndex = t.indexOf("?");
if (qIndex >= 0) {
const hashIndex = t.indexOf("#", qIndex + 1);
const query = t.slice(qIndex + 1, hashIndex >= 0 ? hashIndex : void 0);
return new URLSearchParams(query);
}
return new URLSearchParams(t);
};
FexiosQueryBuilder2.mergeQueries = (...incomes) => {
const output = {};
for (const input of incomes) {
if (input == null) continue;
mergeOne(output, toPlain(input));
}
return output;
};
function toPlain(src) {
if (!src) return {};
if (src instanceof URLSearchParams || src instanceof FormData || src instanceof Map)
return (0, FexiosQueryBuilder2.toQueryRecord)(src);
if (typeof src === "string") return (0, FexiosQueryBuilder2.toQueryRecord)((0, FexiosQueryBuilder2.fromString)(src));
if (isPlainObject.isPlainObject(src)) return src;
throw new TypeError(
`unsupported type transformation, got: ${Object.prototype.toString.call(
src
)}`
);
}
function mergeOne(target, patch) {
for (const [k, v] of Object.entries(patch)) {
if (v === void 0) continue;
if (v === null) {
delete target[k];
continue;
}
const cur = target[k];
if (isPlainObject.isPlainObject(cur) && isPlainObject.isPlainObject(v)) {
mergeOne(cur, v);
} else {
target[k] = isPlainObject.clone(v);
}
}
}
})(exports.FexiosQueryBuilder || (exports.FexiosQueryBuilder = {}));
class FexiosResponse {
constructor(rawResponse, data, responseType) {
this.rawResponse = rawResponse;
this.data = data;
this.responseType = responseType;
["ok", "status", "statusText", "headers", "url", "redirected"].forEach(
(key) => {
Reflect.defineProperty(this, key, {
get: () => rawResponse[key]
});
}
);
}
ok;
status;
statusText;
headers;
url;
redirected;
}
const concatUint8Arrays = (parts) => {
const total = parts.reduce((n, p) => n + p.length, 0);
const out = new Uint8Array(total);
let off = 0;
for (const p of parts) {
out.set(p, off);
off += p.length;
}
return out;
};
async function readBody(stream, contentLength, onProgress) {
const reader = stream.getReader();
if (!reader) {
throw new FexiosError(
FexiosErrorCodes.NO_BODY_READER,
"Failed to get ReadableStream from response body"
);
}
const chunks = [];
let received = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (onProgress && contentLength > 0)
onProgress(received / contentLength, concatUint8Arrays(chunks));
}
} finally {
reader.releaseLock?.();
}
const data = concatUint8Arrays(chunks);
onProgress?.(1, data);
return data;
}
const guessFexiosResponseType = (contentType) => {
if (!contentType) return void 0;
if (contentType.includes("application/json") || contentType.endsWith("+json"))
return "json";
if (contentType.startsWith("text/")) return "text";
if (contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded"))
return "form";
if (/^image\//.test(contentType) || /^video\//.test(contentType) || /^audio\//.test(contentType) || contentType.includes("application/pdf"))
return "blob";
if (contentType.includes("application/octet-stream") || contentType.includes("application/zip") || contentType.includes("application/x-tar") || contentType.includes("application/x-7z-compressed") || contentType.includes("application/x-gzip"))
return "arrayBuffer";
return void 0;
};
async function createFexiosResponse(rawResponse, expectedType, onProgress, shouldThrow, timeout) {
const decodeResponse = rawResponse.clone();
const contentType = rawResponse.headers.get("content-type")?.toLowerCase() ?? "";
const lenHeader = rawResponse.headers.get("content-length");
const total = lenHeader ? Number(lenHeader) : 0;
const upgrade = rawResponse.headers.get("upgrade")?.toLowerCase();
const connection = rawResponse.headers.get("connection")?.toLowerCase();
let resolvedType = expectedType ?? guessFexiosResponseType(contentType) ?? "text";
if (!expectedType) {
if (upgrade === "websocket" && connection === "upgrade") {
resolvedType = "ws";
} else if (contentType.includes("text/event-stream")) {
resolvedType = "stream";
}
}
if (resolvedType === "stream") {
const url = rawResponse.url || rawResponse.url || "";
const response2 = await createFexiosEventSourceResponse(
url,
rawResponse,
timeout
);
const decide = shouldThrow?.(response2);
if (typeof decide === "boolean" ? decide : !response2.ok) {
throw new FexiosResponseError(response2.statusText, response2);
}
return response2;
}
if (resolvedType === "ws") {
const url = rawResponse.url || rawResponse.url || "";
const response2 = await createFexiosWebSocketResponse(
url,
rawResponse,
timeout
);
const decide = shouldThrow?.(response2);
if (typeof decide === "boolean" ? decide : !response2.ok) {
throw new FexiosResponseError(response2.statusText, response2);
}
return response2;
}
const charset = /\bcharset=([^;]+)/i.exec(contentType)?.[1]?.trim() || "utf-8";
const decoder = new TextDecoder(charset);
let data;
try {
if (resolvedType === "form") {
data = await decodeResponse.formData();
} else {
const bytes = await readBody(decodeResponse.body, total, onProgress);
if (resolvedType === "arrayBuffer") {
data = bytes.buffer.slice(
bytes.byteOffset,
bytes.byteOffset + bytes.byteLength
);
} else if (resolvedType === "blob") {
data = new Blob([bytes], {
type: contentType || "application/octet-stream"
});
} else if (resolvedType === "text") {
const text = decoder.decode(bytes);
if (!expectedType) {
const trimmed = text.trim();
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
try {
data = JSON.parse(trimmed);
resolvedType = "json";
} catch {
data = text;
}
} else {
data = text;
}
} else {
data = text;
}
} else if (resolvedType === "json") {
const text = decoder.decode(bytes);
data = text.length ? JSON.parse(text) : null;
} else {
data = bytes;
}
}
} catch (e) {
if (!(e instanceof Error)) throw e;
try {
const t = await decodeResponse.text();
data = t;
resolvedType = "text";
} catch {
throw new FexiosError(
FexiosErrorCodes.BODY_TRANSFORM_ERROR,
`Failed to transform response body to ${resolvedType}`,
void 0,
{ cause: e }
);
}
}
const response = new FexiosResponse(
rawResponse,
data,
resolvedType
);
const decision = shouldThrow?.(response);
if (typeof decision === "boolean" ? decision : !response.ok) {
throw new FexiosResponseError(response.statusText, response);
}
return response;
}
async function createFexiosWebSocketResponse(url, response, timeout) {
const ws = new WebSocket(url.toString());
const delay = timeout ?? 6e4;
await new Promise((resolve, reject) => {
const timer = delay > 0 ? setTimeout(() => {
ws.close();
reject(
new FexiosError(
FexiosErrorCodes.TIMEOUT,
`WebSocket connection timed out after ${delay}ms`
)
);
}, delay) : void 0;
let settled = false;
const cleanup = () => {
clearTimeout(timer);
ws.removeEventListener("open", onOpen);
ws.removeEventListener("error", onError);
ws.removeEventListener("close", onClose);
};
const onOpen = () => {
if (!settled) {
settled = true;
cleanup();
resolve();
}
};
const onError = (event) => {
if (!settled) {
settled = true;
cleanup();
reject(
new FexiosError(
FexiosErrorCodes.NETWORK_ERROR,
`WebSocket connection failed`,
void 0,
{ cause: event }
)
);
}
};
const onClose = (event) => {
if (!settled) {
settled = true;
cleanup();
reject(
new FexiosError(
FexiosErrorCodes.NETWORK_ERROR,
`WebSocket connection closed unexpectedly (code: ${event.code}, reason: ${event.reason})`,
void 0,
{ cause: event }
)
);
}
};
ws.addEventListener("open", onOpen);
ws.addEventListener("error", onError);
ws.addEventListener("close", onClose);
});
return new FexiosResponse(response || new Response(null), ws, "ws");
}
async function createFexiosEventSourceResponse(url, response, timeout) {
const es = new EventSource(url.toString());
const delay = timeout ?? 6e4;
await new Promise((resolve, reject) => {
const timer = delay > 0 ? setTimeout(() => {
es.close();
reject(
new FexiosError(
FexiosErrorCodes.TIMEOUT,
`EventSource connection timed out after ${delay}ms`
)
);
}, delay) : void 0;
let settled = false;
const cleanup = () => {
clearTimeout(timer);
es.removeEventListener("open", onOpen);
es.removeEventListener("error", onError);
};
const onOpen = () => {
if (!settled) {
settled = true;
cleanup();
resolve();
}
};
const onError = (event) => {
if (!settled) {
settled = true;
cleanup();
reject(
new FexiosError(
FexiosErrorCodes.NETWORK_ERROR,
`EventSource connection failed`,
void 0,
{ cause: event }
)
);
}
};
es.addEventListener("open", onOpen);
es.addEventListener("error", onError);
});
return new FexiosResponse(
response || new Response(null),
es,
"stream"
);
}
exports.FexiosError = FexiosError;
exports.FexiosErrorCodes = FexiosErrorCodes;
exports.FexiosResponse = FexiosResponse;
exports.FexiosResponseError = FexiosResponseError;
exports.createFexiosEventSourceResponse = createFexiosEventSourceResponse;
exports.createFexiosResponse = createFexiosResponse;
exports.createFexiosWebSocketResponse = createFexiosWebSocketResponse;
exports.isFexiosError = isFexiosError;
//# sourceMappingURL=index.cjs.map