fets
Version:
TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience
141 lines (140 loc) • 6.05 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createClient = exports.ClientValidationError = void 0;
const fetch_1 = require("@whatwg-node/fetch");
class ClientValidationError extends Error {
constructor(path, method, errors, response) {
super(`Validation failed for ${method} ${path}`);
this.path = path;
this.method = method;
this.errors = errors;
this.response = response;
}
[Symbol.iterator]() {
return this.errors[Symbol.iterator]();
}
}
exports.ClientValidationError = ClientValidationError;
function useValidationErrors() {
return {
async onResponse({ path, method, response }) {
if (response.status === 400 && response.headers.get('x-error-type') === 'validation') {
const resJson = await response.json();
if (resJson.errors) {
throw new ClientValidationError(path, method, resJson.errors, response);
}
}
},
};
}
function createClient({ endpoint, fetchFn = fetch_1.fetch, plugins = [] } = {}) {
plugins.unshift(useValidationErrors());
const onRequestInitHooks = [];
const onFetchHooks = [];
const onResponseHooks = [];
for (const plugin of plugins) {
if (plugin.onRequestInit) {
onRequestInitHooks.push(plugin.onRequestInit);
}
if (plugin.onFetch) {
onFetchHooks.push(plugin.onFetch);
}
if (plugin.onResponse) {
onResponseHooks.push(plugin.onResponse);
}
}
return new Proxy({}, {
get(_target, path) {
return new Proxy({}, {
get(_target, method) {
return async function (requestParams = {}) {
for (const pathParamKey in requestParams?.params || {}) {
const value = requestParams?.params?.[pathParamKey];
if (value) {
path = path.replace(`{${pathParamKey}}`, value).replace(`:${pathParamKey}`, value);
}
}
if (!path.startsWith('/')) {
path = `/${path}`;
}
let searchParams;
if (requestParams?.query) {
searchParams = new fetch_1.URLSearchParams();
for (const queryParamKey in requestParams?.query || {}) {
const value = requestParams?.query?.[queryParamKey];
if (value) {
if (Array.isArray(value)) {
for (const v of value) {
searchParams.append(queryParamKey, v);
}
}
else {
searchParams.append(queryParamKey, value);
}
}
}
}
const requestInit = {
method,
headers: requestParams?.headers || {},
};
if (requestParams?.json) {
requestInit.body = JSON.stringify(requestParams.json);
requestInit.headers['Content-Type'] = 'application/json';
}
if (requestParams?.formData) {
requestInit.body = requestParams.formData;
}
let response;
for (const onRequestParamsHook of onRequestInitHooks) {
await onRequestParamsHook({
path,
method,
requestParams,
requestInit,
endResponse(res) {
response = res;
},
});
}
let finalUrl = path;
if (endpoint) {
finalUrl = `${endpoint}${path}`;
}
if (searchParams) {
if (finalUrl.includes('?')) {
finalUrl += '&' + searchParams.toString();
}
else {
finalUrl += '?' + searchParams.toString();
}
}
let currentFetchFn = fetchFn;
for (const onFetchHook of onFetchHooks) {
await onFetchHook({
url: finalUrl,
init: requestInit,
fetchFn: currentFetchFn,
setFetchFn(newFetchFn) {
currentFetchFn = newFetchFn;
},
});
}
response ||= await currentFetchFn(finalUrl, requestInit);
for (const onResponseHook of onResponseHooks) {
await onResponseHook({
path,
method,
requestParams,
requestInit,
response,
});
}
return response;
};
},
});
},
});
}
exports.createClient = createClient;