accessio 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -207
- package/cjs/core/accessioError.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +11 -8
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/mergeConfig.cjs +1 -1
- package/cjs/core/mergeConfig.cjs.map +1 -1
- package/cjs/core/request.cjs +43 -15
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/helpers/memoryCache.cjs +19 -1
- package/cjs/helpers/memoryCache.cjs.map +1 -1
- package/index.d.ts +7 -0
- package/package.json +4 -3
- package/src/core/accessioError.ts +2 -2
- package/src/core/buildURL.ts +16 -13
- package/src/core/mergeConfig.ts +1 -1
- package/src/core/request.ts +54 -16
- package/src/helpers/memoryCache.ts +24 -1
- package/src/types.ts +5 -0
package/README.md
CHANGED
|
@@ -2,298 +2,256 @@
|
|
|
2
2
|
|
|
3
3
|
**Fast, flexible HTTP client — simple, modular, and dependency-free.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Accessio is a lightweight, modern HTTP client built on top of the native fetch API. It provides a familiar, Promise-based interface with advanced features like interceptors, automatic retries, rate limiting, and structured debug logging, all while maintaining zero external dependencies.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- 📚 **Auto-Pagination** — seamlessly iterate through paginated APIs via `autoPaginate`
|
|
28
|
-
- 🛡️ **Schema Validation** — validate responses automatically using Zod or custom schemas
|
|
29
|
-
- 🗂️ **Caching & Deduplication** — prevent redundant requests and cache responses
|
|
30
|
-
- 🪝 **Lifecycle Hooks** — simple hooks for request/response/error events
|
|
9
|
+
## ⚡ Key Features
|
|
10
|
+
|
|
11
|
+
- **Zero Runtime Dependencies**: Keep your bundle size minimal and avoid supply chain vulnerabilities.
|
|
12
|
+
- **Axios-Compatible API**: Direct drop-in replacement with `.get()`, `.post()`, `.request()`, and custom instances via `.create()`.
|
|
13
|
+
- **🛡️ Built-in Security & Auto-Redaction**: Prevents accidental leakage of secrets in error logs. Automatically redacts:
|
|
14
|
+
- `Authorization`, `Cookie`, and `Set-Cookie` headers.
|
|
15
|
+
- Sensitive request/response parameters (e.g., `api_key`, `token`, `password`, `secret`).
|
|
16
|
+
- Inline credentials inside URLs.
|
|
17
|
+
- **⏳ Concurrency Rate Limiter**: Built-in queue-based rate limiter to throttle concurrent requests, complete with immediate queue ejection on `AbortSignal` cancellation.
|
|
18
|
+
- **🔁 Jittered Exponential Backoff Retry**: Automatic retries for network failures or `5xx` status codes. Fully respects the HTTP 429 `Retry-After` header and supports customizable retry conditions and callbacks.
|
|
19
|
+
- **🌊 SSE & Newline JSON Streaming**: Native asynchronous generator-based parsing for Server-Sent Events (SSE) and newline-delimited JSON streams.
|
|
20
|
+
- **📂 Auto-Pagination**: Seamlessly yields paginated items from APIs using page links (e.g., `next` or `links.next`).
|
|
21
|
+
- **🧪 Type-safe Schema Validation**: Validate API response payloads at runtime using Zod, ArkType, or any validation library with a `.parse()` or `.parseAsync()` method.
|
|
22
|
+
- **📦 Request Deduplication**: Automatically coalesces concurrent duplicate GET requests to avoid redundant network traffic.
|
|
23
|
+
- **💾 Memory Caching**: In-memory caching out-of-the-box with custom TTL, or easily swap in a custom storage provider (e.g., Redis, LocalStorage).
|
|
24
|
+
- **🔗 Synchronous & Asynchronous Interceptors**: Hook into the request/response pipeline to dynamically inject headers, handle global errors, or log metrics.
|
|
25
|
+
- **⚓ Lifecycle Hooks**: Granular callbacks (`onBeforeRequest`, `onRequestResponse`, `onRequestError`) for custom instrumentation.
|
|
26
|
+
- **FormData Serialization**: Automatically converts flat or nested JS objects to `FormData` for multipart submissions.
|
|
31
27
|
|
|
32
28
|
---
|
|
33
29
|
|
|
34
30
|
## 📦 Installation
|
|
35
31
|
|
|
36
32
|
```bash
|
|
37
|
-
# Using npm
|
|
38
33
|
npm install accessio
|
|
39
|
-
|
|
40
|
-
# Using yarn
|
|
41
|
-
yarn add accessio
|
|
42
|
-
|
|
43
|
-
# Using pnpm
|
|
44
|
-
pnpm add accessio
|
|
45
34
|
```
|
|
46
35
|
|
|
47
36
|
---
|
|
48
37
|
|
|
49
38
|
## 🚀 Quick Start
|
|
50
39
|
|
|
40
|
+
### Basic Requests
|
|
41
|
+
|
|
51
42
|
```typescript
|
|
52
43
|
import accessio from 'accessio';
|
|
53
44
|
|
|
54
45
|
// Simple GET request
|
|
55
|
-
const
|
|
46
|
+
const response = await accessio.get('https://api.example.com/users/123');
|
|
47
|
+
console.log(response.data); // Automatically parsed JSON response
|
|
56
48
|
|
|
57
|
-
// POST request with
|
|
58
|
-
const
|
|
59
|
-
name: '
|
|
49
|
+
// POST request with body
|
|
50
|
+
const postResponse = await accessio.post('https://api.example.com/users', {
|
|
51
|
+
name: 'Jane Doe',
|
|
60
52
|
role: 'Developer',
|
|
61
53
|
});
|
|
62
|
-
|
|
63
|
-
console.log(`User created in ${response.duration}ms`);
|
|
64
54
|
```
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
## 📖 API Reference
|
|
69
|
-
|
|
70
|
-
### Request Methods
|
|
71
|
-
|
|
72
|
-
| Method | Description |
|
|
73
|
-
| :----------------------------------------- | :--------------------------------------- |
|
|
74
|
-
| `accessio(config)` | Generic request using config object |
|
|
75
|
-
| `accessio.get(url, config?)` | GET request |
|
|
76
|
-
| `accessio.post(url, data?, config?)` | POST request |
|
|
77
|
-
| `accessio.put(url, data?, config?)` | PUT request |
|
|
78
|
-
| `accessio.patch(url, data?, config?)` | PATCH request |
|
|
79
|
-
| `accessio.delete(url, config?)` | DELETE request |
|
|
80
|
-
| `accessio.head(url, config?)` | HEAD request |
|
|
81
|
-
| `accessio.options(url, config?)` | OPTIONS request |
|
|
82
|
-
| `accessio.postForm(url, data?, config?)` | POST request with `multipart/form-data` |
|
|
83
|
-
| `accessio.putForm(url, data?, config?)` | PUT request with `multipart/form-data` |
|
|
84
|
-
| `accessio.patchForm(url, data?, config?)` | PATCH request with `multipart/form-data` |
|
|
85
|
-
| `accessio.stream(url, config?)` | Server-Sent Events (SSE) streaming |
|
|
86
|
-
| `accessio.autoPaginate(url, config?)` | Async iterator for paginated endpoints |
|
|
87
|
-
| `accessio.gql(url, query, vars?, config?)` | GraphQL query/mutation wrapper |
|
|
88
|
-
|
|
89
|
-
### Configuration Options
|
|
56
|
+
### Custom Instances
|
|
90
57
|
|
|
91
58
|
```typescript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
rateLimiter: limiter, // Concurrency limiter instance
|
|
106
|
-
validateStatus: (s) => s < 400, // Resolve/reject predicate
|
|
107
|
-
signal: abortController.signal, // Custom AbortSignal
|
|
108
|
-
dedupe: true, // Prevent duplicate in-flight requests
|
|
109
|
-
cache: true, // Cache responses (boolean or CacheProvider)
|
|
110
|
-
cacheTTL: 60000, // Cache time-to-live in ms
|
|
111
|
-
schema: z.object({...}), // Schema validator (e.g., Zod)
|
|
112
|
-
fetch: customFetch, // Custom fetch implementation
|
|
113
|
-
retryOn429: true, // Automatically retry on rate limits
|
|
114
|
-
hooks: { // Lifecycle hooks
|
|
115
|
-
onBeforeRequest: (config) => {},
|
|
116
|
-
onRequestResponse: (response) => {},
|
|
117
|
-
onRequestError: (error) => {}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
59
|
+
import accessio from 'accessio';
|
|
60
|
+
|
|
61
|
+
// Create a configured instance
|
|
62
|
+
const api = accessio.create({
|
|
63
|
+
baseURL: 'https://api.example.com/v1',
|
|
64
|
+
headers: {
|
|
65
|
+
'X-Client-Name': 'AccessioClient',
|
|
66
|
+
},
|
|
67
|
+
timeout: 5000, // 5-second timeout
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Use instance methods
|
|
71
|
+
const { data } = await api.get('/users');
|
|
120
72
|
```
|
|
121
73
|
|
|
122
74
|
---
|
|
123
75
|
|
|
124
|
-
##
|
|
76
|
+
## 🛠️ Advanced Features
|
|
125
77
|
|
|
126
|
-
###
|
|
78
|
+
### 🛡️ Auto-Redaction (Zero-leak Logs)
|
|
127
79
|
|
|
128
|
-
|
|
80
|
+
Accessio is built with security first. If a request fails, sensitive credentials in request/response properties are redacted automatically before being attached to the `AccessioError`.
|
|
129
81
|
|
|
130
82
|
```typescript
|
|
131
|
-
|
|
132
|
-
accessio.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
83
|
+
try {
|
|
84
|
+
await accessio.get('https://admin:secret_password@api.example.com/users', {
|
|
85
|
+
params: { api_key: 'super_secret_token_123' },
|
|
86
|
+
headers: { Authorization: 'Bearer token_xyz' },
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (accessio.isAccessioError(error)) {
|
|
90
|
+
console.error(error.toJSON());
|
|
91
|
+
/*
|
|
92
|
+
Outputs:
|
|
93
|
+
{
|
|
94
|
+
"name": "AccessioError",
|
|
95
|
+
"message": "Request failed with status code 401",
|
|
96
|
+
"code": "ERR_BAD_REQUEST",
|
|
97
|
+
"status": 401,
|
|
98
|
+
"config": {
|
|
99
|
+
"url": "https://admin:[REDACTED]@api.example.com/users",
|
|
100
|
+
"params": {
|
|
101
|
+
"api_key": "[REDACTED]"
|
|
102
|
+
},
|
|
103
|
+
"headers": {
|
|
104
|
+
"authorization": "[REDACTED]"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
*/
|
|
109
|
+
}
|
|
110
|
+
}
|
|
145
111
|
```
|
|
146
112
|
|
|
147
|
-
###
|
|
148
|
-
|
|
149
|
-
`Accessio` provides a structured error object with specific codes to help you handle failures gracefully.
|
|
113
|
+
### 🔁 Automatic Retries & Backoff
|
|
150
114
|
|
|
151
|
-
|
|
152
|
-
| :----------------- | :---------------------------- |
|
|
153
|
-
| `ERR_BAD_REQUEST` | 4xx status code |
|
|
154
|
-
| `ERR_BAD_RESPONSE` | 5xx status code |
|
|
155
|
-
| `ERR_NETWORK` | Network connectivity issues |
|
|
156
|
-
| `ETIMEDOUT` | Request exceeded timeout |
|
|
157
|
-
| `ERR_CANCELED` | Request was manually aborted |
|
|
158
|
-
| `ERR_INVALID_URL` | The provided URL is malformed |
|
|
159
|
-
| `ERR_BAD_OPTION` | Invalid configuration option |
|
|
160
|
-
|
|
161
|
-
### Automatic Retries
|
|
162
|
-
|
|
163
|
-
`Accessio` includes a powerful retry mechanism that handles network errors and 5xx responses automatically.
|
|
115
|
+
Automatically retry failed requests using exponential backoff with randomized jitter to prevent thundering herds.
|
|
164
116
|
|
|
165
117
|
```typescript
|
|
166
118
|
const response = await accessio.get('/flaky-endpoint', {
|
|
167
|
-
retry:
|
|
168
|
-
retryDelay: 1000, //
|
|
169
|
-
|
|
119
|
+
retry: 3, // Max retry attempts
|
|
120
|
+
retryDelay: 1000, // Initial delay in ms (doubles each attempt)
|
|
121
|
+
maxRetryDelay: 10000, // Maximum delay cap
|
|
122
|
+
retryOn429: true, // Respect Retry-After header for HTTP 429 responses
|
|
123
|
+
onRetry: (attempt, error, config) => {
|
|
124
|
+
console.warn(`Retry attempt #${attempt} due to: ${error.message}`);
|
|
125
|
+
},
|
|
170
126
|
});
|
|
171
127
|
```
|
|
172
128
|
|
|
173
|
-
### Rate Limiting
|
|
129
|
+
### ⏳ Concurrency Rate Limiting
|
|
174
130
|
|
|
175
|
-
|
|
131
|
+
Throttle outbound requests using a queue-based rate limiter. This is especially useful for third-party APIs with tight request limits.
|
|
176
132
|
|
|
177
133
|
```typescript
|
|
178
|
-
import { createRateLimiter } from 'accessio';
|
|
134
|
+
import accessio, { createRateLimiter } from 'accessio';
|
|
135
|
+
|
|
136
|
+
// Allow a maximum of 2 requests in parallel
|
|
137
|
+
const rateLimiter = createRateLimiter(2);
|
|
179
138
|
|
|
180
|
-
const
|
|
181
|
-
const api = accessio.create({ rateLimiter: limiter });
|
|
139
|
+
const api = accessio.create({ rateLimiter });
|
|
182
140
|
|
|
183
|
-
//
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
api.get('/req-2'),
|
|
187
|
-
// ...
|
|
188
|
-
]);
|
|
141
|
+
// These will run with a max concurrency of 2, queueing the rest
|
|
142
|
+
const requests = [1, 2, 3, 4, 5].map((id) => api.get(`/users/${id}`));
|
|
143
|
+
const responses = await Promise.all(requests);
|
|
189
144
|
```
|
|
190
145
|
|
|
191
|
-
###
|
|
146
|
+
### 🌊 SSE & Newline JSON Streaming
|
|
192
147
|
|
|
193
|
-
|
|
148
|
+
Accessio leverages asynchronous generators to handle incoming response streams dynamically (works with Server-Sent Events or line-by-line JSON streams).
|
|
194
149
|
|
|
195
150
|
```typescript
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
// Size: ~3.2 KB
|
|
151
|
+
// Iterating through an AI completion SSE stream
|
|
152
|
+
for await (const chunk of api.stream('/ai/complete')) {
|
|
153
|
+
console.log(chunk); // Parsed JSON chunk, e.g., { text: "hello" }
|
|
154
|
+
}
|
|
201
155
|
```
|
|
202
156
|
|
|
203
|
-
###
|
|
157
|
+
### 📂 Auto-Pagination
|
|
204
158
|
|
|
205
|
-
|
|
159
|
+
Avoid boilerplate code for pagination. Accessio can auto-follow `next` and `links.next` properties automatically:
|
|
206
160
|
|
|
207
161
|
```typescript
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
});
|
|
162
|
+
// Automatically fetches subsequent pages until next link is null
|
|
163
|
+
for await (const item of api.autoPaginate('/users?page=1')) {
|
|
164
|
+
console.log(item.name); // Yields individual items from each page's items array
|
|
165
|
+
}
|
|
213
166
|
```
|
|
214
167
|
|
|
215
|
-
### Schema Validation
|
|
168
|
+
### 🧪 Runtime Schema Validation
|
|
216
169
|
|
|
217
|
-
|
|
170
|
+
Validate your API payloads at runtime using your favorite validation library (e.g., Zod, ArkType, Superstruct).
|
|
218
171
|
|
|
219
172
|
```typescript
|
|
220
173
|
import { z } from 'zod';
|
|
221
174
|
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
### Server-Sent Events (SSE)
|
|
175
|
+
const UserSchema = z.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
name: z.string(),
|
|
178
|
+
email: z.string().email(),
|
|
179
|
+
});
|
|
229
180
|
|
|
230
|
-
|
|
181
|
+
const response = await api.get('/users/123', {
|
|
182
|
+
schema: UserSchema, // Throws AccessioError (ERR_BAD_RESPONSE) if validation fails
|
|
183
|
+
});
|
|
231
184
|
|
|
232
|
-
|
|
233
|
-
for await (const chunk of accessio.stream('/stream')) {
|
|
234
|
-
console.log(chunk); // Parsed JSON or string data from SSE
|
|
235
|
-
}
|
|
185
|
+
const user = response.data; // Fully typed as { id: string; name: string; email: string }
|
|
236
186
|
```
|
|
237
187
|
|
|
238
|
-
###
|
|
188
|
+
### 📦 Request Deduplication & Caching
|
|
239
189
|
|
|
240
|
-
|
|
190
|
+
Optimize application performance by preventing duplicate queries and utilizing caching.
|
|
241
191
|
|
|
242
192
|
```typescript
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
193
|
+
const api = accessio.create({
|
|
194
|
+
dedupe: true, // Merge concurrent requests targeting the same endpoint
|
|
195
|
+
cache: true, // Enable in-memory cache
|
|
196
|
+
cacheTTL: 60000, // Cache responses for 60 seconds
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Executes exactly 1 network call, resolves both promises
|
|
200
|
+
const [res1, res2] = await Promise.all([api.get('/heavy-report'), api.get('/heavy-report')]);
|
|
246
201
|
```
|
|
247
202
|
|
|
248
|
-
###
|
|
203
|
+
### 🔗 Interceptors & Hooks
|
|
249
204
|
|
|
250
|
-
|
|
205
|
+
Modify requests and responses at runtime, or listen to client lifecycles:
|
|
251
206
|
|
|
252
207
|
```typescript
|
|
253
|
-
const
|
|
254
|
-
query GetUser($id: ID!) {
|
|
255
|
-
user(id: $id) { name, email }
|
|
256
|
-
}
|
|
257
|
-
`;
|
|
258
|
-
|
|
259
|
-
const response = await accessio.gql('/graphql', query, { id: '1' });
|
|
260
|
-
```
|
|
208
|
+
const api = accessio.create();
|
|
261
209
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
210
|
+
// Add Request Interceptor
|
|
211
|
+
api.interceptors.request.use((config) => {
|
|
212
|
+
config.headers = config.headers || {};
|
|
213
|
+
config.headers['X-Request-Timestamp'] = Date.now().toString();
|
|
214
|
+
return config;
|
|
215
|
+
});
|
|
265
216
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
onRequestError: (error) => console.error('Request failed!'),
|
|
217
|
+
// Add Response Interceptor
|
|
218
|
+
api.interceptors.response.use(
|
|
219
|
+
(response) => {
|
|
220
|
+
// Modify output data
|
|
221
|
+
return response;
|
|
272
222
|
},
|
|
273
|
-
|
|
223
|
+
(error) => {
|
|
224
|
+
// Handle global errors (e.g. token refresh)
|
|
225
|
+
return Promise.reject(error);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
274
228
|
```
|
|
275
229
|
|
|
276
230
|
---
|
|
277
231
|
|
|
278
|
-
##
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
232
|
+
## ⚙️ Configuration Options
|
|
233
|
+
|
|
234
|
+
Here is the complete list of config parameters available in `AccessioRequestConfig`:
|
|
235
|
+
|
|
236
|
+
| Option | Type | Default | Description |
|
|
237
|
+
| :----------------- | :-------------------------------------------------------- | :-------------------- | :----------------------------------------------------------------------------- |
|
|
238
|
+
| `baseURL` | `string` | `undefined` | Prepended to relative URLs. |
|
|
239
|
+
| `method` | `string` | `'get'` | HTTP request method (e.g., `'get'`, `'post'`). |
|
|
240
|
+
| `headers` | `Record` | `{}` | Key-value mapping of custom headers. |
|
|
241
|
+
| `params` | `Record` | `undefined` | Query parameters appended to the URL. |
|
|
242
|
+
| `data` | `any` | `undefined` | The payload to send in the request body. |
|
|
243
|
+
| `timeout` | `number` | `0` (disabled) | Request timeout in milliseconds. |
|
|
244
|
+
| `responseType` | `'json' \| 'text' \| 'blob' \| 'arraybuffer' \| 'stream'` | `'json'` | Expected format of the response data. |
|
|
245
|
+
| `retry` | `number` | `0` | Number of times to retry failed requests. |
|
|
246
|
+
| `retryDelay` | `number` | `1000` | Initial delay for exponential backoff (ms). |
|
|
247
|
+
| `retryOn429` | `boolean` | `false` | Automatically retry on 429 using the `Retry-After` header. |
|
|
248
|
+
| `rateLimiter` | `RateLimiter` | `undefined` | A rate limiter instance to enqueue requests. |
|
|
249
|
+
| `dedupe` | `boolean` | `false` | Coalesce concurrent duplicate GET requests. |
|
|
250
|
+
| `cache` | `boolean \| CacheProvider` | `false` | Enable caching using memory or custom provider. |
|
|
251
|
+
| `cacheTTL` | `number` | `undefined` | TTL for cached responses in ms. |
|
|
252
|
+
| `schema` | `SchemaValidator` | `undefined` | Zod/schema parser to validate response body. |
|
|
253
|
+
| `hooks` | `AccessioHooks` | `undefined` | Lifecycle callbacks: `onBeforeRequest`, `onRequestResponse`, `onRequestError`. |
|
|
254
|
+
| `allowedProtocols` | `string[] \| null` | `['http:', 'https:']` | Protocols allowed for requesting. Set to `null` to disable check. |
|
|
297
255
|
|
|
298
256
|
---
|
|
299
257
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/accessioError.ts"],"sourcesContent":["import ErrorCodes from '../constants/errorCodes';\nimport type { AccessioRequestConfig, AccessioResponse } from '../types';\n\nfunction redactHeaders(headers: unknown): unknown {\n if (!headers || typeof headers !== 'object') return headers;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(headers as Record<string, unknown>)) {\n const value = (headers as Record<string, unknown>)[key];\n if (/^authorization$/i.test(key) || /^cookie$/i.test(key) || /^set-cookie$/i.test(key)) {\n out[key] = '[REDACTED]';\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = redactHeaders(value);\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\nconst SENSITIVE_BODY_KEY =\n /^(password|passwd|pwd|token|access_token|refresh_token|id_token|authorization|api[_-]?key|secret|client[_-]?secret|cookie|set[_-]?cookie|private[_-]?key|session)$/i;\n\nexport function redactBody(value: unknown, seen?: WeakSet<object>): unknown {\n if (value === null || typeof value !== 'object') return value;\n const visited = seen ?? new WeakSet<object>();\n if (visited.has(value as object)) return '[Circular]';\n visited.add(value as object);\n\n if (Array.isArray(value)) {\n return value.map((item) => redactBody(item, visited));\n }\n\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const v = (value as Record<string, unknown>)[key];\n if (SENSITIVE_BODY_KEY.test(key)) {\n out[key] = '[REDACTED]';\n } else {\n out[key] = redactBody(v, visited);\n }\n }\n return out;\n}\n\nfunction redactParams(params: unknown): unknown {\n if (!params || typeof params !== 'object') return params;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(params as Record<string, unknown>)) {\n const value = (params as Record<string, unknown>)[key];\n if (SENSITIVE_BODY_KEY.test(key)) {\n out[key] = '[REDACTED]';\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = redactParams(value);\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\nfunction redactURL(url: string | undefined): string | undefined {\n if (!url) return url;\n // Match inline credentials: http://user:pass@host\n return url.replace(/^([a-z][a-z\\d+\\-.]*:\\/\\/)([^/]+)@/i, (match, protocol, userInfo) => {\n const parts = userInfo.split(':');\n if (parts.length > 1) {\n return `${protocol}${parts[0]}:[REDACTED]@`;\n }\n return `${protocol}[REDACTED]@`;\n });\n}\n\nexport function redactConfig(config: AccessioRequestConfig | null): AccessioRequestConfig | null {\n if (!config) return config;\n const clone = { ...config } as AccessioRequestConfig & { auth?: unknown };\n if ('auth' in clone) delete clone.auth;\n if (clone.headers) clone.headers = redactHeaders(clone.headers) as typeof clone.headers;\n if (clone.params) clone.params = redactParams(clone.params) as typeof clone.params;\n if (clone.url) clone.url = redactURL(clone.url);\n if (clone._builtUrl) clone._builtUrl = redactURL(clone._builtUrl);\n return clone;\n}\n\nexport class AccessioError extends Error {\n static ERR_BAD_OPTION_VALUE: string = ErrorCodes.ERR_BAD_OPTION_VALUE;\n static ERR_BAD_OPTION: string = ErrorCodes.ERR_BAD_OPTION;\n static ECONNABORTED: string = ErrorCodes.ECONNABORTED;\n static ETIMEDOUT: string = ErrorCodes.ETIMEDOUT;\n static ERR_NETWORK: string = ErrorCodes.ERR_NETWORK;\n static ERR_FR_TOO_MANY_REDIRECTS: string = ErrorCodes.ERR_FR_TOO_MANY_REDIRECTS;\n static ERR_BAD_RESPONSE: string = ErrorCodes.ERR_BAD_RESPONSE;\n static ERR_BAD_REQUEST: string = ErrorCodes.ERR_BAD_REQUEST;\n static ERR_CANCELED: string = ErrorCodes.ERR_CANCELED;\n static ERR_NOT_SUPPORT: string = ErrorCodes.ERR_NOT_SUPPORT;\n static ERR_INVALID_URL: string = ErrorCodes.ERR_INVALID_URL;\n\n readonly code: string | null;\n readonly config: AccessioRequestConfig | null;\n readonly request: unknown;\n readonly response: AccessioResponse | null;\n readonly isAccessioError: true;\n cause?: Error;\n override name = 'AccessioError' as const;\n\n constructor(\n message: string,\n code: string | null,\n config: AccessioRequestConfig | null,\n request: unknown,\n response: AccessioResponse | null,\n ) {\n super(message);\n this.name = 'AccessioError';\n this.code = code ?? null;\n this.config = redactConfig(config ?? null);\n this.request = request ?? null;\n this.response = response ?? null;\n this.isAccessioError = true;\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, AccessioError);\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.response ? this.response.status : null,\n config: this.config,\n };\n }\n\n static from(\n error: Error,\n code: string,\n config: AccessioRequestConfig | null,\n request: unknown,\n response: AccessioResponse | null,\n ): AccessioError {\n const accessioError = new AccessioError(error.message, code, config, request, response);\n accessioError.cause = error;\n accessioError.stack = error.stack;\n return accessioError;\n }\n}\n\nexport default AccessioError;\n"],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../../src/core/accessioError.ts"],"sourcesContent":["import ErrorCodes from '../constants/errorCodes';\nimport type { AccessioRequestConfig, AccessioResponse } from '../types';\n\nfunction redactHeaders(headers: unknown): unknown {\n if (!headers || typeof headers !== 'object') return headers;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(headers as Record<string, unknown>)) {\n const value = (headers as Record<string, unknown>)[key];\n if (/^authorization$/i.test(key) || /^cookie$/i.test(key) || /^set-cookie$/i.test(key)) {\n out[key] = '[REDACTED]';\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = redactHeaders(value);\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\nconst SENSITIVE_BODY_KEY =\n /^(password|passwd|pwd|token|access_token|refresh_token|id_token|authorization|api[_-]?key|secret|client[_-]?secret|cookie|set[_-]?cookie|private[_-]?key|session)$/i;\n\nexport function redactBody(value: unknown, seen?: WeakSet<object>): unknown {\n if (value === null || typeof value !== 'object') return value;\n const visited = seen ?? new WeakSet<object>();\n if (visited.has(value as object)) return '[Circular]';\n visited.add(value as object);\n\n if (Array.isArray(value)) {\n return value.map((item) => redactBody(item, visited));\n }\n\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const v = (value as Record<string, unknown>)[key];\n if (SENSITIVE_BODY_KEY.test(key)) {\n out[key] = '[REDACTED]';\n } else {\n out[key] = redactBody(v, visited);\n }\n }\n return out;\n}\n\nfunction redactParams(params: unknown): unknown {\n if (!params || typeof params !== 'object') return params;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(params as Record<string, unknown>)) {\n const value = (params as Record<string, unknown>)[key];\n if (SENSITIVE_BODY_KEY.test(key)) {\n out[key] = '[REDACTED]';\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = redactParams(value);\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\nfunction redactURL(url: string | undefined): string | undefined {\n if (!url) return url;\n // Match inline credentials: http://user:pass@host\n return url.replace(/^([a-z][a-z\\d+\\-.]*:\\/\\/)([^/]+)@/i, (match, protocol, userInfo) => {\n const parts = userInfo.split(':');\n if (parts.length > 1) {\n return `${protocol}${parts[0]}:[REDACTED]@`;\n }\n return `${protocol}[REDACTED]@`;\n });\n}\n\nexport function redactConfig(config: AccessioRequestConfig | null): AccessioRequestConfig | null {\n if (!config) return config;\n const clone = { ...config } as AccessioRequestConfig & { auth?: unknown };\n if ('auth' in clone) delete clone.auth;\n if (clone.headers) clone.headers = redactHeaders(clone.headers) as typeof clone.headers;\n if (clone.params) clone.params = redactParams(clone.params) as typeof clone.params;\n if (clone.url) clone.url = redactURL(clone.url);\n if (clone._builtUrl) clone._builtUrl = redactURL(clone._builtUrl);\n return clone;\n}\n\nexport class AccessioError extends Error {\n static ERR_BAD_OPTION_VALUE: string = ErrorCodes.ERR_BAD_OPTION_VALUE;\n static ERR_BAD_OPTION: string = ErrorCodes.ERR_BAD_OPTION;\n static ECONNABORTED: string = ErrorCodes.ECONNABORTED;\n static ETIMEDOUT: string = ErrorCodes.ETIMEDOUT;\n static ERR_NETWORK: string = ErrorCodes.ERR_NETWORK;\n static ERR_FR_TOO_MANY_REDIRECTS: string = ErrorCodes.ERR_FR_TOO_MANY_REDIRECTS;\n static ERR_BAD_RESPONSE: string = ErrorCodes.ERR_BAD_RESPONSE;\n static ERR_BAD_REQUEST: string = ErrorCodes.ERR_BAD_REQUEST;\n static ERR_CANCELED: string = ErrorCodes.ERR_CANCELED;\n static ERR_NOT_SUPPORT: string = ErrorCodes.ERR_NOT_SUPPORT;\n static ERR_INVALID_URL: string = ErrorCodes.ERR_INVALID_URL;\n\n readonly code: string | null;\n readonly config: AccessioRequestConfig | null;\n readonly request: unknown;\n readonly response: AccessioResponse | null;\n readonly isAccessioError: true;\n cause?: Error;\n override name = 'AccessioError' as const;\n\n constructor(\n message: string,\n code: string | null,\n config: AccessioRequestConfig | null,\n request: unknown,\n response: AccessioResponse | null,\n ) {\n super(message);\n this.name = 'AccessioError';\n this.code = code ?? null;\n this.config = redactConfig(config ?? null);\n this.request = request ?? null;\n this.response = response ?? null;\n this.isAccessioError = true;\n\n if ((Error as any).captureStackTrace) {\n (Error as any).captureStackTrace(this, AccessioError);\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.response ? this.response.status : null,\n config: this.config,\n };\n }\n\n static from(\n error: Error,\n code: string,\n config: AccessioRequestConfig | null,\n request: unknown,\n response: AccessioResponse | null,\n ): AccessioError {\n const accessioError = new AccessioError(error.message, code, config, request, response);\n accessioError.cause = error;\n accessioError.stack = error.stack;\n return accessioError;\n }\n}\n\nexport default AccessioError;\n"],"mappings":"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","names":["ErrorCodes"]}
|
package/cjs/core/buildURL.cjs
CHANGED
|
@@ -84,15 +84,18 @@ function buildURL(url, baseURL, params, paramsSerializer) {
|
|
|
84
84
|
if (key === "__proto__" || key === "prototype" || key === "constructor") continue;
|
|
85
85
|
unusedParams[key] = params[key];
|
|
86
86
|
}
|
|
87
|
-
fullURL = fullURL.replace(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
fullURL = fullURL.replace(
|
|
88
|
+
/(?::([a-zA-Z_][a-zA-Z0-9_]*))|(?:{([a-zA-Z_][a-zA-Z0-9_]*)})/g,
|
|
89
|
+
(match, p1, p2) => {
|
|
90
|
+
const key = p1 || p2;
|
|
91
|
+
if (key && Object.prototype.hasOwnProperty.call(unusedParams, key) && unusedParams[key] !== void 0) {
|
|
92
|
+
const val = unusedParams[key];
|
|
93
|
+
delete unusedParams[key];
|
|
94
|
+
return encodeURIComponent(String(val));
|
|
95
|
+
}
|
|
96
|
+
return match;
|
|
93
97
|
}
|
|
94
|
-
|
|
95
|
-
});
|
|
98
|
+
);
|
|
96
99
|
finalParams = unusedParams;
|
|
97
100
|
}
|
|
98
101
|
const serialized = serializeParams(finalParams, paramsSerializer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/buildURL.ts"],"sourcesContent":["import type { ParamsSerializer } from '../types';\n\nfunction serializeParams(\n params: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n if (!params) return '';\n\n if (typeof paramsSerializer === 'function') {\n return paramsSerializer(params);\n }\n\n if (typeof URLSearchParams !== 'undefined' && params instanceof URLSearchParams) {\n return params.toString();\n }\n\n const parts: string[] = [];\n\n function encode(prefix: string, value: unknown): void {\n if (value === null || value === undefined) {\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n if (typeof item === 'object' && item !== null) {\n encode(`${prefix}[${index}]`, item);\n } else {\n encode(prefix, item);\n }\n });\n } else if (typeof value === 'object' && !(value instanceof Date)) {\n Object.keys(value as Record<string, unknown>).forEach((key) => {\n encode(`${prefix}[${key}]`, (value as Record<string, unknown>)[key]);\n });\n } else {\n const encodedValue = value instanceof Date ? value.toISOString() : value;\n parts.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(encodedValue as string)}`);\n }\n }\n\n Object.keys(params).forEach((key) => {\n encode(key, params[key]);\n });\n\n return parts.join('&');\n}\n\nfunction combineURLs(baseURL: string, relativeURL: string): string {\n if (!baseURL) return relativeURL || '';\n if (!relativeURL) return baseURL;\n\n let base = baseURL;\n while (base.endsWith('/')) {\n base = base.slice(0, -1);\n }\n\n let relative = relativeURL;\n while (relative.startsWith('/')) {\n relative = relative.slice(1);\n }\n\n return `${base}/${relative}`;\n}\n\nfunction isAbsoluteURL(url: string): boolean {\n return /^([a-z][a-z\\d+\\-.]*:)/i.test(url);\n}\n\nexport default function buildURL(\n url: string,\n baseURL?: string,\n params?: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n let fullURL = baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url || '';\n\n let finalParams = params;\n if (params && typeof params === 'object' && !(params instanceof URLSearchParams)) {\n const unusedParams: Record<string, unknown> = {};\n for (const key of Object.keys(params)) {\n if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;\n unusedParams[key] = (params as Record<string, unknown>)[key];\n }\n fullURL = fullURL.replace(/(?::([a-zA-Z0-9_]
|
|
1
|
+
{"version":3,"sources":["../../src/core/buildURL.ts"],"sourcesContent":["import type { ParamsSerializer } from '../types';\n\nfunction serializeParams(\n params: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n if (!params) return '';\n\n if (typeof paramsSerializer === 'function') {\n return paramsSerializer(params);\n }\n\n if (typeof URLSearchParams !== 'undefined' && params instanceof URLSearchParams) {\n return params.toString();\n }\n\n const parts: string[] = [];\n\n function encode(prefix: string, value: unknown): void {\n if (value === null || value === undefined) {\n return;\n }\n\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n if (typeof item === 'object' && item !== null) {\n encode(`${prefix}[${index}]`, item);\n } else {\n encode(prefix, item);\n }\n });\n } else if (typeof value === 'object' && !(value instanceof Date)) {\n Object.keys(value as Record<string, unknown>).forEach((key) => {\n encode(`${prefix}[${key}]`, (value as Record<string, unknown>)[key]);\n });\n } else {\n const encodedValue = value instanceof Date ? value.toISOString() : value;\n parts.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(encodedValue as string)}`);\n }\n }\n\n Object.keys(params).forEach((key) => {\n encode(key, params[key]);\n });\n\n return parts.join('&');\n}\n\nfunction combineURLs(baseURL: string, relativeURL: string): string {\n if (!baseURL) return relativeURL || '';\n if (!relativeURL) return baseURL;\n\n let base = baseURL;\n while (base.endsWith('/')) {\n base = base.slice(0, -1);\n }\n\n let relative = relativeURL;\n while (relative.startsWith('/')) {\n relative = relative.slice(1);\n }\n\n return `${base}/${relative}`;\n}\n\nfunction isAbsoluteURL(url: string): boolean {\n return /^([a-z][a-z\\d+\\-.]*:)/i.test(url);\n}\n\nexport default function buildURL(\n url: string,\n baseURL?: string,\n params?: Record<string, unknown>,\n paramsSerializer?: ParamsSerializer,\n): string {\n let fullURL = baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url || '';\n\n let finalParams = params;\n if (params && typeof params === 'object' && !(params instanceof URLSearchParams)) {\n const unusedParams: Record<string, unknown> = {};\n for (const key of Object.keys(params)) {\n if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;\n unusedParams[key] = (params as Record<string, unknown>)[key];\n }\n fullURL = fullURL.replace(\n /(?::([a-zA-Z_][a-zA-Z0-9_]*))|(?:{([a-zA-Z_][a-zA-Z0-9_]*)})/g,\n (match, p1, p2) => {\n const key = p1 || p2;\n if (\n key &&\n Object.prototype.hasOwnProperty.call(unusedParams, key) &&\n unusedParams[key] !== undefined\n ) {\n const val = unusedParams[key];\n delete unusedParams[key];\n return encodeURIComponent(String(val));\n }\n return match;\n },\n );\n finalParams = unusedParams;\n }\n\n const serialized = serializeParams(finalParams as Record<string, unknown>, paramsSerializer);\n if (serialized) {\n const hashIndex = fullURL.indexOf('#');\n let fragment = '';\n if (hashIndex !== -1) {\n fragment = fullURL.slice(hashIndex);\n fullURL = fullURL.slice(0, hashIndex);\n }\n fullURL += (fullURL.indexOf('?') === -1 ? '?' : '&') + serialized + fragment;\n }\n\n return fullURL;\n}\n\nexport { serializeParams, combineURLs, isAbsoluteURL };\n"],"mappings":"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","names":[]}
|
package/cjs/core/mergeConfig.cjs
CHANGED
|
@@ -45,7 +45,7 @@ function deepMerge(...sources) {
|
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
47
47
|
const requestOnlyKeys = /* @__PURE__ */ new Set(["url", "data", "signal"]);
|
|
48
|
-
const deepMergeKeys = /* @__PURE__ */ new Set(["headers"]);
|
|
48
|
+
const deepMergeKeys = /* @__PURE__ */ new Set(["headers", "params", "hooks"]);
|
|
49
49
|
function mergeConfig(config1 = {}, config2 = {}) {
|
|
50
50
|
const merged = /* @__PURE__ */ Object.create(null);
|
|
51
51
|
const allKeys = /* @__PURE__ */ new Set([...Object.keys(config1), ...Object.keys(config2)]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/mergeConfig.ts"],"sourcesContent":["import type { AccessioRequestConfig } from '../types';\n\nfunction deepMerge(...sources: any[]): Record<string, any> {\n const result: Record<string, any> = Object.create(null);\n\n for (const source of sources) {\n if (!source || typeof source !== 'object') continue;\n\n for (const key of Object.keys(source)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n\n const value = source[key];\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n if (\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof Error ||\n (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) ||\n (typeof Blob !== 'undefined' && value instanceof Blob)\n ) {\n result[key] = value;\n } else if (result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {\n result[key] = deepMerge(result[key], value);\n } else {\n result[key] = deepMerge(value);\n }\n } else if (value !== undefined) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n\nconst requestOnlyKeys = new Set<string>(['url', 'data', 'signal']);\nconst deepMergeKeys = new Set<string>(['headers']);\n\nexport default function mergeConfig(\n config1: AccessioRequestConfig = {},\n config2: AccessioRequestConfig = {},\n): AccessioRequestConfig {\n const merged: any = Object.create(null);\n\n const allKeys = new Set<string>([...Object.keys(config1), ...Object.keys(config2)]);\n\n for (const key of allKeys) {\n const val1 = config1[key as keyof AccessioRequestConfig];\n const val2 = config2[key as keyof AccessioRequestConfig];\n\n if (requestOnlyKeys.has(key)) {\n if (val2 !== undefined) {\n merged[key] = val2;\n }\n } else if (deepMergeKeys.has(key)) {\n merged[key] = deepMerge(val1 || {}, val2 || {});\n } else {\n merged[key] = val2 !== undefined ? val2 : val1;\n }\n }\n\n return merged;\n}\n\nexport { deepMerge };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,SAAS,aAAa,SAAqC;AACzD,QAAM,SAA8B,uBAAO,OAAO,IAAI;AAEtD,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU;AAE3C,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AAEzE,YAAM,QAAQ,OAAO,GAAG;AAExB,UAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,YACE,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,SAChB,OAAO,gBAAgB,eAAe,iBAAiB,eACvD,OAAO,SAAS,eAAe,iBAAiB,MACjD;AACA,iBAAO,GAAG,IAAI;AAAA,QAChB,WAAW,OAAO,GAAG,KAAK,OAAO,OAAO,GAAG,MAAM,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG,CAAC,GAAG;AACxF,iBAAO,GAAG,IAAI,UAAU,OAAO,GAAG,GAAG,KAAK;AAAA,QAC5C,OAAO;AACL,iBAAO,GAAG,IAAI,UAAU,KAAK;AAAA,QAC/B;AAAA,MACF,WAAW,UAAU,QAAW;AAC9B,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,kBAAkB,oBAAI,IAAY,CAAC,OAAO,QAAQ,QAAQ,CAAC;AACjE,MAAM,gBAAgB,oBAAI,IAAY,CAAC,
|
|
1
|
+
{"version":3,"sources":["../../src/core/mergeConfig.ts"],"sourcesContent":["import type { AccessioRequestConfig } from '../types';\n\nfunction deepMerge(...sources: any[]): Record<string, any> {\n const result: Record<string, any> = Object.create(null);\n\n for (const source of sources) {\n if (!source || typeof source !== 'object') continue;\n\n for (const key of Object.keys(source)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n\n const value = source[key];\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n if (\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof Error ||\n (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) ||\n (typeof Blob !== 'undefined' && value instanceof Blob)\n ) {\n result[key] = value;\n } else if (result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {\n result[key] = deepMerge(result[key], value);\n } else {\n result[key] = deepMerge(value);\n }\n } else if (value !== undefined) {\n result[key] = value;\n }\n }\n }\n\n return result;\n}\n\nconst requestOnlyKeys = new Set<string>(['url', 'data', 'signal']);\nconst deepMergeKeys = new Set<string>(['headers', 'params', 'hooks']);\n\nexport default function mergeConfig(\n config1: AccessioRequestConfig = {},\n config2: AccessioRequestConfig = {},\n): AccessioRequestConfig {\n const merged: any = Object.create(null);\n\n const allKeys = new Set<string>([...Object.keys(config1), ...Object.keys(config2)]);\n\n for (const key of allKeys) {\n const val1 = config1[key as keyof AccessioRequestConfig];\n const val2 = config2[key as keyof AccessioRequestConfig];\n\n if (requestOnlyKeys.has(key)) {\n if (val2 !== undefined) {\n merged[key] = val2;\n }\n } else if (deepMergeKeys.has(key)) {\n merged[key] = deepMerge(val1 || {}, val2 || {});\n } else {\n merged[key] = val2 !== undefined ? val2 : val1;\n }\n }\n\n return merged;\n}\n\nexport { deepMerge };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,SAAS,aAAa,SAAqC;AACzD,QAAM,SAA8B,uBAAO,OAAO,IAAI;AAEtD,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU;AAE3C,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AAEzE,YAAM,QAAQ,OAAO,GAAG;AAExB,UAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,YACE,iBAAiB,QACjB,iBAAiB,UACjB,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,SAChB,OAAO,gBAAgB,eAAe,iBAAiB,eACvD,OAAO,SAAS,eAAe,iBAAiB,MACjD;AACA,iBAAO,GAAG,IAAI;AAAA,QAChB,WAAW,OAAO,GAAG,KAAK,OAAO,OAAO,GAAG,MAAM,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG,CAAC,GAAG;AACxF,iBAAO,GAAG,IAAI,UAAU,OAAO,GAAG,GAAG,KAAK;AAAA,QAC5C,OAAO;AACL,iBAAO,GAAG,IAAI,UAAU,KAAK;AAAA,QAC/B;AAAA,MACF,WAAW,UAAU,QAAW;AAC9B,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,kBAAkB,oBAAI,IAAY,CAAC,OAAO,QAAQ,QAAQ,CAAC;AACjE,MAAM,gBAAgB,oBAAI,IAAY,CAAC,WAAW,UAAU,OAAO,CAAC;AAErD,SAAR,YACL,UAAiC,CAAC,GAClC,UAAiC,CAAC,GACX;AACvB,QAAM,SAAc,uBAAO,OAAO,IAAI;AAEtC,QAAM,UAAU,oBAAI,IAAY,CAAC,GAAG,OAAO,KAAK,OAAO,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAElF,aAAW,OAAO,SAAS;AACzB,UAAM,OAAO,QAAQ,GAAkC;AACvD,UAAM,OAAO,QAAQ,GAAkC;AAEvD,QAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,UAAI,SAAS,QAAW;AACtB,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF,WAAW,cAAc,IAAI,GAAG,GAAG;AACjC,aAAO,GAAG,IAAI,UAAU,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;AAAA,IAChD,OAAO;AACL,aAAO,GAAG,IAAI,SAAS,SAAY,OAAO;AAAA,IAC5C;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
package/cjs/core/request.cjs
CHANGED
|
@@ -41,23 +41,22 @@ var import_flattenHeaders = require("../helpers/flattenHeaders");
|
|
|
41
41
|
var import_auth = require("../helpers/auth");
|
|
42
42
|
var import_fetchAdapter = __toESM(require("./fetchAdapter"), 1);
|
|
43
43
|
var import_memoryCache = require("../helpers/memoryCache");
|
|
44
|
-
function lookupHeader(headers, name) {
|
|
45
|
-
const target = name.toLowerCase();
|
|
46
|
-
for (const k of Object.keys(headers)) {
|
|
47
|
-
if (k.toLowerCase() === target) {
|
|
48
|
-
const v = headers[k];
|
|
49
|
-
return Array.isArray(v) ? v.join(",") : v ?? "";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return "";
|
|
53
|
-
}
|
|
54
44
|
function buildCacheKey(config, fullURL, flatHeaders) {
|
|
45
|
+
if (typeof config.cacheKeySerializer === "function") {
|
|
46
|
+
return config.cacheKeySerializer(config, fullURL, flatHeaders);
|
|
47
|
+
}
|
|
55
48
|
const method = (config.method || "GET").toUpperCase();
|
|
56
|
-
const auth = lookupHeader(flatHeaders, "authorization");
|
|
57
|
-
const accept = lookupHeader(flatHeaders, "accept");
|
|
58
49
|
const withCreds = config.withCredentials ? "1" : "0";
|
|
59
50
|
const respType = config.responseType || "json";
|
|
60
|
-
|
|
51
|
+
const serializedHeaders = Object.keys(flatHeaders).sort().filter(
|
|
52
|
+
(k) => !["user-agent", "connection", "host", "content-length", "accept-encoding"].includes(
|
|
53
|
+
k.toLowerCase()
|
|
54
|
+
)
|
|
55
|
+
).map((k) => {
|
|
56
|
+
const val = flatHeaders[k];
|
|
57
|
+
return `${k.toLowerCase()}=${Array.isArray(val) ? val.join(",") : val}`;
|
|
58
|
+
}).join("&");
|
|
59
|
+
return `${method}:${fullURL}|h:${serializedHeaders}|c=${withCreds}|t=${respType}`;
|
|
61
60
|
}
|
|
62
61
|
function buildTransformArray(transform) {
|
|
63
62
|
if (!transform) return [];
|
|
@@ -127,8 +126,37 @@ async function dispatchRequest(config) {
|
|
|
127
126
|
if (isGet && config.dedupe) {
|
|
128
127
|
const inflight = activeRequests.get(cacheKey);
|
|
129
128
|
if (inflight) {
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
try {
|
|
130
|
+
const shared = await inflight;
|
|
131
|
+
const response = finalizeResponse(shared, config);
|
|
132
|
+
const settled = await new Promise((resolve, reject) => {
|
|
133
|
+
(0, import_settle.default)(
|
|
134
|
+
resolve,
|
|
135
|
+
reject,
|
|
136
|
+
response,
|
|
137
|
+
config
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
if (config.hooks?.onRequestResponse) {
|
|
141
|
+
await config.hooks.onRequestResponse(settled);
|
|
142
|
+
}
|
|
143
|
+
return settled;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
let finalError = error;
|
|
146
|
+
if (error instanceof import_accessioError.default) {
|
|
147
|
+
finalError = import_accessioError.default.from(
|
|
148
|
+
error,
|
|
149
|
+
error.code || "ERR_DEDUPE",
|
|
150
|
+
config,
|
|
151
|
+
error.request,
|
|
152
|
+
error.response
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (config.hooks?.onRequestError && finalError instanceof import_accessioError.default) {
|
|
156
|
+
await config.hooks.onRequestError(finalError);
|
|
157
|
+
}
|
|
158
|
+
throw finalError;
|
|
159
|
+
}
|
|
132
160
|
}
|
|
133
161
|
}
|
|
134
162
|
const performRequest = async () => {
|
package/cjs/core/request.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/request.ts"],"sourcesContent":["import buildURL from './buildURL';\nimport AccessioError, { redactConfig } from './accessioError';\nimport { ERR_BAD_OPTION } from '../constants/errorCodes';\nimport transformData from '../helpers/transformData';\nimport settle from '../helpers/settle';\nimport { flattenHeaders, removeContentType, buildFetchHeaders } from '../helpers/flattenHeaders';\nimport { setBasicAuth } from '../helpers/auth';\nimport fetchAdapter from './fetchAdapter';\nimport { defaultMemoryCache } from '../helpers/memoryCache';\nimport type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';\n\ntype HeadersConfig = Record<string, Record<string, string | string[]>>;\ntype FlatHeaders = Record<string, string | string[]>;\n\nfunction lookupHeader(headers: FlatHeaders, name: string): string {\n const target = name.toLowerCase();\n for (const k of Object.keys(headers)) {\n if (k.toLowerCase() === target) {\n const v = headers[k];\n return Array.isArray(v) ? v.join(',') : (v ?? '');\n }\n }\n return '';\n}\n\nfunction buildCacheKey(\n config: AccessioRequestConfig,\n fullURL: string,\n flatHeaders: FlatHeaders,\n): string {\n const method = (config.method || 'GET').toUpperCase();\n const auth = lookupHeader(flatHeaders, 'authorization');\n const accept = lookupHeader(flatHeaders, 'accept');\n const withCreds = config.withCredentials ? '1' : '0';\n const respType = config.responseType || 'json';\n return `${method}:${fullURL}|a=${auth}|x=${accept}|c=${withCreds}|t=${respType}`;\n}\n\nfunction buildTransformArray(\n transform: TransformFunction | TransformFunction[] | undefined,\n): TransformFunction[] {\n if (!transform) return [];\n if (Array.isArray(transform)) return transform;\n return [transform];\n}\n\nconst DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:'];\n\nfunction assertAllowedProtocol(fullURL: string, config: AccessioRequestConfig): void {\n if (config.allowedProtocols === null) return;\n const allowed = config.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;\n\n let scheme: string | null = null;\n const match = /^([a-z][a-z\\d+\\-.]*):/i.exec(fullURL);\n if (match) scheme = `${match[1].toLowerCase()}:`;\n if (!scheme) return;\n\n if (!allowed.includes(scheme)) {\n throw new AccessioError(\n `URL protocol \"${scheme}\" is not allowed. Allowed: ${allowed.join(', ')}. ` +\n 'Set config.allowedProtocols to extend, or null to disable the check.',\n ERR_BAD_OPTION,\n config,\n null,\n null,\n );\n }\n}\n\nconst activeRequests = new Map<string, Promise<AccessioResponse>>();\nconst MAX_ACTIVE_REQUESTS = 1024;\n\nexport function __activeRequestsSize(): number {\n return activeRequests.size;\n}\n\nfunction trackActiveRequest(key: string, promise: Promise<AccessioResponse>): void {\n activeRequests.set(key, promise);\n // Evict the oldest entry if we've grown past the cap. Map preserves insertion order.\n while (activeRequests.size > MAX_ACTIVE_REQUESTS) {\n const oldest = activeRequests.keys().next().value;\n if (oldest === undefined || oldest === key) break;\n activeRequests.delete(oldest);\n }\n}\n\nexport default async function dispatchRequest(\n config: AccessioRequestConfig,\n): Promise<AccessioResponse> {\n const fullURL =\n config._builtUrl ||\n buildURL(\n config.url ?? '',\n config.baseURL,\n config.params as Record<string, unknown> | undefined,\n config.paramsSerializer,\n );\n\n assertAllowedProtocol(fullURL, config);\n\n if (config.hooks?.onBeforeRequest) {\n await config.hooks.onBeforeRequest(config);\n }\n\n const flatHeaders = flattenHeaders(config.headers as HeadersConfig | undefined, config.method);\n setBasicAuth(config, flatHeaders);\n\n const isGet = (config.method || 'GET').toUpperCase() === 'GET';\n const cacheKey = isGet ? buildCacheKey(config, fullURL, flatHeaders) : '';\n\n if (isGet && config.cache) {\n const cacheProvider = typeof config.cache === 'object' ? config.cache : defaultMemoryCache;\n const cached = await cacheProvider.get(cacheKey);\n if (cached) {\n const cachedView: AccessioResponse = {\n ...cached,\n config: redactConfig(config) as typeof cached.config,\n };\n if (config.hooks?.onRequestResponse) {\n await config.hooks.onRequestResponse(cachedView);\n }\n return cachedView;\n }\n }\n\n if (isGet && config.dedupe) {\n const inflight = activeRequests.get(cacheKey);\n if (inflight) {\n const shared = await inflight;\n return finalizeResponse(shared, config);\n }\n }\n\n const performRequest = async (): Promise<AccessioResponse> => {\n const requestTransforms = buildTransformArray(config.transformRequest);\n const requestData = await transformData(requestTransforms, config.data, flatHeaders, config);\n\n if (\n requestData === null ||\n requestData === undefined ||\n (typeof FormData !== 'undefined' && requestData instanceof FormData)\n ) {\n removeContentType(flatHeaders);\n }\n\n const fetchOptions: RequestInit = {\n method: (config.method || 'GET').toUpperCase(),\n headers: buildFetchHeaders(flatHeaders),\n };\n\n const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];\n if (\n methodsWithBody.includes(fetchOptions.method!) &&\n requestData !== undefined &&\n requestData !== null\n ) {\n fetchOptions.body = requestData as BodyInit;\n }\n\n if (config.withCredentials) {\n fetchOptions.credentials = 'include';\n }\n\n if (config.dispatcher) {\n (fetchOptions as any).dispatcher = config.dispatcher;\n }\n if (config.agent) {\n (fetchOptions as any).agent = config.agent;\n }\n\n const requestStartTime = Date.now();\n const response = await fetchAdapter(config, fullURL, fetchOptions, requestStartTime);\n\n const responseTransforms = buildTransformArray(config.transformResponse);\n response.data = await transformData(\n responseTransforms,\n response.data,\n response.headers,\n config,\n 'response',\n );\n\n return response;\n };\n\n const promise = performRequest();\n\n if (isGet && config.dedupe) {\n trackActiveRequest(cacheKey, promise);\n const cleanup = () => {\n if (activeRequests.get(cacheKey) === promise) {\n activeRequests.delete(cacheKey);\n }\n };\n promise.then(cleanup, cleanup);\n }\n\n try {\n const shared = await promise;\n const response = finalizeResponse(shared, config);\n\n if (isGet && config.cache) {\n const cacheProvider = typeof config.cache === 'object' ? config.cache : defaultMemoryCache;\n await cacheProvider.set(cacheKey, shared, config.cacheTTL);\n }\n\n const settled = await new Promise<AccessioResponse>((resolve, reject) => {\n settle(\n resolve as (value: AccessioResponse) => void,\n reject as (reason: AccessioError) => void,\n response,\n config,\n );\n });\n\n if (config.hooks?.onRequestResponse) {\n await config.hooks.onRequestResponse(settled);\n }\n\n return settled;\n } catch (error) {\n if (config.hooks?.onRequestError && error instanceof AccessioError) {\n await config.hooks.onRequestError(error);\n }\n throw error;\n }\n}\n\nfunction finalizeResponse(\n shared: AccessioResponse,\n config: AccessioRequestConfig,\n): AccessioResponse {\n return {\n ...shared,\n config: redactConfig(config) as typeof shared.config,\n };\n}\n"],"mappings":"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","names":["AccessioError","buildURL","transformData","fetchAdapter","settle"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/request.ts"],"sourcesContent":["import buildURL from './buildURL';\nimport AccessioError, { redactConfig } from './accessioError';\nimport { ERR_BAD_OPTION } from '../constants/errorCodes';\nimport transformData from '../helpers/transformData';\nimport settle from '../helpers/settle';\nimport { flattenHeaders, removeContentType, buildFetchHeaders } from '../helpers/flattenHeaders';\nimport { setBasicAuth } from '../helpers/auth';\nimport fetchAdapter from './fetchAdapter';\nimport { defaultMemoryCache } from '../helpers/memoryCache';\nimport type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';\n\ntype HeadersConfig = Record<string, Record<string, string | string[]>>;\ntype FlatHeaders = Record<string, string | string[]>;\n\nfunction buildCacheKey(\n config: AccessioRequestConfig,\n fullURL: string,\n flatHeaders: FlatHeaders,\n): string {\n if (typeof config.cacheKeySerializer === 'function') {\n return config.cacheKeySerializer(config, fullURL, flatHeaders);\n }\n const method = (config.method || 'GET').toUpperCase();\n const withCreds = config.withCredentials ? '1' : '0';\n const respType = config.responseType || 'json';\n\n // Sort and serialize headers dynamically to prevent collisions,\n // excluding environment-specific transient headers.\n const serializedHeaders = Object.keys(flatHeaders)\n .sort()\n .filter(\n (k) =>\n !['user-agent', 'connection', 'host', 'content-length', 'accept-encoding'].includes(\n k.toLowerCase(),\n ),\n )\n .map((k) => {\n const val = flatHeaders[k];\n return `${k.toLowerCase()}=${Array.isArray(val) ? val.join(',') : val}`;\n })\n .join('&');\n\n return `${method}:${fullURL}|h:${serializedHeaders}|c=${withCreds}|t=${respType}`;\n}\n\nfunction buildTransformArray(\n transform: TransformFunction | TransformFunction[] | undefined,\n): TransformFunction[] {\n if (!transform) return [];\n if (Array.isArray(transform)) return transform;\n return [transform];\n}\n\nconst DEFAULT_ALLOWED_PROTOCOLS = ['http:', 'https:'];\n\nfunction assertAllowedProtocol(fullURL: string, config: AccessioRequestConfig): void {\n if (config.allowedProtocols === null) return;\n const allowed = config.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;\n\n let scheme: string | null = null;\n const match = /^([a-z][a-z\\d+\\-.]*):/i.exec(fullURL);\n if (match) scheme = `${match[1].toLowerCase()}:`;\n if (!scheme) return;\n\n if (!allowed.includes(scheme)) {\n throw new AccessioError(\n `URL protocol \"${scheme}\" is not allowed. Allowed: ${allowed.join(', ')}. ` +\n 'Set config.allowedProtocols to extend, or null to disable the check.',\n ERR_BAD_OPTION,\n config,\n null,\n null,\n );\n }\n}\n\nconst activeRequests = new Map<string, Promise<AccessioResponse>>();\nconst MAX_ACTIVE_REQUESTS = 1024;\n\nexport function __activeRequestsSize(): number {\n return activeRequests.size;\n}\n\nfunction trackActiveRequest(key: string, promise: Promise<AccessioResponse>): void {\n activeRequests.set(key, promise);\n // Evict the oldest entry if we've grown past the cap. Map preserves insertion order.\n while (activeRequests.size > MAX_ACTIVE_REQUESTS) {\n const oldest = activeRequests.keys().next().value;\n if (oldest === undefined || oldest === key) break;\n activeRequests.delete(oldest);\n }\n}\n\nexport default async function dispatchRequest(\n config: AccessioRequestConfig,\n): Promise<AccessioResponse> {\n const fullURL =\n config._builtUrl ||\n buildURL(\n config.url ?? '',\n config.baseURL,\n config.params as Record<string, unknown> | undefined,\n config.paramsSerializer,\n );\n\n assertAllowedProtocol(fullURL, config);\n\n if (config.hooks?.onBeforeRequest) {\n await config.hooks.onBeforeRequest(config);\n }\n\n const flatHeaders = flattenHeaders(config.headers as HeadersConfig | undefined, config.method);\n setBasicAuth(config, flatHeaders);\n\n const isGet = (config.method || 'GET').toUpperCase() === 'GET';\n const cacheKey = isGet ? buildCacheKey(config, fullURL, flatHeaders) : '';\n\n if (isGet && config.cache) {\n const cacheProvider = typeof config.cache === 'object' ? config.cache : defaultMemoryCache;\n const cached = await cacheProvider.get(cacheKey);\n if (cached) {\n const cachedView: AccessioResponse = {\n ...cached,\n config: redactConfig(config) as typeof cached.config,\n };\n if (config.hooks?.onRequestResponse) {\n await config.hooks.onRequestResponse(cachedView);\n }\n return cachedView;\n }\n }\n\n if (isGet && config.dedupe) {\n const inflight = activeRequests.get(cacheKey);\n if (inflight) {\n try {\n const shared = await inflight;\n const response = finalizeResponse(shared, config);\n const settled = await new Promise<AccessioResponse>((resolve, reject) => {\n settle(\n resolve as (value: AccessioResponse) => void,\n reject as (reason: AccessioError) => void,\n response,\n config,\n );\n });\n\n if (config.hooks?.onRequestResponse) {\n await config.hooks.onRequestResponse(settled);\n }\n\n return settled;\n } catch (error) {\n let finalError = error;\n if (error instanceof AccessioError) {\n finalError = AccessioError.from(\n error,\n error.code || 'ERR_DEDUPE',\n config,\n error.request,\n error.response,\n );\n }\n if (config.hooks?.onRequestError && finalError instanceof AccessioError) {\n await config.hooks.onRequestError(finalError);\n }\n throw finalError;\n }\n }\n }\n\n const performRequest = async (): Promise<AccessioResponse> => {\n const requestTransforms = buildTransformArray(config.transformRequest);\n const requestData = await transformData(requestTransforms, config.data, flatHeaders, config);\n\n if (\n requestData === null ||\n requestData === undefined ||\n (typeof FormData !== 'undefined' && requestData instanceof FormData)\n ) {\n removeContentType(flatHeaders);\n }\n\n const fetchOptions: RequestInit = {\n method: (config.method || 'GET').toUpperCase(),\n headers: buildFetchHeaders(flatHeaders),\n };\n\n const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];\n if (\n methodsWithBody.includes(fetchOptions.method!) &&\n requestData !== undefined &&\n requestData !== null\n ) {\n fetchOptions.body = requestData as BodyInit;\n }\n\n if (config.withCredentials) {\n fetchOptions.credentials = 'include';\n }\n\n if (config.dispatcher) {\n (fetchOptions as any).dispatcher = config.dispatcher;\n }\n if (config.agent) {\n (fetchOptions as any).agent = config.agent;\n }\n\n const requestStartTime = Date.now();\n const response = await fetchAdapter(config, fullURL, fetchOptions, requestStartTime);\n\n const responseTransforms = buildTransformArray(config.transformResponse);\n response.data = await transformData(\n responseTransforms,\n response.data,\n response.headers,\n config,\n 'response',\n );\n\n return response;\n };\n\n const promise = performRequest();\n\n if (isGet && config.dedupe) {\n trackActiveRequest(cacheKey, promise);\n const cleanup = () => {\n if (activeRequests.get(cacheKey) === promise) {\n activeRequests.delete(cacheKey);\n }\n };\n promise.then(cleanup, cleanup);\n }\n\n try {\n const shared = await promise;\n const response = finalizeResponse(shared, config);\n\n if (isGet && config.cache) {\n const cacheProvider = typeof config.cache === 'object' ? config.cache : defaultMemoryCache;\n await cacheProvider.set(cacheKey, shared, config.cacheTTL);\n }\n\n const settled = await new Promise<AccessioResponse>((resolve, reject) => {\n settle(\n resolve as (value: AccessioResponse) => void,\n reject as (reason: AccessioError) => void,\n response,\n config,\n );\n });\n\n if (config.hooks?.onRequestResponse) {\n await config.hooks.onRequestResponse(settled);\n }\n\n return settled;\n } catch (error) {\n if (config.hooks?.onRequestError && error instanceof AccessioError) {\n await config.hooks.onRequestError(error);\n }\n throw error;\n }\n}\n\nfunction finalizeResponse(\n shared: AccessioResponse,\n config: AccessioRequestConfig,\n): AccessioResponse {\n return {\n ...shared,\n config: redactConfig(config) as typeof shared.config,\n };\n}\n"],"mappings":"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","names":["AccessioError","buildURL","settle","transformData","fetchAdapter"]}
|
|
@@ -18,11 +18,16 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var memoryCache_exports = {};
|
|
20
20
|
__export(memoryCache_exports, {
|
|
21
|
+
MemoryCache: () => MemoryCache,
|
|
21
22
|
defaultMemoryCache: () => defaultMemoryCache
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(memoryCache_exports);
|
|
24
25
|
class MemoryCache {
|
|
25
26
|
cache = /* @__PURE__ */ new Map();
|
|
27
|
+
maxItems;
|
|
28
|
+
constructor(maxItems = 1e3) {
|
|
29
|
+
this.maxItems = maxItems;
|
|
30
|
+
}
|
|
26
31
|
get(key) {
|
|
27
32
|
const item = this.cache.get(key);
|
|
28
33
|
if (!item) return null;
|
|
@@ -33,7 +38,19 @@ class MemoryCache {
|
|
|
33
38
|
return item.value;
|
|
34
39
|
}
|
|
35
40
|
set(key, value, ttl) {
|
|
36
|
-
const
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
for (const [k, item] of this.cache.entries()) {
|
|
43
|
+
if (item.expiry && now > item.expiry) {
|
|
44
|
+
this.cache.delete(k);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (this.cache.size >= this.maxItems) {
|
|
48
|
+
const oldest = this.cache.keys().next().value;
|
|
49
|
+
if (oldest !== void 0) {
|
|
50
|
+
this.cache.delete(oldest);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const expiry = ttl ? now + ttl : null;
|
|
37
54
|
this.cache.set(key, { value, expiry });
|
|
38
55
|
}
|
|
39
56
|
delete(key) {
|
|
@@ -46,6 +63,7 @@ class MemoryCache {
|
|
|
46
63
|
const defaultMemoryCache = new MemoryCache();
|
|
47
64
|
// Annotate the CommonJS export names for ESM import in node:
|
|
48
65
|
0 && (module.exports = {
|
|
66
|
+
MemoryCache,
|
|
49
67
|
defaultMemoryCache
|
|
50
68
|
});
|
|
51
69
|
//# sourceMappingURL=memoryCache.cjs.map
|
|
@@ -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\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
|
|
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":[]}
|
package/index.d.ts
CHANGED
|
@@ -150,6 +150,13 @@ export interface AccessioRequestConfig {
|
|
|
150
150
|
/** TTL in ms for cached responses (when supported by the provider) */
|
|
151
151
|
cacheTTL?: number;
|
|
152
152
|
|
|
153
|
+
/** Custom function to serialize request properties into a deterministic cache key */
|
|
154
|
+
cacheKeySerializer?: (
|
|
155
|
+
config: AccessioRequestConfig,
|
|
156
|
+
fullURL: string,
|
|
157
|
+
headers: Record<string, HeaderValue>,
|
|
158
|
+
) => string;
|
|
159
|
+
|
|
153
160
|
// ── Response handling ──────────────────────────────
|
|
154
161
|
|
|
155
162
|
/** Maximum allowed response Content-Length in bytes */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accessio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Fast, flexible HTTP client — simple, modular, and dependency-free",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./cjs/index.cjs",
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"node": ">=18.0.0"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@
|
|
99
|
+
"@types/node": "^25.9.2",
|
|
100
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
100
101
|
"eslint": "^9.0.0",
|
|
101
102
|
"eslint-config-prettier": "^10.1.8",
|
|
102
103
|
"jsdom": "^29.0.2",
|
|
@@ -104,6 +105,6 @@
|
|
|
104
105
|
"tsup": "^8.0.0",
|
|
105
106
|
"typescript": "^5.0.0",
|
|
106
107
|
"typescript-eslint": "^8.59.3",
|
|
107
|
-
"vitest": "^
|
|
108
|
+
"vitest": "^4.1.8"
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -117,8 +117,8 @@ export class AccessioError extends Error {
|
|
|
117
117
|
this.response = response ?? null;
|
|
118
118
|
this.isAccessioError = true;
|
|
119
119
|
|
|
120
|
-
if (Error.captureStackTrace) {
|
|
121
|
-
Error.captureStackTrace(this, AccessioError);
|
|
120
|
+
if ((Error as any).captureStackTrace) {
|
|
121
|
+
(Error as any).captureStackTrace(this, AccessioError);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
package/src/core/buildURL.ts
CHANGED
|
@@ -82,19 +82,22 @@ export default function buildURL(
|
|
|
82
82
|
if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;
|
|
83
83
|
unusedParams[key] = (params as Record<string, unknown>)[key];
|
|
84
84
|
}
|
|
85
|
-
fullURL = fullURL.replace(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
key
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
);
|
|
98
101
|
finalParams = unusedParams;
|
|
99
102
|
}
|
|
100
103
|
|
package/src/core/mergeConfig.ts
CHANGED
|
@@ -37,7 +37,7 @@ function deepMerge(...sources: any[]): Record<string, any> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const requestOnlyKeys = new Set<string>(['url', 'data', 'signal']);
|
|
40
|
-
const deepMergeKeys = new Set<string>(['headers']);
|
|
40
|
+
const deepMergeKeys = new Set<string>(['headers', 'params', 'hooks']);
|
|
41
41
|
|
|
42
42
|
export default function mergeConfig(
|
|
43
43
|
config1: AccessioRequestConfig = {},
|
package/src/core/request.ts
CHANGED
|
@@ -12,28 +12,35 @@ import type { AccessioRequestConfig, AccessioResponse, TransformFunction } from
|
|
|
12
12
|
type HeadersConfig = Record<string, Record<string, string | string[]>>;
|
|
13
13
|
type FlatHeaders = Record<string, string | string[]>;
|
|
14
14
|
|
|
15
|
-
function lookupHeader(headers: FlatHeaders, name: string): string {
|
|
16
|
-
const target = name.toLowerCase();
|
|
17
|
-
for (const k of Object.keys(headers)) {
|
|
18
|
-
if (k.toLowerCase() === target) {
|
|
19
|
-
const v = headers[k];
|
|
20
|
-
return Array.isArray(v) ? v.join(',') : (v ?? '');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return '';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
15
|
function buildCacheKey(
|
|
27
16
|
config: AccessioRequestConfig,
|
|
28
17
|
fullURL: string,
|
|
29
18
|
flatHeaders: FlatHeaders,
|
|
30
19
|
): string {
|
|
20
|
+
if (typeof config.cacheKeySerializer === 'function') {
|
|
21
|
+
return config.cacheKeySerializer(config, fullURL, flatHeaders);
|
|
22
|
+
}
|
|
31
23
|
const method = (config.method || 'GET').toUpperCase();
|
|
32
|
-
const auth = lookupHeader(flatHeaders, 'authorization');
|
|
33
|
-
const accept = lookupHeader(flatHeaders, 'accept');
|
|
34
24
|
const withCreds = config.withCredentials ? '1' : '0';
|
|
35
25
|
const respType = config.responseType || 'json';
|
|
36
|
-
|
|
26
|
+
|
|
27
|
+
// Sort and serialize headers dynamically to prevent collisions,
|
|
28
|
+
// excluding environment-specific transient headers.
|
|
29
|
+
const serializedHeaders = Object.keys(flatHeaders)
|
|
30
|
+
.sort()
|
|
31
|
+
.filter(
|
|
32
|
+
(k) =>
|
|
33
|
+
!['user-agent', 'connection', 'host', 'content-length', 'accept-encoding'].includes(
|
|
34
|
+
k.toLowerCase(),
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
.map((k) => {
|
|
38
|
+
const val = flatHeaders[k];
|
|
39
|
+
return `${k.toLowerCase()}=${Array.isArray(val) ? val.join(',') : val}`;
|
|
40
|
+
})
|
|
41
|
+
.join('&');
|
|
42
|
+
|
|
43
|
+
return `${method}:${fullURL}|h:${serializedHeaders}|c=${withCreds}|t=${respType}`;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function buildTransformArray(
|
|
@@ -126,8 +133,39 @@ export default async function dispatchRequest(
|
|
|
126
133
|
if (isGet && config.dedupe) {
|
|
127
134
|
const inflight = activeRequests.get(cacheKey);
|
|
128
135
|
if (inflight) {
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
try {
|
|
137
|
+
const shared = await inflight;
|
|
138
|
+
const response = finalizeResponse(shared, config);
|
|
139
|
+
const settled = await new Promise<AccessioResponse>((resolve, reject) => {
|
|
140
|
+
settle(
|
|
141
|
+
resolve as (value: AccessioResponse) => void,
|
|
142
|
+
reject as (reason: AccessioError) => void,
|
|
143
|
+
response,
|
|
144
|
+
config,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (config.hooks?.onRequestResponse) {
|
|
149
|
+
await config.hooks.onRequestResponse(settled);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return settled;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
let finalError = error;
|
|
155
|
+
if (error instanceof AccessioError) {
|
|
156
|
+
finalError = AccessioError.from(
|
|
157
|
+
error,
|
|
158
|
+
error.code || 'ERR_DEDUPE',
|
|
159
|
+
config,
|
|
160
|
+
error.request,
|
|
161
|
+
error.response,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (config.hooks?.onRequestError && finalError instanceof AccessioError) {
|
|
165
|
+
await config.hooks.onRequestError(finalError);
|
|
166
|
+
}
|
|
167
|
+
throw finalError;
|
|
168
|
+
}
|
|
131
169
|
}
|
|
132
170
|
}
|
|
133
171
|
|
|
@@ -2,6 +2,11 @@ import type { CacheProvider } from '../types';
|
|
|
2
2
|
|
|
3
3
|
class MemoryCache implements CacheProvider {
|
|
4
4
|
private cache = new Map<string, { value: any; expiry: number | null }>();
|
|
5
|
+
private maxItems: number;
|
|
6
|
+
|
|
7
|
+
constructor(maxItems: number = 1000) {
|
|
8
|
+
this.maxItems = maxItems;
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
get(key: string) {
|
|
7
12
|
const item = this.cache.get(key);
|
|
@@ -14,7 +19,24 @@ class MemoryCache implements CacheProvider {
|
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
set(key: string, value: any, ttl?: number) {
|
|
17
|
-
const
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
|
|
24
|
+
// Proactively evict all expired items first
|
|
25
|
+
for (const [k, item] of this.cache.entries()) {
|
|
26
|
+
if (item.expiry && now > item.expiry) {
|
|
27
|
+
this.cache.delete(k);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Evict oldest item if we are still at limit
|
|
32
|
+
if (this.cache.size >= this.maxItems) {
|
|
33
|
+
const oldest = this.cache.keys().next().value;
|
|
34
|
+
if (oldest !== undefined) {
|
|
35
|
+
this.cache.delete(oldest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const expiry = ttl ? now + ttl : null;
|
|
18
40
|
this.cache.set(key, { value, expiry });
|
|
19
41
|
}
|
|
20
42
|
|
|
@@ -28,3 +50,4 @@ class MemoryCache implements CacheProvider {
|
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
export const defaultMemoryCache = new MemoryCache();
|
|
53
|
+
export { MemoryCache };
|
package/src/types.ts
CHANGED
|
@@ -91,6 +91,11 @@ export interface AccessioRequestConfig {
|
|
|
91
91
|
dedupe?: boolean;
|
|
92
92
|
cache?: boolean | CacheProvider;
|
|
93
93
|
cacheTTL?: number;
|
|
94
|
+
cacheKeySerializer?: (
|
|
95
|
+
config: AccessioRequestConfig,
|
|
96
|
+
fullURL: string,
|
|
97
|
+
headers: Record<string, string | string[]>,
|
|
98
|
+
) => string;
|
|
94
99
|
onDownloadProgress?: (progressEvent: { loaded: number; total: number }) => void;
|
|
95
100
|
hooks?: AccessioHooks;
|
|
96
101
|
schema?: SchemaValidator;
|