UNPKG

7.84 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 isFunction(value) {
61 return typeof value === "function";
62}
63function immutable(record) {
64 return new Proxy(record, {
65 set() {
66 return true;
67 }
68 });
69}
70function merge(initial, patches, customMerge) {
71 return patches.reduce(
72 (merged, patch) => Object.assign(merged, customMerge(merged, patch)),
73 Object.assign(/* @__PURE__ */ Object.create(null), initial)
74 );
75}
76
77function 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
87function setDefaultOptions(patchRequestOptions) {
88 Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
89}
90
91function 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
118function 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
138function 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
153function 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
184function 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
235async 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
252async 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
267function createScopedRequest(scopedOptions = {}) {
268 return function request(url, payload, inlineOptions = {}) {
269 const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
270 return fatcher(options);
271 };
272}
273
274export { FatcherError, canActivate, chunkStreamReader, createScopedRequest, fatcher, isAbortError, isFatcherError, mergeOptions, readStreamByChunk, setDefaultOptions };