1 | function canActivate(data) {
|
2 | return data instanceof Response && !data.bodyUsed && !!data.body;
|
3 | }
|
4 |
|
5 | function isAbortError(error) {
|
6 | return error instanceof DOMException && error.name === "AbortError";
|
7 | }
|
8 |
|
9 | async 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 | }
|
20 | const chunkStreamReader = readStreamByChunk;
|
21 |
|
22 | class 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 |
|
45 | function isFatcherError(error) {
|
46 | return error instanceof FatcherError && error.name === "FatcherError" && error.__isFatcherError__;
|
47 | }
|
48 |
|
49 | const 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 |
|
63 | function isAbsoluteURL(url) {
|
64 | return /^http(s)?:\/\/.+/.test(url);
|
65 | }
|
66 | function 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 | }
|
73 | function 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 | }
|
85 | function isFunction(value) {
|
86 | return typeof value === "function";
|
87 | }
|
88 | function immutable(record) {
|
89 | return new Proxy(record, {
|
90 | set() {
|
91 | return true;
|
92 | }
|
93 | });
|
94 | }
|
95 | function merge(initial, patches, customMerge) {
|
96 | return patches.reduce((merged, patch) => Object.assign(merged, customMerge(merged, patch)), Object.assign( Object.create(null), initial));
|
97 | }
|
98 |
|
99 | function 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 |
|
109 | function setDefaultOptions(patchRequestOptions) {
|
110 | Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
|
111 | }
|
112 |
|
113 | function 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 |
|
133 | function 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 |
|
148 | function 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 |
|
177 | function 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 |
|
202 | function 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 |
|
238 | function 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 |
|
254 | async 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 |
|
269 | function createScopedRequest(scopedOptions = {}) {
|
270 | return function request(url, payload, inlineOptions = {}) {
|
271 | const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
|
272 | return fatcher(options);
|
273 | };
|
274 | }
|
275 |
|
276 | export { FatcherError, canActivate, chunkStreamReader, createScopedRequest, fatcher, isAbortError, isFatcherError, mergeOptions, readStreamByChunk, setDefaultOptions };
|