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.
Files changed (40) hide show
  1. package/README.md +21 -21
  2. package/cjs/accessio.cjs +68 -84
  3. package/cjs/accessio.cjs.map +1 -1
  4. package/cjs/core/accessioError.cjs +49 -3
  5. package/cjs/core/accessioError.cjs.map +1 -1
  6. package/cjs/core/buildURL.cjs +10 -4
  7. package/cjs/core/buildURL.cjs.map +1 -1
  8. package/cjs/core/fetchAdapter.cjs +134 -111
  9. package/cjs/core/fetchAdapter.cjs.map +1 -1
  10. package/cjs/core/request.cjs +97 -24
  11. package/cjs/core/request.cjs.map +1 -1
  12. package/cjs/core/retry.cjs +25 -0
  13. package/cjs/core/retry.cjs.map +1 -1
  14. package/cjs/helpers/debug.cjs +7 -1
  15. package/cjs/helpers/debug.cjs.map +1 -1
  16. package/cjs/helpers/flattenHeaders.cjs +37 -0
  17. package/cjs/helpers/flattenHeaders.cjs.map +1 -1
  18. package/cjs/helpers/rateLimiter.cjs +11 -22
  19. package/cjs/helpers/rateLimiter.cjs.map +1 -1
  20. package/cjs/helpers/settle.cjs +1 -1
  21. package/cjs/helpers/settle.cjs.map +1 -1
  22. package/cjs/helpers/transformData.cjs +2 -2
  23. package/cjs/helpers/transformData.cjs.map +1 -1
  24. package/cjs/interceptors/interceptorManager.cjs +25 -18
  25. package/cjs/interceptors/interceptorManager.cjs.map +1 -1
  26. package/index.d.ts +89 -21
  27. package/package.json +2 -2
  28. package/src/accessio.ts +104 -98
  29. package/src/core/accessioError.ts +50 -1
  30. package/src/core/buildURL.ts +14 -4
  31. package/src/core/fetchAdapter.ts +166 -130
  32. package/src/core/request.ts +115 -28
  33. package/src/core/retry.ts +19 -1
  34. package/src/helpers/debug.ts +7 -2
  35. package/src/helpers/flattenHeaders.ts +30 -0
  36. package/src/helpers/rateLimiter.ts +11 -24
  37. package/src/helpers/settle.ts +1 -1
  38. package/src/helpers/transformData.ts +2 -1
  39. package/src/interceptors/interceptorManager.ts +26 -19
  40. package/src/types.ts +1 -0
