UNPKG

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