UNPKG

@zhengxs/http

Version:

A lightweight cross-platform http request library

939 lines (938 loc) 26.6 kB
import { i as K, F as L, g as B, f as G, a as O, k as Q } from "./registry.mjs"; const x = (r) => r instanceof Error ? r : new Error(r); class p extends Error { } class h extends p { status; headers; error; code; param; type; constructor(e, t, n, s) { super(`${h.makeMessage(e, t, n)}`), this.status = e, this.headers = s; const o = t; this.error = o, this.code = o?.code, this.param = o?.param, this.type = o?.type; } static makeMessage(e, t, n) { const s = t?.message ? typeof t.message == "string" ? t.message : JSON.stringify(t.message) : t ? JSON.stringify(t) : n; return e && s ? `${e} ${s}` : e ? `${e} status code (no body)` : s || "(no status code or body)"; } static generate(e, t, n, s) { if (!e) return new A({ cause: x(t) }); const o = t?.error; return e === 400 ? new V(e, o, n, s) : e === 401 ? new v(e, o, n, s) : e === 403 ? new z(e, o, n, s) : e === 404 ? new X(e, o, n, s) : e === 409 ? new Y(e, o, n, s) : e === 422 ? new Z(e, o, n, s) : e === 429 ? new ee(e, o, n, s) : e >= 500 ? new te(e, o, n, s) : new h(e, o, n, s); } } class P extends h { status = void 0; constructor({ message: e } = {}) { super(void 0, void 0, e || "Request was aborted.", void 0); } } class A extends h { status = void 0; constructor({ message: e, cause: t }) { super(void 0, void 0, e || "Connection error.", void 0), t && (this.cause = t); } } class U extends A { constructor({ message: e } = {}) { super({ message: e ?? "Request timed out." }); } } class V extends h { status = 400; } class v extends h { status = 401; } class z extends h { status = 403; } class X extends h { status = 404; } class Y extends h { status = 409; } class Z extends h { status = 422; } class ee extends h { status = 429; } class te extends h { } class w { constructor(e, t) { this.iterator = e, this.controller = t; } controller; static fromSSEResponse(e, t) { let n = !1; const s = new re(); async function* o() { if (!e.body) throw t.abort(), new p( "Attempted to iterate over a response with no body" ); const i = new R(), c = C(e.body); for await (const u of c) for (const f of i.decode(u)) { const l = s.decode(f); l && (yield l); } for (const u of i.flush()) { const f = s.decode(u); f && (yield f); } } async function* a() { if (n) throw new Error( "Cannot iterate over a consumed stream, use `.tee()` to split the stream." ); n = !0; let i = !1; try { for await (const c of o()) if (!i) { if (c.data.startsWith("[DONE]")) { i = !0; continue; } if (c.event === null) { let u; try { u = JSON.parse(c.data); } catch (f) { throw console.error("Could not parse message into JSON:", c.data), console.error("From chunk:", c.raw), f; } if (u && u.error) throw new h(void 0, u.error, void 0, void 0); yield u; } } i = !0; } catch (c) { if (c instanceof Error && c.name === "AbortError") return; throw c; } finally { i || t.abort(); } } return new w(a, t); } /** * Generates a Stream from a newline-separated ReadableStream * where each item is a JSON value. */ static fromReadableStream(e, t) { let n = !1; async function* s() { const a = new R(), i = C(e); for await (const c of i) for (const u of a.decode(c)) yield u; for (const c of a.flush()) yield c; } async function* o() { if (n) throw new Error( "Cannot iterate over a consumed stream, use `.tee()` to split the stream." ); n = !0; let a = !1; try { for await (const i of s()) a || i && (yield JSON.parse(i)); a = !0; } catch (i) { if (i instanceof Error && i.name === "AbortError") return; throw i; } finally { a || t.abort(); } } return new w(o, t); } [Symbol.asyncIterator]() { return this.iterator(); } /** * Splits the stream into two streams which can be * independently read from at different speeds. */ tee() { const e = [], t = [], n = this.iterator(), s = (o) => ({ next: () => { if (o.length === 0) { const a = n.next(); e.push(a), t.push(a); } return o.shift(); } }); return [ new w(() => s(e), this.controller), new w(() => s(t), this.controller) ]; } /** * Converts this stream to a newline-separated ReadableStream of * JSON stringified values in the stream * which can be turned back into a Stream with `Stream.fromReadableStream()`. */ toReadableStream() { const e = this; let t; const n = new TextEncoder(); return new ReadableStream({ async start() { t = e[Symbol.asyncIterator](); }, async pull(s) { try { const { value: o, done: a } = await t.next(); if (a) return s.close(); const i = n.encode(JSON.stringify(o) + ` `); s.enqueue(i); } catch (o) { s.error(o); } }, async cancel() { await t.return?.(); } }); } } class re { data; event; chunks; constructor() { this.event = null, this.data = [], this.chunks = []; } decode(e) { if (e.endsWith("\r") && (e = e.substring(0, e.length - 1)), !e) { if (!this.event && !this.data.length) return null; const a = { event: this.event, data: this.data.join(` `), raw: this.chunks }; return this.event = null, this.data = [], this.chunks = [], a; } if (this.chunks.push(e), e.startsWith(":")) return null; const [t, n, s] = ne(e, ":"); let o = s; return o.startsWith(" ") && (o = o.substring(1)), t === "event" ? this.event = o : t === "data" && this.data.push(o), null; } } class R { // prettier-ignore static NEWLINE_CHARS = /* @__PURE__ */ new Set([` `, "\r", "\v", "\f", "", "", "", "…", "\u2028", "\u2029"]); static NEWLINE_REGEXP = /\r\n|[\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029]/g; buffer; trailingCR; textDecoder; // TextDecoder found in browsers; not typed to avoid pulling in either "dom" or "node" types. constructor() { this.buffer = [], this.trailingCR = !1; } decode(e) { let t = this.decodeText(e); if (this.trailingCR && (t = "\r" + t, this.trailingCR = !1), t.endsWith("\r") && (this.trailingCR = !0, t = t.slice(0, -1)), !t) return []; const n = R.NEWLINE_CHARS.has( t[t.length - 1] || "" ); let s = t.split(R.NEWLINE_REGEXP); return s.length === 1 && !n ? (this.buffer.push(s[0]), []) : (this.buffer.length > 0 && (s = [this.buffer.join("") + s[0], ...s.slice(1)], this.buffer = []), n || (this.buffer = [s.pop() || ""]), s); } decodeText(e) { if (e == null) return ""; if (typeof e == "string") return e; if (typeof Buffer < "u") { if (e instanceof Buffer) return e.toString(); if (e instanceof Uint8Array) return Buffer.from(e).toString(); throw new p( `Unexpected: received non-Uint8Array (${e.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.` ); } if (typeof TextDecoder < "u") { if (e instanceof Uint8Array || e instanceof ArrayBuffer) return this.textDecoder ??= new TextDecoder("utf8"), this.textDecoder.decode(e); throw new p( `Unexpected: received non-Uint8Array/ArrayBuffer (${e.constructor.name}) in a web platform. Please report this error.` ); } throw new p( "Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error." ); } flush() { if (!this.buffer.length && !this.trailingCR) return []; const e = [this.buffer.join("")]; return this.buffer = [], this.trailingCR = !1, e; } } function ne(r, e) { const t = r.indexOf(e); return t !== -1 ? [ r.substring(0, t), e, r.substring(t + e.length) ] : [r, "", ""]; } function C(r) { if (r[Symbol.asyncIterator]) return r; const e = r.getReader(); return { async next() { try { const t = await e.read(); return t?.done && e.releaseLock(), t; } catch (t) { throw e.releaseLock(), t; } }, async return() { const t = e.cancel(); return e.releaseLock(), await t, { done: !0, value: void 0 }; }, [Symbol.asyncIterator]() { return this; } }; } const D = (r) => r != null && typeof r == "object" && typeof r.url == "string" && typeof r.blob == "function", se = (r) => r != null && typeof r == "object" && typeof r.name == "string" && typeof r.lastModified == "number" && $(r), $ = (r) => r != null && typeof r == "object" && typeof r.size == "number" && typeof r.type == "string" && typeof r.text == "function" && typeof r.slice == "function" && typeof r.arrayBuffer == "function", H = (r) => se(r) || D(r) || K(r); async function oe(r, e, t = {}) { if (r = await r, D(r)) { const s = await r.blob(); return e ||= new URL(r.url).pathname.split(/[\\/]/).pop() ?? "unknown_file", new L([s], e, t); } const n = await ie(r); if (e ||= ce(r) ?? "unknown_file", !t.type) { const s = n[0]?.type; typeof s == "string" && (t = { ...t, type: s }); } return new L(n, e, t); } async function ie(r) { const e = []; if (typeof r == "string" || ArrayBuffer.isView(r) || // includes Uint8Array, Buffer, etc. r instanceof ArrayBuffer) e.push(r); else if ($(r)) e.push(await r.arrayBuffer()); else if (ue(r)) for await (const t of r) e.push(t); else throw new Error( `Unexpected data type: ${typeof r}; constructor: ${r?.constructor?.name}; props: ${ae(r)}` ); return e; } function ae(r) { return `[${Object.getOwnPropertyNames(r).map((t) => `"${t}"`).join(", ")}]`; } function ce(r) { return q(r.name) || q(r.filename) || // For fs.ReadStream q(r.path)?.split(/[\\/]/).pop(); } const q = (r) => { if (typeof r == "string") return r; if (typeof Buffer < "u" && r instanceof Buffer) return String(r); }, ue = (r) => r != null && typeof r == "object" && typeof r[Symbol.asyncIterator] == "function", I = (r) => r && typeof r == "object" && r.body && r[Symbol.toStringTag] === "MultipartBody", be = async (r) => { if (!N(r.body)) return r; const e = await M(r.body); return B(e, r); }, xe = async (r) => { const e = await M(r.body); return B(e, r); }, M = async (r) => { const e = new FormData(); return await Promise.all( Object.entries(r || {}).map( ([t, n]) => k(e, t, n) ) ), e; }, N = (r) => { if (H(r)) return !0; if (Array.isArray(r)) return r.some(N); if (r && typeof r == "object") { for (const e in r) if (N(r[e])) return !0; } return !1; }, k = async (r, e, t) => { if (t !== void 0) { if (t == null) throw new TypeError( `Received null for "${e}"; to pass null in FormData, you must use the string 'null'` ); if (typeof t == "string" || typeof t == "number" || typeof t == "boolean") r.append(e, String(t)); else if (H(t)) { const n = await oe(t); r.append(e, n); } else if (Array.isArray(t)) await Promise.all( t.map((n) => k(r, e + "[]", n)) ); else if (typeof t == "object") await Promise.all( Object.entries(t).map( ([n, s]) => k(r, `${e}[${n}]`, s) ) ); else throw new TypeError( `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${t} instead` ); } }, T = (r) => { try { return JSON.parse(r); } catch { return; } }, fe = (r) => new Promise((e) => setTimeout(e, r)); function F(r) { if (!r) return !0; for (const e in r) return !1; return !0; } function le(r, e) { return Object.prototype.hasOwnProperty.call(r, e); } function m(r, ...e) { typeof process < "u" && process.env.DEBUG === "true" && console.log(`DINGTALK:DEBUG:${r}`, ...e); } const he = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (r) => { const e = Math.random() * 16 | 0; return (r === "x" ? e : e & 3 | 8).toString(16); }), de = new RegExp("^(?:[a-z]+:)?//", "i"), pe = (r) => de.test(r), ye = { method: !0, path: !0, query: !0, body: !0, headers: !0, duplex: !0, maxRetries: !0, stream: !0, timeout: !0, httpAgent: !0, signal: !0, idempotencyKey: !0, __binaryResponse: !0 }, Re = (r) => typeof r == "object" && r !== null && !F(r) && Object.keys(r).every((e) => le(ye, e)), j = (r) => new Proxy( Object.fromEntries( // @ts-ignore r.entries() ), { get(e, t) { const n = t.toString(); return e[n.toLowerCase()] || e[n]; } } ), S = (r, e) => { if (typeof e != "number" || !Number.isInteger(e)) throw new p(`${r} must be an integer`); if (e < 0) throw new p(`${r} must be a positive integer`); return e; }; async function W(r) { const { response: e } = r; if (r.options.stream) return m( "response", e.status, e.url, e.headers, e.body ), w.fromSSEResponse(e, r.controller); if (e.status === 204) return null; if (r.options.__binaryResponse) return e; if (e.headers.get("content-type")?.includes("application/json")) { const s = await e.json(); return m("response", e.status, e.url, e.headers, s), s; } const n = await e.text(); return m("response", e.status, e.url, e.headers, n), n; } class E extends Promise { constructor(e, t = W) { super((n) => { n(null); }), this.responsePromise = e, this.parseResponse = t; } parsedPromise; _thenUnwrap(e) { return new E( this.responsePromise, async (t) => e(await this.parseResponse(t)) ); } /** * Gets the raw `Response` instance instead of parsing the response * data. * * If you want to parse the response body but still get the `Response` * instance, you can use {@link withResponse()}. */ asResponse() { return this.responsePromise.then((e) => e.response); } /** * Gets the parsed response data and the raw `Response` instance. * * If you just want to get the raw `Response` instance without parsing it, * you can use {@link asResponse()}. */ async withResponse() { const [e, t] = await Promise.all([ this.parse(), this.asResponse() ]); return { data: e, response: t }; } parse() { return this.parsedPromise || (this.parsedPromise = this.responsePromise.then(this.parseResponse)), this.parsedPromise; } then(e, t) { return this.parse().then(e, t); } catch(e) { return this.parse().catch(e); } finally(e) { return this.parse().finally(e); } } class J { baseURL; maxRetries; timeout; httpAgent; fetch; idempotencyHeader; constructor({ baseURL: e, maxRetries: t = 2, timeout: n = 6e5, // 10 minutes httpAgent: s, fetch: o }) { this.baseURL = e, this.maxRetries = S("maxRetries", t), this.timeout = S("timeout", n), this.httpAgent = s, this.fetch = o ?? G; } /** * Override this to add your own auth headers. * * ```ts * { * Authorization: 'Bearer 123', * } * ``` */ authHeaders(e) { return {}; } /** * Override this to add your own default headers. */ async defaultHeaders(e) { const t = await this.authHeaders(e); return { Accept: "application/json", "Content-Type": "application/json", "User-Agent": this.getUserAgent(), ...t }; } defaultQuery() { } /** * Override this to add your own headers validation: */ validateHeaders(e, t) { } defaultIdempotencyKey() { return `stainless-node-retry-${he()}`; } get(e, t) { return this.methodRequest("get", e, t); } post(e, t) { return this.methodRequest("post", e, t); } patch(e, t) { return this.methodRequest("patch", e, t); } put(e, t) { return this.methodRequest("put", e, t); } delete(e, t) { return this.methodRequest("delete", e, t); } methodRequest(e, t, n) { return this.request( Promise.resolve(n).then((s) => ({ method: e, path: t, ...s })) ); } getAPIList(e, t, n) { return this.requestAPIList(t, { method: "get", path: e, ...n }); } calculateContentLength(e) { if (typeof e == "string") { if (typeof Buffer < "u") return Buffer.byteLength(e, "utf8").toString(); if (typeof TextEncoder < "u") return new TextEncoder().encode(e).length.toString(); } return null; } async buildRequest(e) { const { method: t, path: n, query: s, headers: o = {} } = e, a = I(e.body) ? e.body.body : e.body ? JSON.stringify(e.body, null, 2) : null, i = this.calculateContentLength(a), c = this.buildURL(n, s); "timeout" in e && S("timeout", e.timeout); const u = e.timeout ?? this.timeout, f = e.httpAgent ?? this.httpAgent ?? O(c), l = u + 1e3; typeof f?.options?.timeout == "number" && l > (f.options.timeout ?? 0) && (f.options.timeout = l), this.idempotencyHeader && t !== "get" && (e.idempotencyKey || (e.idempotencyKey = this.defaultIdempotencyKey()), o[this.idempotencyHeader] = e.idempotencyKey); const y = await this.defaultHeaders(e), d = { ...i && { "Content-Length": i }, ...y, ...o }; I(e.body) && Q !== "node" && delete d["Content-Type"], Object.keys(d).forEach( (b) => d[b] === null && delete d[b] ); const g = { method: t, ...a && { body: a }, headers: d, duplex: e.duplex, ...f && { agent: f }, // @ts-ignore node-fetch uses a custom AbortSignal type that is // not compatible with standard web types signal: e.signal ?? null }; return this.validateHeaders(d, o), { req: g, url: c, timeout: u }; } /** * Used as a callback for mutating the given `RequestInit` object. * * This is useful for cases where you want to add certain headers based off of * the request properties, e.g. `method` or `url`. */ async prepareRequest(e, t) { } parseHeaders(e) { return e ? Symbol.iterator in e ? Object.fromEntries( Array.from(e).map((t) => [ ...t ]) ) : { ...e } : {}; } makeStatusError(e, t, n, s) { return h.generate(e, t, n, s); } request(e, t = null) { return new E(this.makeRequest(e, t)); } async makeRequest(e, t) { const n = await e; t == null && (t = n.maxRetries ?? this.maxRetries); const { req: s, url: o, timeout: a } = await this.buildRequest(n); if (await this.prepareRequest(s, { url: o, options: n }), m("request", o, n, s.headers), n.signal?.aborted) throw new P(); const i = new AbortController(), c = await this.fetchWithTimeout( o, s, a, i ).catch(x); if (c instanceof Error) { if (n.signal?.aborted) throw new P(); if (t) return this.retryRequest(n, t); throw c.name === "AbortError" ? new U() : new A({ cause: c }); } const u = j(c.headers); if (!c.ok) { if (t && this.shouldRetry(c)) return this.retryRequest(n, t, u); const f = await c.text().catch((g) => x(g).message), l = T(f), y = l ? void 0 : f; throw m("response", c.status, o, u, y), this.makeStatusError( c.status, l, y, u ); } return { response: c, options: n, controller: i }; } simple(e, t) { const n = Promise.resolve(t).then( (s) => ({ method: "get", path: e, ...s }) ); return new E(this.makeSimpleRequest(n)); } async makeSimpleRequest(e, t) { const n = await e; t == null && (t = n.maxRetries ?? this.maxRetries); const s = I(n.body) ? n.body.body : n.body ? JSON.stringify(n.body, null, 2) : null, o = this.buildURL(n.path, n.query); "timeout" in n && S("timeout", n.timeout); const a = n.timeout ?? this.timeout, i = n.httpAgent ?? this.httpAgent ?? O(o), c = a + 1e3; typeof i?.options?.timeout == "number" && c > (i.options.timeout ?? 0) && (i.options.timeout = c); const u = { method: n.method || "get", ...s && { body: s }, headers: n.headers, ...i && { agent: i }, // @ts-ignore node-fetch uses a custom AbortSignal type that is // not compatible with standard web types signal: n.signal ?? null }; m("request", o, n, u.headers); const f = new AbortController(), l = await this.fetchWithTimeout( o, u, a, f ).catch(x); if (l instanceof Error) throw u.signal?.aborted ? new P() : l.name === "AbortError" ? new U() : new A({ cause: l }); const y = j(l.headers); if (!l.ok) { const d = await l.text().catch((_) => x(_).message), g = T(d), b = g ? void 0 : d; throw m("response", l.status, o, y, b), this.makeStatusError( l.status, g, b, y ); } return { response: l, options: n, controller: f }; } requestAPIList(e, t) { const n = this.makeRequest(t, null); return new me(this, n, e); } buildURL(e, t) { const n = pe(e) ? new URL(e) : new URL( this.baseURL + (this.baseURL.endsWith("/") && e.startsWith("/") ? e.slice(1) : e) ), s = this.defaultQuery(); return F(s) || (t = { ...s, ...t }), t && (n.search = this.stringifyQuery(t)), n.toString(); } stringifyQuery(e) { return Object.entries(e).filter(([t, n]) => typeof n < "u").map(([t, n]) => { if (typeof n == "string" || typeof n == "number" || typeof n == "boolean") return `${encodeURIComponent(t)}=${encodeURIComponent(n)}`; if (n === null) return `${encodeURIComponent(t)}=`; throw new p( `Cannot stringify type ${typeof n}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.` ); }).join("&"); } async fetchWithTimeout(e, t, n, s) { const { signal: o, ...a } = t || {}; o && o.addEventListener("abort", () => s.abort()); const i = setTimeout(() => s.abort(), n); return this.getRequestClient().fetch.call(void 0, e, { signal: s.signal, ...a }).finally(() => { clearTimeout(i); }); } getRequestClient() { return { fetch: this.fetch }; } shouldRetry(e) { const t = e.headers.get("x-should-retry"); return t === "true" ? !0 : t === "false" ? !1 : e.status === 408 || e.status === 409 || e.status === 429 || e.status >= 500; } async retryRequest(e, t, n) { let s; const o = n?.["retry-after"]; if (o) { const a = parseInt(o); Number.isNaN(a) ? s = Date.parse(o) - Date.now() : s = a * 1e3; } if (!s || !Number.isInteger(s) || s <= 0 || s > 60 * 1e3) { const a = e.maxRetries ?? this.maxRetries; s = this.calculateDefaultRetryTimeoutMillis( t, a ); } return await fe(s), this.makeRequest(e, t - 1); } calculateDefaultRetryTimeoutMillis(e, t) { const o = t - e, a = Math.min( 0.5 * Math.pow(2, o), 8 ), i = 1 - Math.random() * 0.25; return a * i * 1e3; } getUserAgent() { return `${this.constructor.name}/JS`; } static create(e, t) { return new J({ baseURL: e, ...t }); } } class Ee { #e; options; response; body; constructor(e, t, n, s) { this.#e = e, this.options = s, this.response = t, this.body = n; } hasNextPage() { return this.getPaginatedItems().length ? this.nextPageInfo() != null : !1; } async getNextPage() { const e = this.nextPageInfo(); if (!e) throw new p( "No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`." ); const t = { ...this.options }; if ("params" in e) t.query = { ...t.query, ...e.params }; else if ("url" in e) { const n = [ ...Object.entries(t.query || {}), ...e.url.searchParams.entries() ]; for (const [s, o] of n) e.url.searchParams.set(s, o); t.query = void 0, t.path = e.url.toString(); } return await this.#e.requestAPIList( this.constructor, t ); } async *iterPages() { let e = this; for (yield e; e.hasNextPage(); ) e = await e.getNextPage(), yield e; } async *[Symbol.asyncIterator]() { for await (const e of this.iterPages()) for (const t of e.getPaginatedItems()) yield t; } } class me extends E { constructor(e, t, n) { super( t, async (s) => new n( e, s.response, await W(s), s.options ) ); } /** * Allow auto-paginating iteration on an un awaited list call, eg: * * ```ts * for await (const item of client.items.list()) { * console.log(item) * } * ``` */ async *[Symbol.asyncIterator]() { const e = await this; for await (const t of e) yield t; } } export { J as APIClient, A as APIConnectionError, U as APIConnectionTimeoutError, h as APIError, E as APIPromise, P as APIUserAbortError, Ee as AbstractPage, v as AuthenticationError, V as BadRequestError, Y as ConflictError, p as HttpException, te as InternalServerError, R as LineDecoder, X as NotFoundError, me as PagePromise, z as PermissionDeniedError, ee as RateLimitError, re as SSEDecoder, w as Stream, Z as UnprocessableEntityError, x as castToError, M as createForm, j as createResponseHeaders, m as debug, W as defaultParseResponse, le as hasOwn, pe as isAbsoluteURL, $ as isBlobLike, F as isEmptyObj, se as isFileLike, I as isMultipartBody, Re as isRequestOptions, D as isResponseLike, H as isUploadable, be as maybeMultipartFormRequestOptions, xe as multipartFormRequestOptions, C as readableStreamAsyncIterable, T as safeJSON, fe as sleep, oe as toFile, he as uuid4, S as validatePositiveInteger }; //# sourceMappingURL=index.mjs.map