@@ -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":"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","names":[]}
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"]}
@@ -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
- const preview = JSON.stringify(config.data);
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 const preview = JSON.stringify(config.data);\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;AAGA,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,UAAM,UAAU,KAAK,UAAU,OAAO,IAAI;AAC1C,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
+ {"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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,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,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":[]}
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
- let head = 0;
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 (pendingCount >= maxQueueSize) {
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[tail++] = { resolve, reject };
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
- active--;
65
- if (pendingCount > 0 && active < maxConcurrent) {
66
- active++;
67
- const next = queue[head];
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 (pendingCount > 0) {
78
- const next = queue[head];
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 pendingCount;
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 let head = 0;\n let tail = 0;\n let pendingCount = 0;\n const queue: Record<number, 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 (pendingCount >= 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[tail++] = { resolve, reject };\n pendingCount++;\n });\n }\n\n function release(): void {\n if (destroyed) return;\n\n if (active <= 0) return;\n\n active--;\n\n if (pendingCount > 0 && active < maxConcurrent) {\n active++;\n const next = queue[head];\n delete queue[head];\n head++;\n pendingCount--;\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 (pendingCount > 0) {\n const next = queue[head];\n delete queue[head];\n head++;\n pendingCount--;\n next?.reject(reason);\n }\n }\n\n return {\n acquire,\n release,\n destroy,\n get pending() {\n return pendingCount;\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,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,eAAe;AACnB,QAAM,QAAmC,CAAC;AAE1C,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,gBAAgB,cAAc;AAChC,aAAO,QAAQ;AAAA,QACb,IAAI,MAAM,6DAA6D,YAAY,GAAG;AAAA,MACxF;AAAA,IACF;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,MAAM,IAAI,EAAE,SAAS,OAAO;AAClC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AAEf,QAAI,UAAU,EAAG;AAEjB;AAEA,QAAI,eAAe,KAAK,SAAS,eAAe;AAC9C;AACA,YAAM,OAAO,MAAM,IAAI;AACvB,aAAO,MAAM,IAAI;AACjB;AACA;AACA,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,gBAAY;AACZ,UAAM,SAAS,IAAI,MAAM,oEAA+D;AACxF,WAAO,eAAe,GAAG;AACvB,YAAM,OAAO,MAAM,IAAI;AACvB,aAAO,MAAM,IAAI;AACjB;AACA;AACA,YAAM,OAAO,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,UAAU;AACZ,aAAO;AAAA,IACT;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":[]}
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":[]}
@@ -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 (!response.status || !validateStatus || validateStatus(response.status)) {
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 (!response.status || !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,SAAS,UAAU,CAAC,kBAAkB,eAAe,SAAS,MAAM,GAAG;AAC1E,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"]}
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,QACkB;AAClB,MAAI,CAAC,cAAc,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC7C,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AAEb,aAAW,aAAa,YAAY;AAClC,QAAI,OAAO,cAAc,YAAY;AACnC,UAAI;AACF,iBAAS,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC1C,SAAS,KAAK;AACZ,cAAM,qBAAAA,QAAc;AAAA,UAClB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,UAClD,qBAAAA,QAAc;AAAA,UACd,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["AccessioError"]}
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
- handlers;
27
- _activeCount;
26
+ _handlers;
27
+ _nextId;
28
28
  constructor() {
29
- this.handlers = [];
30
- this._activeCount = 0;
29
+ this._handlers = /* @__PURE__ */ new Map();
30
+ this._nextId = 0;
31
31
  }
32
32
  use(fulfilled, rejected, options = {}) {
33
- this.handlers.push({
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
- this._activeCount++;
40
- return this.handlers.length - 1;
40
+ return id;
41
41
  }
42
42
  eject(id) {
43
- if (this.handlers[id]) {
44
- this.handlers[id] = null;
45
- this._activeCount--;
46
- }
43
+ this._handlers.delete(id);
47
44
  }
48
45
  clear() {
49
- this.handlers = [];
50
- this._activeCount = 0;
46
+ this._handlers.clear();
51
47
  }
52
48
  forEach(fn) {
53
- for (const handler of this.handlers) {
54
- if (handler !== null) {
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._activeCount;
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 handlers: Array<InterceptorHandler | null>;\n private _activeCount: number;\n\n constructor() {\n this.handlers = [];\n this._activeCount = 0;\n }\n\n use(\n fulfilled: TransformFunction | null,\n rejected?: ((error: unknown) => unknown) | null,\n options: InterceptorOptions = {},\n ): number {\n this.handlers.push({\n fulfilled: fulfilled || null,\n rejected: rejected || null,\n synchronous: options.synchronous || false,\n runWhen: options.runWhen || null,\n });\n\n this._activeCount++;\n return this.handlers.length - 1;\n }\n\n eject(id: number): void {\n if (this.handlers[id]) {\n this.handlers[id] = null;\n this._activeCount--;\n }\n }\n\n clear(): void {\n this.handlers = [];\n this._activeCount = 0;\n }\n\n forEach(fn: (handler: InterceptorHandler) => void): void {\n for (const handler of this.handlers) {\n if (handler !== null) {\n fn(handler);\n }\n }\n }\n\n get size(): number {\n return this._activeCount;\n }\n}\n\nexport default InterceptorManager;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACQ;AAAA,EAER,cAAc;AACZ,SAAK,WAAW,CAAC;AACjB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,IACE,WACA,UACA,UAA8B,CAAC,GACvB;AACR,SAAK,SAAS,KAAK;AAAA,MACjB,WAAW,aAAa;AAAA,MACxB,UAAU,YAAY;AAAA,MACtB,aAAa,QAAQ,eAAe;AAAA,MACpC,SAAS,QAAQ,WAAW;AAAA,IAC9B,CAAC;AAED,SAAK;AACL,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC;AAAA,EAEA,MAAM,IAAkB;AACtB,QAAI,KAAK,SAAS,EAAE,GAAG;AACrB,WAAK,SAAS,EAAE,IAAI;AACpB,WAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,WAAW,CAAC;AACjB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,QAAQ,IAAiD;AACvD,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,YAAY,MAAM;AACpB,WAAG,OAAO;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AACF;AAEA,IAAO,6BAAQ;","names":[]}
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, string>;
11
- get?: Record<string, string>;
12
- post?: Record<string, string>;
13
- put?: Record<string, string>;
14
- patch?: Record<string, string>;
15
- delete?: Record<string, string>;
16
- head?: Record<string, string>;
17
- options?: Record<string, string>;
18
- /** Any additional custom headers */
19
- [key: string]: string | number | boolean | Record<string, string> | undefined;
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?: Array<
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?: Array<
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, 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: Error, config?: AccessioRequestConfig): void;
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.2.0",
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
+ }