@thepassle/app-tools 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/api/index.js +58 -135
- package/api/plugins/abort.js +36 -0
- package/api/plugins/cache.js +38 -0
- package/api/plugins/delay.js +7 -0
- package/api/plugins/jsonPrefix.js +26 -0
- package/api/plugins/logger.js +26 -0
- package/api/plugins/mock.js +33 -0
- package/package.json +15 -1
package/README.md
CHANGED
package/api/index.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
const TEN_MINUTES = 1000 * 60 * 10;
|
|
2
|
-
|
|
3
|
-
function getCookie(name, _document = document) {
|
|
4
|
-
const match = _document.cookie.match(new RegExp(`(^|;\\s*)(${name})=([^;]*)`));
|
|
5
|
-
return match ? decodeURIComponent(match[3]) : null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function handleAbort(e) {
|
|
9
|
-
if (e.name !== 'AbortError') {
|
|
10
|
-
throw e;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
1
|
function handleStatus(response) {
|
|
15
2
|
if (!response.ok) {
|
|
16
3
|
throw new Error(response.statusText);
|
|
@@ -18,64 +5,48 @@ function handleStatus(response) {
|
|
|
18
5
|
return response;
|
|
19
6
|
}
|
|
20
7
|
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
* @param {() => void} fn
|
|
24
|
-
* @param {number} ms
|
|
25
|
-
* @param {{
|
|
26
|
-
* signal?: AbortSignal
|
|
27
|
-
* }} options
|
|
28
|
-
*/
|
|
29
|
-
function setAbortableTimeout(fn, ms, {signal}) {
|
|
30
|
-
const timeout = setTimeout(fn, ms);
|
|
31
|
-
!signal?.aborted && signal?.addEventListener('abort', () => clearTimeout(timeout), {});
|
|
32
|
-
};
|
|
33
|
-
|
|
34
8
|
/**
|
|
35
9
|
* @typedef {object} Config
|
|
36
|
-
* @property {
|
|
37
|
-
* @property {
|
|
38
|
-
* @property {'text'|'json'|'stream'|'blob'|'arrayBuffer'|'formData'|'stream'} [responseType]
|
|
10
|
+
* @property {Plugin[]} [plugins=[]]
|
|
11
|
+
* @property {'text'|'json'|'stream'|'blob'|'arrayBuffer'|'formData'|'stream'} [responseType=json]
|
|
39
12
|
* @property {string} [baseURL]
|
|
40
13
|
*
|
|
41
14
|
* @typedef {(url: string, data?: object, opts?: RequestOptions) => Promise<FetchResponse>} BodyMethod
|
|
42
15
|
* @typedef {(url: string, opts?: RequestOptions) => Promise<FetchResponse>} BodylessMethod
|
|
43
|
-
* @typedef {(url: string) => any} MockFn
|
|
44
16
|
* @typedef {Response & { [key: string]: any }} FetchResponse
|
|
45
17
|
* @typedef {'GET'|'DELETE'|'HEAD'|'OPTIONS'|'POST'|'PUT'|'PATCH'} Method
|
|
46
18
|
*
|
|
47
19
|
* @typedef {{
|
|
48
|
-
* beforeFetch?: (meta: MetaParams) => void,
|
|
20
|
+
* beforeFetch?: (meta: MetaParams) => MetaParams | Promise<MetaParams> | void,
|
|
49
21
|
* afterFetch?: (res: Response) => Response | Promise<Response>,
|
|
22
|
+
* transform?: (data: any) => any,
|
|
23
|
+
* handleError?: (e: Error) => boolean
|
|
50
24
|
* }} Plugin
|
|
51
25
|
*
|
|
52
26
|
* @typedef {Object} CustomRequestOptions
|
|
53
27
|
* @property {(data: FetchResponse) => FetchResponse} [transform] - callback to transform the received data
|
|
54
28
|
* @property {'text'|'json'|'stream'|'blob'|'arrayBuffer'|'formData'|'stream'} [responseType] - responseType of the request, will call res[responseType](). Defaults to 'json'
|
|
55
|
-
* @property {boolean} [useAbort] - Whether or not to use an abortSignal to cancel subsequent requests that may get fired in quick succession. Defaults to false
|
|
56
|
-
* @property {boolean} [useCache] - Whether or not to cache responses. Defaults to false. When set to true, it will by default cache a request for 10 minutes. This can be customized via `cacheOptions`
|
|
57
|
-
* @property {(mockParams: MetaParams) => Response} [mock] - Return a custom `new Response` with mock data instead of firing the request. Can be used in combination with `delay` as well. E.g.: (meta) => new Response(JSON.stringify({foo:'bar'}, {status: 200}));
|
|
58
|
-
* @property {{maxAge?: number}} [cacheOptions] - Configure caching options
|
|
59
29
|
* @property {Record<string, string>} [params] - An object to be queryParam-ified and added to the request url
|
|
60
|
-
* @property {number} [delay] - Adds an artifical delay to resolving of the request, useful for testing
|
|
61
30
|
* @property {Plugin[]} [plugins] - Array of plugins. Plugins can be added on global level, or on a per request basis
|
|
62
31
|
* @property {string} [baseURL] - BaseURL to resolve all requests from. Can be set globally, or on a per request basis. When set on a per request basis, will override the globally set baseURL (if set)
|
|
63
32
|
*
|
|
64
33
|
* @typedef {RequestInit & CustomRequestOptions} RequestOptions
|
|
65
34
|
*
|
|
66
35
|
* @typedef {{
|
|
36
|
+
* responseType: string,
|
|
37
|
+
* baseURL: string,
|
|
67
38
|
* url: string,
|
|
68
|
-
* method:
|
|
39
|
+
* method: Method,
|
|
40
|
+
* headers: Headers,
|
|
69
41
|
* opts?: RequestOptions,
|
|
70
42
|
* data?: any,
|
|
43
|
+
* fetchFn: typeof globalThis.fetch
|
|
71
44
|
* }} MetaParams
|
|
72
45
|
*/
|
|
73
46
|
|
|
74
|
-
|
|
75
47
|
/**
|
|
76
48
|
* @example
|
|
77
49
|
* const api = new Api({
|
|
78
|
-
* xsrfCookieName: 'XSRF-COOKIE',
|
|
79
50
|
* baseURL: 'https://api.foo.com/',
|
|
80
51
|
* responseType: 'text',
|
|
81
52
|
* plugins: [
|
|
@@ -87,15 +58,9 @@ function setAbortableTimeout(fn, ms, {signal}) {
|
|
|
87
58
|
*});
|
|
88
59
|
*/
|
|
89
60
|
export class Api {
|
|
90
|
-
|
|
91
|
-
#requests = new Map();
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {Config} config
|
|
95
|
-
*/
|
|
61
|
+
/** @param {Config} config */
|
|
96
62
|
constructor(config = {}) {
|
|
97
63
|
this.config = {
|
|
98
|
-
xsrfCookieName: 'XSRF-TOKEN',
|
|
99
64
|
plugins: [],
|
|
100
65
|
responseType: 'json',
|
|
101
66
|
...config
|
|
@@ -116,16 +81,12 @@ export class Api {
|
|
|
116
81
|
*/
|
|
117
82
|
async fetch(url, method, opts, data) {
|
|
118
83
|
const plugins = [...this.config.plugins, ...(opts?.plugins || [])];
|
|
119
|
-
const csrfToken = getCookie(this.config.xsrfCookieName);
|
|
120
|
-
|
|
121
|
-
const baseURL = opts?.baseURL ?? this.config?.baseURL ?? '';
|
|
122
|
-
const responseType = opts?.responseType ?? this.config.responseType;
|
|
123
|
-
|
|
124
|
-
const requestKey = `${method}:${url}`;
|
|
125
84
|
|
|
126
|
-
|
|
85
|
+
let fetchFn = globalThis.fetch;
|
|
86
|
+
let baseURL = opts?.baseURL ?? this.config?.baseURL ?? '';
|
|
87
|
+
let responseType = opts?.responseType ?? this.config.responseType;
|
|
88
|
+
let headers = new Headers({
|
|
127
89
|
'Content-Type': 'application/json',
|
|
128
|
-
...(csrfToken ? { 'X-CSRF-TOKEN': csrfToken } : {}),
|
|
129
90
|
...opts?.headers
|
|
130
91
|
});
|
|
131
92
|
|
|
@@ -133,93 +94,55 @@ export class Api {
|
|
|
133
94
|
url = url.replace(/^(?!.*\/\/)\/?/, baseURL + '/');
|
|
134
95
|
}
|
|
135
96
|
|
|
136
|
-
if(opts?.useCache) {
|
|
137
|
-
if(this.#cache.has(requestKey)) {
|
|
138
|
-
const cached = this.#cache.get(requestKey);
|
|
139
|
-
if(cached.updatedAt > Date.now() - (opts.cacheOptions?.maxAge || TEN_MINUTES)) {
|
|
140
|
-
return Promise.resolve(cached.data);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if(opts?.useAbort) {
|
|
146
|
-
if(this.#requests.has(requestKey)) {
|
|
147
|
-
const request = this.#requests.get(requestKey);
|
|
148
|
-
request.abort();
|
|
149
|
-
}
|
|
150
|
-
this.#requests.set(requestKey, new AbortController());
|
|
151
|
-
}
|
|
152
|
-
|
|
153
97
|
if(opts?.params) {
|
|
154
98
|
url += `${(~url.indexOf('?') ? '&' : '?')}${new URLSearchParams(opts.params)}`;
|
|
155
99
|
}
|
|
156
100
|
|
|
157
101
|
for(const { beforeFetch } of plugins) {
|
|
158
|
-
await beforeFetch?.({ url, method, opts, data });
|
|
102
|
+
const overrides = await beforeFetch?.({ responseType, headers, fetchFn, baseURL, url, method, opts, data });
|
|
103
|
+
if(overrides) {
|
|
104
|
+
({ responseType, headers, fetchFn, baseURL, url, method, opts, data } = {...overrides});
|
|
105
|
+
}
|
|
159
106
|
}
|
|
160
107
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
?
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return res;
|
|
202
|
-
})
|
|
203
|
-
/** [STATUS] */
|
|
204
|
-
.then(handleStatus)
|
|
205
|
-
/** [RESPONSETYPE] */
|
|
206
|
-
.then(res => res[opts?.responseType || responseType]())
|
|
207
|
-
/** [TRANSFORM] */
|
|
208
|
-
.then(data => opts?.transform?.(data) ?? data)
|
|
209
|
-
/** [CACHE] */
|
|
210
|
-
.then(data => {
|
|
211
|
-
if(opts?.useCache) {
|
|
212
|
-
this.#cache.set(requestKey, {
|
|
213
|
-
updatedAt: Date.now(),
|
|
214
|
-
data
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return data;
|
|
218
|
-
})
|
|
219
|
-
/** [DELAY] */
|
|
220
|
-
.then(data => opts?.delay ? new Promise(resolve => setTimeout(() => resolve(data), opts.delay)) : data)
|
|
221
|
-
/** [ABORT] */
|
|
222
|
-
.catch(handleAbort);
|
|
108
|
+
return fetchFn(url, {
|
|
109
|
+
method,
|
|
110
|
+
headers,
|
|
111
|
+
...(data ? { body: JSON.stringify(data) } : {}),
|
|
112
|
+
...(opts?.mode ? { mode: opts.mode } : {}),
|
|
113
|
+
...(opts?.credentials ? { credentials: opts.credentials } : {}),
|
|
114
|
+
...(opts?.cache ? { cache: opts.cache } : {}),
|
|
115
|
+
...(opts?.redirect ? { redirect: opts.redirect } : {}),
|
|
116
|
+
...(opts?.referrer ? { referrer: opts.referrer } : {}),
|
|
117
|
+
...(opts?.referrerPolicy ? { referrerPolicy: opts.referrerPolicy } : {}),
|
|
118
|
+
...(opts?.integrity ? { integrity: opts.integrity } : {}),
|
|
119
|
+
...(opts?.keepalive ? { keepalive: opts.keepalive } : {}),
|
|
120
|
+
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
121
|
+
})
|
|
122
|
+
/** [PLUGINS - AFTERFETCH] */
|
|
123
|
+
.then(async res => {
|
|
124
|
+
for(const { afterFetch } of plugins) {
|
|
125
|
+
res = await afterFetch?.(res) ?? res;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return res;
|
|
129
|
+
})
|
|
130
|
+
/** [STATUS] */
|
|
131
|
+
.then(handleStatus)
|
|
132
|
+
/** [RESPONSETYPE] */
|
|
133
|
+
.then(res => res[responseType]())
|
|
134
|
+
.then(async data => {
|
|
135
|
+
for(const { transform } of plugins) {
|
|
136
|
+
data = await transform?.(data) ?? data;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return data;
|
|
140
|
+
})
|
|
141
|
+
/** [PLUGINS - HANDLEERROR] */
|
|
142
|
+
.catch(async e => {
|
|
143
|
+
const shouldThrow = (await Promise.all(plugins.map(({handleError}) => handleError?.(e) ?? false))).some(_ => !!_);
|
|
144
|
+
if(shouldThrow) throw e;
|
|
145
|
+
});
|
|
223
146
|
}
|
|
224
147
|
|
|
225
148
|
/** @type {BodylessMethod} */
|
|
@@ -238,4 +161,4 @@ export class Api {
|
|
|
238
161
|
patch = (url, data, opts) => this.fetch(url, 'PATCH', opts, data);
|
|
239
162
|
}
|
|
240
163
|
|
|
241
|
-
export const api = new Api();
|
|
164
|
+
export const api = new Api();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @returns {import('../index').Plugin}
|
|
3
|
+
*/
|
|
4
|
+
export function abortPlugin() {
|
|
5
|
+
let requestId;
|
|
6
|
+
const requests = new Map();
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
beforeFetch: (meta) => {
|
|
10
|
+
const { method, url } = meta;
|
|
11
|
+
requestId = `${method}:${url}`;
|
|
12
|
+
|
|
13
|
+
if(requests.has(requestId)) {
|
|
14
|
+
const request = requests.get(requestId);
|
|
15
|
+
request.abort();
|
|
16
|
+
}
|
|
17
|
+
requests.set(requestId, new AbortController());
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...meta,
|
|
21
|
+
opts: {
|
|
22
|
+
...meta.opts,
|
|
23
|
+
signal: requests.get(requestId).signal
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
afterFetch: (res) => {
|
|
28
|
+
requests.delete(requestId);
|
|
29
|
+
return res;
|
|
30
|
+
},
|
|
31
|
+
// return true if an error should throw, return false if an error should be ignored
|
|
32
|
+
handleError: ({name}) => name !== 'AbortError'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const abort = abortPlugin();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const TEN_MINUTES = 1000 * 60 * 10;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {{maxAge?: number}} [options]
|
|
5
|
+
* @returns {import('../index').Plugin}
|
|
6
|
+
*/
|
|
7
|
+
export function cachePlugin({maxAge} = {}) {
|
|
8
|
+
let requestId;
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
beforeFetch: (meta) => {
|
|
13
|
+
const { method, url } = meta;
|
|
14
|
+
requestId = `${method}:${url}`;
|
|
15
|
+
|
|
16
|
+
if(cache.has(requestId)) {
|
|
17
|
+
const cached = cache.get(requestId);
|
|
18
|
+
if(cached.updatedAt > Date.now() - (maxAge || TEN_MINUTES)) {
|
|
19
|
+
meta.fetchFn = () => Promise.resolve(new Response(JSON.stringify(cached.data), {status: 200}));
|
|
20
|
+
return meta;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
afterFetch: async (res) => {
|
|
25
|
+
const clone = await res.clone();
|
|
26
|
+
const data = await clone.json();
|
|
27
|
+
|
|
28
|
+
cache.set(requestId, {
|
|
29
|
+
updatedAt: Date.now(),
|
|
30
|
+
data
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return res;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const cache = cachePlugin();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} jsonPrefix
|
|
3
|
+
* @returns {import('../index').Plugin}
|
|
4
|
+
*/
|
|
5
|
+
export function jsonPrefixPlugin(jsonPrefix) {
|
|
6
|
+
let responseType;
|
|
7
|
+
return {
|
|
8
|
+
beforeFetch: ({responseType: type}) => {
|
|
9
|
+
responseType = type;
|
|
10
|
+
},
|
|
11
|
+
afterFetch: async (res) => {
|
|
12
|
+
if(jsonPrefix && responseType === 'json') {
|
|
13
|
+
let responseAsText = await res.text();
|
|
14
|
+
|
|
15
|
+
if(responseAsText.startsWith(jsonPrefix)) {
|
|
16
|
+
responseAsText = responseAsText.substring(jsonPrefix.length);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return new Response(responseAsText, res);
|
|
20
|
+
}
|
|
21
|
+
return res;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const jsonPrefix = jsonPrefixPlugin(`)]}',\n`);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{
|
|
3
|
+
* collapsed?: boolean
|
|
4
|
+
* }} options
|
|
5
|
+
* @returns {import('../index').Plugin}
|
|
6
|
+
*/
|
|
7
|
+
export function loggerPlugin({collapsed = true} = {}) {
|
|
8
|
+
let m;
|
|
9
|
+
let start;
|
|
10
|
+
const group = collapsed ? 'groupCollapsed' : 'group';
|
|
11
|
+
return {
|
|
12
|
+
beforeFetch: (meta) => {
|
|
13
|
+
console[group](`[START] [${new Date().toLocaleTimeString()}] [${meta.method}] "${meta.url}"`);
|
|
14
|
+
console.table([meta]);
|
|
15
|
+
console.groupEnd()
|
|
16
|
+
start = Date.now();
|
|
17
|
+
m = meta;
|
|
18
|
+
},
|
|
19
|
+
afterFetch: (r) => {
|
|
20
|
+
console.log(`[END] [${m.method}] "${m.url}" Request took ${Date.now() - start}ms`);
|
|
21
|
+
return r;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const logger = loggerPlugin();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {() => void} f
|
|
3
|
+
* @param {number} ms
|
|
4
|
+
* @param {{
|
|
5
|
+
* signal?: AbortSignal
|
|
6
|
+
* }} [options]
|
|
7
|
+
*/
|
|
8
|
+
function setAbortableTimeout(f, ms, {signal}) {
|
|
9
|
+
let t;
|
|
10
|
+
if(!signal?.aborted) {
|
|
11
|
+
t = setTimeout(f, ms);
|
|
12
|
+
}
|
|
13
|
+
signal?.addEventListener('abort', () => clearTimeout(t), {once: true});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Response | (() => Response) | (() => Promise<Response>)} response
|
|
18
|
+
* @returns {import('../index').Plugin}
|
|
19
|
+
*/
|
|
20
|
+
export function mock(response) {
|
|
21
|
+
return {
|
|
22
|
+
beforeFetch: (meta) => {
|
|
23
|
+
meta.fetchFn = function mock(_, opts) {
|
|
24
|
+
return new Promise(r => setAbortableTimeout(
|
|
25
|
+
() => r(typeof response === 'function' ? response() : response),
|
|
26
|
+
Math.random() * 1000,
|
|
27
|
+
opts?.signal ? { signal: opts.signal } : {}
|
|
28
|
+
))
|
|
29
|
+
}
|
|
30
|
+
return meta;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thepassle/app-tools",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,13 +10,27 @@
|
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
"./state.js": "./state.js",
|
|
13
|
+
"./pwa.js": "./pwa.js",
|
|
13
14
|
"./api.js": "./api.js",
|
|
15
|
+
"./api/plugins/abort.js": "./api/plugins/abort.js",
|
|
16
|
+
"./api/plugins/cache.js": "./api/plugins/cache.js",
|
|
17
|
+
"./api/plugins/delay.js": "./api/plugins/delay.js",
|
|
18
|
+
"./api/plugins/jsonPrefix.js": "./api/plugins/jsonPrefix.js",
|
|
19
|
+
"./api/plugins/mock.js": "./api/plugins/mock.js",
|
|
20
|
+
"./api/plugins/logger.js": "./api/plugins/logger.js",
|
|
21
|
+
"./api/plugins/xsrf.js": "./api/plugins/xsrf.js",
|
|
14
22
|
"./utils.js": "./utils.js",
|
|
15
23
|
"./package.json": "./package.json"
|
|
16
24
|
},
|
|
17
25
|
"files": [
|
|
18
26
|
"./api.js",
|
|
19
27
|
"./api/index.js",
|
|
28
|
+
"./api/plugins/abort.js",
|
|
29
|
+
"./api/plugins/cache.js",
|
|
30
|
+
"./api/plugins/delay.js",
|
|
31
|
+
"./api/plugins/jsonPrefix.js",
|
|
32
|
+
"./api/plugins/mock.js",
|
|
33
|
+
"./api/plugins/logger.js",
|
|
20
34
|
"./state.js",
|
|
21
35
|
"./state/index.js",
|
|
22
36
|
"./utils.js",
|