accessio 1.0.0 → 1.1.1
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 +121 -391
- package/cjs/accessio.cjs +23 -52
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/accessioError.cjs +1 -7
- package/cjs/core/accessioError.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +3 -7
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/mergeConfig.cjs +2 -4
- package/cjs/core/mergeConfig.cjs.map +1 -1
- package/cjs/core/request.cjs +74 -66
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/core/retry.cjs +2 -11
- package/cjs/core/retry.cjs.map +1 -1
- package/cjs/defaults/index.cjs.map +1 -1
- package/cjs/defaults/transforms.cjs +8 -1
- package/cjs/defaults/transforms.cjs.map +1 -1
- package/cjs/helpers/debug.cjs +1 -3
- package/cjs/helpers/debug.cjs.map +1 -1
- package/cjs/helpers/parseHeaders.cjs.map +1 -1
- package/cjs/helpers/rateLimiter.cjs +13 -7
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/cjs/helpers/transformData.cjs.map +1 -1
- package/cjs/index.cjs.map +1 -1
- package/cjs/interceptors/interceptorManager.cjs +40 -3
- package/cjs/interceptors/interceptorManager.cjs.map +1 -1
- package/package.json +7 -15
- package/src/accessio.ts +28 -72
- package/src/core/accessioError.ts +1 -7
- package/src/core/buildURL.ts +6 -15
- package/src/core/mergeConfig.ts +6 -16
- package/src/core/request.ts +90 -74
- package/src/core/retry.ts +4 -19
- package/src/defaults/index.ts +1 -3
- package/src/defaults/transforms.ts +9 -5
- package/src/helpers/debug.ts +12 -22
- package/src/helpers/parseHeaders.ts +1 -3
- package/src/helpers/rateLimiter.ts +19 -16
- package/src/helpers/transformData.ts +1 -4
- package/src/index.ts +3 -11
- package/src/interceptors/interceptorManager.ts +50 -2
- package/src/types.ts +8 -68
package/src/core/request.ts
CHANGED
|
@@ -36,10 +36,7 @@ function flattenHeaders(
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
for (const key in headers) {
|
|
39
|
-
if (
|
|
40
|
-
Object.prototype.hasOwnProperty.call(headers, key) &&
|
|
41
|
-
!METHOD_KEYS.has(key)
|
|
42
|
-
) {
|
|
39
|
+
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
43
40
|
merged[key] = headers[key] as unknown as string;
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -48,10 +45,8 @@ function flattenHeaders(
|
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
function removeContentType(headers: Record<string, string>): void {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
if (key) {
|
|
48
|
+
const keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');
|
|
49
|
+
for (const key of keys) {
|
|
55
50
|
delete headers[key];
|
|
56
51
|
}
|
|
57
52
|
}
|
|
@@ -64,9 +59,42 @@ function buildTransformArray(
|
|
|
64
59
|
return [transform];
|
|
65
60
|
}
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
config
|
|
69
|
-
|
|
62
|
+
function setBasicAuth(config: AccessioRequestConfig, headers: Record<string, string>): void {
|
|
63
|
+
if (!config.auth) return;
|
|
64
|
+
const username = config.auth.username || '';
|
|
65
|
+
const password = config.auth.password || '';
|
|
66
|
+
const credentials = `${username}:${password}`;
|
|
67
|
+
|
|
68
|
+
let encoded: string;
|
|
69
|
+
if (typeof Buffer !== 'undefined') {
|
|
70
|
+
encoded = Buffer.from(credentials).toString('base64');
|
|
71
|
+
} else {
|
|
72
|
+
encoded = btoa(
|
|
73
|
+
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
74
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
headers['Authorization'] = `Basic ${encoded}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
82
|
+
switch (responseType) {
|
|
83
|
+
case 'arraybuffer':
|
|
84
|
+
return await fetchResponse.arrayBuffer();
|
|
85
|
+
case 'blob':
|
|
86
|
+
return await fetchResponse.blob();
|
|
87
|
+
case 'text':
|
|
88
|
+
return await fetchResponse.text();
|
|
89
|
+
case 'stream':
|
|
90
|
+
return fetchResponse.body;
|
|
91
|
+
case 'json':
|
|
92
|
+
default:
|
|
93
|
+
return await fetchResponse.text();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default function dispatchRequest(config: AccessioRequestConfig): Promise<AccessioResponse> {
|
|
70
98
|
const fullURL =
|
|
71
99
|
config._builtUrl ||
|
|
72
100
|
buildURL(
|
|
@@ -76,19 +104,11 @@ export default function dispatchRequest(
|
|
|
76
104
|
config.paramsSerializer,
|
|
77
105
|
);
|
|
78
106
|
|
|
79
|
-
const flatHeaders = flattenHeaders(
|
|
80
|
-
config.headers as HeadersConfig | undefined,
|
|
81
|
-
config.method,
|
|
82
|
-
);
|
|
107
|
+
const flatHeaders = flattenHeaders(config.headers as HeadersConfig | undefined, config.method);
|
|
83
108
|
|
|
84
109
|
const requestTransforms = buildTransformArray(config.transformRequest);
|
|
85
110
|
|
|
86
|
-
const requestData = transformData(
|
|
87
|
-
requestTransforms,
|
|
88
|
-
config.data,
|
|
89
|
-
flatHeaders,
|
|
90
|
-
config,
|
|
91
|
-
);
|
|
111
|
+
const requestData = transformData(requestTransforms, config.data, flatHeaders, config);
|
|
92
112
|
|
|
93
113
|
if (
|
|
94
114
|
requestData === null ||
|
|
@@ -98,24 +118,7 @@ export default function dispatchRequest(
|
|
|
98
118
|
removeContentType(flatHeaders);
|
|
99
119
|
}
|
|
100
120
|
|
|
101
|
-
|
|
102
|
-
const username = config.auth.username || '';
|
|
103
|
-
const password = config.auth.password || '';
|
|
104
|
-
const credentials = `${username}:${password}`;
|
|
105
|
-
|
|
106
|
-
let encoded: string;
|
|
107
|
-
if (typeof Buffer !== 'undefined') {
|
|
108
|
-
encoded = Buffer.from(credentials).toString('base64');
|
|
109
|
-
} else {
|
|
110
|
-
const bytes = new TextEncoder().encode(credentials);
|
|
111
|
-
const binString = Array.from(bytes, (x) =>
|
|
112
|
-
String.fromCodePoint(x),
|
|
113
|
-
).join('');
|
|
114
|
-
encoded = btoa(binString);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
flatHeaders['Authorization'] = `Basic ${encoded}`;
|
|
118
|
-
}
|
|
121
|
+
setBasicAuth(config, flatHeaders);
|
|
119
122
|
|
|
120
123
|
const fetchOptions: RequestInit = {
|
|
121
124
|
method: (config.method || 'GET').toUpperCase(),
|
|
@@ -135,39 +138,46 @@ export default function dispatchRequest(
|
|
|
135
138
|
fetchOptions.credentials = 'include';
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
if (config.dispatcher) {
|
|
142
|
+
(fetchOptions as any).dispatcher = config.dispatcher;
|
|
143
|
+
}
|
|
144
|
+
if (config.agent) {
|
|
145
|
+
(fetchOptions as any).agent = config.agent;
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
let abortController: AbortController | null = null;
|
|
139
149
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
140
150
|
let isTimedOut = false;
|
|
141
151
|
let onUserAbort: (() => void) | null = null;
|
|
142
152
|
|
|
143
|
-
|
|
153
|
+
const timeoutValue = Number(config.timeout);
|
|
154
|
+
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
144
155
|
abortController = new AbortController();
|
|
145
156
|
|
|
146
157
|
timeoutId = setTimeout(() => {
|
|
147
158
|
isTimedOut = true;
|
|
148
159
|
abortController!.abort(
|
|
149
160
|
new AccessioError(
|
|
150
|
-
`timeout of ${
|
|
161
|
+
`timeout of ${timeoutValue}ms exceeded`,
|
|
151
162
|
AccessioError.ETIMEDOUT,
|
|
152
163
|
config,
|
|
153
164
|
null,
|
|
154
165
|
null,
|
|
155
166
|
),
|
|
156
167
|
);
|
|
157
|
-
},
|
|
168
|
+
}, timeoutValue);
|
|
158
169
|
|
|
159
170
|
if (config.signal) {
|
|
160
171
|
if (typeof AbortSignal.any === 'function') {
|
|
161
|
-
fetchOptions.signal = AbortSignal.any([
|
|
162
|
-
config.signal,
|
|
163
|
-
abortController.signal,
|
|
164
|
-
]);
|
|
172
|
+
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
165
173
|
} else {
|
|
166
174
|
if (config.signal.aborted) {
|
|
167
175
|
abortController.abort(config.signal.reason);
|
|
168
176
|
} else {
|
|
169
177
|
onUserAbort = () => {
|
|
170
|
-
abortController
|
|
178
|
+
if (!isTimedOut && abortController) {
|
|
179
|
+
abortController.abort(config.signal!.reason);
|
|
180
|
+
}
|
|
171
181
|
};
|
|
172
182
|
config.signal.addEventListener('abort', onUserAbort, {
|
|
173
183
|
once: true,
|
|
@@ -189,25 +199,23 @@ export default function dispatchRequest(
|
|
|
189
199
|
let responseData: unknown;
|
|
190
200
|
const responseType = config.responseType || 'json';
|
|
191
201
|
|
|
202
|
+
const contentLength = fetchResponse.headers.get('content-length');
|
|
203
|
+
if (
|
|
204
|
+
contentLength &&
|
|
205
|
+
config.maxContentLength &&
|
|
206
|
+
parseInt(contentLength, 10) > config.maxContentLength
|
|
207
|
+
) {
|
|
208
|
+
throw new AccessioError(
|
|
209
|
+
`maxContentLength size of ${config.maxContentLength} exceeded`,
|
|
210
|
+
AccessioError.ERR_BAD_RESPONSE,
|
|
211
|
+
config,
|
|
212
|
+
fetchResponse,
|
|
213
|
+
null,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
192
217
|
try {
|
|
193
|
-
|
|
194
|
-
case 'arraybuffer':
|
|
195
|
-
responseData = await fetchResponse.arrayBuffer();
|
|
196
|
-
break;
|
|
197
|
-
case 'blob':
|
|
198
|
-
responseData = await fetchResponse.blob();
|
|
199
|
-
break;
|
|
200
|
-
case 'text':
|
|
201
|
-
responseData = await fetchResponse.text();
|
|
202
|
-
break;
|
|
203
|
-
case 'stream':
|
|
204
|
-
responseData = fetchResponse.body;
|
|
205
|
-
break;
|
|
206
|
-
case 'json':
|
|
207
|
-
default:
|
|
208
|
-
responseData = await fetchResponse.text();
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
218
|
+
responseData = await readResponseData(fetchResponse, responseType);
|
|
211
219
|
} catch (readError) {
|
|
212
220
|
throw AccessioError.from(
|
|
213
221
|
readError as Error,
|
|
@@ -222,12 +230,7 @@ export default function dispatchRequest(
|
|
|
222
230
|
|
|
223
231
|
const responseTransforms = buildTransformArray(config.transformResponse);
|
|
224
232
|
|
|
225
|
-
responseData = transformData(
|
|
226
|
-
responseTransforms,
|
|
227
|
-
responseData,
|
|
228
|
-
responseHeaders,
|
|
229
|
-
config,
|
|
230
|
-
);
|
|
233
|
+
responseData = transformData(responseTransforms, responseData, responseHeaders, config);
|
|
231
234
|
|
|
232
235
|
const response: AccessioResponse = {
|
|
233
236
|
data: responseData,
|
|
@@ -240,7 +243,12 @@ export default function dispatchRequest(
|
|
|
240
243
|
};
|
|
241
244
|
|
|
242
245
|
return new Promise<AccessioResponse>((resolve, reject) => {
|
|
243
|
-
settle(
|
|
246
|
+
settle(
|
|
247
|
+
resolve as (value: AccessioResponse) => void,
|
|
248
|
+
reject as (reason: AccessioError) => void,
|
|
249
|
+
response,
|
|
250
|
+
config,
|
|
251
|
+
);
|
|
244
252
|
});
|
|
245
253
|
})
|
|
246
254
|
.catch((error) => {
|
|
@@ -258,9 +266,17 @@ export default function dispatchRequest(
|
|
|
258
266
|
null,
|
|
259
267
|
);
|
|
260
268
|
}
|
|
269
|
+
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
error instanceof TypeError &&
|
|
274
|
+
(error.message.toLowerCase().includes('url') ||
|
|
275
|
+
error.message.toLowerCase().includes('fetch'))
|
|
276
|
+
) {
|
|
261
277
|
throw new AccessioError(
|
|
262
|
-
|
|
263
|
-
AccessioError.
|
|
278
|
+
`Invalid URL: ${fullURL}`,
|
|
279
|
+
AccessioError.ERR_INVALID_URL,
|
|
264
280
|
config,
|
|
265
281
|
null,
|
|
266
282
|
null,
|
package/src/core/retry.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ERR_CANCELED,
|
|
3
|
-
ERR_NETWORK,
|
|
4
|
-
ETIMEDOUT,
|
|
5
|
-
} from '../constants/errorCodes';
|
|
1
|
+
import { ERR_CANCELED, ERR_NETWORK, ETIMEDOUT } from '../constants/errorCodes';
|
|
6
2
|
import type {
|
|
7
3
|
AccessioRequestConfig,
|
|
8
4
|
AccessioResponse,
|
|
@@ -20,10 +16,6 @@ function defaultRetryCondition(error: any): boolean {
|
|
|
20
16
|
return true;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
if (error.code === ETIMEDOUT) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
if (error.response && error.response.status >= 500) {
|
|
28
20
|
return true;
|
|
29
21
|
}
|
|
@@ -51,9 +43,7 @@ function sleep(ms: number, options?: { signal?: AbortSignal }): Promise<void> {
|
|
|
51
43
|
if (options?.signal) {
|
|
52
44
|
if (options.signal.aborted) {
|
|
53
45
|
clearTimeout(timeoutId);
|
|
54
|
-
return reject(
|
|
55
|
-
options.signal.reason || new Error('Sleep aborted'),
|
|
56
|
-
);
|
|
46
|
+
return reject(options.signal.reason || new Error('Sleep aborted'));
|
|
57
47
|
}
|
|
58
48
|
|
|
59
49
|
onAbort = () => {
|
|
@@ -77,8 +67,7 @@ async function retryRequest(
|
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
const retryDelay = config.retryDelay ?? 1000;
|
|
80
|
-
const retryCondition: RetryConditionFunction =
|
|
81
|
-
config.retryCondition ?? defaultRetryCondition;
|
|
70
|
+
const retryCondition: RetryConditionFunction = config.retryCondition ?? defaultRetryCondition;
|
|
82
71
|
|
|
83
72
|
let lastError: any;
|
|
84
73
|
|
|
@@ -99,11 +88,7 @@ async function retryRequest(
|
|
|
99
88
|
const delay = calculateDelay(attempt, retryDelay);
|
|
100
89
|
|
|
101
90
|
if (typeof config.onRetry === 'function') {
|
|
102
|
-
(config.onRetry as OnRetryFunction)(
|
|
103
|
-
attempt + 1,
|
|
104
|
-
error as AccessioError,
|
|
105
|
-
config,
|
|
106
|
-
);
|
|
91
|
+
(config.onRetry as OnRetryFunction)(attempt + 1, error as AccessioError, config);
|
|
107
92
|
}
|
|
108
93
|
|
|
109
94
|
await sleep(delay, { signal: config.signal });
|
package/src/defaults/index.ts
CHANGED
|
@@ -24,9 +24,7 @@ const defaults: AccessioRequestConfig = {
|
|
|
24
24
|
},
|
|
25
25
|
transformRequest: [defaultTransformRequest],
|
|
26
26
|
transformResponse: [defaultTransformResponse],
|
|
27
|
-
validateStatus: function defaultValidateStatus(
|
|
28
|
-
status: number,
|
|
29
|
-
): boolean {
|
|
27
|
+
validateStatus: function defaultValidateStatus(status: number): boolean {
|
|
30
28
|
return status >= 200 && status < 300;
|
|
31
29
|
},
|
|
32
30
|
responseType: 'json',
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
export function defaultTransformRequest(
|
|
2
|
-
data: unknown,
|
|
3
|
-
headers: Record<string, string>,
|
|
4
|
-
): unknown {
|
|
1
|
+
export function defaultTransformRequest(data: unknown, headers: Record<string, string>): unknown {
|
|
5
2
|
if (data === null || data === undefined) {
|
|
6
3
|
return data;
|
|
7
4
|
}
|
|
@@ -26,7 +23,14 @@ export function defaultTransformRequest(
|
|
|
26
23
|
headers['Content-Type'] = 'application/json';
|
|
27
24
|
}
|
|
28
25
|
}
|
|
29
|
-
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(data);
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
if (e instanceof TypeError && e.message.toLowerCase().includes('circular')) {
|
|
30
|
+
throw new Error('Accessio: Cannot stringify circular structure in request data');
|
|
31
|
+
}
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
return data;
|
package/src/helpers/debug.ts
CHANGED
|
@@ -8,9 +8,11 @@ function formatBytes(bytes: number): string {
|
|
|
8
8
|
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function sanitizeConfigForLog(
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
function sanitizeConfigForLog(config: AccessioRequestConfig): {
|
|
12
|
+
params: Record<string, unknown> | undefined;
|
|
13
|
+
timeout: number | undefined;
|
|
14
|
+
retry: number | undefined;
|
|
15
|
+
} {
|
|
14
16
|
return {
|
|
15
17
|
params: config.params,
|
|
16
18
|
timeout: config.timeout,
|
|
@@ -18,10 +20,7 @@ function sanitizeConfigForLog(
|
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export function logRequest(
|
|
22
|
-
config: AccessioRequestConfig,
|
|
23
|
-
fullUrl: string,
|
|
24
|
-
): void {
|
|
23
|
+
export function logRequest(config: AccessioRequestConfig, fullUrl: string): void {
|
|
25
24
|
if (!config.debug) return;
|
|
26
25
|
|
|
27
26
|
const safe = sanitizeConfigForLog(config);
|
|
@@ -37,8 +36,7 @@ export function logRequest(
|
|
|
37
36
|
|
|
38
37
|
if (config.data && typeof config.data === 'object') {
|
|
39
38
|
const preview = JSON.stringify(config.data);
|
|
40
|
-
const truncated =
|
|
41
|
-
preview.length > 200 ? `${preview.substring(0, 200)}...` : preview;
|
|
39
|
+
const truncated = preview.length > 200 ? `${preview.substring(0, 200)}...` : preview;
|
|
42
40
|
parts.push(` Body: ${truncated}`);
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -57,19 +55,11 @@ export function logResponse(response: AccessioResponse): void {
|
|
|
57
55
|
if (!response.config || !response.config.debug) return;
|
|
58
56
|
const status = response.status;
|
|
59
57
|
const statusText = response.statusText || '';
|
|
60
|
-
const duration =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
? '✅'
|
|
66
|
-
: status >= 400
|
|
67
|
-
? '❌'
|
|
68
|
-
: '⚠️';
|
|
69
|
-
|
|
70
|
-
const parts: string[] = [
|
|
71
|
-
`🐦⬛ [Accessio] ← ${statusIcon} ${status} ${statusText} (${duration})`,
|
|
72
|
-
];
|
|
58
|
+
const duration = response.duration != null ? `${response.duration}ms` : '??';
|
|
59
|
+
|
|
60
|
+
const statusIcon = status >= 200 && status < 300 ? '✅' : status >= 400 ? '❌' : '⚠️';
|
|
61
|
+
|
|
62
|
+
const parts: string[] = [`🐦⬛ [Accessio] ← ${statusIcon} ${status} ${statusText} (${duration})`];
|
|
73
63
|
|
|
74
64
|
if (response.data) {
|
|
75
65
|
try {
|
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RateLimiter,
|
|
3
|
-
AccessioRequestConfig,
|
|
4
|
-
AccessioResponse,
|
|
5
|
-
} from '../types';
|
|
1
|
+
import type { RateLimiter, AccessioRequestConfig, AccessioResponse } from '../types';
|
|
6
2
|
|
|
7
3
|
interface QueueItem {
|
|
8
4
|
resolve: () => void;
|
|
9
5
|
reject: (reason: Error) => void;
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
export function createRateLimiter(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
) {
|
|
8
|
+
export function createRateLimiter(
|
|
9
|
+
maxConcurrent: number = Infinity,
|
|
10
|
+
maxQueueSize: number = Infinity,
|
|
11
|
+
): RateLimiter {
|
|
12
|
+
if (maxConcurrent !== Infinity && (!Number.isInteger(maxConcurrent) || maxConcurrent < 1)) {
|
|
17
13
|
throw new RangeError(
|
|
18
14
|
`[Accessio] maxConcurrent must be a positive integer or Infinity, got: ${maxConcurrent}`,
|
|
19
15
|
);
|
|
20
16
|
}
|
|
17
|
+
if (maxQueueSize !== Infinity && (!Number.isInteger(maxQueueSize) || maxQueueSize < 1)) {
|
|
18
|
+
throw new RangeError(
|
|
19
|
+
`[Accessio] maxQueueSize must be a positive integer or Infinity, got: ${maxQueueSize}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
21
22
|
let active = 0;
|
|
22
23
|
let destroyed = false;
|
|
23
24
|
const queue: QueueItem[] = [];
|
|
24
25
|
|
|
25
26
|
function acquire(): Promise<void> {
|
|
26
27
|
if (destroyed) {
|
|
27
|
-
return Promise.reject(
|
|
28
|
-
new Error('[Accessio] Rate limiter has been destroyed'),
|
|
29
|
-
);
|
|
28
|
+
return Promise.reject(new Error('[Accessio] Rate limiter has been destroyed'));
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
if (active < maxConcurrent) {
|
|
@@ -34,6 +33,12 @@ export function createRateLimiter(maxConcurrent: number = Infinity): RateLimiter
|
|
|
34
33
|
return Promise.resolve();
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
if (queue.length >= maxQueueSize) {
|
|
37
|
+
return Promise.reject(
|
|
38
|
+
new Error(`[Accessio] Rate limiter queue size exceeded maxQueueSize (${maxQueueSize})`),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
return new Promise((resolve, reject) => {
|
|
38
43
|
queue.push({ resolve, reject });
|
|
39
44
|
});
|
|
@@ -55,9 +60,7 @@ export function createRateLimiter(maxConcurrent: number = Infinity): RateLimiter
|
|
|
55
60
|
|
|
56
61
|
function destroy(): void {
|
|
57
62
|
destroyed = true;
|
|
58
|
-
const reason = new Error(
|
|
59
|
-
'[Accessio] Rate limiter destroyed — pending request cancelled',
|
|
60
|
-
);
|
|
63
|
+
const reason = new Error('[Accessio] Rate limiter destroyed — pending request cancelled');
|
|
61
64
|
while (queue.length > 0) {
|
|
62
65
|
const next = queue.shift();
|
|
63
66
|
next?.reject(reason);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import AccessioError from '../core/accessioError';
|
|
2
|
-
import type {
|
|
3
|
-
TransformFunction,
|
|
4
|
-
AccessioRequestConfig,
|
|
5
|
-
} from '../types';
|
|
2
|
+
import type { TransformFunction, AccessioRequestConfig } from '../types';
|
|
6
3
|
|
|
7
4
|
export default function transformData(
|
|
8
5
|
transforms: TransformFunction | TransformFunction[] | undefined,
|
package/src/index.ts
CHANGED
|
@@ -46,23 +46,15 @@ function createInstance(defaultConfig: AccessioRequestConfig) {
|
|
|
46
46
|
instance.all = function all(promises: any[]): Promise<any[]> {
|
|
47
47
|
return Promise.all(promises);
|
|
48
48
|
};
|
|
49
|
-
instance.spread = function spread<T>(
|
|
50
|
-
callback: (...args: any[]) => T,
|
|
51
|
-
): (arr: any[]) => T {
|
|
49
|
+
instance.spread = function spread<T>(callback: (...args: any[]) => T): (arr: any[]) => T {
|
|
52
50
|
return function wrap(arr: any[]): T {
|
|
53
51
|
return callback(...arr);
|
|
54
52
|
};
|
|
55
53
|
};
|
|
56
54
|
instance.isCancel = function isCancel(value: any): boolean {
|
|
57
|
-
return !!(
|
|
58
|
-
value &&
|
|
59
|
-
value.isAccessioError &&
|
|
60
|
-
value.code === ERR_CANCELED
|
|
61
|
-
);
|
|
55
|
+
return !!(value && value.isAccessioError && value.code === ERR_CANCELED);
|
|
62
56
|
};
|
|
63
|
-
instance.isAccessioError = function isAccessioError(
|
|
64
|
-
value: any,
|
|
65
|
-
): boolean {
|
|
57
|
+
instance.isAccessioError = function isAccessioError(value: any): boolean {
|
|
66
58
|
return (
|
|
67
59
|
value instanceof AccessioError ||
|
|
68
60
|
!!(value && typeof value === 'object' && value.isAccessioError === true)
|
|
@@ -1,5 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TransformFunction, InterceptorHandler, InterceptorOptions } from '../types';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export class InterceptorManager {
|
|
4
|
+
handlers: Array<InterceptorHandler | null>;
|
|
5
|
+
private _activeCount: number;
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.handlers = [];
|
|
9
|
+
this._activeCount = 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
use(
|
|
13
|
+
fulfilled: TransformFunction | null,
|
|
14
|
+
rejected?: ((error: unknown) => unknown) | null,
|
|
15
|
+
options: InterceptorOptions = {},
|
|
16
|
+
): number {
|
|
17
|
+
this.handlers.push({
|
|
18
|
+
fulfilled: fulfilled || null,
|
|
19
|
+
rejected: rejected || null,
|
|
20
|
+
synchronous: options.synchronous || false,
|
|
21
|
+
runWhen: options.runWhen || null,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this._activeCount++;
|
|
25
|
+
return this.handlers.length - 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
eject(id: number): void {
|
|
29
|
+
if (this.handlers[id]) {
|
|
30
|
+
this.handlers[id] = null;
|
|
31
|
+
this._activeCount--;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear(): void {
|
|
36
|
+
this.handlers = [];
|
|
37
|
+
this._activeCount = 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
forEach(fn: (handler: InterceptorHandler) => void): void {
|
|
41
|
+
for (const handler of this.handlers) {
|
|
42
|
+
if (handler !== null) {
|
|
43
|
+
fn(handler);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get size(): number {
|
|
49
|
+
return this._activeCount;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
4
52
|
|
|
5
53
|
export default InterceptorManager;
|
package/src/types.ts
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
| 'get'
|
|
3
|
-
| 'delete'
|
|
4
|
-
| 'head'
|
|
5
|
-
| 'options'
|
|
6
|
-
| 'post'
|
|
7
|
-
| 'put'
|
|
8
|
-
| 'patch';
|
|
1
|
+
import type InterceptorManager from './interceptors/interceptorManager';
|
|
9
2
|
|
|
10
|
-
export type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| 'blob'
|
|
14
|
-
| 'arraybuffer'
|
|
15
|
-
| 'stream';
|
|
3
|
+
export type Method = 'get' | 'delete' | 'head' | 'options' | 'post' | 'put' | 'patch';
|
|
4
|
+
|
|
5
|
+
export type ResponseType = 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream';
|
|
16
6
|
|
|
17
7
|
export interface AuthConfig {
|
|
18
8
|
username: string;
|
|
@@ -21,10 +11,7 @@ export interface AuthConfig {
|
|
|
21
11
|
|
|
22
12
|
export type ParamsSerializer = (params: Record<string, unknown>) => string;
|
|
23
13
|
|
|
24
|
-
export type TransformFunction = (
|
|
25
|
-
data: unknown,
|
|
26
|
-
headers: Record<string, string>,
|
|
27
|
-
) => unknown;
|
|
14
|
+
export type TransformFunction = (data: unknown, headers: Record<string, string>) => unknown;
|
|
28
15
|
|
|
29
16
|
export type RetryConditionFunction = (error: AccessioError) => boolean;
|
|
30
17
|
|
|
@@ -76,6 +63,9 @@ export interface AccessioRequestConfig {
|
|
|
76
63
|
debug?: boolean;
|
|
77
64
|
rateLimiter?: RateLimiter;
|
|
78
65
|
_builtUrl?: string;
|
|
66
|
+
maxContentLength?: number;
|
|
67
|
+
dispatcher?: unknown;
|
|
68
|
+
agent?: unknown;
|
|
79
69
|
}
|
|
80
70
|
|
|
81
71
|
export interface AccessioResponse<T = unknown> {
|
|
@@ -99,56 +89,6 @@ export interface AccessioError extends Error {
|
|
|
99
89
|
toJSON(): Record<string, unknown>;
|
|
100
90
|
}
|
|
101
91
|
|
|
102
|
-
export class InterceptorManager {
|
|
103
|
-
handlers: Array<InterceptorHandler | null>;
|
|
104
|
-
private _activeCount: number;
|
|
105
|
-
|
|
106
|
-
constructor() {
|
|
107
|
-
this.handlers = [];
|
|
108
|
-
this._activeCount = 0;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
use(
|
|
112
|
-
fulfilled: TransformFunction,
|
|
113
|
-
rejected?: (error: unknown) => unknown,
|
|
114
|
-
options: InterceptorOptions = {},
|
|
115
|
-
): number {
|
|
116
|
-
this.handlers.push({
|
|
117
|
-
fulfilled: fulfilled || null,
|
|
118
|
-
rejected: rejected || null,
|
|
119
|
-
synchronous: options.synchronous || false,
|
|
120
|
-
runWhen: options.runWhen || null,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
this._activeCount++;
|
|
124
|
-
return this.handlers.length - 1;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
eject(id: number): void {
|
|
128
|
-
if (this.handlers[id]) {
|
|
129
|
-
this.handlers[id] = null;
|
|
130
|
-
this._activeCount--;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
clear(): void {
|
|
135
|
-
this.handlers = [];
|
|
136
|
-
this._activeCount = 0;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
forEach(fn: (handler: InterceptorHandler) => void): void {
|
|
140
|
-
for (const handler of this.handlers) {
|
|
141
|
-
if (handler !== null) {
|
|
142
|
-
fn(handler);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
get size(): number {
|
|
148
|
-
return this._activeCount;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
92
|
export interface RateLimiter {
|
|
153
93
|
acquire: () => Promise<void>;
|
|
154
94
|
release: () => void;
|