@tahanabavi/typefetch 1.2.1 → 1.3.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 +228 -87
- package/dist/index.d.mts +29 -18
- package/dist/index.d.ts +29 -18
- package/dist/index.js +102 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -3
package/README.md
CHANGED
|
@@ -1,80 +1,86 @@
|
|
|
1
1
|
# TypeFetch
|
|
2
2
|
|
|
3
|
-
TypeFetch is a strongly-typed HTTP client built on TypeScript and Zod
|
|
3
|
+
TypeFetch is a strongly-typed HTTP client built on **TypeScript** and **Zod**.
|
|
4
4
|
|
|
5
5
|
You define your API once using Zod schemas, and TypeFetch generates a fully type-safe client with:
|
|
6
6
|
|
|
7
7
|
- End-to-end type safety
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- Structured request support: `{ path, query, body, headers }`
|
|
9
|
+
- Automatic URL handling (path parameters, query string, JSON body)
|
|
10
|
+
- Middleware pipeline (logging, retry, cache, auth, custom)
|
|
10
11
|
- Mock mode for development
|
|
11
12
|
- Dynamic token providers
|
|
12
13
|
- Response wrappers for consistent API envelopes
|
|
13
|
-
- Unified error system (RichError)
|
|
14
|
+
- Unified error system (`RichError`)
|
|
15
|
+
- Optional `form-data` body support for file uploads
|
|
16
|
+
|
|
17
|
+
---
|
|
14
18
|
|
|
15
19
|
## Installation
|
|
16
20
|
|
|
21
|
+
```bash
|
|
17
22
|
npm install @tahanabavi/typefetch
|
|
18
|
-
|
|
19
23
|
# or
|
|
20
|
-
|
|
21
24
|
yarn add @tahanabavi/typefetch
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
22
28
|
|
|
23
|
-
##
|
|
29
|
+
## Core Concepts
|
|
24
30
|
|
|
25
|
-
1. Type-Safe API Client
|
|
26
|
-
Define your API with Zod schemas and get full type safety for request and response types.
|
|
31
|
+
### 1. Type-Safe API Client
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
Each request may contain:
|
|
33
|
+
Define your API with Zod schemas and get full type safety for request and response types.
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
- query: URL query string ?page=1&limit=10
|
|
33
|
-
- body: JSON payload for POST/PUT/PATCH
|
|
35
|
+
Each endpoint has:
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
- `method`: HTTP verb (`GET | POST | PUT | PATCH | DELETE`)
|
|
38
|
+
- `path`: path template (e.g. `/users/:id`)
|
|
39
|
+
- `auth?`: whether a token is required
|
|
40
|
+
- `request`: Zod schema for the request
|
|
41
|
+
- `response`: Zod schema for the response
|
|
42
|
+
- `mockData?`: static or dynamic mock response
|
|
43
|
+
- `headers?`: static or function-based default headers
|
|
44
|
+
- `bodyType?`: `"json"` (default) or `"form-data"`
|
|
36
45
|
|
|
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.
|
|
46
|
+
### 2. Structured Request Shape
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
Middlewares allow modifying requests and responses.
|
|
42
|
-
Built-in middlewares include:
|
|
48
|
+
The recommended request shape is:
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
```ts
|
|
51
|
+
z.object({
|
|
52
|
+
path: z.object({ ... }).optional(), // URL params → /users/:id
|
|
53
|
+
query: z.object({ ... }).optional(), // query string → ?page=1
|
|
54
|
+
body: z.object({ ... }).optional(), // JSON body
|
|
55
|
+
headers: z.record(z.string()).optional(), // per-call extra headers
|
|
56
|
+
})
|
|
57
|
+
```
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
Provide tokens dynamically using:
|
|
59
|
+
TypeFetch will:
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
- Replace `:param` segments in the path using `path`
|
|
62
|
+
- Build query string from `query`
|
|
63
|
+
- Serialize `body` as JSON (or `FormData` if `bodyType: "form-data"`)
|
|
64
|
+
- Merge headers from:
|
|
65
|
+
- auth (Authorization)
|
|
66
|
+
- endpoint-level `headers`
|
|
67
|
+
- per-call `headers` in the request (highest priority)
|
|
54
68
|
|
|
55
|
-
|
|
56
|
-
Provides mock responses based on endpoint.mockData, with configurable fake delays.
|
|
69
|
+
### 3. Backward Compatibility
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
For APIs that wrap responses:
|
|
71
|
+
If your `request` schema is **flat** (e.g. `z.object({ name: z.string() })`) and does **not** contain `path`, `query`, `body`, or `headers`, TypeFetch treats the entire object as the request body for non-GET methods.
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
"success": true,
|
|
64
|
-
"data": {...},
|
|
65
|
-
"timestamp": "...",
|
|
66
|
-
"requestId": "..."
|
|
67
|
-
}
|
|
68
|
-
```
|
|
73
|
+
This makes migration to the structured format incremental and safe.
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
---
|
|
71
76
|
|
|
72
77
|
## Defining API Contracts
|
|
73
78
|
|
|
74
79
|
Example contract definition:
|
|
75
80
|
|
|
76
|
-
```
|
|
81
|
+
```ts
|
|
77
82
|
import { z } from "zod";
|
|
83
|
+
import { Contracts, EndpointDef } from "@tahanabavi/typefetch";
|
|
78
84
|
|
|
79
85
|
const contracts = {
|
|
80
86
|
user: {
|
|
@@ -86,12 +92,13 @@ const contracts = {
|
|
|
86
92
|
path: z.object({ id: z.string() }).optional(),
|
|
87
93
|
query: z.object({}).optional(),
|
|
88
94
|
body: z.never().optional(),
|
|
95
|
+
headers: z.record(z.string()).optional(),
|
|
89
96
|
}),
|
|
90
97
|
response: z.object({
|
|
91
98
|
id: z.string(),
|
|
92
99
|
name: z.string(),
|
|
93
100
|
}),
|
|
94
|
-
mockData: { id: "1", name: "John Doe" }
|
|
101
|
+
mockData: { id: "1", name: "John Doe" },
|
|
95
102
|
},
|
|
96
103
|
|
|
97
104
|
createUser: {
|
|
@@ -101,7 +108,12 @@ const contracts = {
|
|
|
101
108
|
request: z.object({
|
|
102
109
|
path: z.object({}).optional(),
|
|
103
110
|
query: z.object({}).optional(),
|
|
104
|
-
body: z
|
|
111
|
+
body: z
|
|
112
|
+
.object({
|
|
113
|
+
name: z.string(),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
116
|
+
headers: z.record(z.string()).optional(),
|
|
105
117
|
}),
|
|
106
118
|
response: z.object({
|
|
107
119
|
id: z.string(),
|
|
@@ -111,21 +123,26 @@ const contracts = {
|
|
|
111
123
|
id: Math.random().toString(36).slice(2),
|
|
112
124
|
name: "Mock User",
|
|
113
125
|
}),
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
} as const;
|
|
117
129
|
```
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
You **do not** have to use these explicit generic annotations if you don’t want to – they are shown here only for clarity. In most cases, simple `as const` + inference is enough.
|
|
120
132
|
|
|
121
|
-
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Using `ApiClient`
|
|
136
|
+
|
|
137
|
+
```ts
|
|
122
138
|
import { ApiClient } from "@tahanabavi/typefetch";
|
|
139
|
+
import { contracts } from "./contracts";
|
|
123
140
|
|
|
124
141
|
const client = new ApiClient(
|
|
125
142
|
{
|
|
126
143
|
baseUrl: "https://api.example.com",
|
|
127
|
-
tokenProvider: () => "dynamic-token",
|
|
128
|
-
useMockData: false
|
|
144
|
+
tokenProvider: () => "dynamic-token", // or undefined for public endpoints
|
|
145
|
+
useMockData: false,
|
|
129
146
|
},
|
|
130
147
|
contracts
|
|
131
148
|
);
|
|
@@ -138,11 +155,18 @@ const user = await api.user.getUser({ path: { id: "123" } });
|
|
|
138
155
|
const created = await api.user.createUser({ body: { name: "Alice" } });
|
|
139
156
|
```
|
|
140
157
|
|
|
158
|
+
- `client.init()` builds the typed `modules` API using your contracts.
|
|
159
|
+
- `api.user.getUser` and `api.user.createUser` are fully typed from the Zod schemas.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
141
163
|
## Middlewares
|
|
142
164
|
|
|
143
|
-
|
|
165
|
+
Middlewares allow you to hook into the request/response lifecycle.
|
|
144
166
|
|
|
145
|
-
|
|
167
|
+
### Custom Middleware Example
|
|
168
|
+
|
|
169
|
+
```ts
|
|
146
170
|
client.use(async (ctx, next) => {
|
|
147
171
|
console.log("Request to:", ctx.url);
|
|
148
172
|
const res = await next();
|
|
@@ -151,72 +175,189 @@ client.use(async (ctx, next) => {
|
|
|
151
175
|
});
|
|
152
176
|
```
|
|
153
177
|
|
|
154
|
-
Built-in
|
|
178
|
+
### Built-in Middlewares
|
|
155
179
|
|
|
156
|
-
```
|
|
180
|
+
```ts
|
|
157
181
|
import {
|
|
158
182
|
loggingMiddleware,
|
|
159
183
|
retryMiddleware,
|
|
160
184
|
cacheMiddleware,
|
|
161
|
-
authMiddleware
|
|
185
|
+
authMiddleware,
|
|
162
186
|
} from "@tahanabavi/typefetch/middlewares";
|
|
163
187
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
client.use(loggingMiddleware, {
|
|
189
|
+
logRequest: true,
|
|
190
|
+
logResponse: true,
|
|
191
|
+
debug: true,
|
|
192
|
+
});
|
|
193
|
+
client.use(retryMiddleware, { maxRetries: 3, delay: 100 });
|
|
194
|
+
client.use(cacheMiddleware, { ttl: 60000 });
|
|
195
|
+
client.use(authMiddleware, {
|
|
196
|
+
refreshToken: async () => "refreshed-token",
|
|
169
197
|
});
|
|
170
198
|
```
|
|
171
199
|
|
|
200
|
+
- **`loggingMiddleware`** – logs requests and responses (controlled by `debug`, `logRequest`, `logResponse`).
|
|
201
|
+
- **`retryMiddleware`** – retries failed requests with configurable `maxRetries` and `delay`.
|
|
202
|
+
- **`cacheMiddleware`** – caches GET responses in-memory per URL with `ttl` (ms).
|
|
203
|
+
- **`authMiddleware`** – can refresh tokens and inject `Authorization` headers before the request.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
172
207
|
## Mock Mode
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
|
|
209
|
+
Enable or disable mock mode globally:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
client.setMockMode(true, { min: 200, max: 1000 }); // simulate network delay
|
|
213
|
+
// ...
|
|
175
214
|
client.setMockMode(false);
|
|
176
215
|
```
|
|
216
|
+
|
|
217
|
+
When mock mode is enabled and `endpoint.mockData` is defined, requests will return mock data instead of hitting the network. The response wrapper and response transform still apply.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
177
221
|
## Response Transformation
|
|
178
|
-
|
|
222
|
+
|
|
223
|
+
You can apply a global transformation to all successful responses:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
179
226
|
client.useResponseTransform((data) => {
|
|
180
227
|
return {
|
|
181
228
|
...data,
|
|
182
|
-
transformedAt: new Date().toISOString()
|
|
229
|
+
transformedAt: new Date().toISOString(),
|
|
183
230
|
};
|
|
184
231
|
});
|
|
185
232
|
```
|
|
233
|
+
|
|
234
|
+
This runs after:
|
|
235
|
+
|
|
236
|
+
1. The HTTP call succeeds (`res.ok` is true)
|
|
237
|
+
2. Optional response wrapper has been unwrapped
|
|
238
|
+
3. The response has been validated with the endpoint’s Zod schema
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
186
242
|
## Response Wrapper Example
|
|
243
|
+
|
|
244
|
+
For APIs that wrap responses like this:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"success": true,
|
|
249
|
+
"data": { ... },
|
|
250
|
+
"timestamp": "...",
|
|
251
|
+
"requestId": "..."
|
|
252
|
+
}
|
|
187
253
|
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
|
|
255
|
+
You can define a single wrapper schema:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { z } from "zod";
|
|
259
|
+
|
|
260
|
+
const wrapper = (successResponse: z.ZodTypeAny) =>
|
|
261
|
+
z.union([
|
|
262
|
+
z.object({
|
|
263
|
+
success: z.literal(true),
|
|
264
|
+
data: successResponse,
|
|
265
|
+
timestamp: z.string(),
|
|
266
|
+
requestId: z.string(),
|
|
267
|
+
}),
|
|
268
|
+
z.object({
|
|
269
|
+
success: z.literal(false),
|
|
270
|
+
message: z.string(),
|
|
271
|
+
code: z.number(),
|
|
272
|
+
timestamp: z.string(),
|
|
273
|
+
requestId: z.string(),
|
|
274
|
+
}),
|
|
275
|
+
]);
|
|
204
276
|
|
|
205
277
|
client.setResponseWrapper(wrapper);
|
|
206
278
|
```
|
|
279
|
+
|
|
280
|
+
On `success: true`, `data` is passed to the endpoint’s `response` schema.
|
|
281
|
+
On `success: false`, a `RichError` is thrown with normalized information.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
207
285
|
## Error Handling
|
|
208
|
-
|
|
286
|
+
|
|
287
|
+
```ts
|
|
209
288
|
client.onError((err) => {
|
|
210
289
|
console.error("API Error:", err.message, err.status, err.code);
|
|
211
290
|
});
|
|
212
291
|
```
|
|
292
|
+
|
|
293
|
+
Errors are normalized into `RichError` (or your custom type if you change the generic). Zod validation errors are also wrapped into a `VALIDATION_ERROR` with a readable message.
|
|
294
|
+
|
|
295
|
+
You can still handle errors per-call with `try/catch`:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
try {
|
|
299
|
+
const user = await api.user.getUser({ path: { id: "123" } });
|
|
300
|
+
} catch (err) {
|
|
301
|
+
// err is RichError
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## File Uploads (`form-data`)
|
|
308
|
+
|
|
309
|
+
For endpoints that need file upload, set `bodyType: "form-data"` and put file(s) inside `body`:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const uploadAvatarRequest = z.object({
|
|
313
|
+
path: z.object({}).optional(),
|
|
314
|
+
query: z.object({}).optional(),
|
|
315
|
+
body: z.object({
|
|
316
|
+
file: z.any(), // or z.instanceof(File) in browser
|
|
317
|
+
}),
|
|
318
|
+
headers: z.record(z.string()).optional(),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const uploadAvatarResponse = z.object({
|
|
322
|
+
url: z.string(),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const contracts = {
|
|
326
|
+
user: {
|
|
327
|
+
uploadAvatar: {
|
|
328
|
+
method: "POST",
|
|
329
|
+
path: "/users/avatar",
|
|
330
|
+
auth: true,
|
|
331
|
+
bodyType: "form-data",
|
|
332
|
+
request: uploadAvatarRequest,
|
|
333
|
+
response: uploadAvatarResponse,
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
} as const;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Usage:
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
const file = input.files?.[0];
|
|
343
|
+
await api.user.uploadAvatar({
|
|
344
|
+
body: { file },
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The client will build a `FormData` object and let the browser set the `Content-Type` header.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
213
352
|
## Notes
|
|
214
353
|
|
|
215
|
-
- Always call client.init() before using client.modules
|
|
216
|
-
- Middlewares
|
|
217
|
-
- Endpoints
|
|
218
|
-
- All responses are validated
|
|
219
|
-
-
|
|
354
|
+
- Always call `client.init()` before using `client.modules`.
|
|
355
|
+
- Middlewares execute in **reverse registration order** (last registered runs first).
|
|
356
|
+
- Endpoints with `auth: true` require a valid token from `token` or `tokenProvider`.
|
|
357
|
+
- All responses are parsed and validated by Zod using each endpoint’s `response` schema.
|
|
358
|
+
- Structured `{ path, query, body, headers }` shape is the canonical model; flat request schemas are still supported for backwards compatibility.
|
|
359
|
+
|
|
360
|
+
---
|
|
220
361
|
|
|
221
362
|
## License
|
|
222
363
|
|
package/dist/index.d.mts
CHANGED
|
@@ -18,6 +18,8 @@ type EndpointDef<TReq extends RequestSchema, TRes extends ResponseSchema> = {
|
|
|
18
18
|
request: TReq;
|
|
19
19
|
response: TRes;
|
|
20
20
|
mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
|
|
21
|
+
headers?: Record<string, string> | ((input: z.infer<TReq>) => Record<string, string>);
|
|
22
|
+
bodyType?: "json" | "form-data";
|
|
21
23
|
};
|
|
22
24
|
type Contracts = {
|
|
23
25
|
[ModuleName: string]: {
|
|
@@ -61,15 +63,6 @@ declare class RichError extends Error implements ErrorLike {
|
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* Strongly-typed HTTP client built from Zod contracts.
|
|
64
|
-
*
|
|
65
|
-
* Features:
|
|
66
|
-
* - Type-safe request & response based on Zod schemas
|
|
67
|
-
* - Path/query/body support via { path?, query?, body? } shape
|
|
68
|
-
* - Backwards compatible with flat request bodies
|
|
69
|
-
* - Pluggable middleware pipeline
|
|
70
|
-
* - Token and tokenProvider support
|
|
71
|
-
* - Mock mode with configurable delay
|
|
72
|
-
* - Optional response wrapper for APIs that nest data
|
|
73
66
|
*/
|
|
74
67
|
declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
75
68
|
private config;
|
|
@@ -148,20 +141,16 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
|
148
141
|
* path: z.object({...}).optional(),
|
|
149
142
|
* query: z.object({...}).optional(),
|
|
150
143
|
* body: z.any().optional(),
|
|
144
|
+
* header: z.object({...}).optional(),
|
|
151
145
|
* })
|
|
152
146
|
*
|
|
153
|
-
* If the parsed request does not contain `path`, `query` or `body`,
|
|
147
|
+
* If the parsed request does not contain `path`, `header`,`query` or `body`,
|
|
154
148
|
* the entire input is treated as the legacy flat request body.
|
|
155
149
|
*/
|
|
156
150
|
private request;
|
|
157
151
|
/**
|
|
158
|
-
* Builds
|
|
159
|
-
*
|
|
160
|
-
* - Legacy mode: if the input does not contain `path`, `query` or `body`,
|
|
161
|
-
* the entire input is used as the JSON request body for non-GET methods.
|
|
162
|
-
*
|
|
163
|
-
* - New mode: uses `path` to interpolate `:param` segments, `query` to
|
|
164
|
-
* construct the query string, and `body` as the JSON payload.
|
|
152
|
+
* Builds final URL and body from endpoint + request input.
|
|
153
|
+
* Supports both structured `{ path, query, body, headers }` and legacy flat input.
|
|
165
154
|
*/
|
|
166
155
|
private buildUrlAndBody;
|
|
167
156
|
/**
|
|
@@ -207,4 +196,26 @@ type CacheOptions = {
|
|
|
207
196
|
};
|
|
208
197
|
declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
209
198
|
|
|
210
|
-
|
|
199
|
+
declare const makeRequestSchema: <TPath extends z.ZodRawShape = {}, TQuery extends z.ZodRawShape = {}, TBody extends z.ZodTypeAny = z.ZodUndefined>() => (defs: {
|
|
200
|
+
path?: z.ZodObject<TPath>;
|
|
201
|
+
query?: z.ZodObject<TQuery>;
|
|
202
|
+
body?: TBody;
|
|
203
|
+
headers?: z.ZodTypeAny;
|
|
204
|
+
}) => z.ZodObject<{
|
|
205
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T ? { [k in keyof T]: T[k]; } : never, z.baseObjectInputType<TPath> extends infer T_1 ? { [k_1 in keyof T_1]: T_1[k_1]; } : never>>;
|
|
206
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_2 ? { [k_2 in keyof T_2]: T_2[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_3 ? { [k_3 in keyof T_3]: T_3[k_3]; } : never>>;
|
|
207
|
+
body: TBody | z.ZodUndefined;
|
|
208
|
+
headers: any;
|
|
209
|
+
}, "strip", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<{
|
|
210
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T_5 ? { [k in keyof T_5]: T_5[k]; } : never, z.baseObjectInputType<TPath> extends infer T_6 ? { [k_1 in keyof T_6]: T_6[k_1]; } : never>>;
|
|
211
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_7 ? { [k_2 in keyof T_7]: T_7[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_8 ? { [k_3 in keyof T_8]: T_8[k_3]; } : never>>;
|
|
212
|
+
body: TBody | z.ZodUndefined;
|
|
213
|
+
headers: any;
|
|
214
|
+
}>, any> extends infer T_4 ? { [k_4 in keyof T_4]: T_4[k_4]; } : never, z.baseObjectInputType<{
|
|
215
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T_10 ? { [k in keyof T_10]: T_10[k]; } : never, z.baseObjectInputType<TPath> extends infer T_11 ? { [k_1 in keyof T_11]: T_11[k_1]; } : never>>;
|
|
216
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_12 ? { [k_2 in keyof T_12]: T_12[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_13 ? { [k_3 in keyof T_13]: T_13[k_3]; } : never>>;
|
|
217
|
+
body: TBody | z.ZodUndefined;
|
|
218
|
+
headers: any;
|
|
219
|
+
}> extends infer T_9 ? { [k_5 in keyof T_9]: T_9[k_5]; } : never>;
|
|
220
|
+
|
|
221
|
+
export { ApiClient, type AuthOptions, type CacheOptions, type Contracts, type EndpointDef, type EndpointDefZ, type EndpointMethods, type ErrorLike, type LoggingOptions, type Middleware, type MiddlewareContext, type MiddlewareNext, type RequestSchema, type ResponseSchema, type RetryOptions, RichError, type TokenProvider, authMiddleware, cacheMiddleware, loggingMiddleware, makeRequestSchema, retryMiddleware };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ type EndpointDef<TReq extends RequestSchema, TRes extends ResponseSchema> = {
|
|
|
18
18
|
request: TReq;
|
|
19
19
|
response: TRes;
|
|
20
20
|
mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
|
|
21
|
+
headers?: Record<string, string> | ((input: z.infer<TReq>) => Record<string, string>);
|
|
22
|
+
bodyType?: "json" | "form-data";
|
|
21
23
|
};
|
|
22
24
|
type Contracts = {
|
|
23
25
|
[ModuleName: string]: {
|
|
@@ -61,15 +63,6 @@ declare class RichError extends Error implements ErrorLike {
|
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* Strongly-typed HTTP client built from Zod contracts.
|
|
64
|
-
*
|
|
65
|
-
* Features:
|
|
66
|
-
* - Type-safe request & response based on Zod schemas
|
|
67
|
-
* - Path/query/body support via { path?, query?, body? } shape
|
|
68
|
-
* - Backwards compatible with flat request bodies
|
|
69
|
-
* - Pluggable middleware pipeline
|
|
70
|
-
* - Token and tokenProvider support
|
|
71
|
-
* - Mock mode with configurable delay
|
|
72
|
-
* - Optional response wrapper for APIs that nest data
|
|
73
66
|
*/
|
|
74
67
|
declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
75
68
|
private config;
|
|
@@ -148,20 +141,16 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
|
|
|
148
141
|
* path: z.object({...}).optional(),
|
|
149
142
|
* query: z.object({...}).optional(),
|
|
150
143
|
* body: z.any().optional(),
|
|
144
|
+
* header: z.object({...}).optional(),
|
|
151
145
|
* })
|
|
152
146
|
*
|
|
153
|
-
* If the parsed request does not contain `path`, `query` or `body`,
|
|
147
|
+
* If the parsed request does not contain `path`, `header`,`query` or `body`,
|
|
154
148
|
* the entire input is treated as the legacy flat request body.
|
|
155
149
|
*/
|
|
156
150
|
private request;
|
|
157
151
|
/**
|
|
158
|
-
* Builds
|
|
159
|
-
*
|
|
160
|
-
* - Legacy mode: if the input does not contain `path`, `query` or `body`,
|
|
161
|
-
* the entire input is used as the JSON request body for non-GET methods.
|
|
162
|
-
*
|
|
163
|
-
* - New mode: uses `path` to interpolate `:param` segments, `query` to
|
|
164
|
-
* construct the query string, and `body` as the JSON payload.
|
|
152
|
+
* Builds final URL and body from endpoint + request input.
|
|
153
|
+
* Supports both structured `{ path, query, body, headers }` and legacy flat input.
|
|
165
154
|
*/
|
|
166
155
|
private buildUrlAndBody;
|
|
167
156
|
/**
|
|
@@ -207,4 +196,26 @@ type CacheOptions = {
|
|
|
207
196
|
};
|
|
208
197
|
declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
209
198
|
|
|
210
|
-
|
|
199
|
+
declare const makeRequestSchema: <TPath extends z.ZodRawShape = {}, TQuery extends z.ZodRawShape = {}, TBody extends z.ZodTypeAny = z.ZodUndefined>() => (defs: {
|
|
200
|
+
path?: z.ZodObject<TPath>;
|
|
201
|
+
query?: z.ZodObject<TQuery>;
|
|
202
|
+
body?: TBody;
|
|
203
|
+
headers?: z.ZodTypeAny;
|
|
204
|
+
}) => z.ZodObject<{
|
|
205
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T ? { [k in keyof T]: T[k]; } : never, z.baseObjectInputType<TPath> extends infer T_1 ? { [k_1 in keyof T_1]: T_1[k_1]; } : never>>;
|
|
206
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_2 ? { [k_2 in keyof T_2]: T_2[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_3 ? { [k_3 in keyof T_3]: T_3[k_3]; } : never>>;
|
|
207
|
+
body: TBody | z.ZodUndefined;
|
|
208
|
+
headers: any;
|
|
209
|
+
}, "strip", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<{
|
|
210
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T_5 ? { [k in keyof T_5]: T_5[k]; } : never, z.baseObjectInputType<TPath> extends infer T_6 ? { [k_1 in keyof T_6]: T_6[k_1]; } : never>>;
|
|
211
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_7 ? { [k_2 in keyof T_7]: T_7[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_8 ? { [k_3 in keyof T_8]: T_8[k_3]; } : never>>;
|
|
212
|
+
body: TBody | z.ZodUndefined;
|
|
213
|
+
headers: any;
|
|
214
|
+
}>, any> extends infer T_4 ? { [k_4 in keyof T_4]: T_4[k_4]; } : never, z.baseObjectInputType<{
|
|
215
|
+
path: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TPath, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TPath>, any> extends infer T_10 ? { [k in keyof T_10]: T_10[k]; } : never, z.baseObjectInputType<TPath> extends infer T_11 ? { [k_1 in keyof T_11]: T_11[k_1]; } : never>>;
|
|
216
|
+
query: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>> | z.ZodOptional<z.ZodObject<TQuery, z.UnknownKeysParam, z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<TQuery>, any> extends infer T_12 ? { [k_2 in keyof T_12]: T_12[k_2]; } : never, z.baseObjectInputType<TQuery> extends infer T_13 ? { [k_3 in keyof T_13]: T_13[k_3]; } : never>>;
|
|
217
|
+
body: TBody | z.ZodUndefined;
|
|
218
|
+
headers: any;
|
|
219
|
+
}> extends infer T_9 ? { [k_5 in keyof T_9]: T_9[k_5]; } : never>;
|
|
220
|
+
|
|
221
|
+
export { ApiClient, type AuthOptions, type CacheOptions, type Contracts, type EndpointDef, type EndpointDefZ, type EndpointMethods, type ErrorLike, type LoggingOptions, type Middleware, type MiddlewareContext, type MiddlewareNext, type RequestSchema, type ResponseSchema, type RetryOptions, RichError, type TokenProvider, authMiddleware, cacheMiddleware, loggingMiddleware, makeRequestSchema, retryMiddleware };
|