@marianmeres/http-utils 2.7.1 → 2.9.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/AGENTS.md +138 -99
- package/API.md +341 -197
- package/README.md +126 -49
- package/dist/api.d.ts +100 -0
- package/dist/api.js +143 -4
- package/dist/error.d.ts +28 -1
- package/dist/error.js +106 -64
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +2 -2
- package/dist/status.js +77 -61
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```yaml
|
|
6
6
|
name: "@marianmeres/http-utils"
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.8.0"
|
|
8
8
|
license: MIT
|
|
9
9
|
runtime: deno, node
|
|
10
10
|
type: library
|
|
@@ -13,7 +13,10 @@ category: http-client
|
|
|
13
13
|
|
|
14
14
|
## Purpose
|
|
15
15
|
|
|
16
|
-
Lightweight, opinionated HTTP client wrapper for the native `fetch` API. Provides
|
|
16
|
+
Lightweight, opinionated HTTP client wrapper for the native `fetch` API. Provides
|
|
17
|
+
type-safe HTTP errors mapped to specific error classes (e.g., 404 → NotFound), convenient
|
|
18
|
+
defaults (auto JSON parsing, Bearer token support, base URLs), and flexible three-tier
|
|
19
|
+
error message extraction.
|
|
17
20
|
|
|
18
21
|
## Architecture
|
|
19
22
|
|
|
@@ -31,121 +34,142 @@ src/
|
|
|
31
34
|
|
|
32
35
|
```typescript
|
|
33
36
|
function createHttpApi(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
): HttpApi
|
|
37
|
+
base?: string | null,
|
|
38
|
+
defaults?: Partial<FetchParams> | (() => Promise<Partial<FetchParams>>),
|
|
39
|
+
factoryErrorMessageExtractor?: ErrorMessageExtractor | null,
|
|
40
|
+
): HttpApi;
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
### HttpApi Methods
|
|
41
44
|
|
|
42
|
-
| Method
|
|
43
|
-
|
|
44
|
-
| `get`
|
|
45
|
-
| `post`
|
|
46
|
-
| `put`
|
|
47
|
-
| `patch`
|
|
48
|
-
| `del`
|
|
49
|
-
| `url`
|
|
50
|
-
| `base`
|
|
51
|
-
| `onRequest`
|
|
52
|
-
| `onResponse` | `onResponse(i: ResponseInterceptor \| null): this`
|
|
45
|
+
| Method | Signature | Description |
|
|
46
|
+
| ------------ | ------------------------------------------------------ | -------------------------------------------------------------------- |
|
|
47
|
+
| `get` | `get(path, options?: GetOptions): Promise<unknown>` | GET request |
|
|
48
|
+
| `post` | `post(path, options?: DataOptions): Promise<unknown>` | POST request |
|
|
49
|
+
| `put` | `put(path, options?: DataOptions): Promise<unknown>` | PUT request |
|
|
50
|
+
| `patch` | `patch(path, options?: DataOptions): Promise<unknown>` | PATCH request |
|
|
51
|
+
| `del` | `del(path, options?: DataOptions): Promise<unknown>` | DELETE request |
|
|
52
|
+
| `url` | `url(path: string): string` | Build full URL (base trailing slash + path leading slash normalized) |
|
|
53
|
+
| `base` | `get/set base: string \| null` | Base URL property |
|
|
54
|
+
| `onRequest` | `onRequest(i: RequestInterceptor \| null): this` | Register request interceptor |
|
|
55
|
+
| `onResponse` | `onResponse(i: ResponseInterceptor \| null): this` | Register response interceptor |
|
|
53
56
|
|
|
54
57
|
### Exported Types
|
|
55
58
|
|
|
56
59
|
```typescript
|
|
57
60
|
type RequestData =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
| Record<string, unknown>
|
|
62
|
+
| unknown[]
|
|
63
|
+
| FormData
|
|
64
|
+
| Blob
|
|
65
|
+
| ArrayBuffer
|
|
66
|
+
| ArrayBufferView
|
|
67
|
+
| URLSearchParams
|
|
68
|
+
| ReadableStream
|
|
69
|
+
| string
|
|
70
|
+
| number
|
|
71
|
+
| boolean
|
|
72
|
+
| null;
|
|
70
73
|
|
|
71
74
|
type QueryValue =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
| string
|
|
76
|
+
| number
|
|
77
|
+
| boolean
|
|
78
|
+
| (string | number | boolean)[]
|
|
79
|
+
| null
|
|
80
|
+
| undefined;
|
|
75
81
|
|
|
76
82
|
interface FetchParams {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
data?: RequestData;
|
|
84
|
+
token?: string | null;
|
|
85
|
+
headers?: HeadersInit | null;
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
timeout?: number | null; // ms
|
|
88
|
+
query?: Record<string, QueryValue> | null;
|
|
89
|
+
credentials?: "omit" | "same-origin" | "include" | null;
|
|
90
|
+
raw?: boolean | null;
|
|
91
|
+
assert?: boolean | null;
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
interface GetOptions {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
params?: FetchParams;
|
|
96
|
+
respHeaders?: ResponseHeaders | null;
|
|
97
|
+
errorExtractor?: ErrorMessageExtractor | null;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
interface DataOptions {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
data?: RequestData;
|
|
102
|
+
params?: FetchParams;
|
|
103
|
+
respHeaders?: ResponseHeaders | null;
|
|
104
|
+
errorExtractor?: ErrorMessageExtractor | null;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
type ErrorMessageExtractor = (body: unknown, response: Response) => string;
|
|
102
108
|
type ResponseHeaders = Record<string, string | number>;
|
|
103
109
|
|
|
104
110
|
type RequestInterceptor = (
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
init: RequestInit,
|
|
112
|
+
ctx: { method: string; url: string },
|
|
107
113
|
) => RequestInit | void | Promise<RequestInit | void>;
|
|
108
114
|
|
|
109
115
|
type ResponseInterceptor = (
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
response: Response,
|
|
117
|
+
ctx: { method: string; url: string },
|
|
112
118
|
) => Response | void | Promise<Response | void>;
|
|
113
119
|
```
|
|
114
120
|
|
|
115
121
|
### Error Classes (HTTP_ERROR namespace)
|
|
116
122
|
|
|
117
123
|
```typescript
|
|
118
|
-
HTTP_ERROR.HttpError
|
|
119
|
-
HTTP_ERROR.BadRequest
|
|
120
|
-
HTTP_ERROR.Unauthorized
|
|
121
|
-
HTTP_ERROR.Forbidden
|
|
122
|
-
HTTP_ERROR.NotFound
|
|
123
|
-
HTTP_ERROR.MethodNotAllowed // 405
|
|
124
|
-
HTTP_ERROR.RequestTimeout
|
|
125
|
-
HTTP_ERROR.Conflict
|
|
126
|
-
HTTP_ERROR.Gone
|
|
127
|
-
HTTP_ERROR.LengthRequired
|
|
128
|
-
HTTP_ERROR.ImATeapot
|
|
129
|
-
HTTP_ERROR.UnprocessableContent // 422
|
|
130
|
-
HTTP_ERROR.TooManyRequests
|
|
131
|
-
HTTP_ERROR.InternalServerError // 500
|
|
132
|
-
HTTP_ERROR.NotImplemented
|
|
133
|
-
HTTP_ERROR.BadGateway
|
|
134
|
-
HTTP_ERROR.ServiceUnavailable // 503
|
|
124
|
+
HTTP_ERROR.HttpError; // Base class (default 500)
|
|
125
|
+
HTTP_ERROR.BadRequest; // 400
|
|
126
|
+
HTTP_ERROR.Unauthorized; // 401
|
|
127
|
+
HTTP_ERROR.Forbidden; // 403
|
|
128
|
+
HTTP_ERROR.NotFound; // 404
|
|
129
|
+
HTTP_ERROR.MethodNotAllowed; // 405
|
|
130
|
+
HTTP_ERROR.RequestTimeout; // 408
|
|
131
|
+
HTTP_ERROR.Conflict; // 409
|
|
132
|
+
HTTP_ERROR.Gone; // 410
|
|
133
|
+
HTTP_ERROR.LengthRequired; // 411
|
|
134
|
+
HTTP_ERROR.ImATeapot; // 418
|
|
135
|
+
HTTP_ERROR.UnprocessableContent; // 422
|
|
136
|
+
HTTP_ERROR.TooManyRequests; // 429
|
|
137
|
+
HTTP_ERROR.InternalServerError; // 500
|
|
138
|
+
HTTP_ERROR.NotImplemented; // 501
|
|
139
|
+
HTTP_ERROR.BadGateway; // 502
|
|
140
|
+
HTTP_ERROR.ServiceUnavailable; // 503
|
|
141
|
+
HTTP_ERROR.NetworkError; // 0 (transport-level failure: DNS/refused/timeout/unreachable; extends HttpError, cause = underlying error)
|
|
135
142
|
```
|
|
136
143
|
|
|
137
144
|
### Helper Functions
|
|
138
145
|
|
|
139
146
|
```typescript
|
|
140
147
|
// Marks options object for options-based API (vs legacy positional API)
|
|
141
|
-
function opts<T extends GetOptions | DataOptions>(options: T): T
|
|
148
|
+
function opts<T extends GetOptions | DataOptions>(options: T): T;
|
|
142
149
|
```
|
|
143
150
|
|
|
144
151
|
### Utility Functions
|
|
145
152
|
|
|
146
153
|
```typescript
|
|
147
|
-
function createHttpError(
|
|
148
|
-
|
|
154
|
+
function createHttpError(
|
|
155
|
+
code: number | string,
|
|
156
|
+
message?: string | null,
|
|
157
|
+
body?: unknown,
|
|
158
|
+
cause?: unknown,
|
|
159
|
+
): HttpError;
|
|
160
|
+
function getErrorMessage(e: unknown, stripErrorPrefix?: boolean): string;
|
|
161
|
+
// Wraps fetch; on transport failure throws NetworkError("<what> unreachable (<url>): <reason>", { cause }).
|
|
162
|
+
// AbortError/TimeoutError pass through untouched. Used internally by HttpApi.
|
|
163
|
+
// 3rd arg is a label string OR FetchOrThrowOptions { what?, onRequest?, onError? } (pure observers; a string normalizes to { what }).
|
|
164
|
+
// onRequest fires before dispatch (throw => request not sent). onError fires on every failure
|
|
165
|
+
// with kind: "abort" | "timeout" | "network" (throw => swallowed, real error preserved).
|
|
166
|
+
// Global defaults (overridable per call; resolution: per-call ?? global; instruments HttpApi too):
|
|
167
|
+
// fetchOrThrow.global.onRequest / fetchOrThrow.global.onError
|
|
168
|
+
function fetchOrThrow(
|
|
169
|
+
input: string | URL | Request,
|
|
170
|
+
init?: RequestInit,
|
|
171
|
+
whatOrOptions?: string | FetchOrThrowOptions,
|
|
172
|
+
): Promise<Response>;
|
|
149
173
|
```
|
|
150
174
|
|
|
151
175
|
### HTTP Status Codes
|
|
@@ -169,23 +193,38 @@ class HTTP_STATUS {
|
|
|
169
193
|
|
|
170
194
|
## Key Behaviors
|
|
171
195
|
|
|
172
|
-
1. **Auto JSON parsing**: Response bodies are parsed as JSON if possible; empty bodies
|
|
196
|
+
1. **Auto JSON parsing**: Response bodies are parsed as JSON if possible; empty bodies
|
|
197
|
+
(204/205) return `null`
|
|
173
198
|
2. **Bearer token**: `token` param auto-adds `Authorization: Bearer {token}` header
|
|
174
|
-
3. **Error throwing**: By default, non-OK responses throw HttpError (disable with
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
199
|
+
3. **Error throwing**: By default, non-OK responses throw HttpError (disable with
|
|
200
|
+
`assert: false`)
|
|
201
|
+
4. **Response headers**: Pass `respHeaders: {}` to capture response headers (mutated in
|
|
202
|
+
place)
|
|
203
|
+
5. **Raw response**: Use `raw: true` to get raw Response object; the caller must consume
|
|
204
|
+
the body
|
|
205
|
+
6. **Error priority**: per-request extractor → per-instance → global → built-in fallback;
|
|
206
|
+
a throwing extractor falls through to the next priority rather than crashing
|
|
207
|
+
7. **URL normalization**: `#url(path)` strips trailing `/` from base and ensures leading
|
|
208
|
+
`/` on path, so `base + path` never produces `//` or missing `/`
|
|
209
|
+
8. **Timeout**: `params.timeout` (ms) aborts via `AbortSignal.timeout`; composed with
|
|
210
|
+
`params.signal` via `AbortSignal.any`
|
|
211
|
+
9. **Query**: `params.query` object appended as URL search params; `null`/`undefined`
|
|
212
|
+
skipped, arrays → repeated keys
|
|
213
|
+
10. **Transport errors**: connectivity failures (DNS, refused connection, unreachable
|
|
214
|
+
host) throw `HTTP_ERROR.NetworkError` (status 0) via `fetchOrThrow`, with the real
|
|
215
|
+
reason in the message and the underlying error as `cause` — never an opaque "fetch
|
|
216
|
+
failed". Deliberate `AbortError`/`TimeoutError` are not wrapped.
|
|
217
|
+
11. **Request tracing**: `fetchOrThrow.global.onRequest` / `onError` are observer-only
|
|
218
|
+
hooks (no recovery/transform), overridable per call (`per-call ?? global`). They also
|
|
219
|
+
fire for `HttpApi` requests, since the client routes through `fetchOrThrow`.
|
|
181
220
|
|
|
182
221
|
## Request Body Serialization
|
|
183
222
|
|
|
184
|
-
| Runtime type
|
|
185
|
-
|
|
186
|
-
| `null` / `undefined`
|
|
187
|
-
| `string`
|
|
188
|
-
| `number` / `boolean` / plain object / array
|
|
223
|
+
| Runtime type | Sent as | Content-Type set |
|
|
224
|
+
| ------------------------------------------------------------------------------------ | ------------------------- | -------------------------------------------------------- |
|
|
225
|
+
| `null` / `undefined` | No body | — |
|
|
226
|
+
| `string` | raw | caller-controlled |
|
|
227
|
+
| `number` / `boolean` / plain object / array | `JSON.stringify` | `application/json` only if not already set |
|
|
189
228
|
| `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer`, typed arrays, `ReadableStream` | passed to fetch unchanged | fetch handles (e.g. multipart boundary, form-urlencoded) |
|
|
190
229
|
|
|
191
230
|
User-provided `Content-Type` header is always preserved.
|
|
@@ -206,14 +245,14 @@ deno task publish # Publish to JSR and NPM
|
|
|
206
245
|
|
|
207
246
|
## File Locations
|
|
208
247
|
|
|
209
|
-
| Purpose
|
|
210
|
-
|
|
211
|
-
| Entry point
|
|
212
|
-
| HttpApi implementation | `src/api.ts`
|
|
213
|
-
| Error classes
|
|
214
|
-
| Status codes
|
|
215
|
-
| Tests
|
|
216
|
-
| NPM output
|
|
248
|
+
| Purpose | Path |
|
|
249
|
+
| ---------------------- | ----------------- |
|
|
250
|
+
| Entry point | `src/mod.ts` |
|
|
251
|
+
| HttpApi implementation | `src/api.ts` |
|
|
252
|
+
| Error classes | `src/error.ts` |
|
|
253
|
+
| Status codes | `src/status.ts` |
|
|
254
|
+
| Tests | `tests/*.test.ts` |
|
|
255
|
+
| NPM output | `.npm-dist/` |
|
|
217
256
|
|
|
218
257
|
## Common Patterns
|
|
219
258
|
|
|
@@ -223,7 +262,7 @@ deno task publish # Publish to JSR and NPM
|
|
|
223
262
|
import { createHttpApi, opts } from "@marianmeres/http-utils";
|
|
224
263
|
|
|
225
264
|
const api = createHttpApi("https://api.example.com", {
|
|
226
|
-
|
|
265
|
+
headers: { "Authorization": "Bearer token" },
|
|
227
266
|
});
|
|
228
267
|
|
|
229
268
|
// Legacy API (default - object is request body)
|
|
@@ -238,11 +277,11 @@ await api.post("/users", opts({ data: { name: "John" }, params: { token: "abc" }
|
|
|
238
277
|
|
|
239
278
|
```typescript
|
|
240
279
|
try {
|
|
241
|
-
|
|
280
|
+
await api.get("/resource");
|
|
242
281
|
} catch (error) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
282
|
+
if (error instanceof HTTP_ERROR.NotFound) {
|
|
283
|
+
// Handle 404
|
|
284
|
+
}
|
|
246
285
|
}
|
|
247
286
|
```
|
|
248
287
|
|
|
@@ -250,6 +289,6 @@ try {
|
|
|
250
289
|
|
|
251
290
|
```typescript
|
|
252
291
|
const api = createHttpApi("https://api.example.com", async () => ({
|
|
253
|
-
|
|
292
|
+
headers: { "Authorization": `Bearer ${await getToken()}` },
|
|
254
293
|
}));
|
|
255
294
|
```
|