UNPKG

fets

Version:

TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience

141 lines (140 loc) 6.05 kB
"use strict"; 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;