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