accessio 1.2.0 → 1.4.0
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 +21 -21
- package/cjs/accessio.cjs +68 -84
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/accessioError.cjs +49 -3
- package/cjs/core/accessioError.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +10 -4
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/fetchAdapter.cjs +134 -111
- package/cjs/core/fetchAdapter.cjs.map +1 -1
- package/cjs/core/request.cjs +97 -24
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/core/retry.cjs +25 -0
- package/cjs/core/retry.cjs.map +1 -1
- package/cjs/helpers/debug.cjs +7 -1
- package/cjs/helpers/debug.cjs.map +1 -1
- package/cjs/helpers/flattenHeaders.cjs +37 -0
- package/cjs/helpers/flattenHeaders.cjs.map +1 -1
- package/cjs/helpers/rateLimiter.cjs +11 -22
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/cjs/helpers/settle.cjs +1 -1
- package/cjs/helpers/settle.cjs.map +1 -1
- package/cjs/helpers/transformData.cjs +2 -2
- package/cjs/helpers/transformData.cjs.map +1 -1
- package/cjs/interceptors/interceptorManager.cjs +25 -18
- package/cjs/interceptors/interceptorManager.cjs.map +1 -1
- package/index.d.ts +89 -21
- package/package.json +2 -2
- package/src/accessio.ts +104 -98
- package/src/core/accessioError.ts +50 -1
- package/src/core/buildURL.ts +14 -4
- package/src/core/fetchAdapter.ts +166 -130
- package/src/core/request.ts +115 -28
- package/src/core/retry.ts +19 -1
- package/src/helpers/debug.ts +7 -2
- package/src/helpers/flattenHeaders.ts +30 -0
- package/src/helpers/rateLimiter.ts +11 -24
- package/src/helpers/settle.ts +1 -1
- package/src/helpers/transformData.ts +2 -1
- package/src/interceptors/interceptorManager.ts +26 -19
- package/src/types.ts +1 -0
package/cjs/core/retry.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/retry.ts"],"sourcesContent":["import { ERR_CANCELED, ERR_NETWORK } from '../constants/errorCodes';\nimport type {\n AccessioRequestConfig,\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 if (error.config?.retryOn429 && error.response && error.response.status === 429) {\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 && !config.retryOn429) {\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 const actualMaxRetries = Math.max(maxRetries, config.retryOn429 ? 3 : 0);\n\n for (let attempt = 0; attempt <= actualMaxRetries; attempt++) {\n try {\n const response = await dispatchFn(config);\n return response;\n } catch (error) {\n lastError = error;\n\n const isLastAttempt = attempt >= actualMaxRetries;\n const shouldRetry = !isLastAttempt && retryCondition(error as AccessioError);\n\n if (!shouldRetry) {\n throw error;\n }\n\n let delay = calculateDelay(attempt, retryDelay);\n\n if (config.retryOn429 && (error as any).response?.status === 429) {\n const headers = (error as any).response?.headers;\n const retryAfterStr = headers?.['retry-after'] || headers?.['Retry-After'];\n if (retryAfterStr) {\n const parsed = parseInt(retryAfterStr, 10);\n if (!isNaN(parsed)) {\n delay = parsed * 1000;\n } else {\n const date = new Date(retryAfterStr);\n if (!isNaN(date.getTime())) {\n delay = Math.max(0, date.getTime() - Date.now());\n }\n }\n }\n }\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":"
|
|
1
|
+
{"version":3,"sources":["../../src/core/retry.ts"],"sourcesContent":["import { ERR_BAD_OPTION, ERR_CANCELED, ERR_NETWORK } from '../constants/errorCodes';\nimport AccessioErrorClass from './accessioError';\nimport type {\n AccessioRequestConfig,\n AccessioError,\n RetryConditionFunction,\n OnRetryFunction,\n} from '../types';\n\nfunction isUnretriableBody(data: unknown): boolean {\n if (data == null) return false;\n if (typeof ReadableStream !== 'undefined' && data instanceof ReadableStream) return true;\n return false;\n}\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 if (error.config?.retryOn429 && error.response && error.response.status === 429) {\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 && !config.retryOn429) {\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 const actualMaxRetries = Math.max(maxRetries, config.retryOn429 ? 3 : 0);\n\n for (let attempt = 0; attempt <= actualMaxRetries; attempt++) {\n try {\n const response = await dispatchFn(config);\n return response;\n } catch (error) {\n lastError = error;\n\n const isLastAttempt = attempt >= actualMaxRetries;\n const shouldRetry = !isLastAttempt && retryCondition(error as AccessioError);\n\n if (!shouldRetry) {\n throw error;\n }\n\n if (isUnretriableBody(config.data)) {\n throw new AccessioErrorClass(\n 'Request body is a ReadableStream and cannot be retried after consumption. ' +\n 'Buffer the stream upstream or set retry: 0 for this call.',\n ERR_BAD_OPTION,\n config,\n null,\n (error as AccessioError).response ?? null,\n );\n }\n\n let delay = calculateDelay(attempt, retryDelay);\n\n if (config.retryOn429 && (error as any).response?.status === 429) {\n const headers = (error as any).response?.headers;\n const retryAfterStr = headers?.['retry-after'] || headers?.['Retry-After'];\n if (retryAfterStr) {\n const parsed = parseInt(retryAfterStr, 10);\n if (!isNaN(parsed)) {\n delay = parsed * 1000;\n } else {\n const date = new Date(retryAfterStr);\n if (!isNaN(date.getTime())) {\n delay = Math.max(0, date.getTime() - Date.now());\n }\n }\n }\n }\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":"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","names":["AccessioErrorClass"]}
|
package/cjs/helpers/debug.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(debug_exports, {
|
|
|
23
23
|
logResponse: () => logResponse
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(debug_exports);
|
|
26
|
+
var import_accessioError = require("../core/accessioError");
|
|
26
27
|
function formatBytes(bytes) {
|
|
27
28
|
if (bytes === 0) return "0 B";
|
|
28
29
|
const sizes = ["B", "KB", "MB", "GB"];
|
|
@@ -46,7 +47,12 @@ function logRequest(config, fullUrl) {
|
|
|
46
47
|
parts.push(` Params: ${JSON.stringify(safe.params)}`);
|
|
47
48
|
}
|
|
48
49
|
if (config.data && typeof config.data === "object") {
|
|
49
|
-
|
|
50
|
+
let preview;
|
|
51
|
+
try {
|
|
52
|
+
preview = JSON.stringify((0, import_accessioError.redactBody)(config.data));
|
|
53
|
+
} catch {
|
|
54
|
+
preview = "[unserializable body]";
|
|
55
|
+
}
|
|
50
56
|
const truncated = preview.length > 200 ? `${preview.substring(0, 200)}...` : preview;
|
|
51
57
|
parts.push(` Body: ${truncated}`);
|
|
52
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/debug.ts"],"sourcesContent":["import type { AccessioRequestConfig, AccessioResponse } from '../types';\nimport AccessioError from '../core/accessioError';\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;\n}\n\nfunction sanitizeConfigForLog(config: AccessioRequestConfig): {\n params: Record<string, unknown> | undefined;\n timeout: number | undefined;\n retry: number | undefined;\n} {\n return {\n params: config.params,\n timeout: config.timeout,\n retry: config.retry,\n };\n}\n\nexport function logRequest(config: AccessioRequestConfig, fullUrl: string): void {\n if (!config.debug) return;\n\n const safe = sanitizeConfigForLog(config);\n\n const method = (config.method || 'GET').toUpperCase();\n const url = fullUrl || config.url || '';\n\n const parts: string[] = [`🐦⬛ [Accessio] → ${method} ${url}`];\n\n if (safe.params && Object.keys(safe.params).length > 0) {\n parts.push(` Params: ${JSON.stringify(safe.params)}`);\n }\n\n if (config.data && typeof config.data === 'object') {\n
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/debug.ts"],"sourcesContent":["import type { AccessioRequestConfig, AccessioResponse } from '../types';\nimport AccessioError, { redactBody } from '../core/accessioError';\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;\n}\n\nfunction sanitizeConfigForLog(config: AccessioRequestConfig): {\n params: Record<string, unknown> | undefined;\n timeout: number | undefined;\n retry: number | undefined;\n} {\n return {\n params: config.params,\n timeout: config.timeout,\n retry: config.retry,\n };\n}\n\nexport function logRequest(config: AccessioRequestConfig, fullUrl: string): void {\n if (!config.debug) return;\n\n const safe = sanitizeConfigForLog(config);\n\n const method = (config.method || 'GET').toUpperCase();\n const url = fullUrl || config.url || '';\n\n const parts: string[] = [`🐦⬛ [Accessio] → ${method} ${url}`];\n\n if (safe.params && Object.keys(safe.params).length > 0) {\n parts.push(` Params: ${JSON.stringify(safe.params)}`);\n }\n\n if (config.data && typeof config.data === 'object') {\n let preview: string;\n try {\n preview = JSON.stringify(redactBody(config.data));\n } catch {\n preview = '[unserializable body]';\n }\n const truncated = preview.length > 200 ? `${preview.substring(0, 200)}...` : preview;\n parts.push(` Body: ${truncated}`);\n }\n\n if (safe.timeout) {\n parts.push(` Timeout: ${safe.timeout}ms`);\n }\n\n if (safe.retry) {\n parts.push(` Retry: ${safe.retry}x`);\n }\n\n console.log(parts.join('\\n'));\n}\n\nexport function logResponse(response: AccessioResponse): void {\n if (!response.config || !response.config.debug) return;\n const status = response.status;\n const statusText = response.statusText || '';\n const duration = response.duration != null ? `${response.duration}ms` : '??';\n\n const statusIcon = status >= 200 && status < 300 ? '✅' : status >= 400 ? '❌' : '⚠️';\n\n const parts: string[] = [`🐦⬛ [Accessio] ← ${statusIcon} ${status} ${statusText} (${duration})`];\n\n if (response.data) {\n try {\n const size =\n typeof response.data === 'string'\n ? response.data.length\n : JSON.stringify(response.data).length;\n parts.push(` Size: ~${formatBytes(size)}`);\n } catch {\n // ignore\n }\n }\n\n console.log(parts.join('\\n'));\n}\n\nexport function logError(error: AccessioError, config?: AccessioRequestConfig): void {\n if (!config || !config.debug) return;\n\n const parts: string[] = [`🐦⬛ [Accessio] ← ❌ ERROR: ${error.message}`];\n\n if (error.code) {\n parts.push(` Code: ${error.code}`);\n }\n\n if (error.response) {\n parts.push(` Status: ${error.response.status}`);\n }\n\n console.log(parts.join('\\n'));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAA0C;AAE1C,SAAS,YAAY,OAAuB;AAC1C,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,QAAQ,CAAC,KAAK,MAAM,MAAM,IAAI;AACpC,QAAM,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC;AACrD,SAAO,IAAI,QAAQ,KAAK,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAC9D;AAEA,SAAS,qBAAqB,QAI5B;AACA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,OAAO,OAAO;AAAA,EAChB;AACF;AAEO,SAAS,WAAW,QAA+B,SAAuB;AAC/E,MAAI,CAAC,OAAO,MAAO;AAEnB,QAAM,OAAO,qBAAqB,MAAM;AAExC,QAAM,UAAU,OAAO,UAAU,OAAO,YAAY;AACpD,QAAM,MAAM,WAAW,OAAO,OAAO;AAErC,QAAM,QAAkB,CAAC,2CAAqB,MAAM,IAAI,GAAG,EAAE;AAE7D,MAAI,KAAK,UAAU,OAAO,KAAK,KAAK,MAAM,EAAE,SAAS,GAAG;AACtD,UAAM,KAAK,cAAc,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE;AAAA,EACxD;AAEA,MAAI,OAAO,QAAQ,OAAO,OAAO,SAAS,UAAU;AAClD,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,cAAU,iCAAW,OAAO,IAAI,CAAC;AAAA,IAClD,QAAQ;AACN,gBAAU;AAAA,IACZ;AACA,UAAM,YAAY,QAAQ,SAAS,MAAM,GAAG,QAAQ,UAAU,GAAG,GAAG,CAAC,QAAQ;AAC7E,UAAM,KAAK,YAAY,SAAS,EAAE;AAAA,EACpC;AAEA,MAAI,KAAK,SAAS;AAChB,UAAM,KAAK,eAAe,KAAK,OAAO,IAAI;AAAA,EAC5C;AAEA,MAAI,KAAK,OAAO;AACd,UAAM,KAAK,aAAa,KAAK,KAAK,GAAG;AAAA,EACvC;AAEA,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;AAEO,SAAS,YAAY,UAAkC;AAC5D,MAAI,CAAC,SAAS,UAAU,CAAC,SAAS,OAAO,MAAO;AAChD,QAAM,SAAS,SAAS;AACxB,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,WAAW,SAAS,YAAY,OAAO,GAAG,SAAS,QAAQ,OAAO;AAExE,QAAM,aAAa,UAAU,OAAO,SAAS,MAAM,WAAM,UAAU,MAAM,WAAM;AAE/E,QAAM,QAAkB,CAAC,2CAAqB,UAAU,IAAI,MAAM,IAAI,UAAU,KAAK,QAAQ,GAAG;AAEhG,MAAI,SAAS,MAAM;AACjB,QAAI;AACF,YAAM,OACJ,OAAO,SAAS,SAAS,WACrB,SAAS,KAAK,SACd,KAAK,UAAU,SAAS,IAAI,EAAE;AACpC,YAAM,KAAK,aAAa,YAAY,IAAI,CAAC,EAAE;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;AAEO,SAAS,SAAS,OAAsB,QAAsC;AACnF,MAAI,CAAC,UAAU,CAAC,OAAO,MAAO;AAE9B,QAAM,QAAkB,CAAC,yDAA8B,MAAM,OAAO,EAAE;AAEtE,MAAI,MAAM,MAAM;AACd,UAAM,KAAK,YAAY,MAAM,IAAI,EAAE;AAAA,EACrC;AAEA,MAAI,MAAM,UAAU;AAClB,UAAM,KAAK,cAAc,MAAM,SAAS,MAAM,EAAE;AAAA,EAClD;AAEA,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var flattenHeaders_exports = {};
|
|
20
30
|
__export(flattenHeaders_exports, {
|
|
@@ -23,6 +33,32 @@ __export(flattenHeaders_exports, {
|
|
|
23
33
|
removeContentType: () => removeContentType
|
|
24
34
|
});
|
|
25
35
|
module.exports = __toCommonJS(flattenHeaders_exports);
|
|
36
|
+
var import_accessioError = __toESM(require("../core/accessioError"), 1);
|
|
37
|
+
var import_errorCodes = require("../constants/errorCodes");
|
|
38
|
+
const HEADER_FORBIDDEN_CHAR = /[\r\n\0]/;
|
|
39
|
+
function assertSafeHeader(name, value) {
|
|
40
|
+
if (typeof name !== "string" || HEADER_FORBIDDEN_CHAR.test(name)) {
|
|
41
|
+
throw new import_accessioError.default(
|
|
42
|
+
`Invalid header name "${String(name)}": CR, LF and NUL are not allowed`,
|
|
43
|
+
import_errorCodes.ERR_BAD_OPTION,
|
|
44
|
+
null,
|
|
45
|
+
null,
|
|
46
|
+
null
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const values = Array.isArray(value) ? value : [value];
|
|
50
|
+
for (const v of values) {
|
|
51
|
+
if (typeof v === "string" && HEADER_FORBIDDEN_CHAR.test(v)) {
|
|
52
|
+
throw new import_accessioError.default(
|
|
53
|
+
`Invalid value for header "${name}": CR, LF and NUL are not allowed`,
|
|
54
|
+
import_errorCodes.ERR_BAD_OPTION,
|
|
55
|
+
null,
|
|
56
|
+
null,
|
|
57
|
+
null
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
26
62
|
const METHOD_KEYS = /* @__PURE__ */ new Set([
|
|
27
63
|
"common",
|
|
28
64
|
"delete",
|
|
@@ -59,6 +95,7 @@ function removeContentType(headers) {
|
|
|
59
95
|
function buildFetchHeaders(headers) {
|
|
60
96
|
const fetchHeaders = new Headers();
|
|
61
97
|
for (const [key, value] of Object.entries(headers)) {
|
|
98
|
+
assertSafeHeader(key, value);
|
|
62
99
|
if (Array.isArray(value)) {
|
|
63
100
|
for (const v of value) {
|
|
64
101
|
fetchHeaders.append(key, v);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/flattenHeaders.ts"],"sourcesContent":["const 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 | string[]>>;\n\nexport function flattenHeaders(\n headers: HeadersConfig | undefined,\n method?: string,\n): Record<string, string | string[]> {\n if (!headers) return {};\n\n const merged: Record<string, 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 | string[];\n }\n }\n\n return merged;\n}\n\nexport function removeContentType(headers: Record<string, 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\nexport function buildFetchHeaders(headers: Record<string, string | string[]>): Headers {\n const fetchHeaders = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (Array.isArray(value)) {\n for (const v of value) {\n fetchHeaders.append(key, v);\n }\n } else {\n fetchHeaders.set(key, value);\n }\n }\n return fetchHeaders;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/flattenHeaders.ts"],"sourcesContent":["import AccessioError from '../core/accessioError';\nimport { ERR_BAD_OPTION } from '../constants/errorCodes';\n\nconst HEADER_FORBIDDEN_CHAR = /[\\r\\n\\0]/;\n\nfunction assertSafeHeader(name: string, value: string | string[]): void {\n if (typeof name !== 'string' || HEADER_FORBIDDEN_CHAR.test(name)) {\n throw new AccessioError(\n `Invalid header name \"${String(name)}\": CR, LF and NUL are not allowed`,\n ERR_BAD_OPTION,\n null,\n null,\n null,\n );\n }\n const values = Array.isArray(value) ? value : [value];\n for (const v of values) {\n if (typeof v === 'string' && HEADER_FORBIDDEN_CHAR.test(v)) {\n throw new AccessioError(\n `Invalid value for header \"${name}\": CR, LF and NUL are not allowed`,\n ERR_BAD_OPTION,\n null,\n null,\n null,\n );\n }\n }\n}\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 | string[]>>;\n\nexport function flattenHeaders(\n headers: HeadersConfig | undefined,\n method?: string,\n): Record<string, string | string[]> {\n if (!headers) return {};\n\n const merged: Record<string, 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 | string[];\n }\n }\n\n return merged;\n}\n\nexport function removeContentType(headers: Record<string, 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\nexport function buildFetchHeaders(headers: Record<string, string | string[]>): Headers {\n const fetchHeaders = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n assertSafeHeader(key, value);\n if (Array.isArray(value)) {\n for (const v of value) {\n fetchHeaders.append(key, v);\n }\n } else {\n fetchHeaders.set(key, value);\n }\n }\n return fetchHeaders;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAA0B;AAC1B,wBAA+B;AAE/B,MAAM,wBAAwB;AAE9B,SAAS,iBAAiB,MAAc,OAAgC;AACtE,MAAI,OAAO,SAAS,YAAY,sBAAsB,KAAK,IAAI,GAAG;AAChE,UAAM,IAAI,qBAAAA;AAAA,MACR,wBAAwB,OAAO,IAAI,CAAC;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACpD,aAAW,KAAK,QAAQ;AACtB,QAAI,OAAO,MAAM,YAAY,sBAAsB,KAAK,CAAC,GAAG;AAC1D,YAAM,IAAI,qBAAAA;AAAA,QACR,6BAA6B,IAAI;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,cAAc,oBAAI,IAAY;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAIM,SAAS,eACd,SACA,QACmC;AACnC,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,SAA4C,CAAC;AACnD,QAAM,eAAe,UAAU,OAAO,YAAY;AAElD,MAAI,QAAQ,QAAQ,GAAG;AACrB,WAAO,OAAO,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EACzC;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,OAAO,QAAQ,QAAQ,WAAW,CAAC;AAAA,EAC5C;AAEA,aAAW,OAAO,SAAS;AACzB,QAAI,OAAO,UAAU,eAAe,KAAK,SAAS,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,GAAG;AAC/E,aAAO,GAAG,IAAI,QAAQ,GAAG;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,SAAkD;AAClF,QAAM,OAAO,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM,cAAc;AAClF,aAAW,OAAO,MAAM;AACtB,WAAO,QAAQ,GAAG;AAAA,EACpB;AACF;AAEO,SAAS,kBAAkB,SAAqD;AACrF,QAAM,eAAe,IAAI,QAAQ;AACjC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,qBAAiB,KAAK,KAAK;AAC3B,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,KAAK,OAAO;AACrB,qBAAa,OAAO,KAAK,CAAC;AAAA,MAC5B;AAAA,IACF,OAAO;AACL,mBAAa,IAAI,KAAK,KAAK;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;","names":["AccessioError"]}
|
|
@@ -36,10 +36,7 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
36
36
|
}
|
|
37
37
|
let active = 0;
|
|
38
38
|
let destroyed = false;
|
|
39
|
-
|
|
40
|
-
let tail = 0;
|
|
41
|
-
let pendingCount = 0;
|
|
42
|
-
const queue = {};
|
|
39
|
+
const queue = [];
|
|
43
40
|
function acquire() {
|
|
44
41
|
if (destroyed) {
|
|
45
42
|
return Promise.reject(new Error("[Accessio] Rate limiter has been destroyed"));
|
|
@@ -48,38 +45,30 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
48
45
|
active++;
|
|
49
46
|
return Promise.resolve();
|
|
50
47
|
}
|
|
51
|
-
if (
|
|
48
|
+
if (queue.length >= maxQueueSize) {
|
|
52
49
|
return Promise.reject(
|
|
53
50
|
new Error(`[Accessio] Rate limiter queue size exceeded maxQueueSize (${maxQueueSize})`)
|
|
54
51
|
);
|
|
55
52
|
}
|
|
56
53
|
return new Promise((resolve, reject) => {
|
|
57
|
-
queue
|
|
58
|
-
pendingCount++;
|
|
54
|
+
queue.push({ resolve, reject });
|
|
59
55
|
});
|
|
60
56
|
}
|
|
61
57
|
function release() {
|
|
62
58
|
if (destroyed) return;
|
|
63
59
|
if (active <= 0) return;
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
delete queue[head];
|
|
69
|
-
head++;
|
|
70
|
-
pendingCount--;
|
|
71
|
-
next?.resolve();
|
|
60
|
+
const next = queue.shift();
|
|
61
|
+
if (next) {
|
|
62
|
+
next.resolve();
|
|
63
|
+
return;
|
|
72
64
|
}
|
|
65
|
+
active--;
|
|
73
66
|
}
|
|
74
67
|
function destroy() {
|
|
75
68
|
destroyed = true;
|
|
76
69
|
const reason = new Error("[Accessio] Rate limiter destroyed \u2014 pending request cancelled");
|
|
77
|
-
while (
|
|
78
|
-
|
|
79
|
-
delete queue[head];
|
|
80
|
-
head++;
|
|
81
|
-
pendingCount--;
|
|
82
|
-
next?.reject(reason);
|
|
70
|
+
while (queue.length > 0) {
|
|
71
|
+
queue.shift().reject(reason);
|
|
83
72
|
}
|
|
84
73
|
}
|
|
85
74
|
return {
|
|
@@ -87,7 +76,7 @@ function createRateLimiter(maxConcurrent = Infinity, maxQueueSize = Infinity) {
|
|
|
87
76
|
release,
|
|
88
77
|
destroy,
|
|
89
78
|
get pending() {
|
|
90
|
-
return
|
|
79
|
+
return queue.length;
|
|
91
80
|
},
|
|
92
81
|
get active() {
|
|
93
82
|
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 if (active <= 0) return;\n\n const next = queue.shift();\n if (next) {\n next.resolve();\n return;\n }\n active--;\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 queue.shift()!.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;AACf,QAAI,UAAU,EAAG;AAEjB,UAAM,OAAO,MAAM,MAAM;AACzB,QAAI,MAAM;AACR,WAAK,QAAQ;AACb;AAAA,IACF;AACA;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,gBAAY;AACZ,UAAM,SAAS,IAAI,MAAM,oEAA+D;AACxF,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,MAAM,EAAG,OAAO,MAAM;AAAA,IAC9B;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/cjs/helpers/settle.cjs
CHANGED
|
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(settle_exports);
|
|
|
34
34
|
var import_accessioError = __toESM(require("../core/accessioError"), 1);
|
|
35
35
|
function settle(resolve, reject, response, config) {
|
|
36
36
|
const validateStatus = config.validateStatus;
|
|
37
|
-
if (!
|
|
37
|
+
if (!validateStatus || validateStatus(response.status)) {
|
|
38
38
|
resolve(response);
|
|
39
39
|
} else {
|
|
40
40
|
const error = new import_accessioError.default(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/settle.ts"],"sourcesContent":["import AccessioError from '../core/accessioError';\nimport type { AccessioResponse, AccessioRequestConfig } from '../types';\n\nexport default function settle(\n resolve: (value: AccessioResponse) => void,\n reject: (reason: AccessioError) => void,\n response: AccessioResponse,\n config: AccessioRequestConfig,\n): void {\n const validateStatus = config.validateStatus;\n\n if (!
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/settle.ts"],"sourcesContent":["import AccessioError from '../core/accessioError';\nimport type { AccessioResponse, AccessioRequestConfig } from '../types';\n\nexport default function settle(\n resolve: (value: AccessioResponse) => void,\n reject: (reason: AccessioError) => void,\n response: AccessioResponse,\n config: AccessioRequestConfig,\n): void {\n const validateStatus = config.validateStatus;\n\n if (!validateStatus || validateStatus(response.status)) {\n resolve(response);\n } else {\n const error = new AccessioError(\n `Request failed with status code ${response.status}`,\n response.status >= 400 && response.status < 500\n ? AccessioError.ERR_BAD_REQUEST\n : AccessioError.ERR_BAD_RESPONSE,\n config,\n response.request,\n response,\n );\n reject(error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAA0B;AAGX,SAAR,OACL,SACA,QACA,UACA,QACM;AACN,QAAM,iBAAiB,OAAO;AAE9B,MAAI,CAAC,kBAAkB,eAAe,SAAS,MAAM,GAAG;AACtD,YAAQ,QAAQ;AAAA,EAClB,OAAO;AACL,UAAM,QAAQ,IAAI,qBAAAA;AAAA,MAChB,mCAAmC,SAAS,MAAM;AAAA,MAClD,SAAS,UAAU,OAAO,SAAS,SAAS,MACxC,qBAAAA,QAAc,kBACd,qBAAAA,QAAc;AAAA,MAClB;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AACF;","names":["AccessioError"]}
|
|
@@ -32,7 +32,7 @@ __export(transformData_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(transformData_exports);
|
|
34
34
|
var import_accessioError = __toESM(require("../core/accessioError"), 1);
|
|
35
|
-
async function transformData(transforms, data, headers, config) {
|
|
35
|
+
async function transformData(transforms, data, headers, config, direction = "request") {
|
|
36
36
|
if (!transforms || !Array.isArray(transforms)) {
|
|
37
37
|
return data;
|
|
38
38
|
}
|
|
@@ -44,7 +44,7 @@ async function transformData(transforms, data, headers, config) {
|
|
|
44
44
|
} catch (err) {
|
|
45
45
|
throw import_accessioError.default.from(
|
|
46
46
|
err instanceof Error ? err : new Error(String(err)),
|
|
47
|
-
import_accessioError.default.ERR_BAD_REQUEST,
|
|
47
|
+
direction === "response" ? import_accessioError.default.ERR_BAD_RESPONSE : import_accessioError.default.ERR_BAD_REQUEST,
|
|
48
48
|
config ?? null,
|
|
49
49
|
null,
|
|
50
50
|
null
|
|
@@ -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 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,
|
|
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 direction: 'request' | 'response' = 'request',\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 direction === 'response' ? AccessioError.ERR_BAD_RESPONSE : 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,QACA,YAAoC,WAClB;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,cAAc,aAAa,qBAAAA,QAAc,mBAAmB,qBAAAA,QAAc;AAAA,UAC1E,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["AccessioError"]}
|
|
@@ -23,41 +23,48 @@ __export(interceptorManager_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(interceptorManager_exports);
|
|
25
25
|
class InterceptorManager {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
_handlers;
|
|
27
|
+
_nextId;
|
|
28
28
|
constructor() {
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
29
|
+
this._handlers = /* @__PURE__ */ new Map();
|
|
30
|
+
this._nextId = 0;
|
|
31
31
|
}
|
|
32
32
|
use(fulfilled, rejected, options = {}) {
|
|
33
|
-
this.
|
|
33
|
+
const id = this._nextId++;
|
|
34
|
+
this._handlers.set(id, {
|
|
34
35
|
fulfilled: fulfilled || null,
|
|
35
36
|
rejected: rejected || null,
|
|
36
37
|
synchronous: options.synchronous || false,
|
|
37
38
|
runWhen: options.runWhen || null
|
|
38
39
|
});
|
|
39
|
-
|
|
40
|
-
return this.handlers.length - 1;
|
|
40
|
+
return id;
|
|
41
41
|
}
|
|
42
42
|
eject(id) {
|
|
43
|
-
|
|
44
|
-
this.handlers[id] = null;
|
|
45
|
-
this._activeCount--;
|
|
46
|
-
}
|
|
43
|
+
this._handlers.delete(id);
|
|
47
44
|
}
|
|
48
45
|
clear() {
|
|
49
|
-
this.
|
|
50
|
-
this._activeCount = 0;
|
|
46
|
+
this._handlers.clear();
|
|
51
47
|
}
|
|
52
48
|
forEach(fn) {
|
|
53
|
-
for (const handler of this.
|
|
54
|
-
|
|
55
|
-
fn(handler);
|
|
56
|
-
}
|
|
49
|
+
for (const handler of this._handlers.values()) {
|
|
50
|
+
fn(handler);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
get size() {
|
|
60
|
-
return this.
|
|
54
|
+
return this._handlers.size;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Snapshot view for backward-compat introspection. Slot index = interceptor ID;
|
|
58
|
+
* ejected IDs appear as `null`. Reading this builds a fresh array each time —
|
|
59
|
+
* prefer `forEach`/`size` in hot paths.
|
|
60
|
+
*/
|
|
61
|
+
get handlers() {
|
|
62
|
+
const max = this._nextId;
|
|
63
|
+
const out = new Array(max);
|
|
64
|
+
for (let i = 0; i < max; i++) {
|
|
65
|
+
out[i] = this._handlers.get(i) ?? null;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
var interceptorManager_default = InterceptorManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/interceptors/interceptorManager.ts"],"sourcesContent":["import type { TransformFunction, InterceptorHandler, InterceptorOptions } from '../types';\n\nexport class InterceptorManager {\n
|
|
1
|
+
{"version":3,"sources":["../../src/interceptors/interceptorManager.ts"],"sourcesContent":["import type { TransformFunction, InterceptorHandler, InterceptorOptions } from '../types';\n\nexport class InterceptorManager {\n private _handlers: Map<number, InterceptorHandler>;\n private _nextId: number;\n\n constructor() {\n this._handlers = new Map();\n this._nextId = 0;\n }\n\n use(\n fulfilled: TransformFunction | null,\n rejected?: ((error: unknown) => unknown) | null,\n options: InterceptorOptions = {},\n ): number {\n const id = this._nextId++;\n this._handlers.set(id, {\n fulfilled: fulfilled || null,\n rejected: rejected || null,\n synchronous: options.synchronous || false,\n runWhen: options.runWhen || null,\n });\n return id;\n }\n\n eject(id: number): void {\n this._handlers.delete(id);\n }\n\n clear(): void {\n this._handlers.clear();\n }\n\n forEach(fn: (handler: InterceptorHandler) => void): void {\n for (const handler of this._handlers.values()) {\n fn(handler);\n }\n }\n\n get size(): number {\n return this._handlers.size;\n }\n\n /**\n * Snapshot view for backward-compat introspection. Slot index = interceptor ID;\n * ejected IDs appear as `null`. Reading this builds a fresh array each time —\n * prefer `forEach`/`size` in hot paths.\n */\n get handlers(): Array<InterceptorHandler | null> {\n const max = this._nextId;\n const out: Array<InterceptorHandler | null> = new Array(max);\n for (let i = 0; i < max; i++) {\n out[i] = this._handlers.get(i) ?? null;\n }\n return out;\n }\n}\n\nexport default InterceptorManager;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,MAAM,mBAAmB;AAAA,EACtB;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,YAAY,oBAAI,IAAI;AACzB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,IACE,WACA,UACA,UAA8B,CAAC,GACvB;AACR,UAAM,KAAK,KAAK;AAChB,SAAK,UAAU,IAAI,IAAI;AAAA,MACrB,WAAW,aAAa;AAAA,MACxB,UAAU,YAAY;AAAA,MACtB,aAAa,QAAQ,eAAe;AAAA,MACpC,SAAS,QAAQ,WAAW;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAkB;AACtB,SAAK,UAAU,OAAO,EAAE;AAAA,EAC1B;AAAA,EAEA,QAAc;AACZ,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEA,QAAQ,IAAiD;AACvD,eAAW,WAAW,KAAK,UAAU,OAAO,GAAG;AAC7C,SAAG,OAAO;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,WAA6C;AAC/C,UAAM,MAAM,KAAK;AACjB,UAAM,MAAwC,IAAI,MAAM,GAAG;AAC3D,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAI,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AACF;AAEA,IAAO,6BAAQ;","names":[]}
|
package/index.d.ts
CHANGED
|
@@ -6,19 +6,44 @@
|
|
|
6
6
|
* Avoids ambiguous intersection of Record<string, string> & { common?, get?, ... }
|
|
7
7
|
* which TypeScript accepts but can confuse type consumers.
|
|
8
8
|
*/
|
|
9
|
+
export type HeaderValue = string | string[];
|
|
10
|
+
|
|
9
11
|
export type AccessioHeaders = {
|
|
10
|
-
common?: Record<string,
|
|
11
|
-
get?: Record<string,
|
|
12
|
-
post?: Record<string,
|
|
13
|
-
put?: Record<string,
|
|
14
|
-
patch?: Record<string,
|
|
15
|
-
delete?: Record<string,
|
|
16
|
-
head?: Record<string,
|
|
17
|
-
options?: Record<string,
|
|
18
|
-
/** Any additional custom headers */
|
|
19
|
-
[key: string]:
|
|
12
|
+
common?: Record<string, HeaderValue>;
|
|
13
|
+
get?: Record<string, HeaderValue>;
|
|
14
|
+
post?: Record<string, HeaderValue>;
|
|
15
|
+
put?: Record<string, HeaderValue>;
|
|
16
|
+
patch?: Record<string, HeaderValue>;
|
|
17
|
+
delete?: Record<string, HeaderValue>;
|
|
18
|
+
head?: Record<string, HeaderValue>;
|
|
19
|
+
options?: Record<string, HeaderValue>;
|
|
20
|
+
/** Any additional custom headers (flat or per-method-nested) */
|
|
21
|
+
[key: string]: HeaderValue | Record<string, HeaderValue> | undefined;
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
export interface AccessioHooks {
|
|
25
|
+
onBeforeRequest?: (config: AccessioRequestConfig) => void | Promise<void>;
|
|
26
|
+
onRequestResponse?: (response: AccessioResponse) => void | Promise<void>;
|
|
27
|
+
onRequestError?: (error: AccessioError) => void | Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CacheProvider {
|
|
31
|
+
get(key: string): Promise<any> | any;
|
|
32
|
+
set(key: string, value: any, ttl?: number): Promise<void> | void;
|
|
33
|
+
delete(key: string): Promise<void> | void;
|
|
34
|
+
clear(): Promise<void> | void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SchemaValidator<T = any> {
|
|
38
|
+
parse(data: unknown): T;
|
|
39
|
+
parseAsync?(data: unknown): Promise<T>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type TransformFunction = (
|
|
43
|
+
data: any,
|
|
44
|
+
headers: Record<string, HeaderValue>,
|
|
45
|
+
) => any | Promise<any>;
|
|
46
|
+
|
|
22
47
|
export interface AccessioRequestConfig {
|
|
23
48
|
/** Request URL (path or full URL) */
|
|
24
49
|
url?: string;
|
|
@@ -55,21 +80,24 @@ export interface AccessioRequestConfig {
|
|
|
55
80
|
/** Include credentials in cross-site requests */
|
|
56
81
|
withCredentials?: boolean;
|
|
57
82
|
|
|
83
|
+
/**
|
|
84
|
+
* URL protocols accepted by the client. Defaults to `["http:", "https:"]`.
|
|
85
|
+
* Pass an extended array to allow more (e.g. `["http:", "https:", "ws:"]`),
|
|
86
|
+
* or `null` to disable the check entirely.
|
|
87
|
+
*/
|
|
88
|
+
allowedProtocols?: string[] | null;
|
|
89
|
+
|
|
58
90
|
/** Expected response data type */
|
|
59
91
|
responseType?: "json" | "text" | "blob" | "arraybuffer" | "stream";
|
|
60
92
|
|
|
61
93
|
/** Transform functions applied to request data */
|
|
62
|
-
transformRequest?:
|
|
63
|
-
(data: any, headers?: Record<string, string>) => any
|
|
64
|
-
>;
|
|
94
|
+
transformRequest?: TransformFunction | TransformFunction[];
|
|
65
95
|
|
|
66
96
|
/** Transform functions applied to response data */
|
|
67
|
-
transformResponse?:
|
|
68
|
-
(data: any, headers?: Record<string, string>) => any
|
|
69
|
-
>;
|
|
97
|
+
transformResponse?: TransformFunction | TransformFunction[];
|
|
70
98
|
|
|
71
|
-
/** Function to determine if a status code should resolve or reject */
|
|
72
|
-
validateStatus?: (status: number) => boolean;
|
|
99
|
+
/** Function to determine if a status code should resolve or reject. Pass `null` to disable. */
|
|
100
|
+
validateStatus?: ((status: number) => boolean) | null;
|
|
73
101
|
|
|
74
102
|
/** AbortSignal for request cancellation */
|
|
75
103
|
signal?: AbortSignal;
|
|
@@ -98,6 +126,9 @@ export interface AccessioRequestConfig {
|
|
|
98
126
|
config: AccessioRequestConfig,
|
|
99
127
|
) => void;
|
|
100
128
|
|
|
129
|
+
/** Retry on HTTP 429 (Too Many Requests), honoring Retry-After when present */
|
|
130
|
+
retryOn429?: boolean;
|
|
131
|
+
|
|
101
132
|
// ── Debug ──────────────────────────────────────────
|
|
102
133
|
|
|
103
134
|
/** Enable debug logging for this request */
|
|
@@ -108,6 +139,43 @@ export interface AccessioRequestConfig {
|
|
|
108
139
|
/** Rate limiter instance to control concurrent requests */
|
|
109
140
|
rateLimiter?: RateLimiter;
|
|
110
141
|
|
|
142
|
+
// ── Caching / dedupe ───────────────────────────────
|
|
143
|
+
|
|
144
|
+
/** Dedupe in-flight identical GETs (cache/dedupe key is per fullURL+auth+accept+responseType+withCredentials) */
|
|
145
|
+
dedupe?: boolean;
|
|
146
|
+
|
|
147
|
+
/** Enable response caching for GETs. `true` uses the default in-memory cache; pass a provider to customize. */
|
|
148
|
+
cache?: boolean | CacheProvider;
|
|
149
|
+
|
|
150
|
+
/** TTL in ms for cached responses (when supported by the provider) */
|
|
151
|
+
cacheTTL?: number;
|
|
152
|
+
|
|
153
|
+
// ── Response handling ──────────────────────────────
|
|
154
|
+
|
|
155
|
+
/** Maximum allowed response Content-Length in bytes */
|
|
156
|
+
maxContentLength?: number;
|
|
157
|
+
|
|
158
|
+
/** Schema validator applied to response data (Zod-compatible: `parse` / `parseAsync`) */
|
|
159
|
+
schema?: SchemaValidator;
|
|
160
|
+
|
|
161
|
+
/** Progress callback for downloads. Wraps the response body in a passthrough ReadableStream. */
|
|
162
|
+
onDownloadProgress?: (progressEvent: { loaded: number; total: number }) => void;
|
|
163
|
+
|
|
164
|
+
// ── Lifecycle hooks ────────────────────────────────
|
|
165
|
+
|
|
166
|
+
hooks?: AccessioHooks;
|
|
167
|
+
|
|
168
|
+
// ── Adapter / runtime ──────────────────────────────
|
|
169
|
+
|
|
170
|
+
/** Custom fetch implementation (defaults to global `fetch`) */
|
|
171
|
+
fetch?: typeof fetch;
|
|
172
|
+
|
|
173
|
+
/** Undici dispatcher (Node.js) */
|
|
174
|
+
dispatcher?: unknown;
|
|
175
|
+
|
|
176
|
+
/** Node.js http(s).Agent */
|
|
177
|
+
agent?: unknown;
|
|
178
|
+
|
|
111
179
|
/** Allow any additional custom properties */
|
|
112
180
|
[key: string]: any;
|
|
113
181
|
}
|
|
@@ -122,8 +190,8 @@ export interface AccessioResponse<T = any> {
|
|
|
122
190
|
/** HTTP status text */
|
|
123
191
|
statusText: string;
|
|
124
192
|
|
|
125
|
-
/** Response headers (lowercased keys) */
|
|
126
|
-
headers: Record<string,
|
|
193
|
+
/** Response headers (lowercased keys; repeated headers become string arrays) */
|
|
194
|
+
headers: Record<string, HeaderValue>;
|
|
127
195
|
|
|
128
196
|
/** The config used for this request */
|
|
129
197
|
config: AccessioRequestConfig;
|
|
@@ -451,4 +519,4 @@ export const createRateLimiter: (maxConcurrent?: number) => RateLimiter;
|
|
|
451
519
|
|
|
452
520
|
export function logRequest(config: AccessioRequestConfig, fullUrl: string): void;
|
|
453
521
|
export function logResponse(response: AccessioResponse): void;
|
|
454
|
-
export function logError(error:
|
|
522
|
+
export function logError(error: AccessioError, config?: AccessioRequestConfig): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accessio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Fast, flexible HTTP client — simple, modular, and dependency-free",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./cjs/index.cjs",
|
|
@@ -106,4 +106,4 @@
|
|
|
106
106
|
"typescript-eslint": "^8.59.3",
|
|
107
107
|
"vitest": "^3.1.0"
|
|
108
108
|
}
|
|
109
|
-
}
|
|
109
|
+
}
|