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.
@@ -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
- Object.assign(merged, headers["common"]);
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
- Object.assign(merged, headers[methodLower]);
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[key] = headers[key];
102
+ setHeader(merged, key, headers[key]);
85
103
  }
86
104
  }
87
105
  return merged;
88
106
  }
89
107
  function removeContentType(headers) {
90
- const keys = Object.keys(headers).filter((k) => k.toLowerCase() === "content-type");
91
- for (const key of keys) {
92
- delete headers[key];
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 [key, value] of Object.entries(headers)) {
98
- if (value === void 0 || value === null) continue;
99
- assertSafeHeader(key, value);
100
- if (Array.isArray(value)) {
101
- for (const v of value) {
102
- if (v !== void 0 && v !== null) {
103
- fetchHeaders.append(key, v);
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.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 (value === undefined || value === null) continue;\n assertSafeHeader(key, value);\n if (Array.isArray(value)) {\n for (const v of value) {\n if (v !== undefined && v !== null) {\n fetchHeaders.append(key, v);\n }\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,QAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,qBAAiB,KAAK,KAAK;AAC3B,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,KAAK,OAAO;AACrB,YAAI,MAAM,UAAa,MAAM,MAAM;AACjC,uBAAa,OAAO,KAAK,CAAC;AAAA,QAC5B;AAAA,MACF;AAAA,IACF,OAAO;AACL,mBAAa,IAAI,KAAK,KAAK;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;","names":["AccessioError"]}
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
- for (const [k, item] of this.cache.entries()) {
43
- if (item.expiry && now > item.expiry) {
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 all expired items first\n for (const [k, item] of this.cache.entries()) {\n if (item.expiry && now > item.expiry) {\n this.cache.delete(k);\n }\n }\n\n // Evict 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;AAGrB,eAAW,CAAC,GAAG,IAAI,KAAK,KAAK,MAAM,QAAQ,GAAG;AAC5C,UAAI,KAAK,UAAU,MAAM,KAAK,QAAQ;AACpC,aAAK,MAAM,OAAO,CAAC;AAAA,MACrB;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":[]}
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
- if (parsed[k]) {
30
- if (Array.isArray(parsed[k])) {
31
- parsed[k].push(value);
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] = [parsed[k], value];
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 if (Array.isArray(parsed[k])) {\n (parsed[k] as string[]).push(value);\n } else {\n parsed[k] = [parsed[k] as string, value];\n }\n } else {\n parsed[k] = value;\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,UAAkB;AAChD,UAAM,IAAI,IAAI,YAAY;AAC1B,QAAI,OAAO,CAAC,GAAG;AACb,UAAI,MAAM,QAAQ,OAAO,CAAC,CAAC,GAAG;AAC5B,QAAC,OAAO,CAAC,EAAe,KAAK,KAAK;AAAA,MACpC,OAAO;AACL,eAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAa,KAAK;AAAA,MACzC;AAAA,IACF,OAAO;AACL,aAAO,CAAC,IAAI;AAAA,IACd;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":[]}
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,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"]}
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.6.0",
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
- ): Promise<AccessioRequestConfig> {
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
- return isRejected ? Promise.reject(rejectReason) : Promise.resolve(cfg);
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 = fullUrl !== (cfg.url || '') ? { ...cfg, _builtUrl: fullUrl } : cfg;
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> = synchronous
118
- ? runRequestInterceptorsSync(mergedConfig, requestInterceptors)
119
- : runRequestInterceptorsAsync(mergedConfig, requestInterceptors);
120
+ let promise: Promise<any>;
120
121
 
121
- promise = promise.then((cfg: AccessioRequestConfig) => dispatchAndRetry(cfg));
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
- for (const interceptor of responseInterceptors) {
135
- promise = promise.then((value: any) => {
136
- if (interceptor.fulfilled) {
137
- return (interceptor.fulfilled as any)(value);
138
- }
139
- return value;
140
- }, interceptor.rejected ?? undefined);
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;
@@ -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 base = baseURL;
54
- while (base.endsWith('/')) {
55
- base = base.slice(0, -1);
56
- }
53
+ let baseEnd = baseURL.length;
54
+ while (baseEnd > 0 && baseURL[baseEnd - 1] === '/') baseEnd--;
57
55
 
58
- let relative = relativeURL;
59
- while (relative.startsWith('/')) {
60
- relative = relative.slice(1);
61
- }
56
+ let relStart = 0;
57
+ while (relStart < relativeURL.length && relativeURL[relStart] === '/') relStart++;
62
58
 
63
- return `${base}/${relative}`;
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
- const unusedParams: Record<string, unknown> = {};
81
- for (const key of Object.keys(params)) {
82
- if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;
83
- unusedParams[key] = (params as Record<string, unknown>)[key];
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);
@@ -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
- return new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
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(
@@ -45,20 +45,24 @@ export default function mergeConfig(
45
45
  ): AccessioRequestConfig {
46
46
  const merged: any = Object.create(null);
47
47
 
48
- const allKeys = new Set<string>([...Object.keys(config1), ...Object.keys(config2)]);
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
- for (const key of allKeys) {
51
- const val1 = config1[key as keyof AccessioRequestConfig];
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
- if (requestOnlyKeys.has(key)) {
55
- if (val2 !== undefined) {
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
 
@@ -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
- Object.assign(merged, headers['common']);
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
- Object.assign(merged, headers[methodLower]);
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[key] = headers[key] as unknown as string | string[];
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 keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');
71
- for (const key of keys) {
72
- delete headers[key];
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 [key, value] of Object.entries(headers)) {
79
- if (value === undefined || value === null) continue;
80
- assertSafeHeader(key, value);
81
- if (Array.isArray(value)) {
82
- for (const v of value) {
83
- if (v !== undefined && v !== null) {
84
- fetchHeaders.append(key, v);
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
- // Proactively evict all expired items first
25
- for (const [k, item] of this.cache.entries()) {
26
- if (item.expiry && now > item.expiry) {
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) {