UNPKG

7.75 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5function canActivate(data) {
6 return data instanceof Response && !data.bodyUsed && !!data.body;
7}
8
9function isAbortError(error) {
10 return error instanceof DOMException && error.name === "AbortError";
11}
12
13async function readStreamByChunk(readableStream, callback) {
14 async function read(reader) {
15 const { value, done } = await reader.read();
16 if (done || !value) {
17 return;
18 }
19 await callback(value);
20 await read(reader);
21 }
22 return read(readableStream.getReader());
23}
24const chunkStreamReader = readStreamByChunk;
25
26class FatcherError extends Error {
27 constructor(context, response) {
28 super(`[Fatcher] Fetch failed with status code ${response.status}`);
29 this.name = "FatcherError";
30 this.__isFatcherError__ = true;
31 this._response = response;
32 this._context = context;
33 }
34 toJSON() {
35 const headers = {};
36 for (const [key, value] of this._response.headers) {
37 headers[key] = value;
38 }
39 return {
40 status: this._response.status,
41 statusText: this._response.statusText,
42 context: this._context,
43 headers,
44 data: this._response.body
45 };
46 }
47}
48
49function isFatcherError(error) {
50 return error instanceof FatcherError && error.name === "FatcherError" && error.__isFatcherError__;
51}
52
53const defaultOptions = {
54 headers: {
55 "Content-Type": "application/x-www-form-urlencoded"
56 },
57 credentials: "same-origin",
58 cache: "default",
59 redirect: "follow",
60 referrerPolicy: "no-referrer-when-downgrade",
61 mode: "cors"
62};
63
64function normalizeURL(baseURL, url) {
65 let _url = `${baseURL}/${url}`;
66 let schema = "";
67 const [a, b] = _url.matchAll(/([a-z][a-z\d+\-.]*:)\/\//gi);
68 if (b) {
69 return url;
70 }
71 if (a) {
72 schema = a[0];
73 _url = _url.replace(schema, "");
74 }
75 const paths = [];
76 for (const path of _url.split("/")) {
77 if (path === "..") {
78 paths.pop();
79 } else if (path && path !== ".") {
80 paths.push(path);
81 }
82 }
83 return `${schema || "/"}${paths.join("/")}`;
84}
85function isFunction(value) {
86 return typeof value === "function";
87}
88function immutable(record) {
89 return new Proxy(record, {
90 set() {
91 return true;
92 }
93 });
94}
95function merge(initial, patches, customMerge) {
96 return patches.reduce((merged, patch) => Object.assign(merged, customMerge(merged, patch)), Object.assign(/* @__PURE__ */ Object.create(null), initial));
97}
98
99function 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
109function setDefaultOptions(patchRequestOptions) {
110 Object.assign(defaultOptions, mergeOptions(defaultOptions, patchRequestOptions));
111}
112
113function 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
133function 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
148function 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
177function fetcher() {
178 return {
179 name: "fatcher-middleware-fetch",
180 async use(context) {
181 let { url = "", requestHeaders: headers, payload, method = "GET", body, params, ...rest } = context;
182 const contentType = headers.get("content-type");
183 if (["GET", "HEAD"].includes(method)) {
184 params = Object.assign({}, params, body);
185 body = null;
186 } else if (payload && contentType) {
187 if (contentType.includes("application/json")) {
188 body = JSON.stringify(payload);
189 }
190 if (contentType.includes("application/x-www-form-urlencoded")) {
191 body = new URLSearchParams(payload);
192 }
193 }
194 if (Object.keys(params).length) {
195 url = `${url}?${new URLSearchParams(params)}`;
196 }
197 const response = await fetch(url, {
198 ...rest,
199 headers,
200 body,
201 method
202 });
203 const { status, statusText, ok, headers: responseHeaders } = response;
204 const result = {
205 status,
206 statusText,
207 headers: responseHeaders,
208 url,
209 data: response
210 };
211 if (ok) {
212 return result;
213 }
214 return Promise.reject(new FatcherError(context, response));
215 }
216 };
217}
218
219function registerMiddlewares(unregisteredMiddlewares) {
220 return unregisteredMiddlewares.reduce((total, current) => {
221 var _a;
222 let middleware;
223 if (Array.isArray(current)) {
224 middleware = registerMiddlewares(current);
225 } else {
226 middleware = [isFunction(current) ? current() : current];
227 if ((_a = middleware[0].presets) == null ? void 0 : _a.length) {
228 middleware = registerMiddlewares(middleware[0].presets).concat(middleware);
229 }
230 }
231 return total.concat(middleware);
232 }, []);
233}
234
235async function fatcher(inlineOptions = {}) {
236 const options = mergeOptions(defaultOptions, inlineOptions);
237 const { middlewares: customMiddlewares = [], ...rest } = options;
238 const registeredMiddlewares = registerMiddlewares([...customMiddlewares, fetcher]);
239 const useMiddlewares = composeMiddlewares(registeredMiddlewares);
240 const initialContext = createContext(rest);
241 const result = await useMiddlewares(initialContext);
242 const data = canActivate(result.data) ? result.data.body : result.data;
243 return {
244 ...result,
245 options,
246 data
247 };
248}
249
250function createScopedRequest(scopedOptions = {}) {
251 return function request(url, payload, inlineOptions = {}) {
252 const options = mergeOptions(scopedOptions, { ...inlineOptions, url, payload });
253 return fatcher(options);
254 };
255}
256
257exports.FatcherError = FatcherError;
258exports.canActivate = canActivate;
259exports.chunkStreamReader = chunkStreamReader;
260exports.createScopedRequest = createScopedRequest;
261exports.fatcher = fatcher;
262exports.isAbortError = isAbortError;
263exports.isFatcherError = isFatcherError;
264exports.mergeOptions = mergeOptions;
265exports.readStreamByChunk = readStreamByChunk;
266exports.setDefaultOptions = setDefaultOptions;