accessio 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/accessio.cjs +76 -10
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +24 -26
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/fetchAdapter.cjs +9 -1
- package/cjs/core/fetchAdapter.cjs.map +1 -1
- package/cjs/core/mergeConfig.cjs +14 -9
- package/cjs/core/mergeConfig.cjs.map +1 -1
- package/cjs/core/request.cjs +1 -1
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/defaults/transforms.cjs +4 -1
- package/cjs/defaults/transforms.cjs.map +1 -1
- package/cjs/helpers/flattenHeaders.cjs +40 -15
- package/cjs/helpers/flattenHeaders.cjs.map +1 -1
- package/cjs/helpers/memoryCache.cjs +14 -2
- package/cjs/helpers/memoryCache.cjs.map +1 -1
- package/cjs/helpers/parseHeaders.cjs +9 -6
- package/cjs/helpers/parseHeaders.cjs.map +1 -1
- package/cjs/helpers/transformData.cjs +1 -1
- package/cjs/helpers/transformData.cjs.map +1 -1
- package/package.json +4 -1
- package/src/accessio.ts +78 -14
- package/src/core/buildURL.ts +28 -30
- package/src/core/fetchAdapter.ts +14 -1
- package/src/core/mergeConfig.ts +14 -10
- package/src/core/request.ts +1 -1
- package/src/defaults/transforms.ts +4 -1
- package/src/helpers/flattenHeaders.ts +41 -15
- package/src/helpers/memoryCache.ts +18 -4
- package/src/helpers/parseHeaders.ts +10 -7
- package/src/helpers/transformData.ts +1 -1
- package/src/types.ts +1 -0
|
@@ -72,39 +72,64 @@ const METHOD_KEYS = /* @__PURE__ */ new Set([
|
|
|
72
72
|
function flattenHeaders(headers, method) {
|
|
73
73
|
if (!headers) return {};
|
|
74
74
|
const merged = {};
|
|
75
|
+
const lowerKeys = {};
|
|
75
76
|
const methodLower = (method || "get").toLowerCase();
|
|
77
|
+
const setHeader = (target, key, value) => {
|
|
78
|
+
const keyLower = key.toLowerCase();
|
|
79
|
+
const existingKey = lowerKeys[keyLower];
|
|
80
|
+
if (existingKey !== void 0) {
|
|
81
|
+
delete target[existingKey];
|
|
82
|
+
}
|
|
83
|
+
target[key] = value;
|
|
84
|
+
lowerKeys[keyLower] = key;
|
|
85
|
+
};
|
|
76
86
|
if (headers["common"]) {
|
|
77
|
-
|
|
87
|
+
for (const key in headers["common"]) {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(headers["common"], key)) {
|
|
89
|
+
setHeader(merged, key, headers["common"][key]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
78
92
|
}
|
|
79
93
|
if (headers[methodLower]) {
|
|
80
|
-
|
|
94
|
+
for (const key in headers[methodLower]) {
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(headers[methodLower], key)) {
|
|
96
|
+
setHeader(merged, key, headers[methodLower][key]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
81
99
|
}
|
|
82
100
|
for (const key in headers) {
|
|
83
101
|
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
84
|
-
merged
|
|
102
|
+
setHeader(merged, key, headers[key]);
|
|
85
103
|
}
|
|
86
104
|
}
|
|
87
105
|
return merged;
|
|
88
106
|
}
|
|
89
107
|
function removeContentType(headers) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
for (const key in headers) {
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
|
110
|
+
if (key.toLowerCase() === "content-type") {
|
|
111
|
+
delete headers[key];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
93
114
|
}
|
|
94
115
|
}
|
|
95
116
|
function buildFetchHeaders(headers) {
|
|
96
117
|
const fetchHeaders = new Headers();
|
|
97
|
-
for (const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
for (const key in headers) {
|
|
119
|
+
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
|
120
|
+
const value = headers[key];
|
|
121
|
+
if (value === void 0 || value === null) continue;
|
|
122
|
+
assertSafeHeader(key, value);
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
for (let i = 0; i < value.length; i++) {
|
|
125
|
+
const v = value[i];
|
|
126
|
+
if (v !== void 0 && v !== null) {
|
|
127
|
+
fetchHeaders.append(key, v);
|
|
128
|
+
}
|
|
104
129
|
}
|
|
130
|
+
} else {
|
|
131
|
+
fetchHeaders.set(key, value);
|
|
105
132
|
}
|
|
106
|
-
} else {
|
|
107
|
-
fetchHeaders.set(key, value);
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
return fetchHeaders;
|
|
@@ -1 +1 @@
|
|
|
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.
|
|
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 lowerKeys: Record<string, string> = {};\n const methodLower = (method || 'get').toLowerCase();\n\n const setHeader = (target: Record<string, string | string[]>, key: string, value: any) => {\n const keyLower = key.toLowerCase();\n const existingKey = lowerKeys[keyLower];\n if (existingKey !== undefined) {\n delete target[existingKey];\n }\n target[key] = value;\n lowerKeys[keyLower] = key;\n };\n\n if (headers['common']) {\n for (const key in headers['common']) {\n if (Object.prototype.hasOwnProperty.call(headers['common'], key)) {\n setHeader(merged, key, headers['common'][key]);\n }\n }\n }\n\n if (headers[methodLower]) {\n for (const key in headers[methodLower]) {\n if (Object.prototype.hasOwnProperty.call(headers[methodLower], key)) {\n setHeader(merged, key, headers[methodLower][key]);\n }\n }\n }\n\n for (const key in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {\n setHeader(merged, key, headers[key]);\n }\n }\n\n return merged;\n}\n\nexport function removeContentType(headers: Record<string, string | string[]>): void {\n for (const key in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, key)) {\n if (key.toLowerCase() === 'content-type') {\n delete headers[key];\n }\n }\n }\n}\n\nexport function buildFetchHeaders(headers: Record<string, string | string[]>): Headers {\n const fetchHeaders = new Headers();\n for (const key in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, key)) {\n const value = headers[key];\n if (value === undefined || value === null) continue;\n assertSafeHeader(key, value);\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const v = value[i];\n if (v !== undefined && v !== null) {\n fetchHeaders.append(key, v);\n }\n }\n } else {\n fetchHeaders.set(key, value);\n }\n }\n }\n return fetchHeaders;\n}\n"],"mappings":"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","names":["AccessioError"]}
|
|
@@ -39,10 +39,22 @@ class MemoryCache {
|
|
|
39
39
|
}
|
|
40
40
|
set(key, value, ttl) {
|
|
41
41
|
const now = Date.now();
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (this.cache.has(key)) {
|
|
43
|
+
const expiry2 = ttl ? now + ttl : null;
|
|
44
|
+
this.cache.set(key, { value, expiry: expiry2 });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let count = 0;
|
|
48
|
+
const keys = this.cache.keys();
|
|
49
|
+
while (count < 5) {
|
|
50
|
+
const next = keys.next();
|
|
51
|
+
if (next.done) break;
|
|
52
|
+
const k = next.value;
|
|
53
|
+
const item = this.cache.get(k);
|
|
54
|
+
if (item && item.expiry && now > item.expiry) {
|
|
44
55
|
this.cache.delete(k);
|
|
45
56
|
}
|
|
57
|
+
count++;
|
|
46
58
|
}
|
|
47
59
|
if (this.cache.size >= this.maxItems) {
|
|
48
60
|
const oldest = this.cache.keys().next().value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/memoryCache.ts"],"sourcesContent":["import type { CacheProvider } from '../types';\n\nclass MemoryCache implements CacheProvider {\n private cache = new Map<string, { value: any; expiry: number | null }>();\n private maxItems: number;\n\n constructor(maxItems: number = 1000) {\n this.maxItems = maxItems;\n }\n\n get(key: string) {\n const item = this.cache.get(key);\n if (!item) return null;\n if (item.expiry && Date.now() > item.expiry) {\n this.cache.delete(key);\n return null;\n }\n return item.value;\n }\n\n set(key: string, value: any, ttl?: number) {\n const now = Date.now();\n\n // Proactively evict
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/memoryCache.ts"],"sourcesContent":["import type { CacheProvider } from '../types';\n\nclass MemoryCache implements CacheProvider {\n private cache = new Map<string, { value: any; expiry: number | null }>();\n private maxItems: number;\n\n constructor(maxItems: number = 1000) {\n this.maxItems = maxItems;\n }\n\n get(key: string) {\n const item = this.cache.get(key);\n if (!item) return null;\n if (item.expiry && Date.now() > item.expiry) {\n this.cache.delete(key);\n return null;\n }\n return item.value;\n }\n\n set(key: string, value: any, ttl?: number) {\n const now = Date.now();\n\n if (this.cache.has(key)) {\n const expiry = ttl ? now + ttl : null;\n this.cache.set(key, { value, expiry });\n return;\n }\n\n // Proactively evict up to 5 of the oldest items to prevent memory build-up without O(N) cost.\n // Map entries are ordered by insertion, so the oldest are checked first.\n let count = 0;\n const keys = this.cache.keys();\n while (count < 5) {\n const next = keys.next();\n if (next.done) break;\n const k = next.value;\n const item = this.cache.get(k);\n if (item && item.expiry && now > item.expiry) {\n this.cache.delete(k);\n }\n count++;\n }\n\n // Evict the oldest item if we are still at limit\n if (this.cache.size >= this.maxItems) {\n const oldest = this.cache.keys().next().value;\n if (oldest !== undefined) {\n this.cache.delete(oldest);\n }\n }\n\n const expiry = ttl ? now + ttl : null;\n this.cache.set(key, { value, expiry });\n }\n\n delete(key: string) {\n this.cache.delete(key);\n }\n\n clear() {\n this.cache.clear();\n }\n}\n\nexport const defaultMemoryCache = new MemoryCache();\nexport { MemoryCache };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,MAAM,YAAqC;AAAA,EACjC,QAAQ,oBAAI,IAAmD;AAAA,EAC/D;AAAA,EAER,YAAY,WAAmB,KAAM;AACnC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAI,KAAa;AACf,UAAM,OAAO,KAAK,MAAM,IAAI,GAAG;AAC/B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,UAAU,KAAK,IAAI,IAAI,KAAK,QAAQ;AAC3C,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,KAAa,OAAY,KAAc;AACzC,UAAM,MAAM,KAAK,IAAI;AAErB,QAAI,KAAK,MAAM,IAAI,GAAG,GAAG;AACvB,YAAMA,UAAS,MAAM,MAAM,MAAM;AACjC,WAAK,MAAM,IAAI,KAAK,EAAE,OAAO,QAAAA,QAAO,CAAC;AACrC;AAAA,IACF;AAIA,QAAI,QAAQ;AACZ,UAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,WAAO,QAAQ,GAAG;AAChB,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,KAAK,KAAM;AACf,YAAM,IAAI,KAAK;AACf,YAAM,OAAO,KAAK,MAAM,IAAI,CAAC;AAC7B,UAAI,QAAQ,KAAK,UAAU,MAAM,KAAK,QAAQ;AAC5C,aAAK,MAAM,OAAO,CAAC;AAAA,MACrB;AACA;AAAA,IACF;AAGA,QAAI,KAAK,MAAM,QAAQ,KAAK,UAAU;AACpC,YAAM,SAAS,KAAK,MAAM,KAAK,EAAE,KAAK,EAAE;AACxC,UAAI,WAAW,QAAW;AACxB,aAAK,MAAM,OAAO,MAAM;AAAA,MAC1B;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,MAAM,MAAM;AACjC,SAAK,MAAM,IAAI,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,EACvC;AAAA,EAEA,OAAO,KAAa;AAClB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,QAAQ;AACN,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;AAEO,MAAM,qBAAqB,IAAI,YAAY;","names":["expiry"]}
|
|
@@ -26,14 +26,17 @@ function parseHeaders(headers) {
|
|
|
26
26
|
if (!headers) return parsed;
|
|
27
27
|
const addHeader = (key, value) => {
|
|
28
28
|
const k = key.toLowerCase();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const values = Array.isArray(value) ? value : [value];
|
|
30
|
+
for (const val of values) {
|
|
31
|
+
if (parsed[k]) {
|
|
32
|
+
if (Array.isArray(parsed[k])) {
|
|
33
|
+
parsed[k].push(val);
|
|
34
|
+
} else {
|
|
35
|
+
parsed[k] = [parsed[k], val];
|
|
36
|
+
}
|
|
32
37
|
} else {
|
|
33
|
-
parsed[k] =
|
|
38
|
+
parsed[k] = val;
|
|
34
39
|
}
|
|
35
|
-
} else {
|
|
36
|
-
parsed[k] = value;
|
|
37
40
|
}
|
|
38
41
|
};
|
|
39
42
|
if (typeof headers.forEach === "function") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/parseHeaders.ts"],"sourcesContent":["export default function parseHeaders(headers: any): Record<string, string | string[]> {\n const parsed: Record<string, string | string[]> = {};\n\n if (!headers) return parsed;\n\n const addHeader = (key: string, value: string) => {\n const k = key.toLowerCase();\n if (parsed[k]) {\n
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/parseHeaders.ts"],"sourcesContent":["export default function parseHeaders(headers: any): Record<string, string | string[]> {\n const parsed: Record<string, string | string[]> = {};\n\n if (!headers) return parsed;\n\n const addHeader = (key: string, value: string | string[]) => {\n const k = key.toLowerCase();\n const values = Array.isArray(value) ? value : [value];\n for (const val of values) {\n if (parsed[k]) {\n if (Array.isArray(parsed[k])) {\n (parsed[k] as string[]).push(val);\n } else {\n parsed[k] = [parsed[k] as string, val];\n }\n } else {\n parsed[k] = val;\n }\n }\n };\n\n if (typeof headers.forEach === 'function') {\n headers.forEach((value: string, key: string) => {\n addHeader(key, value);\n });\n return parsed;\n }\n\n if (typeof headers === 'string') {\n headers.split('\\n').forEach((line: string) => {\n const index = line.indexOf(':');\n if (index > 0) {\n const key = line.substring(0, index).trim();\n const value = line.substring(index + 1).trim();\n addHeader(key, value);\n }\n });\n return parsed;\n }\n\n if (typeof headers === 'object') {\n Object.keys(headers).forEach((key) => {\n addHeader(key, headers[key]);\n });\n return parsed;\n }\n\n return parsed;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAe,SAAR,aAA8B,SAAiD;AACpF,QAAM,SAA4C,CAAC;AAEnD,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,YAAY,CAAC,KAAa,UAA6B;AAC3D,UAAM,IAAI,IAAI,YAAY;AAC1B,UAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACpD,eAAW,OAAO,QAAQ;AACxB,UAAI,OAAO,CAAC,GAAG;AACb,YAAI,MAAM,QAAQ,OAAO,CAAC,CAAC,GAAG;AAC5B,UAAC,OAAO,CAAC,EAAe,KAAK,GAAG;AAAA,QAClC,OAAO;AACL,iBAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAa,GAAG;AAAA,QACvC;AAAA,MACF,OAAO;AACL,eAAO,CAAC,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,QAAQ,YAAY,YAAY;AACzC,YAAQ,QAAQ,CAAC,OAAe,QAAgB;AAC9C,gBAAU,KAAK,KAAK;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,YAAQ,MAAM,IAAI,EAAE,QAAQ,CAAC,SAAiB;AAC5C,YAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,UAAI,QAAQ,GAAG;AACb,cAAM,MAAM,KAAK,UAAU,GAAG,KAAK,EAAE,KAAK;AAC1C,cAAM,QAAQ,KAAK,UAAU,QAAQ,CAAC,EAAE,KAAK;AAC7C,kBAAU,KAAK,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AACpC,gBAAU,KAAK,QAAQ,GAAG,CAAC;AAAA,IAC7B,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -40,7 +40,7 @@ async function transformData(transforms, data, headers, config, direction = "req
|
|
|
40
40
|
for (const transform of transforms) {
|
|
41
41
|
if (typeof transform === "function") {
|
|
42
42
|
try {
|
|
43
|
-
result = await transform(result, headers);
|
|
43
|
+
result = await transform(result, headers, config);
|
|
44
44
|
} catch (err) {
|
|
45
45
|
throw import_accessioError.default.from(
|
|
46
46
|
err instanceof Error ? err : new Error(String(err)),
|
|
@@ -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 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,
|
|
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, config);\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,SAAS,MAAM;AAAA,MAClD,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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accessio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Fast, flexible HTTP client — simple, modular, and dependency-free",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./cjs/index.cjs",
|
|
@@ -106,5 +106,8 @@
|
|
|
106
106
|
"typescript": "^5.0.0",
|
|
107
107
|
"typescript-eslint": "^8.59.3",
|
|
108
108
|
"vitest": "^4.1.8"
|
|
109
|
+
},
|
|
110
|
+
"overrides": {
|
|
111
|
+
"esbuild": "^0.28.1"
|
|
109
112
|
}
|
|
110
113
|
}
|
package/src/accessio.ts
CHANGED
|
@@ -18,7 +18,7 @@ import defaultsConfig from './defaults/index';
|
|
|
18
18
|
function runRequestInterceptorsSync(
|
|
19
19
|
startConfig: AccessioRequestConfig,
|
|
20
20
|
interceptors: InterceptorHandler[],
|
|
21
|
-
):
|
|
21
|
+
): AccessioRequestConfig {
|
|
22
22
|
let cfg = startConfig;
|
|
23
23
|
let rejectReason: any = null;
|
|
24
24
|
let isRejected = false;
|
|
@@ -44,7 +44,10 @@ function runRequestInterceptorsSync(
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
if (isRejected) {
|
|
48
|
+
throw rejectReason;
|
|
49
|
+
}
|
|
50
|
+
return cfg;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
function runRequestInterceptorsAsync(
|
|
@@ -65,7 +68,7 @@ function dispatchAndRetry(cfg: AccessioRequestConfig): Promise<AccessioResponse>
|
|
|
65
68
|
const fullUrl = buildURL(cfg.url ?? '', cfg.baseURL, cfg.params, cfg.paramsSerializer);
|
|
66
69
|
logRequest(cfg, fullUrl);
|
|
67
70
|
|
|
68
|
-
const enrichedCfg =
|
|
71
|
+
const enrichedCfg = { ...cfg, _builtUrl: fullUrl };
|
|
69
72
|
|
|
70
73
|
const dispatchFn = cfg.rateLimiter
|
|
71
74
|
? (config: AccessioRequestConfig) =>
|
|
@@ -114,11 +117,20 @@ export class Accessio {
|
|
|
114
117
|
const { requestInterceptors, responseInterceptors, synchronous } =
|
|
115
118
|
this.collectInterceptors(mergedConfig);
|
|
116
119
|
|
|
117
|
-
let promise: Promise<any
|
|
118
|
-
? runRequestInterceptorsSync(mergedConfig, requestInterceptors)
|
|
119
|
-
: runRequestInterceptorsAsync(mergedConfig, requestInterceptors);
|
|
120
|
+
let promise: Promise<any>;
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
if (synchronous) {
|
|
123
|
+
try {
|
|
124
|
+
const finalCfg = runRequestInterceptorsSync(mergedConfig, requestInterceptors);
|
|
125
|
+
promise = dispatchAndRetry(finalCfg);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
promise = Promise.reject(err);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
promise = runRequestInterceptorsAsync(mergedConfig, requestInterceptors).then(
|
|
131
|
+
(cfg: AccessioRequestConfig) => dispatchAndRetry(cfg),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
122
134
|
|
|
123
135
|
promise = promise.then(
|
|
124
136
|
(value: AccessioResponse) => {
|
|
@@ -131,13 +143,65 @@ export class Accessio {
|
|
|
131
143
|
},
|
|
132
144
|
);
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
promise = promise.then(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
if (responseInterceptors.length > 0) {
|
|
147
|
+
promise = promise.then(
|
|
148
|
+
async (value: AccessioResponse) => {
|
|
149
|
+
let current: any = value;
|
|
150
|
+
let isRejected = false;
|
|
151
|
+
for (const interceptor of responseInterceptors) {
|
|
152
|
+
if (!isRejected) {
|
|
153
|
+
try {
|
|
154
|
+
if (interceptor.fulfilled) {
|
|
155
|
+
current = await (interceptor.fulfilled as any)(current);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
current = err;
|
|
159
|
+
isRejected = true;
|
|
160
|
+
}
|
|
161
|
+
} else if (interceptor.rejected) {
|
|
162
|
+
try {
|
|
163
|
+
current = await interceptor.rejected(current);
|
|
164
|
+
isRejected = false;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
current = err;
|
|
167
|
+
isRejected = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (isRejected) {
|
|
172
|
+
throw current;
|
|
173
|
+
}
|
|
174
|
+
return current;
|
|
175
|
+
},
|
|
176
|
+
async (error: any) => {
|
|
177
|
+
let current: any = error;
|
|
178
|
+
let isRejected = true;
|
|
179
|
+
for (const interceptor of responseInterceptors) {
|
|
180
|
+
if (!isRejected) {
|
|
181
|
+
try {
|
|
182
|
+
if (interceptor.fulfilled) {
|
|
183
|
+
current = await (interceptor.fulfilled as any)(current);
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
current = err;
|
|
187
|
+
isRejected = true;
|
|
188
|
+
}
|
|
189
|
+
} else if (interceptor.rejected) {
|
|
190
|
+
try {
|
|
191
|
+
current = await interceptor.rejected(current);
|
|
192
|
+
isRejected = false;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
current = err;
|
|
195
|
+
isRejected = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (isRejected) {
|
|
200
|
+
throw current;
|
|
201
|
+
}
|
|
202
|
+
return current;
|
|
203
|
+
},
|
|
204
|
+
);
|
|
141
205
|
}
|
|
142
206
|
|
|
143
207
|
return promise;
|
package/src/core/buildURL.ts
CHANGED
|
@@ -50,17 +50,13 @@ function combineURLs(baseURL: string, relativeURL: string): string {
|
|
|
50
50
|
if (!baseURL) return relativeURL || '';
|
|
51
51
|
if (!relativeURL) return baseURL;
|
|
52
52
|
|
|
53
|
-
let
|
|
54
|
-
while (
|
|
55
|
-
base = base.slice(0, -1);
|
|
56
|
-
}
|
|
53
|
+
let baseEnd = baseURL.length;
|
|
54
|
+
while (baseEnd > 0 && baseURL[baseEnd - 1] === '/') baseEnd--;
|
|
57
55
|
|
|
58
|
-
let
|
|
59
|
-
while (
|
|
60
|
-
relative = relative.slice(1);
|
|
61
|
-
}
|
|
56
|
+
let relStart = 0;
|
|
57
|
+
while (relStart < relativeURL.length && relativeURL[relStart] === '/') relStart++;
|
|
62
58
|
|
|
63
|
-
return
|
|
59
|
+
return baseURL.slice(0, baseEnd) + '/' + relativeURL.slice(relStart);
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
function isAbsoluteURL(url: string): boolean {
|
|
@@ -77,28 +73,30 @@ export default function buildURL(
|
|
|
77
73
|
|
|
78
74
|
let finalParams = params;
|
|
79
75
|
if (params && typeof params === 'object' && !(params instanceof URLSearchParams)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
if (fullURL.includes('{') || /:[a-zA-Z_]/.test(fullURL)) {
|
|
77
|
+
const unusedParams: Record<string, unknown> = {};
|
|
78
|
+
for (const key of Object.keys(params)) {
|
|
79
|
+
if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;
|
|
80
|
+
unusedParams[key] = (params as Record<string, unknown>)[key];
|
|
81
|
+
}
|
|
82
|
+
fullURL = fullURL.replace(
|
|
83
|
+
/(?::([a-zA-Z_][a-zA-Z0-9_]*))|(?:{([a-zA-Z_][a-zA-Z0-9_]*)})/g,
|
|
84
|
+
(match, p1, p2) => {
|
|
85
|
+
const key = p1 || p2;
|
|
86
|
+
if (
|
|
87
|
+
key &&
|
|
88
|
+
Object.prototype.hasOwnProperty.call(unusedParams, key) &&
|
|
89
|
+
unusedParams[key] !== undefined
|
|
90
|
+
) {
|
|
91
|
+
const val = unusedParams[key];
|
|
92
|
+
delete unusedParams[key];
|
|
93
|
+
return encodeURIComponent(String(val));
|
|
94
|
+
}
|
|
95
|
+
return match;
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
finalParams = unusedParams;
|
|
84
99
|
}
|
|
85
|
-
fullURL = fullURL.replace(
|
|
86
|
-
/(?::([a-zA-Z_][a-zA-Z0-9_]*))|(?:{([a-zA-Z_][a-zA-Z0-9_]*)})/g,
|
|
87
|
-
(match, p1, p2) => {
|
|
88
|
-
const key = p1 || p2;
|
|
89
|
-
if (
|
|
90
|
-
key &&
|
|
91
|
-
Object.prototype.hasOwnProperty.call(unusedParams, key) &&
|
|
92
|
-
unusedParams[key] !== undefined
|
|
93
|
-
) {
|
|
94
|
-
const val = unusedParams[key];
|
|
95
|
-
delete unusedParams[key];
|
|
96
|
-
return encodeURIComponent(String(val));
|
|
97
|
-
}
|
|
98
|
-
return match;
|
|
99
|
-
},
|
|
100
|
-
);
|
|
101
|
-
finalParams = unusedParams;
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
const serialized = serializeParams(finalParams as Record<string, unknown>, paramsSerializer);
|
package/src/core/fetchAdapter.ts
CHANGED
|
@@ -14,6 +14,8 @@ async function readResponseData(
|
|
|
14
14
|
return await fetchResponse.blob();
|
|
15
15
|
case 'stream':
|
|
16
16
|
return fetchResponse.body;
|
|
17
|
+
case 'text':
|
|
18
|
+
return await fetchResponse.text();
|
|
17
19
|
case 'json':
|
|
18
20
|
default: {
|
|
19
21
|
const contentType = fetchResponse.headers.get('content-type') || '';
|
|
@@ -186,7 +188,18 @@ function classifyFetchError(
|
|
|
186
188
|
const isAbort =
|
|
187
189
|
(error instanceof Error && error.name === 'AbortError') || !!config.signal?.aborted;
|
|
188
190
|
if (isAbort) {
|
|
189
|
-
|
|
191
|
+
const reason = config.signal?.reason;
|
|
192
|
+
const message =
|
|
193
|
+
reason instanceof Error
|
|
194
|
+
? reason.message
|
|
195
|
+
: typeof reason === 'string'
|
|
196
|
+
? reason
|
|
197
|
+
: 'Request aborted';
|
|
198
|
+
const err = new AccessioError(message, AccessioError.ERR_CANCELED, config, null, null);
|
|
199
|
+
if (reason instanceof Error) {
|
|
200
|
+
err.cause = reason;
|
|
201
|
+
}
|
|
202
|
+
return err;
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
return AccessioError.from(
|
package/src/core/mergeConfig.ts
CHANGED
|
@@ -45,20 +45,24 @@ export default function mergeConfig(
|
|
|
45
45
|
): AccessioRequestConfig {
|
|
46
46
|
const merged: any = Object.create(null);
|
|
47
47
|
|
|
48
|
-
const
|
|
48
|
+
const keys1 = Object.keys(config1);
|
|
49
|
+
for (let i = 0; i < keys1.length; i++) {
|
|
50
|
+
const key = keys1[i];
|
|
51
|
+
if (requestOnlyKeys.has(key)) continue;
|
|
52
|
+
merged[key] = config1[key as keyof AccessioRequestConfig];
|
|
53
|
+
}
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
const keys2 = Object.keys(config2);
|
|
56
|
+
for (let i = 0; i < keys2.length; i++) {
|
|
57
|
+
const key = keys2[i];
|
|
52
58
|
const val2 = config2[key as keyof AccessioRequestConfig];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
if (val2 !== undefined) {
|
|
60
|
+
if (deepMergeKeys.has(key)) {
|
|
61
|
+
const val1 = config1[key as keyof AccessioRequestConfig];
|
|
62
|
+
merged[key] = deepMerge(val1 || {}, val2);
|
|
63
|
+
} else {
|
|
56
64
|
merged[key] = val2;
|
|
57
65
|
}
|
|
58
|
-
} else if (deepMergeKeys.has(key)) {
|
|
59
|
-
merged[key] = deepMerge(val1 || {}, val2 || {});
|
|
60
|
-
} else {
|
|
61
|
-
merged[key] = val2 !== undefined ? val2 : val1;
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
package/src/core/request.ts
CHANGED
|
@@ -113,7 +113,7 @@ export default async function dispatchRequest(
|
|
|
113
113
|
setBasicAuth(config, flatHeaders);
|
|
114
114
|
|
|
115
115
|
const isGet = (config.method || 'GET').toUpperCase() === 'GET';
|
|
116
|
-
const cacheKey = isGet ? buildCacheKey(config, fullURL, flatHeaders) : '';
|
|
116
|
+
const cacheKey = isGet && (config.cache || config.dedupe) ? buildCacheKey(config, fullURL, flatHeaders) : '';
|
|
117
117
|
|
|
118
118
|
if (isGet && config.cache) {
|
|
119
119
|
const cacheProvider = typeof config.cache === 'object' ? config.cache : defaultMemoryCache;
|
|
@@ -39,7 +39,10 @@ export function defaultTransformRequest(
|
|
|
39
39
|
return data;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function defaultTransformResponse(data: unknown): unknown {
|
|
42
|
+
export function defaultTransformResponse(data: unknown, headers?: any, config?: any): unknown {
|
|
43
|
+
if (config && config.responseType === 'text') {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
43
46
|
if (typeof data === 'string') {
|
|
44
47
|
try {
|
|
45
48
|
return JSON.parse(data);
|
|
@@ -47,19 +47,38 @@ export function flattenHeaders(
|
|
|
47
47
|
if (!headers) return {};
|
|
48
48
|
|
|
49
49
|
const merged: Record<string, string | string[]> = {};
|
|
50
|
+
const lowerKeys: Record<string, string> = {};
|
|
50
51
|
const methodLower = (method || 'get').toLowerCase();
|
|
51
52
|
|
|
53
|
+
const setHeader = (target: Record<string, string | string[]>, key: string, value: any) => {
|
|
54
|
+
const keyLower = key.toLowerCase();
|
|
55
|
+
const existingKey = lowerKeys[keyLower];
|
|
56
|
+
if (existingKey !== undefined) {
|
|
57
|
+
delete target[existingKey];
|
|
58
|
+
}
|
|
59
|
+
target[key] = value;
|
|
60
|
+
lowerKeys[keyLower] = key;
|
|
61
|
+
};
|
|
62
|
+
|
|
52
63
|
if (headers['common']) {
|
|
53
|
-
|
|
64
|
+
for (const key in headers['common']) {
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(headers['common'], key)) {
|
|
66
|
+
setHeader(merged, key, headers['common'][key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
if (headers[methodLower]) {
|
|
57
|
-
|
|
72
|
+
for (const key in headers[methodLower]) {
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(headers[methodLower], key)) {
|
|
74
|
+
setHeader(merged, key, headers[methodLower][key]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
for (const key in headers) {
|
|
61
80
|
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
62
|
-
merged
|
|
81
|
+
setHeader(merged, key, headers[key]);
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
|
|
@@ -67,25 +86,32 @@ export function flattenHeaders(
|
|
|
67
86
|
}
|
|
68
87
|
|
|
69
88
|
export function removeContentType(headers: Record<string, string | string[]>): void {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
for (const key in headers) {
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
|
91
|
+
if (key.toLowerCase() === 'content-type') {
|
|
92
|
+
delete headers[key];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
73
95
|
}
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
export function buildFetchHeaders(headers: Record<string, string | string[]>): Headers {
|
|
77
99
|
const fetchHeaders = new Headers();
|
|
78
|
-
for (const
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
for (const key in headers) {
|
|
101
|
+
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
|
102
|
+
const value = headers[key];
|
|
103
|
+
if (value === undefined || value === null) continue;
|
|
104
|
+
assertSafeHeader(key, value);
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
for (let i = 0; i < value.length; i++) {
|
|
107
|
+
const v = value[i];
|
|
108
|
+
if (v !== undefined && v !== null) {
|
|
109
|
+
fetchHeaders.append(key, v);
|
|
110
|
+
}
|
|
85
111
|
}
|
|
112
|
+
} else {
|
|
113
|
+
fetchHeaders.set(key, value);
|
|
86
114
|
}
|
|
87
|
-
} else {
|
|
88
|
-
fetchHeaders.set(key, value);
|
|
89
115
|
}
|
|
90
116
|
}
|
|
91
117
|
return fetchHeaders;
|
|
@@ -21,14 +21,28 @@ class MemoryCache implements CacheProvider {
|
|
|
21
21
|
set(key: string, value: any, ttl?: number) {
|
|
22
22
|
const now = Date.now();
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (this.cache.has(key)) {
|
|
25
|
+
const expiry = ttl ? now + ttl : null;
|
|
26
|
+
this.cache.set(key, { value, expiry });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Proactively evict up to 5 of the oldest items to prevent memory build-up without O(N) cost.
|
|
31
|
+
// Map entries are ordered by insertion, so the oldest are checked first.
|
|
32
|
+
let count = 0;
|
|
33
|
+
const keys = this.cache.keys();
|
|
34
|
+
while (count < 5) {
|
|
35
|
+
const next = keys.next();
|
|
36
|
+
if (next.done) break;
|
|
37
|
+
const k = next.value;
|
|
38
|
+
const item = this.cache.get(k);
|
|
39
|
+
if (item && item.expiry && now > item.expiry) {
|
|
27
40
|
this.cache.delete(k);
|
|
28
41
|
}
|
|
42
|
+
count++;
|
|
29
43
|
}
|
|
30
44
|
|
|
31
|
-
// Evict oldest item if we are still at limit
|
|
45
|
+
// Evict the oldest item if we are still at limit
|
|
32
46
|
if (this.cache.size >= this.maxItems) {
|
|
33
47
|
const oldest = this.cache.keys().next().value;
|
|
34
48
|
if (oldest !== undefined) {
|