@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 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
- - Automatic URL handling (path, query, body)
9
- - Middleware pipeline (logging, retry, cache, auth)
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
- ## Key Features
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
- 2. Structured Request Support
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
- - path: URL parameters (fills /users/:id)
32
- - query: URL query string ?page=1&limit=10
33
- - body: JSON payload for POST/PUT/PATCH
35
+ Each endpoint has:
34
36
 
35
- The client automatically builds URLs and bodies correctly.
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
- 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.
46
+ ### 2. Structured Request Shape
39
47
 
40
- 4. Middlewares
41
- Middlewares allow modifying requests and responses.
42
- Built-in middlewares include:
48
+ The recommended request shape is:
43
49
 
44
- - loggingMiddleware
45
- - retryMiddleware
46
- - authMiddleware
47
- - cacheMiddleware
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
- 5. Token Provider System
50
- Provide tokens dynamically using:
59
+ TypeFetch will:
51
60
 
52
- - token: static auth token
53
- - tokenProvider: function or async function returning a token
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
- 6. Mock Mode
56
- Provides mock responses based on endpoint.mockData, with configurable fake delays.
69
+ ### 3. Backward Compatibility
57
70
 
58
- 7. Response Wrapper
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
- TypeFetch unwraps the response automatically.
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.object({ name: z.string() }).optional(),
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
- ## Using ApiClient
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
- Example custom middleware:
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 middlewares:
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
- 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"
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
- client.setMockMode(true, { min: 200, max: 1000 });
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
- 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
- ]);
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 run in reverse registration order.
217
- - Endpoints requiring auth must have a token or tokenProvider.
218
- - All responses are validated via Zod.
219
- - Backward-compatible request support makes migration safe.
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 the effective URL and request body for an endpoint.
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
- 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, retryMiddleware };
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 the effective URL and request body for an endpoint.
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
- 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, retryMiddleware };
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 };