accessio 1.1.0 → 1.1.2
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 +4 -2
- package/cjs/accessio.cjs +43 -7
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +2 -2
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/fetchAdapter.cjs +187 -0
- package/cjs/core/fetchAdapter.cjs.map +1 -0
- package/cjs/core/mergeConfig.cjs +2 -2
- package/cjs/core/mergeConfig.cjs.map +1 -1
- package/cjs/core/request.cjs +23 -171
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/core/retry.cjs +0 -3
- package/cjs/core/retry.cjs.map +1 -1
- package/cjs/defaults/transforms.cjs +8 -1
- package/cjs/defaults/transforms.cjs.map +1 -1
- package/cjs/helpers/auth.cjs +45 -0
- package/cjs/helpers/auth.cjs.map +1 -0
- package/cjs/helpers/flattenHeaders.cjs +78 -0
- package/cjs/helpers/flattenHeaders.cjs.map +1 -0
- package/cjs/helpers/parseHeaders.cjs +16 -4
- package/cjs/helpers/parseHeaders.cjs.map +1 -1
- package/cjs/helpers/rateLimiter.cjs +20 -12
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/cjs/helpers/transformData.cjs +2 -2
- package/cjs/helpers/transformData.cjs.map +1 -1
- package/package.json +3 -3
- package/src/accessio.ts +46 -8
- package/src/core/buildURL.ts +2 -2
- package/src/core/fetchAdapter.ts +184 -0
- package/src/core/mergeConfig.ts +2 -2
- package/src/core/request.ts +28 -193
- package/src/core/retry.ts +0 -4
- package/src/defaults/transforms.ts +12 -2
- package/src/helpers/auth.ts +26 -0
- package/src/helpers/flattenHeaders.ts +59 -0
- package/src/helpers/parseHeaders.ts +19 -6
- package/src/helpers/rateLimiter.ts +20 -12
- package/src/helpers/transformData.ts +4 -4
- package/src/types.ts +9 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/transformData.ts"],"sourcesContent":["import AccessioError from '../core/accessioError';\nimport type { TransformFunction, AccessioRequestConfig } from '../types';\n\nexport default function transformData(\n transforms: TransformFunction | TransformFunction[] | undefined,\n data: unknown,\n headers: Record<string, string>,\n config?: AccessioRequestConfig,\n): unknown {\n if (!transforms || !Array.isArray(transforms)) {\n return data;\n }\n\n let result = data;\n\n for (const transform of transforms) {\n if (typeof transform === 'function') {\n try {\n result = transform(result, headers);\n } catch (err) {\n throw AccessioError.from(\n err instanceof Error ? err : new Error(String(err)),\n AccessioError.ERR_BAD_REQUEST,\n config ?? null,\n null,\n null,\n );\n }\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAA0B;
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/transformData.ts"],"sourcesContent":["import AccessioError from '../core/accessioError';\nimport type { TransformFunction, AccessioRequestConfig } from '../types';\n\nexport default async function transformData(\n transforms: TransformFunction | TransformFunction[] | undefined,\n data: unknown,\n headers: Record<string, string | string[]>,\n config?: AccessioRequestConfig,\n): Promise<unknown> {\n if (!transforms || !Array.isArray(transforms)) {\n return data;\n }\n\n let result = data;\n\n for (const transform of transforms) {\n if (typeof transform === 'function') {\n try {\n result = await transform(result, headers);\n } catch (err) {\n throw AccessioError.from(\n err instanceof Error ? err : new Error(String(err)),\n AccessioError.ERR_BAD_REQUEST,\n config ?? null,\n null,\n null,\n );\n }\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAA0B;AAG1B,eAAO,cACL,YACA,MACA,SACA,QACkB;AAClB,MAAI,CAAC,cAAc,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC7C,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AAEb,aAAW,aAAa,YAAY;AAClC,QAAI,OAAO,cAAc,YAAY;AACnC,UAAI;AACF,iBAAS,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC1C,SAAS,KAAK;AACZ,cAAM,qBAAAA,QAAc;AAAA,UAClB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,UAClD,qBAAAA,QAAc;AAAA,UACd,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["AccessioError"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accessio",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Fast, flexible HTTP client
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "Fast, flexible HTTP client — simple, modular, and dependency-free",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./cjs/index.cjs",
|
|
7
7
|
"module": "./src/index.ts",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"test": "vitest run",
|
|
78
78
|
"test:watch": "vitest",
|
|
79
79
|
"test:coverage": "vitest run --coverage",
|
|
80
|
-
"test:browser": "vitest run --config vitest.browser.config.
|
|
80
|
+
"test:browser": "vitest run --config vitest.browser.config.ts",
|
|
81
81
|
"release:npm": "gh workflow run publish-npm.yml -f publish_tag=$(git describe --tags --abbrev=0)",
|
|
82
82
|
"typecheck": "tsc --noEmit"
|
|
83
83
|
},
|
package/src/accessio.ts
CHANGED
|
@@ -52,11 +52,13 @@ export class Accessio {
|
|
|
52
52
|
|
|
53
53
|
const requestInterceptors: any[] = [];
|
|
54
54
|
const responseInterceptors: any[] = [];
|
|
55
|
+
let synchronousRequestInterceptors = true;
|
|
55
56
|
|
|
56
57
|
this.interceptors.request.forEach((interceptor: InterceptorHandler) => {
|
|
57
58
|
if (interceptor.runWhen && !interceptor.runWhen(mergedConfig)) {
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
61
|
+
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
|
|
60
62
|
requestInterceptors.unshift(interceptor);
|
|
61
63
|
});
|
|
62
64
|
|
|
@@ -64,15 +66,51 @@ export class Accessio {
|
|
|
64
66
|
responseInterceptors.push(interceptor);
|
|
65
67
|
});
|
|
66
68
|
|
|
67
|
-
let promise: Promise<any
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
let promise: Promise<any>;
|
|
70
|
+
|
|
71
|
+
if (synchronousRequestInterceptors) {
|
|
72
|
+
let newConfig = mergedConfig;
|
|
73
|
+
let rejectReason: any = null;
|
|
74
|
+
let isRejected = false;
|
|
75
|
+
|
|
76
|
+
for (const interceptor of requestInterceptors) {
|
|
77
|
+
if (!isRejected) {
|
|
78
|
+
try {
|
|
79
|
+
if (interceptor.fulfilled) {
|
|
80
|
+
newConfig = interceptor.fulfilled(newConfig);
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
rejectReason = err;
|
|
84
|
+
isRejected = true;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
if (interceptor.rejected) {
|
|
88
|
+
try {
|
|
89
|
+
newConfig = interceptor.rejected(rejectReason);
|
|
90
|
+
isRejected = false;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
rejectReason = err;
|
|
93
|
+
isRejected = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
73
96
|
}
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isRejected) {
|
|
100
|
+
promise = Promise.reject(rejectReason);
|
|
101
|
+
} else {
|
|
102
|
+
promise = Promise.resolve(newConfig);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
promise = Promise.resolve(mergedConfig);
|
|
106
|
+
for (const interceptor of requestInterceptors) {
|
|
107
|
+
promise = promise.then((value: any) => {
|
|
108
|
+
if (interceptor.fulfilled) {
|
|
109
|
+
return interceptor.fulfilled(value);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}, interceptor.rejected);
|
|
113
|
+
}
|
|
76
114
|
}
|
|
77
115
|
|
|
78
116
|
promise = promise.then((cfg: any) => {
|
package/src/core/buildURL.ts
CHANGED
|
@@ -26,7 +26,7 @@ function serializeParams(
|
|
|
26
26
|
if (typeof item === 'object' && item !== null) {
|
|
27
27
|
encode(`${prefix}[${index}]`, item);
|
|
28
28
|
} else {
|
|
29
|
-
encode(
|
|
29
|
+
encode(prefix, item);
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
} else if (typeof value === 'object' && !(value instanceof Date)) {
|
|
@@ -64,7 +64,7 @@ function combineURLs(baseURL: string, relativeURL: string): string {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function isAbsoluteURL(url: string): boolean {
|
|
67
|
-
return /^([a-z][a-z\d+\-.]*:)
|
|
67
|
+
return /^([a-z][a-z\d+\-.]*:)/i.test(url);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export default function buildURL(
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import AccessioError from './accessioError';
|
|
2
|
+
import parseHeaders from '../helpers/parseHeaders';
|
|
3
|
+
import type { AccessioRequestConfig, AccessioResponse } from '../types';
|
|
4
|
+
|
|
5
|
+
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
6
|
+
switch (responseType) {
|
|
7
|
+
case 'arraybuffer':
|
|
8
|
+
return await fetchResponse.arrayBuffer();
|
|
9
|
+
case 'blob':
|
|
10
|
+
return await fetchResponse.blob();
|
|
11
|
+
case 'stream':
|
|
12
|
+
return fetchResponse.body;
|
|
13
|
+
case 'json':
|
|
14
|
+
default: {
|
|
15
|
+
const contentType = fetchResponse.headers.get('content-type') || '';
|
|
16
|
+
if (contentType.includes('application/json')) {
|
|
17
|
+
if (typeof fetchResponse.clone === 'function') {
|
|
18
|
+
const text = await fetchResponse.clone().text();
|
|
19
|
+
return text ? await fetchResponse.json() : '';
|
|
20
|
+
} else {
|
|
21
|
+
const text = await fetchResponse.text();
|
|
22
|
+
return text ? JSON.parse(text) : '';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return await fetchResponse.text();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default async function fetchAdapter(
|
|
31
|
+
config: AccessioRequestConfig,
|
|
32
|
+
fullURL: string,
|
|
33
|
+
fetchOptions: RequestInit,
|
|
34
|
+
requestStartTime: number,
|
|
35
|
+
): Promise<AccessioResponse> {
|
|
36
|
+
let abortController: AbortController | null = null;
|
|
37
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
38
|
+
let isTimedOut = false;
|
|
39
|
+
let onUserAbort: (() => void) | null = null;
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
config.timeout !== undefined &&
|
|
43
|
+
(typeof config.timeout !== 'number' || isNaN(config.timeout) || config.timeout < 0)
|
|
44
|
+
) {
|
|
45
|
+
throw new AccessioError(
|
|
46
|
+
`Invalid timeout value: ${config.timeout}`,
|
|
47
|
+
AccessioError.ERR_BAD_OPTION_VALUE,
|
|
48
|
+
config,
|
|
49
|
+
null,
|
|
50
|
+
null,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const timeoutValue = Number(config.timeout);
|
|
55
|
+
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
56
|
+
abortController = new AbortController();
|
|
57
|
+
|
|
58
|
+
timeoutId = setTimeout(() => {
|
|
59
|
+
isTimedOut = true;
|
|
60
|
+
abortController!.abort(
|
|
61
|
+
new AccessioError(
|
|
62
|
+
`timeout of ${timeoutValue}ms exceeded`,
|
|
63
|
+
AccessioError.ETIMEDOUT,
|
|
64
|
+
config,
|
|
65
|
+
null,
|
|
66
|
+
null,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}, timeoutValue);
|
|
70
|
+
|
|
71
|
+
if (config.signal) {
|
|
72
|
+
if (typeof AbortSignal.any === 'function') {
|
|
73
|
+
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
74
|
+
} else {
|
|
75
|
+
if (config.signal.aborted) {
|
|
76
|
+
abortController.abort(config.signal.reason);
|
|
77
|
+
} else {
|
|
78
|
+
onUserAbort = () => {
|
|
79
|
+
if (!isTimedOut && abortController) {
|
|
80
|
+
abortController.abort(config.signal!.reason);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
config.signal.addEventListener('abort', onUserAbort, {
|
|
84
|
+
once: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
fetchOptions.signal = abortController.signal;
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
fetchOptions.signal = abortController.signal;
|
|
91
|
+
}
|
|
92
|
+
} else if (config.signal) {
|
|
93
|
+
fetchOptions.signal = config.signal;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const fetchResponse = await fetch(fullURL, fetchOptions);
|
|
98
|
+
|
|
99
|
+
let responseData: unknown;
|
|
100
|
+
const responseType = config.responseType || 'json';
|
|
101
|
+
|
|
102
|
+
const contentLength = fetchResponse.headers.get('content-length');
|
|
103
|
+
if (
|
|
104
|
+
contentLength &&
|
|
105
|
+
config.maxContentLength &&
|
|
106
|
+
parseInt(contentLength, 10) > config.maxContentLength
|
|
107
|
+
) {
|
|
108
|
+
throw new AccessioError(
|
|
109
|
+
`maxContentLength size of ${config.maxContentLength} exceeded`,
|
|
110
|
+
AccessioError.ERR_BAD_RESPONSE,
|
|
111
|
+
config,
|
|
112
|
+
fetchResponse,
|
|
113
|
+
null,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
responseData = await readResponseData(fetchResponse, responseType);
|
|
119
|
+
} catch (readError) {
|
|
120
|
+
throw AccessioError.from(
|
|
121
|
+
readError as Error,
|
|
122
|
+
AccessioError.ERR_BAD_RESPONSE,
|
|
123
|
+
config,
|
|
124
|
+
fetchResponse,
|
|
125
|
+
null,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const responseHeaders = parseHeaders(fetchResponse.headers);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
data: responseData,
|
|
133
|
+
status: fetchResponse.status,
|
|
134
|
+
statusText: fetchResponse.statusText,
|
|
135
|
+
headers: responseHeaders,
|
|
136
|
+
config: config,
|
|
137
|
+
request: fetchResponse,
|
|
138
|
+
duration: Date.now() - requestStartTime,
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof AccessioError) {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
146
|
+
if (isTimedOut) {
|
|
147
|
+
throw new AccessioError(
|
|
148
|
+
`timeout of ${config.timeout}ms exceeded`,
|
|
149
|
+
AccessioError.ETIMEDOUT,
|
|
150
|
+
config,
|
|
151
|
+
null,
|
|
152
|
+
null,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
error instanceof TypeError &&
|
|
160
|
+
(error.message.toLowerCase().includes('url') || error.message.toLowerCase().includes('fetch'))
|
|
161
|
+
) {
|
|
162
|
+
throw new AccessioError(
|
|
163
|
+
`Invalid URL: ${fullURL}`,
|
|
164
|
+
AccessioError.ERR_INVALID_URL,
|
|
165
|
+
config,
|
|
166
|
+
null,
|
|
167
|
+
null,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw AccessioError.from(
|
|
172
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
173
|
+
AccessioError.ERR_NETWORK,
|
|
174
|
+
config,
|
|
175
|
+
null,
|
|
176
|
+
null,
|
|
177
|
+
);
|
|
178
|
+
} finally {
|
|
179
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
180
|
+
if (config.signal && onUserAbort) {
|
|
181
|
+
config.signal.removeEventListener('abort', onUserAbort);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/core/mergeConfig.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AccessioRequestConfig } from '../types';
|
|
2
2
|
|
|
3
3
|
function deepMerge(...sources: any[]): Record<string, any> {
|
|
4
|
-
const result: Record<string, any> =
|
|
4
|
+
const result: Record<string, any> = Object.create(null);
|
|
5
5
|
|
|
6
6
|
for (const source of sources) {
|
|
7
7
|
if (!source || typeof source !== 'object') continue;
|
|
@@ -43,7 +43,7 @@ export default function mergeConfig(
|
|
|
43
43
|
config1: AccessioRequestConfig = {},
|
|
44
44
|
config2: AccessioRequestConfig = {},
|
|
45
45
|
): AccessioRequestConfig {
|
|
46
|
-
const merged: any =
|
|
46
|
+
const merged: any = Object.create(null);
|
|
47
47
|
|
|
48
48
|
const allKeys = new Set<string>([...Object.keys(config1), ...Object.keys(config2)]);
|
|
49
49
|
|
package/src/core/request.ts
CHANGED
|
@@ -1,55 +1,13 @@
|
|
|
1
1
|
import buildURL from './buildURL';
|
|
2
2
|
import AccessioError from './accessioError';
|
|
3
|
-
import parseHeaders from '../helpers/parseHeaders';
|
|
4
3
|
import transformData from '../helpers/transformData';
|
|
5
4
|
import settle from '../helpers/settle';
|
|
5
|
+
import { flattenHeaders, removeContentType, buildFetchHeaders } from '../helpers/flattenHeaders';
|
|
6
|
+
import { setBasicAuth } from '../helpers/auth';
|
|
7
|
+
import fetchAdapter from './fetchAdapter';
|
|
6
8
|
import type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
'common',
|
|
10
|
-
'delete',
|
|
11
|
-
'get',
|
|
12
|
-
'head',
|
|
13
|
-
'options',
|
|
14
|
-
'post',
|
|
15
|
-
'put',
|
|
16
|
-
'patch',
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
type HeadersConfig = Record<string, Record<string, string>>;
|
|
20
|
-
|
|
21
|
-
function flattenHeaders(
|
|
22
|
-
headers: HeadersConfig | undefined,
|
|
23
|
-
method?: string,
|
|
24
|
-
): Record<string, string> {
|
|
25
|
-
if (!headers) return {};
|
|
26
|
-
|
|
27
|
-
const merged: Record<string, string> = {};
|
|
28
|
-
const methodLower = (method || 'get').toLowerCase();
|
|
29
|
-
|
|
30
|
-
if (headers['common']) {
|
|
31
|
-
Object.assign(merged, headers['common']);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (headers[methodLower]) {
|
|
35
|
-
Object.assign(merged, headers[methodLower]);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const key in headers) {
|
|
39
|
-
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
40
|
-
merged[key] = headers[key] as unknown as string;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return merged;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function removeContentType(headers: Record<string, string>): void {
|
|
48
|
-
const key = Object.keys(headers).find((k) => k.toLowerCase() === 'content-type');
|
|
49
|
-
if (key) {
|
|
50
|
-
delete headers[key];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
10
|
+
type HeadersConfig = Record<string, Record<string, string | string[]>>;
|
|
53
11
|
|
|
54
12
|
function buildTransformArray(
|
|
55
13
|
transform: TransformFunction | TransformFunction[] | undefined,
|
|
@@ -59,35 +17,9 @@ function buildTransformArray(
|
|
|
59
17
|
return [transform];
|
|
60
18
|
}
|
|
61
19
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
const bytes = new TextEncoder().encode(credentials);
|
|
73
|
-
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join('');
|
|
74
|
-
encoded = btoa(binString);
|
|
75
|
-
}
|
|
76
|
-
headers['Authorization'] = `Basic ${encoded}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
80
|
-
switch (responseType) {
|
|
81
|
-
case 'arraybuffer': return await fetchResponse.arrayBuffer();
|
|
82
|
-
case 'blob': return await fetchResponse.blob();
|
|
83
|
-
case 'text': return await fetchResponse.text();
|
|
84
|
-
case 'stream': return fetchResponse.body;
|
|
85
|
-
case 'json':
|
|
86
|
-
default: return await fetchResponse.text();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export default function dispatchRequest(config: AccessioRequestConfig): Promise<AccessioResponse> {
|
|
20
|
+
export default async function dispatchRequest(
|
|
21
|
+
config: AccessioRequestConfig,
|
|
22
|
+
): Promise<AccessioResponse> {
|
|
91
23
|
const fullURL =
|
|
92
24
|
config._builtUrl ||
|
|
93
25
|
buildURL(
|
|
@@ -101,7 +33,7 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
101
33
|
|
|
102
34
|
const requestTransforms = buildTransformArray(config.transformRequest);
|
|
103
35
|
|
|
104
|
-
const requestData = transformData(requestTransforms, config.data, flatHeaders, config);
|
|
36
|
+
const requestData = await transformData(requestTransforms, config.data, flatHeaders, config);
|
|
105
37
|
|
|
106
38
|
if (
|
|
107
39
|
requestData === null ||
|
|
@@ -115,7 +47,7 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
115
47
|
|
|
116
48
|
const fetchOptions: RequestInit = {
|
|
117
49
|
method: (config.method || 'GET').toUpperCase(),
|
|
118
|
-
headers: flatHeaders,
|
|
50
|
+
headers: buildFetchHeaders(flatHeaders),
|
|
119
51
|
};
|
|
120
52
|
|
|
121
53
|
const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
@@ -131,124 +63,27 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
131
63
|
fetchOptions.credentials = 'include';
|
|
132
64
|
}
|
|
133
65
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (config.timeout && config.timeout > 0) {
|
|
140
|
-
abortController = new AbortController();
|
|
141
|
-
|
|
142
|
-
timeoutId = setTimeout(() => {
|
|
143
|
-
isTimedOut = true;
|
|
144
|
-
abortController!.abort(
|
|
145
|
-
new AccessioError(
|
|
146
|
-
`timeout of ${config.timeout}ms exceeded`,
|
|
147
|
-
AccessioError.ETIMEDOUT,
|
|
148
|
-
config,
|
|
149
|
-
null,
|
|
150
|
-
null,
|
|
151
|
-
),
|
|
152
|
-
);
|
|
153
|
-
}, config.timeout);
|
|
154
|
-
|
|
155
|
-
if (config.signal) {
|
|
156
|
-
if (typeof AbortSignal.any === 'function') {
|
|
157
|
-
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
158
|
-
} else {
|
|
159
|
-
if (config.signal.aborted) {
|
|
160
|
-
abortController.abort(config.signal.reason);
|
|
161
|
-
} else {
|
|
162
|
-
onUserAbort = () => {
|
|
163
|
-
abortController!.abort(config.signal!.reason);
|
|
164
|
-
};
|
|
165
|
-
config.signal.addEventListener('abort', onUserAbort, {
|
|
166
|
-
once: true,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
fetchOptions.signal = abortController.signal;
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
fetchOptions.signal = abortController.signal;
|
|
173
|
-
}
|
|
174
|
-
} else if (config.signal) {
|
|
175
|
-
fetchOptions.signal = config.signal;
|
|
66
|
+
if (config.dispatcher) {
|
|
67
|
+
(fetchOptions as any).dispatcher = config.dispatcher;
|
|
68
|
+
}
|
|
69
|
+
if (config.agent) {
|
|
70
|
+
(fetchOptions as any).agent = config.agent;
|
|
176
71
|
}
|
|
177
72
|
|
|
178
73
|
const requestStartTime = Date.now();
|
|
179
74
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const responseHeaders = parseHeaders(fetchResponse.headers);
|
|
198
|
-
|
|
199
|
-
const responseTransforms = buildTransformArray(config.transformResponse);
|
|
200
|
-
|
|
201
|
-
responseData = transformData(responseTransforms, responseData, responseHeaders, config);
|
|
202
|
-
|
|
203
|
-
const response: AccessioResponse = {
|
|
204
|
-
data: responseData,
|
|
205
|
-
status: fetchResponse.status,
|
|
206
|
-
statusText: fetchResponse.statusText,
|
|
207
|
-
headers: responseHeaders,
|
|
208
|
-
config: config,
|
|
209
|
-
request: fetchResponse,
|
|
210
|
-
duration: Date.now() - requestStartTime,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return new Promise<AccessioResponse>((resolve, reject) => {
|
|
214
|
-
settle(
|
|
215
|
-
resolve as (value: AccessioResponse) => void,
|
|
216
|
-
reject as (reason: AccessioError) => void,
|
|
217
|
-
response,
|
|
218
|
-
config,
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
})
|
|
222
|
-
.catch((error) => {
|
|
223
|
-
if (error instanceof AccessioError) {
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
228
|
-
if (isTimedOut) {
|
|
229
|
-
throw new AccessioError(
|
|
230
|
-
`timeout of ${config.timeout}ms exceeded`,
|
|
231
|
-
AccessioError.ETIMEDOUT,
|
|
232
|
-
config,
|
|
233
|
-
null,
|
|
234
|
-
null,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
throw AccessioError.from(
|
|
241
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
242
|
-
AccessioError.ERR_NETWORK,
|
|
243
|
-
config,
|
|
244
|
-
null,
|
|
245
|
-
null,
|
|
246
|
-
);
|
|
247
|
-
})
|
|
248
|
-
.finally(() => {
|
|
249
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
250
|
-
if (config.signal && onUserAbort) {
|
|
251
|
-
config.signal.removeEventListener('abort', onUserAbort);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
75
|
+
const response = await fetchAdapter(config, fullURL, fetchOptions, requestStartTime);
|
|
76
|
+
|
|
77
|
+
const responseTransforms = buildTransformArray(config.transformResponse);
|
|
78
|
+
|
|
79
|
+
response.data = await transformData(responseTransforms, response.data, response.headers, config);
|
|
80
|
+
|
|
81
|
+
return new Promise<AccessioResponse>((resolve, reject) => {
|
|
82
|
+
settle(
|
|
83
|
+
resolve as (value: AccessioResponse) => void,
|
|
84
|
+
reject as (reason: AccessioError) => void,
|
|
85
|
+
response,
|
|
86
|
+
config,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
254
89
|
}
|
package/src/core/retry.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export function defaultTransformRequest(
|
|
1
|
+
export function defaultTransformRequest(
|
|
2
|
+
data: unknown,
|
|
3
|
+
headers: Record<string, string | string[]>,
|
|
4
|
+
): unknown {
|
|
2
5
|
if (data === null || data === undefined) {
|
|
3
6
|
return data;
|
|
4
7
|
}
|
|
@@ -23,7 +26,14 @@ export function defaultTransformRequest(data: unknown, headers: Record<string, s
|
|
|
23
26
|
headers['Content-Type'] = 'application/json';
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
|
|
29
|
+
try {
|
|
30
|
+
return JSON.stringify(data);
|
|
31
|
+
} catch (e: any) {
|
|
32
|
+
if (e instanceof TypeError && e.message.toLowerCase().includes('circular')) {
|
|
33
|
+
throw new Error('Accessio: Cannot stringify circular structure in request data');
|
|
34
|
+
}
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
return data;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AccessioRequestConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
export function setBasicAuth(
|
|
4
|
+
config: AccessioRequestConfig,
|
|
5
|
+
headers: Record<string, string | string[]>,
|
|
6
|
+
): void {
|
|
7
|
+
if (!config.auth) return;
|
|
8
|
+
const username = config.auth.username || '';
|
|
9
|
+
const password = config.auth.password || '';
|
|
10
|
+
const credentials = `${username}:${password}`;
|
|
11
|
+
|
|
12
|
+
let encoded: string;
|
|
13
|
+
if (typeof Buffer !== 'undefined') {
|
|
14
|
+
encoded = Buffer.from(credentials).toString('base64');
|
|
15
|
+
} else {
|
|
16
|
+
// Cryptic but effective UTF-8 to Base64 conversion for browsers lacking Buffer.
|
|
17
|
+
// encodeURIComponent converts non-ASCII to %XX, then we replace %XX with raw bytes
|
|
18
|
+
// before applying btoa.
|
|
19
|
+
encoded = btoa(
|
|
20
|
+
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
21
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
headers['Authorization'] = `Basic ${encoded}`;
|
|
26
|
+
}
|