@tahanabavi/typefetch 1.2.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -134
- package/dist/index.d.mts +270 -90
- package/dist/index.d.ts +270 -90
- package/dist/index.js +710 -665
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +709 -666
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -1,79 +1,127 @@
|
|
|
1
1
|
# TypeFetch
|
|
2
2
|
|
|
3
|
-
TypeFetch is a strongly-typed HTTP client built on
|
|
3
|
+
TypeFetch is a production-grade, strongly-typed HTTP client built on
|
|
4
|
+
**TypeScript** and **Zod**.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Define your API once using Zod schemas, and TypeFetch generates a fully
|
|
7
|
+
type-safe client with:
|
|
6
8
|
|
|
7
9
|
- End-to-end type safety
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
+
- Structured request support: `{ path, query, body, headers }`
|
|
11
|
+
- Automatic URL handling (path parameters, query string, JSON body)
|
|
12
|
+
- Middleware pipeline (logging, retry, cache, auth, custom)
|
|
13
|
+
- Built-in retry engine with backoff strategies
|
|
14
|
+
- Timeout & AbortController support
|
|
10
15
|
- Mock mode for development
|
|
11
16
|
- Dynamic token providers
|
|
12
17
|
- Response wrappers for consistent API envelopes
|
|
13
|
-
- Unified error system (RichError)
|
|
18
|
+
- Unified error system (`RichError`)
|
|
19
|
+
- Optional `form-data` body support for file uploads
|
|
20
|
+
- Concurrency-safe request handling
|
|
21
|
+
- Production-grade validation and error normalization
|
|
22
|
+
|
|
23
|
+
---
|
|
14
24
|
|
|
15
25
|
## Installation
|
|
16
26
|
|
|
27
|
+
```bash
|
|
17
28
|
npm install @tahanabavi/typefetch
|
|
18
|
-
|
|
19
29
|
# or
|
|
20
|
-
|
|
21
30
|
yarn add @tahanabavi/typefetch
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# What's New / Updated
|
|
36
|
+
|
|
37
|
+
## 1. Advanced Retry Engine
|
|
38
|
+
|
|
39
|
+
TypeFetch now includes:
|
|
40
|
+
|
|
41
|
+
- Configurable `maxRetries`
|
|
42
|
+
- Custom `retryCondition`
|
|
43
|
+
- Built-in backoff strategies:
|
|
44
|
+
- `fixed`
|
|
45
|
+
- `exponential`
|
|
46
|
+
- Fully normalized retry errors
|
|
47
|
+
|
|
48
|
+
Example:
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
```ts
|
|
51
|
+
client.setRetryConfig({
|
|
52
|
+
maxRetries: 3,
|
|
53
|
+
backoff: "exponential",
|
|
54
|
+
retryCondition: (err) => err.status === 500,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. Backoff Strategies
|
|
61
|
+
|
|
62
|
+
Supported strategies:
|
|
24
63
|
|
|
25
|
-
|
|
26
|
-
|
|
64
|
+
- **fixed** → constant delay
|
|
65
|
+
- **exponential** → 100ms, 200ms, 400ms...
|
|
27
66
|
|
|
28
|
-
|
|
29
|
-
Each request may contain:
|
|
67
|
+
Backoff is applied automatically between retries.
|
|
30
68
|
|
|
31
|
-
|
|
32
|
-
- query: URL query string ?page=1&limit=10
|
|
33
|
-
- body: JSON payload for POST/PUT/PATCH
|
|
69
|
+
---
|
|
34
70
|
|
|
35
|
-
|
|
71
|
+
## 3. Timeout & Abort Support
|
|
36
72
|
|
|
37
|
-
|
|
38
|
-
If your request schema is flat (z.object({ name: z.string() })), TypeFetch automatically treats it as a simple body payload—no breaking changes.
|
|
73
|
+
Per-request timeout:
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
75
|
+
```ts
|
|
76
|
+
await api.user.getUser({ path: { id: "123" } }, { timeout: 5000 });
|
|
77
|
+
```
|
|
43
78
|
|
|
44
|
-
|
|
45
|
-
- retryMiddleware
|
|
46
|
-
- authMiddleware
|
|
47
|
-
- cacheMiddleware
|
|
79
|
+
Internally uses `AbortController` for safe cancellation.
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
Provide tokens dynamically using:
|
|
81
|
+
---
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
- tokenProvider: function or async function returning a token
|
|
83
|
+
## 4. Structured Request Model (Canonical Format)
|
|
54
84
|
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
```ts
|
|
86
|
+
z.object({
|
|
87
|
+
path: z.object({...}).optional(),
|
|
88
|
+
query: z.object({...}).optional(),
|
|
89
|
+
body: z.object({...}).optional(),
|
|
90
|
+
headers: z.record(z.string()).optional(),
|
|
91
|
+
})
|
|
92
|
+
```
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
For APIs that wrap responses:
|
|
94
|
+
TypeFetch automatically:
|
|
60
95
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```
|
|
96
|
+
- Injects path params
|
|
97
|
+
- Builds query string
|
|
98
|
+
- Serializes JSON body
|
|
99
|
+
- Merges headers in priority order:
|
|
100
|
+
1. auth
|
|
101
|
+
2. endpoint-level headers
|
|
102
|
+
3. per-call headers
|
|
69
103
|
|
|
70
|
-
|
|
104
|
+
---
|
|
71
105
|
|
|
72
|
-
##
|
|
106
|
+
## 5. Backward Compatibility
|
|
73
107
|
|
|
74
|
-
|
|
108
|
+
Flat request schemas still work:
|
|
75
109
|
|
|
110
|
+
```ts
|
|
111
|
+
z.object({
|
|
112
|
+
name: z.string(),
|
|
113
|
+
});
|
|
76
114
|
```
|
|
115
|
+
|
|
116
|
+
For non-GET requests, the entire object becomes the JSON body.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
# Defining API Contracts
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
77
125
|
import { z } from "zod";
|
|
78
126
|
|
|
79
127
|
const contracts = {
|
|
@@ -83,51 +131,30 @@ const contracts = {
|
|
|
83
131
|
path: "/users/:id",
|
|
84
132
|
auth: true,
|
|
85
133
|
request: z.object({
|
|
86
|
-
path: z.object({ id: z.string() })
|
|
87
|
-
query: z.object({}).optional(),
|
|
88
|
-
body: z.never().optional(),
|
|
134
|
+
path: z.object({ id: z.string() }),
|
|
89
135
|
}),
|
|
90
136
|
response: z.object({
|
|
91
137
|
id: z.string(),
|
|
92
138
|
name: z.string(),
|
|
93
139
|
}),
|
|
94
|
-
mockData: { id: "1", name: "John Doe" }
|
|
95
140
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
method: "POST",
|
|
99
|
-
path: "/users",
|
|
100
|
-
auth: true,
|
|
101
|
-
request: z.object({
|
|
102
|
-
path: z.object({}).optional(),
|
|
103
|
-
query: z.object({}).optional(),
|
|
104
|
-
body: z.object({ name: z.string() }).optional(),
|
|
105
|
-
}),
|
|
106
|
-
response: z.object({
|
|
107
|
-
id: z.string(),
|
|
108
|
-
name: z.string(),
|
|
109
|
-
}),
|
|
110
|
-
mockData: () => ({
|
|
111
|
-
id: Math.random().toString(36).slice(2),
|
|
112
|
-
name: "Mock User",
|
|
113
|
-
}),
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
141
|
+
},
|
|
142
|
+
} as const;
|
|
117
143
|
```
|
|
118
144
|
|
|
119
|
-
|
|
145
|
+
---
|
|
120
146
|
|
|
121
|
-
|
|
147
|
+
# Using ApiClient
|
|
148
|
+
|
|
149
|
+
```ts
|
|
122
150
|
import { ApiClient } from "@tahanabavi/typefetch";
|
|
123
151
|
|
|
124
152
|
const client = new ApiClient(
|
|
125
153
|
{
|
|
126
154
|
baseUrl: "https://api.example.com",
|
|
127
|
-
tokenProvider: () => "dynamic-token",
|
|
128
|
-
useMockData: false
|
|
155
|
+
tokenProvider: async () => "dynamic-token",
|
|
129
156
|
},
|
|
130
|
-
contracts
|
|
157
|
+
contracts,
|
|
131
158
|
);
|
|
132
159
|
|
|
133
160
|
client.init();
|
|
@@ -135,89 +162,142 @@ client.init();
|
|
|
135
162
|
const api = client.modules;
|
|
136
163
|
|
|
137
164
|
const user = await api.user.getUser({ path: { id: "123" } });
|
|
138
|
-
const created = await api.user.createUser({ body: { name: "Alice" } });
|
|
139
165
|
```
|
|
140
166
|
|
|
141
|
-
|
|
167
|
+
---
|
|
142
168
|
|
|
143
|
-
|
|
169
|
+
# Middleware System
|
|
144
170
|
|
|
145
|
-
|
|
171
|
+
Middlewares execute in reverse registration order.
|
|
172
|
+
|
|
173
|
+
## Custom Middleware
|
|
174
|
+
|
|
175
|
+
```ts
|
|
146
176
|
client.use(async (ctx, next) => {
|
|
147
|
-
console.log("Request
|
|
177
|
+
console.log("Request:", ctx.url);
|
|
148
178
|
const res = await next();
|
|
149
179
|
console.log("Response:", res.status);
|
|
150
180
|
return res;
|
|
151
181
|
});
|
|
152
182
|
```
|
|
153
183
|
|
|
154
|
-
Built-in
|
|
184
|
+
## Built-in Middlewares
|
|
155
185
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} from "@tahanabavi/typefetch/middlewares";
|
|
163
|
-
|
|
164
|
-
client.use(loggingMiddleware, { logRequest: true });
|
|
165
|
-
client.use(retryMiddleware, { maxRetries: 3, delay: 100 });
|
|
166
|
-
client.use(cacheMiddleware, { ttl: 60000 });
|
|
167
|
-
client.use(authMiddleware, {
|
|
168
|
-
refreshToken: async () => "refreshed-token"
|
|
169
|
-
});
|
|
170
|
-
```
|
|
186
|
+
- `loggingMiddleware`
|
|
187
|
+
- `retryMiddleware`
|
|
188
|
+
- `cacheMiddleware`
|
|
189
|
+
- `authMiddleware`
|
|
190
|
+
|
|
191
|
+
Example:
|
|
171
192
|
|
|
172
|
-
|
|
193
|
+
```ts
|
|
194
|
+
client.use(loggingMiddleware);
|
|
195
|
+
client.use(retryMiddleware, { maxRetries: 3 });
|
|
196
|
+
client.use(cacheMiddleware, { ttl: 60000 });
|
|
197
|
+
client.use(authMiddleware);
|
|
173
198
|
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
# Mock Mode
|
|
203
|
+
|
|
204
|
+
```ts
|
|
174
205
|
client.setMockMode(true, { min: 200, max: 1000 });
|
|
175
|
-
client.setMockMode(false);
|
|
176
206
|
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
timestamp: z.string(),
|
|
194
|
-
requestId: z.string(),
|
|
195
|
-
}),
|
|
196
|
-
z.object({
|
|
197
|
-
success: z.literal(false),
|
|
198
|
-
message: z.string(),
|
|
199
|
-
code: z.number(),
|
|
200
|
-
timestamp: z.string(),
|
|
201
|
-
requestId: z.string(),
|
|
202
|
-
}),
|
|
203
|
-
]);
|
|
204
|
-
|
|
205
|
-
client.setResponseWrapper(wrapper);
|
|
207
|
+
|
|
208
|
+
- Returns `mockData` instead of calling network
|
|
209
|
+
- Still applies response validation and wrapper
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
# Response Wrapper
|
|
214
|
+
|
|
215
|
+
Supports envelope APIs:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"success": true,
|
|
220
|
+
"data": {...},
|
|
221
|
+
"timestamp": "..."
|
|
222
|
+
}
|
|
206
223
|
```
|
|
207
|
-
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
client.setResponseWrapper(wrapperSchema);
|
|
208
229
|
```
|
|
230
|
+
|
|
231
|
+
On failure, throws normalized `RichError`.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
# Error Handling
|
|
236
|
+
|
|
237
|
+
All errors are normalized into `RichError`:
|
|
238
|
+
|
|
239
|
+
- HTTP errors
|
|
240
|
+
- Network failures
|
|
241
|
+
- Validation errors
|
|
242
|
+
- Timeout errors
|
|
243
|
+
- Retry exhaustion
|
|
244
|
+
|
|
245
|
+
Global handler:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
209
248
|
client.onError((err) => {
|
|
210
|
-
console.error(
|
|
249
|
+
console.error(err.message, err.status);
|
|
211
250
|
});
|
|
212
251
|
```
|
|
213
|
-
## Notes
|
|
214
252
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
# File Uploads (FormData)
|
|
256
|
+
|
|
257
|
+
Set `bodyType: "form-data"` in endpoint definition.
|
|
258
|
+
|
|
259
|
+
TypeFetch builds `FormData` automatically.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
# Concurrency Safety
|
|
264
|
+
|
|
265
|
+
TypeFetch safely handles parallel requests:
|
|
266
|
+
|
|
267
|
+
- No shared mutable state issues
|
|
268
|
+
- Independent retry cycles
|
|
269
|
+
- Independent AbortControllers
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
# Production-Grade Test Coverage
|
|
274
|
+
|
|
275
|
+
The project now includes:
|
|
276
|
+
|
|
277
|
+
- Validation tests
|
|
278
|
+
- Middleware tests
|
|
279
|
+
- Retry tests
|
|
280
|
+
- Backoff timing tests
|
|
281
|
+
- Timeout & abort tests
|
|
282
|
+
- Concurrency tests
|
|
283
|
+
- Error propagation tests
|
|
284
|
+
- Mock mode tests
|
|
285
|
+
- TokenProvider tests
|
|
286
|
+
- Edge case handling tests
|
|
287
|
+
|
|
288
|
+
Suitable for publishing as a production SDK.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
# Notes
|
|
293
|
+
|
|
294
|
+
- Always call `client.init()` before using modules.
|
|
218
295
|
- All responses are validated via Zod.
|
|
219
|
-
-
|
|
296
|
+
- Structured request shape is recommended.
|
|
297
|
+
- Retry + Timeout can be combined safely.
|
|
298
|
+
|
|
299
|
+
---
|
|
220
300
|
|
|
221
|
-
|
|
301
|
+
# License
|
|
222
302
|
|
|
223
303
|
MIT
|