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 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 | }
|
81 | function isFunction(value) {
|
82 | return typeof value === "function";
|
83 | }
|
84 | function immutable(record) {
|
85 | return new Proxy(record, {
|
86 | set() {
|
87 | return true;
|
88 | }
|
89 | });
|
90 | }
|
91 | function merge(initial, patches, customMerge) {
|
92 | return patches.reduce((merged, patch) => Object.assign(merged, customMerge(merged, patch)), Object.assign( Object.create(null), initial));
|
93 | }
|
94 |
|
95 | function 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 |
|
105 | function setDefaultOptions(patchRequestOptions) {
|
106 | Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
|
107 | }
|
108 |
|
109 | function 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 |
|
129 | function 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 |
|
144 | function 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 |
|
173 | function 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 |
|
215 | function 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 |
|
231 | async 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 |
|
246 | function createScopedRequest(scopedOptions = {}) {
|
247 | return function request(url, payload, inlineOptions = {}) {
|
248 | const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
|
249 | return fatcher(options);
|
250 | };
|
251 | }
|
252 |
|
253 | export { FatcherError, canActivate, chunkStreamReader, createScopedRequest, fatcher, isAbortError, isFatcherError, mergeOptions, readStreamByChunk, setDefaultOptions };
|