@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 CHANGED
@@ -1,79 +1,127 @@
1
1
  # TypeFetch
2
2
 
3
- TypeFetch is a strongly-typed HTTP client built on TypeScript and Zod.
3
+ TypeFetch is a production-grade, strongly-typed HTTP client built on
4
+ **TypeScript** and **Zod**.
4
5
 
5
- You define your API once using Zod schemas, and TypeFetch generates a fully type-safe client with:
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
- - Automatic URL handling (path, query, body)
9
- - Middleware pipeline (logging, retry, cache, auth)
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
- ## Key Features
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
- 1. Type-Safe API Client
26
- Define your API with Zod schemas and get full type safety for request and response types.
64
+ - **fixed** → constant delay
65
+ - **exponential** 100ms, 200ms, 400ms...
27
66
 
28
- 2. Structured Request Support
29
- Each request may contain:
67
+ Backoff is applied automatically between retries.
30
68
 
31
- - path: URL parameters (fills /users/:id)
32
- - query: URL query string ?page=1&limit=10
33
- - body: JSON payload for POST/PUT/PATCH
69
+ ---
34
70
 
35
- The client automatically builds URLs and bodies correctly.
71
+ ## 3. Timeout & Abort Support
36
72
 
37
- 3. Backward Compatibility
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
- 4. Middlewares
41
- Middlewares allow modifying requests and responses.
42
- Built-in middlewares include:
75
+ ```ts
76
+ await api.user.getUser({ path: { id: "123" } }, { timeout: 5000 });
77
+ ```
43
78
 
44
- - loggingMiddleware
45
- - retryMiddleware
46
- - authMiddleware
47
- - cacheMiddleware
79
+ Internally uses `AbortController` for safe cancellation.
48
80
 
49
- 5. Token Provider System
50
- Provide tokens dynamically using:
81
+ ---
51
82
 
52
- - token: static auth token
53
- - tokenProvider: function or async function returning a token
83
+ ## 4. Structured Request Model (Canonical Format)
54
84
 
55
- 6. Mock Mode
56
- Provides mock responses based on endpoint.mockData, with configurable fake delays.
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
- 7. Response Wrapper
59
- For APIs that wrap responses:
94
+ TypeFetch automatically:
60
95
 
61
- ```
62
- {
63
- "success": true,
64
- "data": {...},
65
- "timestamp": "...",
66
- "requestId": "..."
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
- TypeFetch unwraps the response automatically.
104
+ ---
71
105
 
72
- ## Defining API Contracts
106
+ ## 5. Backward Compatibility
73
107
 
74
- Example contract definition:
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() }).optional(),
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
- createUser: {
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
- ## Using ApiClient
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
- ## Middlewares
167
+ ---
142
168
 
143
- Example custom middleware:
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 to:", ctx.url);
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 middlewares:
184
+ ## Built-in Middlewares
155
185
 
156
- ```
157
- import {
158
- loggingMiddleware,
159
- retryMiddleware,
160
- cacheMiddleware,
161
- authMiddleware
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
- ## Mock Mode
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
- ## Response Transformation
178
- ```
179
- client.useResponseTransform((data) => {
180
- return {
181
- ...data,
182
- transformedAt: new Date().toISOString()
183
- };
184
- });
185
- ```
186
- ## Response Wrapper Example
187
- ```
188
- const wrapper = (successResponse) =>
189
- z.union([
190
- z.object({
191
- success: z.literal(true),
192
- data: successResponse,
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
- ## Error Handling
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("API Error:", err.message, err.status, err.code);
249
+ console.error(err.message, err.status);
211
250
  });
212
251
  ```
213
- ## Notes
214
252
 
215
- - Always call client.init() before using client.modules.
216
- - Middlewares run in reverse registration order.
217
- - Endpoints requiring auth must have a token or tokenProvider.
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
- - Backward-compatible request support makes migration safe.
296
+ - Structured request shape is recommended.
297
+ - Retry + Timeout can be combined safely.
298
+
299
+ ---
220
300
 
221
- ## License
301
+ # License
222
302
 
223
303
  MIT