UNPKG

8.1 kBJavaScriptView Raw
1function canActivate(data) {
2 return data instanceof Response && !data.bodyUsed && !!data.body;
3}
4
5function isAbortError(error) {
6 return error instanceof DOMException && error.name === "AbortError";
7}
8
9async function readStreamByChunk(readableStream, callback) {
10 async function read(reader) {
11 const { value, done } = await reader.read();
12 if (done || !value) {
13 return;
14 }
15 callback(value);
16 await read(reader);
17 }
18 return read(readableStream.getReader());
19}
20const chunkStreamReader = readStreamByChunk;
21
22class FatcherError extends Error {
23 constructor(context, response) {
24 super(`[Fatcher] Fetch failed with status code ${response.status}`);
25 this.name = "FatcherError";
26 this.__isFatcherError__ = true;
27 this._response = response;
28 this._context = context;
29 }
30 toJSON() {
31 const headers = {};
32 for (const [key, value] of this._response.headers) {
33 headers[key] = value;
34 }
35 return {
36 status: this._response.status,
37 statusText: this._response.statusText,
38 context: this._context,
39 headers,
40 data: this._response.body
41 };
42 }
43}
44
45function isFatcherError(error) {
46 return error instanceof FatcherError && error.name === "FatcherError" && error.__isFatcherError__;
47}
48
49const defaultOptions = {
50 baseUrl: "/",
51 middlewares: [],
52 headers: {
53 "Content-Type": "application/x-www-form-urlencoded"
54 },
55 params: {},
56 credentials: "same-origin",
57 cache: "default",
58 redirect: "follow",
59 referrerPolicy: "no-referrer-when-downgrade",
60 mode: "cors"
61};
62
63function isAbsoluteURL(url) {
64 return /^http(s)?:\/\/.+/.test(url);
65}
66function normalize(url) {
67 let protocol;
68 return url.replace(/http(s?):\/\//, (str) => {
69 protocol = str;
70 return "";
71 }).replace(/\/\/(\/)?/g, "/").replace(/.*\S/, (str) => (protocol != null ? protocol : "") + str);
72}
73function normalizeURL(baseURL, url) {
74 if (!url) {
75 return baseURL;
76 }
77 if (isAbsoluteURL(url)) {
78 return normalize(url);
79 }
80 if (baseURL === "/") {
81 return url;
82 }
83 return normalize(`${baseURL}/${url}`);
84}
85function isFunction(value) {
86 return typeof value === "function";
87}
88function immutable(record) {
89 return new Proxy(record, {
90 set() {
91 return true;
92 }
93 });
94}
95function merge(initial, patches, customMerge) {
96 return patches.reduce((merged, patch) => Object.assign(merged, customMerge(merged, patch)), Object.assign(/* @__PURE__ */ Object.create(null), initial));
97}
98
99function mergeOptions(options, ...patchOptions) {
100 return merge(options, patchOptions, (merged, current) => {
101 const { headers } = current;
102 if (headers) {
103 current.headers = Object.assign({}, merged.headers || {}, headers);
104 }
105 return current;
106 });
107}
108
109function setDefaultOptions(patchRequestOptions) {
110 Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
111}
112
113function createContext(options) {
114 const { baseUrl = "", url = "", params = {}, headers = {} } = options;
115 if (!url) {
116 throw new Error("[Fatcher] URL is required.");
117 }
118 const [normalizedURL, querystring] = normalizeURL(baseUrl, url).split("?");
119 if (querystring) {
120 for (const [key, value] of new URLSearchParams(querystring)) {
121 params[key] = value;
122 }
123 }
124 const requestHeaders = new Headers();
125 for (const [key, value] of Object.entries(headers)) {
126 if (value) {
127 requestHeaders.set(key, value);
128 }
129 }
130 return { ...options, url: normalizedURL, params, requestHeaders };
131}
132
133function mergeContext(context, ...patchContext) {
134 return merge(context, patchContext, (merged, current) => {
135 const { headers } = current;
136 if (headers) {
137 current.headers = Object.assign(merged.headers || {}, headers);
138 for (const [key, value] of Object.entries(headers)) {
139 if (value) {
140 context.requestHeaders.set(key, value);
141 }
142 }
143 }
144 return current;
145 });
146}
147
148function composeMiddlewares(middlewares) {
149 return function use(initialContext) {
150 let currentIndex = -1;
151 let response;
152 let context = initialContext;
153 let immutableContext = immutable(context);
154 async function dispatch(index, patchContext) {
155 if (index <= currentIndex) {
156 return Promise.reject(new Error(`Middleware <${middlewares[index - 1].name}> call next() more than once.`));
157 }
158 currentIndex = index;
159 const middleware = middlewares[index];
160 if (!middleware) {
161 return response;
162 }
163 if (patchContext) {
164 context = mergeContext(context, patchContext);
165 immutableContext = immutable(context);
166 }
167 try {
168 return response = await middleware.use(immutableContext, async (patch) => dispatch(index + 1, patch));
169 } catch (error) {
170 return Promise.reject(error);
171 }
172 }
173 return dispatch(0);
174 };
175}
176
177function fetcher() {
178 return {
179 name: "fatcher-middleware-http-fetcher",
180 async use(context) {
181 const { url = "", requestHeaders: headers, ...rest } = context;
182 const response = await fetch(url, {
183 ...rest,
184 headers
185 });
186 const { status, statusText, ok, headers: responseHeaders } = response;
187 const result = {
188 status,
189 statusText,
190 headers: responseHeaders,
191 url,
192 data: response
193 };
194 if (ok) {
195 return result;
196 }
197 return Promise.reject(new FatcherError(context, response));
198 }
199 };
200}
201
202function payloadConsumer() {
203 return {
204 name: "fatcher-middleware-payload-consumer",
205 use(context, next) {
206 const { payload, url, requestHeaders, method, body, params = {} } = context;
207 const contentType = requestHeaders.get("content-type");
208 let normalizeUrl = url;
209 if (["GET", "HEAD"].includes(method)) {
210 if (Object.keys(params).length) {
211 normalizeUrl = `${url}?${new URLSearchParams(Object.assign({}, params, body)).toString()}`;
212 }
213 return next({
214 url: normalizeUrl,
215 body: null
216 });
217 }
218 let normalizedBody = body;
219 if (payload) {
220 if (contentType == null ? void 0 : contentType.includes("application/json")) {
221 normalizedBody = JSON.stringify(payload);
222 }
223 if (contentType == null ? void 0 : contentType.includes("application/x-www-form-urlencoded")) {
224 normalizedBody = new URLSearchParams(payload);
225 }
226 }
227 if (Object.keys(params).length) {
228 normalizeUrl = `${url}?${new URLSearchParams(params).toString()}`;
229 }
230 return next({
231 url: normalizeUrl,
232 body: normalizedBody
233 });
234 }
235 };
236}
237
238function registerMiddlewares(unregisteredMiddlewares) {
239 return unregisteredMiddlewares.reduce((total, current) => {
240 var _a;
241 let middleware;
242 if (Array.isArray(current)) {
243 middleware = registerMiddlewares(current);
244 } else {
245 middleware = [isFunction(current) ? current() : current];
246 if ((_a = middleware[0].presets) == null ? void 0 : _a.length) {
247 middleware = registerMiddlewares(middleware[0].presets).concat(middleware);
248 }
249 }
250 return total.concat(middleware);
251 }, []);
252}
253
254async function fatcher(inlineOptions = {}) {
255 const options = mergeOptions(defaultOptions, inlineOptions);
256 const { middlewares: customMiddlewares = [], ...rest } = options;
257 const registeredMiddlewares = registerMiddlewares([...customMiddlewares, payloadConsumer, fetcher]);
258 const useMiddlewares = composeMiddlewares(registeredMiddlewares);
259 const initialContext = createContext(rest);
260 const result = await useMiddlewares(initialContext);
261 const data = canActivate(result.data) ? result.data.body : result.data;
262 return {
263 ...result,
264 options,
265 data
266 };
267}
268
269function createScopedRequest(scopedOptions = {}) {
270 return function request(url, payload, inlineOptions = {}) {
271 const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
272 return fatcher(options);
273 };
274}
275
276export { FatcherError, canActivate, chunkStreamReader, createScopedRequest, fatcher, isAbortError, isFatcherError, mergeOptions, readStreamByChunk, setDefaultOptions };