UNPKG

fexios

Version:

Fetch based HTTP client with similar API to axios for browser and Node.js

631 lines (624 loc) 20.4 kB
'use strict'; 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