UNPKG

7.44 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 await 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 headers: {
51 "Content-Type": "application/x-www-form-urlencoded"
52 },
53 credentials: "same-origin",
54 cache: "default",
55 redirect: "follow",
56 referrerPolicy: "no-referrer-when-downgrade",
57 mode: "cors"
58};
59
60function normalizeURL(baseURL, url) {
61 let _url = `${baseURL}/${url}`;
62 let schema = "";
63 const [a, b] = _url.matchAll(/([a-z][a-z\d+\-.]*:)\/\//gi);
64 if (b) {
65 return url;
66 }
67 if (a) {
68 schema = a[0];
69 _url = _url.replace(schema, "");
70 }
71 const paths = [];
72 for (const path of _url.split("/")) {
73 if (path === "..") {
74 paths.pop();
75 } else if (path && path !== ".") {
76 paths.push(path);
77 }
78 }
79 return `${schema || "/"}${paths.join("/")}`;
80}
81function isFunction(value) {
82 return typeof value === "function";
83}
84function immutable(record) {
85 return new Proxy(record, {
86 set() {
87 return true;
88 }
89 });
90}
91function merge(initial, patches, customMerge) {
92 return patches.reduce((merged, patch) => Object.assign(merged, customMerge(merged, patch)), Object.assign(/* @__PURE__ */ Object.create(null), initial));
93}
94
95function mergeOptions(options, ...patchOptions) {
96 return merge(options, patchOptions, (merged, current) => {
97 const { headers } = current;
98 if (headers) {
99 current.headers = Object.assign({}, merged.headers || {}, headers);
100 }
101 return current;
102 });
103}
104
105function setDefaultOptions(patchRequestOptions) {
106 Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
107}
108
109function createContext(options) {
110 const { baseUrl = "", url = "", params = {}, headers = {} } = options;
111 if (!url) {
112 throw new Error("[Fatcher] URL is required.");
113 }
114 const [normalizedURL, querystring] = normalizeURL(baseUrl, url).split("?");
115 if (querystring) {
116 for (const [key, value] of new URLSearchParams(querystring)) {
117 params[key] = value;
118 }
119 }
120 const requestHeaders = new Headers();
121 for (const [key, value] of Object.entries(headers)) {
122 if (value) {
123 requestHeaders.set(key, value);
124 }
125 }
126 return { ...options, url: normalizedURL, params, requestHeaders };
127}
128
129function mergeContext(context, ...patchContext) {
130 return merge(context, patchContext, (merged, current) => {
131 const { headers } = current;
132 if (headers) {
133 current.headers = Object.assign(merged.headers || {}, headers);
134 for (const [key, value] of Object.entries(headers)) {
135 if (value) {
136 context.requestHeaders.set(key, value);
137 }
138 }
139 }
140 return current;
141 });
142}
143
144function composeMiddlewares(middlewares) {
145 return function use(initialContext) {
146 let currentIndex = -1;
147 let response;
148 let context = initialContext;
149 let immutableContext = immutable(context);
150 async function dispatch(index, patchContext) {
151 if (index <= currentIndex) {
152 return Promise.reject(new Error(`Middleware <${middlewares[index - 1].name}> call next() more than once.`));
153 }
154 currentIndex = index;
155 const middleware = middlewares[index];
156 if (!middleware) {
157 return response;
158 }
159 if (patchContext) {
160 context = mergeContext(context, patchContext);
161 immutableContext = immutable(context);
162 }
163 try {
164 return response = await middleware.use(immutableContext, async (patch) => dispatch(index + 1, patch));
165 } catch (error) {
166 return Promise.reject(error);
167 }
168 }
169 return dispatch(0);
170 };
171}
172
173function fetcher() {
174 return {
175 name: "fatcher-middleware-fetch",
176 async use(context) {
177 let { url = "", requestHeaders: headers, payload, method = "GET", body, params, ...rest } = context;
178 const contentType = headers.get("content-type");
179 if (["GET", "HEAD"].includes(method)) {
180 params = Object.assign({}, params, body);
181 body = null;
182 } else if (payload && contentType) {
183 if (contentType.includes("application/json")) {
184 body = JSON.stringify(payload);
185 }
186 if (contentType.includes("application/x-www-form-urlencoded")) {
187 body = new URLSearchParams(payload);
188 }
189 }
190 if (Object.keys(params).length) {
191 url = `${url}?${new URLSearchParams(params)}`;
192 }
193 const response = await fetch(url, {
194 ...rest,
195 headers,
196 body,
197 method
198 });
199 const { status, statusText, ok, headers: responseHeaders } = response;
200 const result = {
201 status,
202 statusText,
203 headers: responseHeaders,
204 url,
205 data: response
206 };
207 if (ok) {
208 return result;
209 }
210 return Promise.reject(new FatcherError(context, response));
211 }
212 };
213}
214
215function registerMiddlewares(unregisteredMiddlewares) {
216 return unregisteredMiddlewares.reduce((total, current) => {
217 var _a;
218 let middleware;
219 if (Array.isArray(current)) {
220 middleware = registerMiddlewares(current);
221 } else {
222 middleware = [isFunction(current) ? current() : current];
223 if ((_a = middleware[0].presets) == null ? void 0 : _a.length) {
224 middleware = registerMiddlewares(middleware[0].presets).concat(middleware);
225 }
226 }
227 return total.concat(middleware);
228 }, []);
229}
230
231async function fatcher(inlineOptions = {}) {
232 const options = mergeOptions(defaultOptions, inlineOptions);
233 const { middlewares: customMiddlewares = [], ...rest } = options;
234 const registeredMiddlewares = registerMiddlewares([...customMiddlewares, fetcher]);
235 const useMiddlewares = composeMiddlewares(registeredMiddlewares);
236 const initialContext = createContext(rest);
237 const result = await useMiddlewares(initialContext);
238 const data = canActivate(result.data) ? result.data.body : result.data;
239 return {
240 ...result,
241 options,
242 data
243 };
244}
245
246function createScopedRequest(scopedOptions = {}) {
247 return function request(url, payload, inlineOptions = {}) {
248 const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
249 return fatcher(options);
250 };
251}
252
253export { FatcherError, canActivate, chunkStreamReader, createScopedRequest, fatcher, isAbortError, isFatcherError, mergeOptions, readStreamByChunk, setDefaultOptions };