@vercel/sdk
Version:
<p align="center"> <a href="https://vercel.com"> <img src="https://assets.vercel.com/image/upload/v1588805858/repositories/vercel/logo.png" height="96"> <h3 align="center">Vercel</h3> </a> <p align="center">Develop. Preview. Ship.</p> </p>
291 lines • 11.4 kB
JavaScript
/*
* Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
*/
import { SDKHooks } from "../hooks/hooks.js";
import { ConnectionError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, UnexpectedClientError, } from "../models/httpclienterrors.js";
import { ERR, OK } from "../types/fp.js";
import { stringToBase64 } from "./base64.js";
import { SDK_METADATA, serverURLFromOptions } from "./config.js";
import { encodeForm } from "./encodings.js";
import { HTTPClient, isAbortError, isConnectionError, isTimeoutError, matchContentType, } from "./http.js";
import { combineSignals } from "./primitives.js";
import { retry } from "./retries.js";
const gt = typeof globalThis === "undefined" ? null : globalThis;
const webWorkerLike = typeof gt === "object"
&& gt != null
&& "importScripts" in gt
&& typeof gt["importScripts"] === "function";
const isBrowserLike = webWorkerLike
|| (typeof navigator !== "undefined" && "serviceWorker" in navigator)
|| (typeof window === "object" && typeof window.document !== "undefined");
export class ClientSDK {
#httpClient;
#hooks;
#logger;
_baseURL;
_options;
constructor(options = {}) {
const opt = options;
if (typeof opt === "object"
&& opt != null
&& "hooks" in opt
&& opt.hooks instanceof SDKHooks) {
this.#hooks = opt.hooks;
}
else {
this.#hooks = new SDKHooks();
}
const url = serverURLFromOptions(options);
if (url) {
url.pathname = url.pathname.replace(/\/+$/, "") + "/";
}
const { baseURL, client } = this.#hooks.sdkInit({
baseURL: url,
client: options.httpClient || new HTTPClient(),
});
this._baseURL = baseURL;
this.#httpClient = client;
this._options = { ...options, hooks: this.#hooks };
this.#logger = this._options.debugLogger;
}
_createRequest(context, conf, options) {
const { method, path, query, headers: opHeaders, security } = conf;
const base = conf.baseURL ?? this._baseURL;
if (!base) {
return ERR(new InvalidRequestError("No base URL provided for operation"));
}
const baseURL = new URL(base);
let reqURL;
if (path) {
baseURL.pathname = baseURL.pathname.replace(/\/+$/, "") + "/";
reqURL = new URL(path, baseURL);
if (!reqURL.search && baseURL.search) {
reqURL.search = baseURL.search;
}
}
else {
reqURL = baseURL;
}
reqURL.hash = "";
// Appends already-encoded query pairs to a query string, replacing any
// existing pairs with the same key so later sources take precedence.
const mergeQuery = (current, additions) => {
if (!additions) {
return current;
}
const additionKeys = new Set(additions
.split("&")
.filter((pair) => pair !== "")
.map((pair) => pair.split("=")[0] ?? ""));
const kept = current.split("&").filter((pair) => {
return pair !== "" && !additionKeys.has(pair.split("=")[0] ?? "");
});
return [...kept, additions].join("&");
};
const encodeQueryRecord = (record) => {
return Object.entries(record)
.map(([k, v]) => {
if (v == null) {
return undefined;
}
const value = v;
return encodeForm(k, value, {
explode: Array.isArray(value),
charEncoding: "percent",
});
})
.filter((pair) => typeof pair !== "undefined")
.join("&");
};
const finalQuery = [
query || "",
encodeQueryRecord(security?.queryParams || {}),
].reduce(mergeQuery, reqURL.search.slice(1));
if (finalQuery) {
reqURL.search = `?${finalQuery}`;
}
const headers = new Headers(opHeaders);
const username = security?.basic.username;
const password = security?.basic.password;
if (username != null || password != null) {
const encoded = stringToBase64([username || "", password || ""].join(":"));
headers.set("Authorization", `Basic ${encoded}`);
}
const securityHeaders = new Headers(security?.headers || {});
for (const [k, v] of securityHeaders) {
headers.set(k, v);
}
let cookie = headers.get("cookie") || "";
for (const [k, v] of Object.entries(security?.cookies || {})) {
cookie += `; ${k}=${v}`;
}
cookie = cookie.startsWith("; ") ? cookie.slice(2) : cookie;
headers.set("cookie", cookie);
const userHeaders = new Headers(options?.headers ?? options?.fetchOptions?.headers);
for (const [k, v] of userHeaders) {
headers.set(k, v);
}
// Only set user agent header in non-browser-like environments since CORS
// policy disallows setting it in browsers e.g. Chrome throws an error.
if (!isBrowserLike) {
headers.set(conf.uaHeader ?? "user-agent", conf.userAgent ?? SDK_METADATA.userAgent);
}
const fetchOptions = {
...options?.fetchOptions,
...options,
};
if (!fetchOptions?.signal && conf.timeoutMs != null && conf.timeoutMs > 0) {
context.timeoutMs = conf.timeoutMs;
}
if (conf.body instanceof ReadableStream) {
Object.assign(fetchOptions, { duplex: "half" });
}
let input;
try {
input = this.#hooks.beforeCreateRequest(context, {
url: reqURL,
options: {
...fetchOptions,
body: conf.body ?? null,
headers,
method,
},
});
}
catch (err) {
return ERR(new UnexpectedClientError("Create request hook failed to execute", {
cause: err,
}));
}
return OK(new Request(input.url, input.options));
}
async _do(request, options) {
const { context, isErrorStatusCode } = options;
const timeoutMs = context.timeoutMs;
return retry(async () => {
const cloned = request.clone();
let attempt = cloned;
if (timeoutMs != null && timeoutMs > 0) {
const timeoutSignal = AbortSignal.timeout(timeoutMs);
const combined = combineSignals(cloned.signal, timeoutSignal)
?? timeoutSignal;
attempt = new Request(cloned, { signal: combined });
}
const req = await this.#hooks.beforeRequest(context, attempt);
await logRequest(this.#logger, req).catch((e) => this.#logger?.log("Failed to log request:", e));
let response = await this.#httpClient.request(req);
try {
if (isErrorStatusCode(response.status)) {
const result = await this.#hooks.afterError(context, response, null);
if (result.error) {
throw result.error;
}
response = result.response || response;
}
else {
response = await this.#hooks.afterSuccess(context, response);
}
}
finally {
await logResponse(this.#logger, response, req)
.catch(e => this.#logger?.log("Failed to log response:", e));
}
return response;
}, { config: options.retryConfig, statusCodes: options.retryCodes }).then((r) => OK(r), (err) => {
switch (true) {
case isAbortError(err):
return ERR(new RequestAbortedError("Request aborted by client", {
cause: err,
}));
case isTimeoutError(err):
return ERR(new RequestTimeoutError("Request timed out", { cause: err }));
case isConnectionError(err):
return ERR(new ConnectionError("Unable to make request", { cause: err }));
default:
return ERR(new UnexpectedClientError("Unexpected HTTP client error", {
cause: err,
}));
}
});
}
}
const jsonLikeContentTypeRE = /^(application|text)\/([^+]+\+)*json.*/;
const jsonlLikeContentTypeRE = /^(application|text)\/([^+]+\+)*(jsonl|x-ndjson)\b.*/;
async function logRequest(logger, req) {
if (!logger) {
return;
}
const contentType = req.headers.get("content-type");
const ct = contentType?.split(";")[0] || "";
logger.group(`> Request: ${req.method} ${req.url}`);
logger.group("Headers:");
for (const [k, v] of req.headers.entries()) {
logger.log(`${k}: ${v}`);
}
logger.groupEnd();
logger.group("Body:");
switch (true) {
case jsonLikeContentTypeRE.test(ct):
logger.log(await req.clone().json());
break;
case ct.startsWith("text/"):
logger.log(await req.clone().text());
break;
case ct === "multipart/form-data": {
const body = await req.clone().formData();
for (const [k, v] of body) {
const vlabel = v instanceof Blob ? "<Blob>" : v;
logger.log(`${k}: ${vlabel}`);
}
break;
}
default:
logger.log(`<${contentType}>`);
break;
}
logger.groupEnd();
logger.groupEnd();
}
async function logResponse(logger, res, req) {
if (!logger) {
return;
}
const contentType = res.headers.get("content-type");
const ct = contentType?.split(";")[0] || "";
logger.group(`< Response: ${req.method} ${req.url}`);
logger.log("Status Code:", res.status, res.statusText);
logger.group("Headers:");
for (const [k, v] of res.headers.entries()) {
logger.log(`${k}: ${v}`);
}
logger.groupEnd();
logger.group("Body:");
switch (true) {
case matchContentType(res, "application/json")
|| jsonLikeContentTypeRE.test(ct) && !jsonlLikeContentTypeRE.test(ct):
logger.log(await res.clone().json());
break;
case matchContentType(res, "application/jsonl")
|| jsonlLikeContentTypeRE.test(ct):
case matchContentType(res, "text/event-stream"):
logger.log(`<${contentType}>`);
break;
case matchContentType(res, "text/*"):
logger.log(await res.clone().text());
break;
case matchContentType(res, "multipart/form-data"): {
const body = await res.clone().formData();
for (const [k, v] of body) {
const vlabel = v instanceof Blob ? "<Blob>" : v;
logger.log(`${k}: ${vlabel}`);
}
break;
}
default:
logger.log(`<${contentType}>`);
break;
}
logger.groupEnd();
logger.groupEnd();
}
//# sourceMappingURL=sdks.js.map