accessio 1.1.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 +1 -1
- package/cjs/core/buildURL.cjs +2 -2
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/request.cjs +39 -9
- 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/rateLimiter.cjs +11 -13
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/package.json +2 -2
- package/src/core/buildURL.ts +2 -2
- package/src/core/request.ts +60 -14
- package/src/core/retry.ts +0 -4
- package/src/defaults/transforms.ts +8 -1
- package/src/helpers/rateLimiter.ts +11 -13
- package/src/types.ts +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Accessio 🎯
|
|
2
2
|
|
|
3
|
-
**Fast, flexible HTTP client
|
|
3
|
+
**Fast, flexible HTTP client — simple, modular, and dependency-free.**
|
|
4
4
|
|
|
5
5
|
`accessio` is a lightweight, modern HTTP client built on top of the native `fetch` API. It provides a familiar, Promise-based interface with advanced features like interceptors, automatic retries, rate limiting, and structured debug logging, all while maintaining **zero external dependencies**.
|
|
6
6
|
|
package/cjs/core/buildURL.cjs
CHANGED
|
@@ -42,7 +42,7 @@ function serializeParams(params, paramsSerializer) {
|
|
|
42
42
|
if (typeof item === "object" && item !== null) {
|
|
43
43
|
encode(`${prefix}[${index}]`, item);
|
|
44
44
|
} else {
|
|
45
|
-
encode(
|
|
45
|
+
encode(prefix, item);
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
} else if (typeof value === "object" && !(value instanceof Date)) {
|
|
@@ -73,7 +73,7 @@ function combineURLs(baseURL, relativeURL) {
|
|
|
73
73
|
return `${base}/${relative}`;
|
|
74
74
|
}
|
|
75
75
|
function isAbsoluteURL(url) {
|
|
76
|
-
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
|
|
76
|
+
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url) || /^([a-z][a-z\d+\-.]*:)/i.test(url);
|
|
77
77
|
}
|
|
78
78
|
function buildURL(url, baseURL, params, paramsSerializer) {
|
|
79
79
|
let fullURL = baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url || "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/buildURL.ts"],"sourcesContent":["import type { ParamsSerializer } from '../types';\n\nfunction serializeParams(\n params: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n if (!params) return '';\n\n if (typeof paramsSerializer === 'function') {\n return paramsSerializer(params);\n }\n\n if (typeof URLSearchParams !== 'undefined' && params instanceof URLSearchParams) {\n return params.toString();\n }\n\n const parts: string[] = [];\n\n function encode(prefix: string, value: unknown): void {\n if (value === null || value === undefined) {\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n if (typeof item === 'object' && item !== null) {\n encode(`${prefix}[${index}]`, item);\n } else {\n encode(
|
|
1
|
+
{"version":3,"sources":["../../src/core/buildURL.ts"],"sourcesContent":["import type { ParamsSerializer } from '../types';\n\nfunction serializeParams(\n params: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n if (!params) return '';\n\n if (typeof paramsSerializer === 'function') {\n return paramsSerializer(params);\n }\n\n if (typeof URLSearchParams !== 'undefined' && params instanceof URLSearchParams) {\n return params.toString();\n }\n\n const parts: string[] = [];\n\n function encode(prefix: string, value: unknown): void {\n if (value === null || value === undefined) {\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n if (typeof item === 'object' && item !== null) {\n encode(`${prefix}[${index}]`, item);\n } else {\n encode(prefix, item);\n }\n });\n } else if (typeof value === 'object' && !(value instanceof Date)) {\n Object.keys(value as Record<string, unknown>).forEach((key) => {\n encode(`${prefix}[${key}]`, (value as Record<string, unknown>)[key]);\n });\n } else {\n const encodedValue = value instanceof Date ? value.toISOString() : value;\n parts.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(encodedValue as string)}`);\n }\n }\n\n Object.keys(params).forEach((key) => {\n encode(key, params[key]);\n });\n\n return parts.join('&');\n}\n\nfunction combineURLs(baseURL: string, relativeURL: string): string {\n if (!baseURL) return relativeURL || '';\n if (!relativeURL) return baseURL;\n\n let base = baseURL;\n while (base.endsWith('/')) {\n base = base.slice(0, -1);\n }\n\n let relative = relativeURL;\n while (relative.startsWith('/')) {\n relative = relative.slice(1);\n }\n\n return `${base}/${relative}`;\n}\n\nfunction isAbsoluteURL(url: string): boolean {\n return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url) || /^([a-z][a-z\\d+\\-.]*:)/i.test(url);\n}\n\nexport default function buildURL(\n url: string,\n baseURL?: string,\n params?: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n let fullURL = baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url || '';\n\n const serialized = serializeParams(params as Record<string, unknown>, paramsSerializer);\n if (serialized) {\n const hashIndex = fullURL.indexOf('#');\n if (hashIndex !== -1) {\n fullURL = fullURL.slice(0, hashIndex);\n }\n fullURL += (fullURL.indexOf('?') === -1 ? '?' : '&') + serialized;\n }\n\n return fullURL;\n}\n\nexport { serializeParams, combineURLs, isAbsoluteURL };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,SAAS,gBACP,QACA,kBACQ;AACR,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,OAAO,qBAAqB,YAAY;AAC1C,WAAO,iBAAiB,MAAM;AAAA,EAChC;AAEA,MAAI,OAAO,oBAAoB,eAAe,kBAAkB,iBAAiB;AAC/E,WAAO,OAAO,SAAS;AAAA,EACzB;AAEA,QAAM,QAAkB,CAAC;AAEzB,WAAS,OAAO,QAAgB,OAAsB;AACpD,QAAI,UAAU,QAAQ,UAAU,QAAW;AACzC;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,YAAM,QAAQ,CAAC,MAAM,UAAU;AAC7B,YAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,iBAAO,GAAG,MAAM,IAAI,KAAK,KAAK,IAAI;AAAA,QACpC,OAAO;AACL,iBAAO,QAAQ,IAAI;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IACH,WAAW,OAAO,UAAU,YAAY,EAAE,iBAAiB,OAAO;AAChE,aAAO,KAAK,KAAgC,EAAE,QAAQ,CAAC,QAAQ;AAC7D,eAAO,GAAG,MAAM,IAAI,GAAG,KAAM,MAAkC,GAAG,CAAC;AAAA,MACrE,CAAC;AAAA,IACH,OAAO;AACL,YAAM,eAAe,iBAAiB,OAAO,MAAM,YAAY,IAAI;AACnE,YAAM,KAAK,GAAG,mBAAmB,MAAM,CAAC,IAAI,mBAAmB,YAAsB,CAAC,EAAE;AAAA,IAC1F;AAAA,EACF;AAEA,SAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,QAAQ;AACnC,WAAO,KAAK,OAAO,GAAG,CAAC;AAAA,EACzB,CAAC;AAED,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAS,YAAY,SAAiB,aAA6B;AACjE,MAAI,CAAC,QAAS,QAAO,eAAe;AACpC,MAAI,CAAC,YAAa,QAAO;AAEzB,MAAI,OAAO;AACX,SAAO,KAAK,SAAS,GAAG,GAAG;AACzB,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AAEA,MAAI,WAAW;AACf,SAAO,SAAS,WAAW,GAAG,GAAG;AAC/B,eAAW,SAAS,MAAM,CAAC;AAAA,EAC7B;AAEA,SAAO,GAAG,IAAI,IAAI,QAAQ;AAC5B;AAEA,SAAS,cAAc,KAAsB;AAC3C,SAAO,8BAA8B,KAAK,GAAG,KAAK,yBAAyB,KAAK,GAAG;AACrF;AAEe,SAAR,SACL,KACA,SACA,QACA,kBACQ;AACR,MAAI,UAAU,WAAW,CAAC,cAAc,GAAG,IAAI,YAAY,SAAS,GAAG,IAAI,OAAO;AAElF,QAAM,aAAa,gBAAgB,QAAmC,gBAAgB;AACtF,MAAI,YAAY;AACd,UAAM,YAAY,QAAQ,QAAQ,GAAG;AACrC,QAAI,cAAc,IAAI;AACpB,gBAAU,QAAQ,MAAM,GAAG,SAAS;AAAA,IACtC;AACA,gBAAY,QAAQ,QAAQ,GAAG,MAAM,KAAK,MAAM,OAAO;AAAA,EACzD;AAEA,SAAO;AACT;","names":[]}
|
package/cjs/core/request.cjs
CHANGED
|
@@ -64,8 +64,8 @@ function flattenHeaders(headers, method) {
|
|
|
64
64
|
return merged;
|
|
65
65
|
}
|
|
66
66
|
function removeContentType(headers) {
|
|
67
|
-
const
|
|
68
|
-
|
|
67
|
+
const keys = Object.keys(headers).filter((k) => k.toLowerCase() === "content-type");
|
|
68
|
+
for (const key of keys) {
|
|
69
69
|
delete headers[key];
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -83,9 +83,11 @@ function setBasicAuth(config, headers) {
|
|
|
83
83
|
if (typeof Buffer !== "undefined") {
|
|
84
84
|
encoded = Buffer.from(credentials).toString("base64");
|
|
85
85
|
} else {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
encoded = btoa(
|
|
87
|
+
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
88
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
89
|
+
})
|
|
90
|
+
);
|
|
89
91
|
}
|
|
90
92
|
headers["Authorization"] = `Basic ${encoded}`;
|
|
91
93
|
}
|
|
@@ -129,24 +131,31 @@ function dispatchRequest(config) {
|
|
|
129
131
|
if (config.withCredentials) {
|
|
130
132
|
fetchOptions.credentials = "include";
|
|
131
133
|
}
|
|
134
|
+
if (config.dispatcher) {
|
|
135
|
+
fetchOptions.dispatcher = config.dispatcher;
|
|
136
|
+
}
|
|
137
|
+
if (config.agent) {
|
|
138
|
+
fetchOptions.agent = config.agent;
|
|
139
|
+
}
|
|
132
140
|
let abortController = null;
|
|
133
141
|
let timeoutId = null;
|
|
134
142
|
let isTimedOut = false;
|
|
135
143
|
let onUserAbort = null;
|
|
136
|
-
|
|
144
|
+
const timeoutValue = Number(config.timeout);
|
|
145
|
+
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
137
146
|
abortController = new AbortController();
|
|
138
147
|
timeoutId = setTimeout(() => {
|
|
139
148
|
isTimedOut = true;
|
|
140
149
|
abortController.abort(
|
|
141
150
|
new import_accessioError.default(
|
|
142
|
-
`timeout of ${
|
|
151
|
+
`timeout of ${timeoutValue}ms exceeded`,
|
|
143
152
|
import_accessioError.default.ETIMEDOUT,
|
|
144
153
|
config,
|
|
145
154
|
null,
|
|
146
155
|
null
|
|
147
156
|
)
|
|
148
157
|
);
|
|
149
|
-
},
|
|
158
|
+
}, timeoutValue);
|
|
150
159
|
if (config.signal) {
|
|
151
160
|
if (typeof AbortSignal.any === "function") {
|
|
152
161
|
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
@@ -155,7 +164,9 @@ function dispatchRequest(config) {
|
|
|
155
164
|
abortController.abort(config.signal.reason);
|
|
156
165
|
} else {
|
|
157
166
|
onUserAbort = () => {
|
|
158
|
-
abortController
|
|
167
|
+
if (!isTimedOut && abortController) {
|
|
168
|
+
abortController.abort(config.signal.reason);
|
|
169
|
+
}
|
|
159
170
|
};
|
|
160
171
|
config.signal.addEventListener("abort", onUserAbort, {
|
|
161
172
|
once: true
|
|
@@ -173,6 +184,16 @@ function dispatchRequest(config) {
|
|
|
173
184
|
return fetch(fullURL, fetchOptions).then(async (fetchResponse) => {
|
|
174
185
|
let responseData;
|
|
175
186
|
const responseType = config.responseType || "json";
|
|
187
|
+
const contentLength = fetchResponse.headers.get("content-length");
|
|
188
|
+
if (contentLength && config.maxContentLength && parseInt(contentLength, 10) > config.maxContentLength) {
|
|
189
|
+
throw new import_accessioError.default(
|
|
190
|
+
`maxContentLength size of ${config.maxContentLength} exceeded`,
|
|
191
|
+
import_accessioError.default.ERR_BAD_RESPONSE,
|
|
192
|
+
config,
|
|
193
|
+
fetchResponse,
|
|
194
|
+
null
|
|
195
|
+
);
|
|
196
|
+
}
|
|
176
197
|
try {
|
|
177
198
|
responseData = await readResponseData(fetchResponse, responseType);
|
|
178
199
|
} catch (readError) {
|
|
@@ -220,6 +241,15 @@ function dispatchRequest(config) {
|
|
|
220
241
|
}
|
|
221
242
|
throw new import_accessioError.default("Request aborted", import_accessioError.default.ERR_CANCELED, config, null, null);
|
|
222
243
|
}
|
|
244
|
+
if (error instanceof TypeError && (error.message.toLowerCase().includes("url") || error.message.toLowerCase().includes("fetch"))) {
|
|
245
|
+
throw new import_accessioError.default(
|
|
246
|
+
`Invalid URL: ${fullURL}`,
|
|
247
|
+
import_accessioError.default.ERR_INVALID_URL,
|
|
248
|
+
config,
|
|
249
|
+
null,
|
|
250
|
+
null
|
|
251
|
+
);
|
|
252
|
+
}
|
|
223
253
|
throw import_accessioError.default.from(
|
|
224
254
|
error instanceof Error ? error : new Error(String(error)),
|
|
225
255
|
import_accessioError.default.ERR_NETWORK,
|
package/cjs/core/request.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/request.ts"],"sourcesContent":["import buildURL from './buildURL';\nimport AccessioError from './accessioError';\nimport parseHeaders from '../helpers/parseHeaders';\nimport transformData from '../helpers/transformData';\nimport settle from '../helpers/settle';\nimport type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';\n\nconst METHOD_KEYS = new Set<string>([\n 'common',\n 'delete',\n 'get',\n 'head',\n 'options',\n 'post',\n 'put',\n 'patch',\n]);\n\ntype HeadersConfig = Record<string, Record<string, string>>;\n\nfunction flattenHeaders(\n headers: HeadersConfig | undefined,\n method?: string,\n): Record<string, string> {\n if (!headers) return {};\n\n const merged: Record<string, string> = {};\n const methodLower = (method || 'get').toLowerCase();\n\n if (headers['common']) {\n Object.assign(merged, headers['common']);\n }\n\n if (headers[methodLower]) {\n Object.assign(merged, headers[methodLower]);\n }\n\n for (const key in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {\n merged[key] = headers[key] as unknown as string;\n }\n }\n\n return merged;\n}\n\nfunction removeContentType(headers: Record<string, string>): void {\n const key = Object.keys(headers).find((k) => k.toLowerCase() === 'content-type');\n if (key) {\n delete headers[key];\n }\n}\n\nfunction buildTransformArray(\n transform: TransformFunction | TransformFunction[] | undefined,\n): TransformFunction[] {\n if (!transform) return [];\n if (Array.isArray(transform)) return transform;\n return [transform];\n}\n\nfunction setBasicAuth(config: AccessioRequestConfig, headers: Record<string, string>): void {\n if (!config.auth) return;\n const username = config.auth.username || '';\n const password = config.auth.password || '';\n const credentials = `${username}:${password}`;\n\n let encoded: string;\n if (typeof Buffer !== 'undefined') {\n encoded = Buffer.from(credentials).toString('base64');\n } else {\n const bytes = new TextEncoder().encode(credentials);\n const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join('');\n encoded = btoa(binString);\n }\n headers['Authorization'] = `Basic ${encoded}`;\n}\n\nasync function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {\n switch (responseType) {\n case 'arraybuffer': return await fetchResponse.arrayBuffer();\n case 'blob': return await fetchResponse.blob();\n case 'text': return await fetchResponse.text();\n case 'stream': return fetchResponse.body;\n case 'json':\n default: return await fetchResponse.text();\n }\n}\n\nexport default function dispatchRequest(config: AccessioRequestConfig): Promise<AccessioResponse> {\n const fullURL =\n config._builtUrl ||\n buildURL(\n config.url ?? '',\n config.baseURL,\n config.params as Record<string, unknown> | undefined,\n config.paramsSerializer,\n );\n\n const flatHeaders = flattenHeaders(config.headers as HeadersConfig | undefined, config.method);\n\n const requestTransforms = buildTransformArray(config.transformRequest);\n\n const requestData = transformData(requestTransforms, config.data, flatHeaders, config);\n\n if (\n requestData === null ||\n requestData === undefined ||\n (typeof FormData !== 'undefined' && requestData instanceof FormData)\n ) {\n removeContentType(flatHeaders);\n }\n\n setBasicAuth(config, flatHeaders);\n\n const fetchOptions: RequestInit = {\n method: (config.method || 'GET').toUpperCase(),\n headers: flatHeaders,\n };\n\n const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];\n if (\n methodsWithBody.includes(fetchOptions.method!) &&\n requestData !== undefined &&\n requestData !== null\n ) {\n fetchOptions.body = requestData as BodyInit;\n }\n\n if (config.withCredentials) {\n fetchOptions.credentials = 'include';\n }\n\n let abortController: AbortController | null = null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let isTimedOut = false;\n let onUserAbort: (() => void) | null = null;\n\n if (config.timeout && config.timeout > 0) {\n abortController = new AbortController();\n\n timeoutId = setTimeout(() => {\n isTimedOut = true;\n abortController!.abort(\n new AccessioError(\n `timeout of ${config.timeout}ms exceeded`,\n AccessioError.ETIMEDOUT,\n config,\n null,\n null,\n ),\n );\n }, config.timeout);\n\n if (config.signal) {\n if (typeof AbortSignal.any === 'function') {\n fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);\n } else {\n if (config.signal.aborted) {\n abortController.abort(config.signal.reason);\n } else {\n onUserAbort = () => {\n abortController!.abort(config.signal!.reason);\n };\n config.signal.addEventListener('abort', onUserAbort, {\n once: true,\n });\n }\n fetchOptions.signal = abortController.signal;\n }\n } else {\n fetchOptions.signal = abortController.signal;\n }\n } else if (config.signal) {\n fetchOptions.signal = config.signal;\n }\n\n const requestStartTime = Date.now();\n\n return fetch(fullURL, fetchOptions)\n .then(async (fetchResponse) => {\n let responseData: unknown;\n const responseType = config.responseType || 'json';\n\n try {\n responseData = await readResponseData(fetchResponse, responseType);\n } catch (readError) {\n throw AccessioError.from(\n readError as Error,\n AccessioError.ERR_BAD_RESPONSE,\n config,\n fetchResponse,\n null,\n );\n }\n\n const responseHeaders = parseHeaders(fetchResponse.headers);\n\n const responseTransforms = buildTransformArray(config.transformResponse);\n\n responseData = transformData(responseTransforms, responseData, responseHeaders, config);\n\n const response: AccessioResponse = {\n data: responseData,\n status: fetchResponse.status,\n statusText: fetchResponse.statusText,\n headers: responseHeaders,\n config: config,\n request: fetchResponse,\n duration: Date.now() - requestStartTime,\n };\n\n return new Promise<AccessioResponse>((resolve, reject) => {\n settle(\n resolve as (value: AccessioResponse) => void,\n reject as (reason: AccessioError) => void,\n response,\n config,\n );\n });\n })\n .catch((error) => {\n if (error instanceof AccessioError) {\n throw error;\n }\n\n if (error instanceof Error && error.name === 'AbortError') {\n if (isTimedOut) {\n throw new AccessioError(\n `timeout of ${config.timeout}ms exceeded`,\n AccessioError.ETIMEDOUT,\n config,\n null,\n null,\n );\n }\n throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);\n }\n\n throw AccessioError.from(\n error instanceof Error ? error : new Error(String(error)),\n AccessioError.ERR_NETWORK,\n config,\n null,\n null,\n );\n })\n .finally(() => {\n if (timeoutId) clearTimeout(timeoutId);\n if (config.signal && onUserAbort) {\n config.signal.removeEventListener('abort', onUserAbort);\n }\n });\n}\n"],"mappings":"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","names":["buildURL","transformData","AccessioError","parseHeaders","settle"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/request.ts"],"sourcesContent":["import buildURL from './buildURL';\nimport AccessioError from './accessioError';\nimport parseHeaders from '../helpers/parseHeaders';\nimport transformData from '../helpers/transformData';\nimport settle from '../helpers/settle';\nimport type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';\n\nconst METHOD_KEYS = new Set<string>([\n 'common',\n 'delete',\n 'get',\n 'head',\n 'options',\n 'post',\n 'put',\n 'patch',\n]);\n\ntype HeadersConfig = Record<string, Record<string, string>>;\n\nfunction flattenHeaders(\n headers: HeadersConfig | undefined,\n method?: string,\n): Record<string, string> {\n if (!headers) return {};\n\n const merged: Record<string, string> = {};\n const methodLower = (method || 'get').toLowerCase();\n\n if (headers['common']) {\n Object.assign(merged, headers['common']);\n }\n\n if (headers[methodLower]) {\n Object.assign(merged, headers[methodLower]);\n }\n\n for (const key in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {\n merged[key] = headers[key] as unknown as string;\n }\n }\n\n return merged;\n}\n\nfunction removeContentType(headers: Record<string, string>): void {\n const keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');\n for (const key of keys) {\n delete headers[key];\n }\n}\n\nfunction buildTransformArray(\n transform: TransformFunction | TransformFunction[] | undefined,\n): TransformFunction[] {\n if (!transform) return [];\n if (Array.isArray(transform)) return transform;\n return [transform];\n}\n\nfunction setBasicAuth(config: AccessioRequestConfig, headers: Record<string, string>): void {\n if (!config.auth) return;\n const username = config.auth.username || '';\n const password = config.auth.password || '';\n const credentials = `${username}:${password}`;\n\n let encoded: string;\n if (typeof Buffer !== 'undefined') {\n encoded = Buffer.from(credentials).toString('base64');\n } else {\n encoded = btoa(\n encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {\n return String.fromCharCode(parseInt(p1, 16));\n }),\n );\n }\n headers['Authorization'] = `Basic ${encoded}`;\n}\n\nasync function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {\n switch (responseType) {\n case 'arraybuffer':\n return await fetchResponse.arrayBuffer();\n case 'blob':\n return await fetchResponse.blob();\n case 'text':\n return await fetchResponse.text();\n case 'stream':\n return fetchResponse.body;\n case 'json':\n default:\n return await fetchResponse.text();\n }\n}\n\nexport default function dispatchRequest(config: AccessioRequestConfig): Promise<AccessioResponse> {\n const fullURL =\n config._builtUrl ||\n buildURL(\n config.url ?? '',\n config.baseURL,\n config.params as Record<string, unknown> | undefined,\n config.paramsSerializer,\n );\n\n const flatHeaders = flattenHeaders(config.headers as HeadersConfig | undefined, config.method);\n\n const requestTransforms = buildTransformArray(config.transformRequest);\n\n const requestData = transformData(requestTransforms, config.data, flatHeaders, config);\n\n if (\n requestData === null ||\n requestData === undefined ||\n (typeof FormData !== 'undefined' && requestData instanceof FormData)\n ) {\n removeContentType(flatHeaders);\n }\n\n setBasicAuth(config, flatHeaders);\n\n const fetchOptions: RequestInit = {\n method: (config.method || 'GET').toUpperCase(),\n headers: flatHeaders,\n };\n\n const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];\n if (\n methodsWithBody.includes(fetchOptions.method!) &&\n requestData !== undefined &&\n requestData !== null\n ) {\n fetchOptions.body = requestData as BodyInit;\n }\n\n if (config.withCredentials) {\n fetchOptions.credentials = 'include';\n }\n\n if (config.dispatcher) {\n (fetchOptions as any).dispatcher = config.dispatcher;\n }\n if (config.agent) {\n (fetchOptions as any).agent = config.agent;\n }\n\n let abortController: AbortController | null = null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let isTimedOut = false;\n let onUserAbort: (() => void) | null = null;\n\n const timeoutValue = Number(config.timeout);\n if (!isNaN(timeoutValue) && timeoutValue > 0) {\n abortController = new AbortController();\n\n timeoutId = setTimeout(() => {\n isTimedOut = true;\n abortController!.abort(\n new AccessioError(\n `timeout of ${timeoutValue}ms exceeded`,\n AccessioError.ETIMEDOUT,\n config,\n null,\n null,\n ),\n );\n }, timeoutValue);\n\n if (config.signal) {\n if (typeof AbortSignal.any === 'function') {\n fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);\n } else {\n if (config.signal.aborted) {\n abortController.abort(config.signal.reason);\n } else {\n onUserAbort = () => {\n if (!isTimedOut && abortController) {\n abortController.abort(config.signal!.reason);\n }\n };\n config.signal.addEventListener('abort', onUserAbort, {\n once: true,\n });\n }\n fetchOptions.signal = abortController.signal;\n }\n } else {\n fetchOptions.signal = abortController.signal;\n }\n } else if (config.signal) {\n fetchOptions.signal = config.signal;\n }\n\n const requestStartTime = Date.now();\n\n return fetch(fullURL, fetchOptions)\n .then(async (fetchResponse) => {\n let responseData: unknown;\n const responseType = config.responseType || 'json';\n\n const contentLength = fetchResponse.headers.get('content-length');\n if (\n contentLength &&\n config.maxContentLength &&\n parseInt(contentLength, 10) > config.maxContentLength\n ) {\n throw new AccessioError(\n `maxContentLength size of ${config.maxContentLength} exceeded`,\n AccessioError.ERR_BAD_RESPONSE,\n config,\n fetchResponse,\n null,\n );\n }\n\n try {\n responseData = await readResponseData(fetchResponse, responseType);\n } catch (readError) {\n throw AccessioError.from(\n readError as Error,\n AccessioError.ERR_BAD_RESPONSE,\n config,\n fetchResponse,\n null,\n );\n }\n\n const responseHeaders = parseHeaders(fetchResponse.headers);\n\n const responseTransforms = buildTransformArray(config.transformResponse);\n\n responseData = transformData(responseTransforms, responseData, responseHeaders, config);\n\n const response: AccessioResponse = {\n data: responseData,\n status: fetchResponse.status,\n statusText: fetchResponse.statusText,\n headers: responseHeaders,\n config: config,\n request: fetchResponse,\n duration: Date.now() - requestStartTime,\n };\n\n return new Promise<AccessioResponse>((resolve, reject) => {\n settle(\n resolve as (value: AccessioResponse) => void,\n reject as (reason: AccessioError) => void,\n response,\n config,\n );\n });\n })\n .catch((error) => {\n if (error instanceof AccessioError) {\n throw error;\n }\n\n if (error instanceof Error && error.name === 'AbortError') {\n if (isTimedOut) {\n throw new AccessioError(\n `timeout of ${config.timeout}ms exceeded`,\n AccessioError.ETIMEDOUT,\n config,\n null,\n null,\n );\n }\n throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);\n }\n\n if (\n error instanceof TypeError &&\n (error.message.toLowerCase().includes('url') ||\n error.message.toLowerCase().includes('fetch'))\n ) {\n throw new AccessioError(\n `Invalid URL: ${fullURL}`,\n AccessioError.ERR_INVALID_URL,\n config,\n null,\n null,\n );\n }\n\n throw AccessioError.from(\n error instanceof Error ? error : new Error(String(error)),\n AccessioError.ERR_NETWORK,\n config,\n null,\n null,\n );\n })\n .finally(() => {\n if (timeoutId) clearTimeout(timeoutId);\n if (config.signal && onUserAbort) {\n config.signal.removeEventListener('abort', onUserAbort);\n }\n });\n}\n"],"mappings":"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","names":["buildURL","transformData","AccessioError","parseHeaders","settle"]}
|
package/cjs/core/retry.cjs
CHANGED
|
@@ -31,9 +31,6 @@ function defaultRetryCondition(error) {
|
|
|
31
31
|
if (error.code === import_errorCodes.ERR_NETWORK) {
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
|
-
if (error.code === import_errorCodes.ETIMEDOUT) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
34
|
if (error.response && error.response.status >= 500) {
|
|
38
35
|
return true;
|
|
39
36
|
}
|
package/cjs/core/retry.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/retry.ts"],"sourcesContent":["import { ERR_CANCELED, ERR_NETWORK, ETIMEDOUT } from '../constants/errorCodes';\nimport type {\n AccessioRequestConfig,\n AccessioResponse,\n AccessioError,\n RetryConditionFunction,\n OnRetryFunction,\n} from '../types';\n\nfunction defaultRetryCondition(error: any): boolean {\n if (error.code === ERR_CANCELED) {\n return false;\n }\n\n if (error.code === ERR_NETWORK) {\n return true;\n }\n\n if (error.
|
|
1
|
+
{"version":3,"sources":["../../src/core/retry.ts"],"sourcesContent":["import { ERR_CANCELED, ERR_NETWORK, ETIMEDOUT } from '../constants/errorCodes';\nimport type {\n AccessioRequestConfig,\n AccessioResponse,\n AccessioError,\n RetryConditionFunction,\n OnRetryFunction,\n} from '../types';\n\nfunction defaultRetryCondition(error: any): boolean {\n if (error.code === ERR_CANCELED) {\n return false;\n }\n\n if (error.code === ERR_NETWORK) {\n return true;\n }\n\n if (error.response && error.response.status >= 500) {\n return true;\n }\n\n return false;\n}\n\nfunction calculateDelay(attempt: number, baseDelay: number): number {\n const exponentialDelay = baseDelay * Math.pow(2, attempt);\n const jitter = exponentialDelay * 0.25 * (Math.random() * 2 - 1);\n return Math.round(exponentialDelay + jitter);\n}\n\nfunction sleep(ms: number, options?: { signal?: AbortSignal }): Promise<void> {\n return new Promise((resolve, reject) => {\n let onAbort: (() => void) | undefined;\n\n const timeoutId = setTimeout(() => {\n if (options?.signal && onAbort) {\n options.signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (options?.signal) {\n if (options.signal.aborted) {\n clearTimeout(timeoutId);\n return reject(options.signal.reason || new Error('Sleep aborted'));\n }\n\n onAbort = () => {\n clearTimeout(timeoutId);\n reject(options.signal!.reason || new Error('Sleep aborted'));\n };\n\n options.signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\nasync function retryRequest(\n dispatchFn: (config: AccessioRequestConfig) => Promise<any>,\n config: AccessioRequestConfig,\n): Promise<any> {\n const maxRetries = config.retry ?? 0;\n\n if (maxRetries <= 0) {\n return dispatchFn(config);\n }\n\n const retryDelay = config.retryDelay ?? 1000;\n const retryCondition: RetryConditionFunction = config.retryCondition ?? defaultRetryCondition;\n\n let lastError: any;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await dispatchFn(config);\n return response;\n } catch (error) {\n lastError = error;\n\n const isLastAttempt = attempt >= maxRetries;\n const shouldRetry = !isLastAttempt && retryCondition(error as AccessioError);\n\n if (!shouldRetry) {\n throw error;\n }\n\n const delay = calculateDelay(attempt, retryDelay);\n\n if (typeof config.onRetry === 'function') {\n (config.onRetry as OnRetryFunction)(attempt + 1, error as AccessioError, config);\n }\n\n await sleep(delay, { signal: config.signal });\n }\n }\n\n throw lastError;\n}\n\nexport { defaultRetryCondition, calculateDelay };\nexport default retryRequest;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAqD;AASrD,SAAS,sBAAsB,OAAqB;AAClD,MAAI,MAAM,SAAS,gCAAc;AAC/B,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,SAAS,+BAAa;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,YAAY,MAAM,SAAS,UAAU,KAAK;AAClD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,SAAiB,WAA2B;AAClE,QAAM,mBAAmB,YAAY,KAAK,IAAI,GAAG,OAAO;AACxD,QAAM,SAAS,mBAAmB,QAAQ,KAAK,OAAO,IAAI,IAAI;AAC9D,SAAO,KAAK,MAAM,mBAAmB,MAAM;AAC7C;AAEA,SAAS,MAAM,IAAY,SAAmD;AAC5E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI;AAEJ,UAAM,YAAY,WAAW,MAAM;AACjC,UAAI,SAAS,UAAU,SAAS;AAC9B,gBAAQ,OAAO,oBAAoB,SAAS,OAAO;AAAA,MACrD;AACA,cAAQ;AAAA,IACV,GAAG,EAAE;AAEL,QAAI,SAAS,QAAQ;AACnB,UAAI,QAAQ,OAAO,SAAS;AAC1B,qBAAa,SAAS;AACtB,eAAO,OAAO,QAAQ,OAAO,UAAU,IAAI,MAAM,eAAe,CAAC;AAAA,MACnE;AAEA,gBAAU,MAAM;AACd,qBAAa,SAAS;AACtB,eAAO,QAAQ,OAAQ,UAAU,IAAI,MAAM,eAAe,CAAC;AAAA,MAC7D;AAEA,cAAQ,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,IAClE;AAAA,EACF,CAAC;AACH;AAEA,eAAe,aACb,YACA,QACc;AACd,QAAM,aAAa,OAAO,SAAS;AAEnC,MAAI,cAAc,GAAG;AACnB,WAAO,WAAW,MAAM;AAAA,EAC1B;AAEA,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,iBAAyC,OAAO,kBAAkB;AAExE,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,MAAM;AACxC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,kBAAY;AAEZ,YAAM,gBAAgB,WAAW;AACjC,YAAM,cAAc,CAAC,iBAAiB,eAAe,KAAsB;AAE3E,UAAI,CAAC,aAAa;AAChB,cAAM;AAAA,MACR;AAEA,YAAM,QAAQ,eAAe,SAAS,UAAU;AAEhD,UAAI,OAAO,OAAO,YAAY,YAAY;AACxC,QAAC,OAAO,QAA4B,UAAU,GAAG,OAAwB,MAAM;AAAA,MACjF;AAEA,YAAM,MAAM,OAAO,EAAE,QAAQ,OAAO,OAAO,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,QAAM;AACR;AAGA,IAAO,gBAAQ;","names":[]}
|
|
@@ -38,7 +38,14 @@ function defaultTransformRequest(data, headers) {
|
|
|
38
38
|
headers["Content-Type"] = "application/json";
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
try {
|
|
42
|
+
return JSON.stringify(data);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e instanceof TypeError && e.message.toLowerCase().includes("circular")) {
|
|
45
|
+
throw new Error("Accessio: Cannot stringify circular structure in request data");
|
|
46
|
+
}
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
return data;
|
|
44
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/defaults/transforms.ts"],"sourcesContent":["export function defaultTransformRequest(data: unknown, headers: Record<string, string>): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n if (\n typeof data === 'string' ||\n data instanceof ArrayBuffer ||\n (typeof Blob !== 'undefined' && data instanceof Blob) ||\n (typeof FormData !== 'undefined' && data instanceof FormData) ||\n (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) ||\n (typeof ReadableStream !== 'undefined' && data instanceof ReadableStream)\n ) {\n return data;\n }\n\n if (typeof data === 'object') {\n if (headers && typeof headers === 'object') {\n const hasContentType = Object.keys(headers).some(\n (key) => key.toLowerCase() === 'content-type',\n );\n if (!hasContentType) {\n headers['Content-Type'] = 'application/json';\n }\n }\n return JSON.stringify(data);\n }\n\n return data;\n}\n\nexport function defaultTransformResponse(data: unknown): unknown {\n if (typeof data === 'string') {\n try {\n return JSON.parse(data);\n } catch {\n // Not JSON — return as-is\n }\n }\n return data;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,wBAAwB,MAAe,SAA0C;AAC/F,MAAI,SAAS,QAAQ,SAAS,QAAW;AACvC,WAAO;AAAA,EACT;AAEA,MACE,OAAO,SAAS,YAChB,gBAAgB,eACf,OAAO,SAAS,eAAe,gBAAgB,QAC/C,OAAO,aAAa,eAAe,gBAAgB,YACnD,OAAO,oBAAoB,eAAe,gBAAgB,mBAC1D,OAAO,mBAAmB,eAAe,gBAAgB,gBAC1D;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,QAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,YAAM,iBAAiB,OAAO,KAAK,OAAO,EAAE;AAAA,QAC1C,CAAC,QAAQ,IAAI,YAAY,MAAM;AAAA,MACjC;AACA,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AACA,
|
|
1
|
+
{"version":3,"sources":["../../src/defaults/transforms.ts"],"sourcesContent":["export function defaultTransformRequest(data: unknown, headers: Record<string, string>): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n if (\n typeof data === 'string' ||\n data instanceof ArrayBuffer ||\n (typeof Blob !== 'undefined' && data instanceof Blob) ||\n (typeof FormData !== 'undefined' && data instanceof FormData) ||\n (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) ||\n (typeof ReadableStream !== 'undefined' && data instanceof ReadableStream)\n ) {\n return data;\n }\n\n if (typeof data === 'object') {\n if (headers && typeof headers === 'object') {\n const hasContentType = Object.keys(headers).some(\n (key) => key.toLowerCase() === 'content-type',\n );\n if (!hasContentType) {\n headers['Content-Type'] = 'application/json';\n }\n }\n try {\n return JSON.stringify(data);\n } catch (e: any) {\n if (e instanceof TypeError && e.message.toLowerCase().includes('circular')) {\n throw new Error('Accessio: Cannot stringify circular structure in request data');\n }\n throw e;\n }\n }\n\n return data;\n}\n\nexport function defaultTransformResponse(data: unknown): unknown {\n if (typeof data === 'string') {\n try {\n return JSON.parse(data);\n } catch {\n // Not JSON — return as-is\n }\n }\n return data;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,wBAAwB,MAAe,SAA0C;AAC/F,MAAI,SAAS,QAAQ,SAAS,QAAW;AACvC,WAAO;AAAA,EACT;AAEA,MACE,OAAO,SAAS,YAChB,gBAAgB,eACf,OAAO,SAAS,eAAe,gBAAgB,QAC/C,OAAO,aAAa,eAAe,gBAAgB,YACnD,OAAO,oBAAoB,eAAe,gBAAgB,mBAC1D,OAAO,mBAAmB,eAAe,gBAAgB,gBAC1D;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,QAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,YAAM,iBAAiB,OAAO,KAAK,OAAO,EAAE;AAAA,QAC1C,CAAC,QAAQ,IAAI,YAAY,MAAM;AAAA,MACjC;AACA,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AACA,QAAI;AACF,aAAO,KAAK,UAAU,IAAI;AAAA,IAC5B,SAAS,GAAQ;AACf,UAAI,aAAa,aAAa,EAAE,QAAQ,YAAY,EAAE,SAAS,UAAU,GAAG;AAC1E,cAAM,IAAI,MAAM,+DAA+D;AAAA,MACjF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,yBAAyB,MAAwB;AAC/D,MAAI,OAAO,SAAS,UAAU;AAC5B,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -36,9 +36,7 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
36
36
|
}
|
|
37
37
|
let active = 0;
|
|
38
38
|
let destroyed = false;
|
|
39
|
-
|
|
40
|
-
let tailIndex = 0;
|
|
41
|
-
const queue = {};
|
|
39
|
+
const queue = [];
|
|
42
40
|
function acquire() {
|
|
43
41
|
if (destroyed) {
|
|
44
42
|
return Promise.reject(new Error("[Accessio] Rate limiter has been destroyed"));
|
|
@@ -47,30 +45,30 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
47
45
|
active++;
|
|
48
46
|
return Promise.resolve();
|
|
49
47
|
}
|
|
50
|
-
if (
|
|
51
|
-
return Promise.reject(
|
|
48
|
+
if (queue.length >= maxQueueSize) {
|
|
49
|
+
return Promise.reject(
|
|
50
|
+
new Error(`[Accessio] Rate limiter queue size exceeded maxQueueSize (${maxQueueSize})`)
|
|
51
|
+
);
|
|
52
52
|
}
|
|
53
53
|
return new Promise((resolve, reject) => {
|
|
54
|
-
queue
|
|
54
|
+
queue.push({ resolve, reject });
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
function release() {
|
|
58
58
|
if (destroyed) return;
|
|
59
59
|
if (active <= 0) return;
|
|
60
60
|
active--;
|
|
61
|
-
if (
|
|
61
|
+
if (queue.length > 0 && active < maxConcurrent) {
|
|
62
62
|
active++;
|
|
63
|
-
const next = queue
|
|
64
|
-
delete queue[headIndex++];
|
|
63
|
+
const next = queue.shift();
|
|
65
64
|
next?.resolve();
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
function destroy() {
|
|
69
68
|
destroyed = true;
|
|
70
69
|
const reason = new Error("[Accessio] Rate limiter destroyed \u2014 pending request cancelled");
|
|
71
|
-
while (
|
|
72
|
-
const next = queue
|
|
73
|
-
delete queue[headIndex++];
|
|
70
|
+
while (queue.length > 0) {
|
|
71
|
+
const next = queue.shift();
|
|
74
72
|
next?.reject(reason);
|
|
75
73
|
}
|
|
76
74
|
}
|
|
@@ -79,7 +77,7 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
79
77
|
release,
|
|
80
78
|
destroy,
|
|
81
79
|
get pending() {
|
|
82
|
-
return
|
|
80
|
+
return queue.length;
|
|
83
81
|
},
|
|
84
82
|
get active() {
|
|
85
83
|
return active;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/rateLimiter.ts"],"sourcesContent":["import type { RateLimiter, AccessioRequestConfig, AccessioResponse } from '../types';\n\ninterface QueueItem {\n resolve: () => void;\n reject: (reason: Error) => void;\n}\n\nexport function createRateLimiter(\n maxConcurrent: number = Infinity,\n maxQueueSize: number = Infinity,\n): RateLimiter {\n if (maxConcurrent !== Infinity && (!Number.isInteger(maxConcurrent) || maxConcurrent < 1)) {\n throw new RangeError(\n `[Accessio] maxConcurrent must be a positive integer or Infinity, got: ${maxConcurrent}`,\n );\n }\n if (maxQueueSize !== Infinity && (!Number.isInteger(maxQueueSize) || maxQueueSize < 1)) {\n throw new RangeError(\n `[Accessio] maxQueueSize must be a positive integer or Infinity, got: ${maxQueueSize}`,\n );\n }\n let active = 0;\n let destroyed = false;\n
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/rateLimiter.ts"],"sourcesContent":["import type { RateLimiter, AccessioRequestConfig, AccessioResponse } from '../types';\n\ninterface QueueItem {\n resolve: () => void;\n reject: (reason: Error) => void;\n}\n\nexport function createRateLimiter(\n maxConcurrent: number = Infinity,\n maxQueueSize: number = Infinity,\n): RateLimiter {\n if (maxConcurrent !== Infinity && (!Number.isInteger(maxConcurrent) || maxConcurrent < 1)) {\n throw new RangeError(\n `[Accessio] maxConcurrent must be a positive integer or Infinity, got: ${maxConcurrent}`,\n );\n }\n if (maxQueueSize !== Infinity && (!Number.isInteger(maxQueueSize) || maxQueueSize < 1)) {\n throw new RangeError(\n `[Accessio] maxQueueSize must be a positive integer or Infinity, got: ${maxQueueSize}`,\n );\n }\n let active = 0;\n let destroyed = false;\n const queue: QueueItem[] = [];\n\n function acquire(): Promise<void> {\n if (destroyed) {\n return Promise.reject(new Error('[Accessio] Rate limiter has been destroyed'));\n }\n\n if (active < maxConcurrent) {\n active++;\n return Promise.resolve();\n }\n\n if (queue.length >= maxQueueSize) {\n return Promise.reject(\n new Error(`[Accessio] Rate limiter queue size exceeded maxQueueSize (${maxQueueSize})`),\n );\n }\n\n return new Promise((resolve, reject) => {\n queue.push({ resolve, reject });\n });\n }\n\n function release(): void {\n if (destroyed) return;\n\n if (active <= 0) return;\n\n active--;\n\n if (queue.length > 0 && active < maxConcurrent) {\n active++;\n const next = queue.shift();\n next?.resolve();\n }\n }\n\n function destroy(): void {\n destroyed = true;\n const reason = new Error('[Accessio] Rate limiter destroyed — pending request cancelled');\n while (queue.length > 0) {\n const next = queue.shift();\n next?.reject(reason);\n }\n }\n\n return {\n acquire,\n release,\n destroy,\n get pending() {\n return queue.length;\n },\n get active() {\n return active;\n },\n get destroyed() {\n return destroyed;\n },\n };\n}\n\nexport async function rateLimitedRequest<T = unknown>(\n dispatchFn: (config: AccessioRequestConfig) => Promise<AccessioResponse<T>>,\n limiter: RateLimiter,\n config: AccessioRequestConfig,\n): Promise<AccessioResponse<T>> {\n await limiter.acquire();\n try {\n return await dispatchFn(config);\n } finally {\n limiter.release();\n }\n}\n\nexport default createRateLimiter;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,SAAS,kBACd,gBAAwB,UACxB,eAAuB,UACV;AACb,MAAI,kBAAkB,aAAa,CAAC,OAAO,UAAU,aAAa,KAAK,gBAAgB,IAAI;AACzF,UAAM,IAAI;AAAA,MACR,yEAAyE,aAAa;AAAA,IACxF;AAAA,EACF;AACA,MAAI,iBAAiB,aAAa,CAAC,OAAO,UAAU,YAAY,KAAK,eAAe,IAAI;AACtF,UAAM,IAAI;AAAA,MACR,wEAAwE,YAAY;AAAA,IACtF;AAAA,EACF;AACA,MAAI,SAAS;AACb,MAAI,YAAY;AAChB,QAAM,QAAqB,CAAC;AAE5B,WAAS,UAAyB;AAChC,QAAI,WAAW;AACb,aAAO,QAAQ,OAAO,IAAI,MAAM,4CAA4C,CAAC;AAAA,IAC/E;AAEA,QAAI,SAAS,eAAe;AAC1B;AACA,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,QAAI,MAAM,UAAU,cAAc;AAChC,aAAO,QAAQ;AAAA,QACb,IAAI,MAAM,6DAA6D,YAAY,GAAG;AAAA,MACxF;AAAA,IACF;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,KAAK,EAAE,SAAS,OAAO,CAAC;AAAA,IAChC,CAAC;AAAA,EACH;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AAEf,QAAI,UAAU,EAAG;AAEjB;AAEA,QAAI,MAAM,SAAS,KAAK,SAAS,eAAe;AAC9C;AACA,YAAM,OAAO,MAAM,MAAM;AACzB,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,gBAAY;AACZ,UAAM,SAAS,IAAI,MAAM,oEAA+D;AACxF,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,OAAO,MAAM,MAAM;AACzB,YAAM,OAAO,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,UAAU;AACZ,aAAO,MAAM;AAAA,IACf;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,IAAI,YAAY;AACd,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,mBACpB,YACA,SACA,QAC8B;AAC9B,QAAM,QAAQ,QAAQ;AACtB,MAAI;AACF,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,UAAE;AACA,YAAQ,QAAQ;AAAA,EAClB;AACF;AAEA,IAAO,sBAAQ;","names":[]}
|
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.1",
|
|
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",
|
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+\-.]*:)?\/\//i.test(url);
|
|
67
|
+
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url) || /^([a-z][a-z\d+\-.]*:)/i.test(url);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export default function buildURL(
|
package/src/core/request.ts
CHANGED
|
@@ -45,8 +45,8 @@ function flattenHeaders(
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function removeContentType(headers: Record<string, string>): void {
|
|
48
|
-
const
|
|
49
|
-
|
|
48
|
+
const keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');
|
|
49
|
+
for (const key of keys) {
|
|
50
50
|
delete headers[key];
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -69,21 +69,28 @@ function setBasicAuth(config: AccessioRequestConfig, headers: Record<string, str
|
|
|
69
69
|
if (typeof Buffer !== 'undefined') {
|
|
70
70
|
encoded = Buffer.from(credentials).toString('base64');
|
|
71
71
|
} else {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
encoded = btoa(
|
|
73
|
+
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
74
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
75
77
|
}
|
|
76
78
|
headers['Authorization'] = `Basic ${encoded}`;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
80
82
|
switch (responseType) {
|
|
81
|
-
case 'arraybuffer':
|
|
82
|
-
|
|
83
|
-
case '
|
|
84
|
-
|
|
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;
|
|
85
91
|
case 'json':
|
|
86
|
-
default:
|
|
92
|
+
default:
|
|
93
|
+
return await fetchResponse.text();
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -131,26 +138,34 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
131
138
|
fetchOptions.credentials = 'include';
|
|
132
139
|
}
|
|
133
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
|
+
|
|
134
148
|
let abortController: AbortController | null = null;
|
|
135
149
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
136
150
|
let isTimedOut = false;
|
|
137
151
|
let onUserAbort: (() => void) | null = null;
|
|
138
152
|
|
|
139
|
-
|
|
153
|
+
const timeoutValue = Number(config.timeout);
|
|
154
|
+
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
140
155
|
abortController = new AbortController();
|
|
141
156
|
|
|
142
157
|
timeoutId = setTimeout(() => {
|
|
143
158
|
isTimedOut = true;
|
|
144
159
|
abortController!.abort(
|
|
145
160
|
new AccessioError(
|
|
146
|
-
`timeout of ${
|
|
161
|
+
`timeout of ${timeoutValue}ms exceeded`,
|
|
147
162
|
AccessioError.ETIMEDOUT,
|
|
148
163
|
config,
|
|
149
164
|
null,
|
|
150
165
|
null,
|
|
151
166
|
),
|
|
152
167
|
);
|
|
153
|
-
},
|
|
168
|
+
}, timeoutValue);
|
|
154
169
|
|
|
155
170
|
if (config.signal) {
|
|
156
171
|
if (typeof AbortSignal.any === 'function') {
|
|
@@ -160,7 +175,9 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
160
175
|
abortController.abort(config.signal.reason);
|
|
161
176
|
} else {
|
|
162
177
|
onUserAbort = () => {
|
|
163
|
-
abortController
|
|
178
|
+
if (!isTimedOut && abortController) {
|
|
179
|
+
abortController.abort(config.signal!.reason);
|
|
180
|
+
}
|
|
164
181
|
};
|
|
165
182
|
config.signal.addEventListener('abort', onUserAbort, {
|
|
166
183
|
once: true,
|
|
@@ -182,6 +199,21 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
182
199
|
let responseData: unknown;
|
|
183
200
|
const responseType = config.responseType || 'json';
|
|
184
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
|
+
|
|
185
217
|
try {
|
|
186
218
|
responseData = await readResponseData(fetchResponse, responseType);
|
|
187
219
|
} catch (readError) {
|
|
@@ -237,6 +269,20 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
237
269
|
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
238
270
|
}
|
|
239
271
|
|
|
272
|
+
if (
|
|
273
|
+
error instanceof TypeError &&
|
|
274
|
+
(error.message.toLowerCase().includes('url') ||
|
|
275
|
+
error.message.toLowerCase().includes('fetch'))
|
|
276
|
+
) {
|
|
277
|
+
throw new AccessioError(
|
|
278
|
+
`Invalid URL: ${fullURL}`,
|
|
279
|
+
AccessioError.ERR_INVALID_URL,
|
|
280
|
+
config,
|
|
281
|
+
null,
|
|
282
|
+
null,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
240
286
|
throw AccessioError.from(
|
|
241
287
|
error instanceof Error ? error : new Error(String(error)),
|
|
242
288
|
AccessioError.ERR_NETWORK,
|
package/src/core/retry.ts
CHANGED
|
@@ -23,7 +23,14 @@ export function defaultTransformRequest(data: unknown, headers: Record<string, s
|
|
|
23
23
|
headers['Content-Type'] = 'application/json';
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
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
|
+
}
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
return data;
|
|
@@ -21,9 +21,7 @@ export function createRateLimiter(
|
|
|
21
21
|
}
|
|
22
22
|
let active = 0;
|
|
23
23
|
let destroyed = false;
|
|
24
|
-
|
|
25
|
-
let tailIndex = 0;
|
|
26
|
-
const queue: Record<number, QueueItem> = {};
|
|
24
|
+
const queue: QueueItem[] = [];
|
|
27
25
|
|
|
28
26
|
function acquire(): Promise<void> {
|
|
29
27
|
if (destroyed) {
|
|
@@ -35,12 +33,14 @@ export function createRateLimiter(
|
|
|
35
33
|
return Promise.resolve();
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
if (
|
|
39
|
-
return Promise.reject(
|
|
36
|
+
if (queue.length >= maxQueueSize) {
|
|
37
|
+
return Promise.reject(
|
|
38
|
+
new Error(`[Accessio] Rate limiter queue size exceeded maxQueueSize (${maxQueueSize})`),
|
|
39
|
+
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return new Promise((resolve, reject) => {
|
|
43
|
-
queue
|
|
43
|
+
queue.push({ resolve, reject });
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -51,10 +51,9 @@ export function createRateLimiter(
|
|
|
51
51
|
|
|
52
52
|
active--;
|
|
53
53
|
|
|
54
|
-
if (
|
|
54
|
+
if (queue.length > 0 && active < maxConcurrent) {
|
|
55
55
|
active++;
|
|
56
|
-
const next = queue
|
|
57
|
-
delete queue[headIndex++];
|
|
56
|
+
const next = queue.shift();
|
|
58
57
|
next?.resolve();
|
|
59
58
|
}
|
|
60
59
|
}
|
|
@@ -62,9 +61,8 @@ export function createRateLimiter(
|
|
|
62
61
|
function destroy(): void {
|
|
63
62
|
destroyed = true;
|
|
64
63
|
const reason = new Error('[Accessio] Rate limiter destroyed — pending request cancelled');
|
|
65
|
-
while (
|
|
66
|
-
const next = queue
|
|
67
|
-
delete queue[headIndex++];
|
|
64
|
+
while (queue.length > 0) {
|
|
65
|
+
const next = queue.shift();
|
|
68
66
|
next?.reject(reason);
|
|
69
67
|
}
|
|
70
68
|
}
|
|
@@ -74,7 +72,7 @@ export function createRateLimiter(
|
|
|
74
72
|
release,
|
|
75
73
|
destroy,
|
|
76
74
|
get pending() {
|
|
77
|
-
return
|
|
75
|
+
return queue.length;
|
|
78
76
|
},
|
|
79
77
|
get active() {
|
|
80
78
|
return active;
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface AccessioRequestConfig {
|
|
|
63
63
|
debug?: boolean;
|
|
64
64
|
rateLimiter?: RateLimiter;
|
|
65
65
|
_builtUrl?: string;
|
|
66
|
+
maxContentLength?: number;
|
|
67
|
+
dispatcher?: unknown;
|
|
68
|
+
agent?: unknown;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export interface AccessioResponse<T = unknown> {
|
|
@@ -86,8 +89,6 @@ export interface AccessioError extends Error {
|
|
|
86
89
|
toJSON(): Record<string, unknown>;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
92
|
export interface RateLimiter {
|
|
92
93
|
acquire: () => Promise<void>;
|
|
93
94
|
release: () => void;
|