@tahanabavi/typefetch 1.1.1 → 1.2.2

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,309 +1,222 @@
1
1
  # TypeFetch
2
2
 
3
- TypeFetch is a type-safe client for working with APIs, built with TypeScript and Zod. This project allows you to define API contracts and safely use types, while also supporting middlewares, error handling, response transformation, mock data, and response wrappers.
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
- ## Features
8
-
9
- - Fully type-safe using TypeScript and Zod
10
- - Define contracts for modules and endpoints
11
- - Support for middlewares to add custom behavior before or after requests
12
- - Error handling with the `RichError` class
13
- - Ability to transform responses using a response transformer
14
- - Authentication support via token
15
- - Mock data support for development and testing
16
- - Response wrapper for consistent API response formats
17
-
18
- ---
7
+ - End-to-end type safety
8
+ - Automatic URL handling (path, query, body)
9
+ - Middleware pipeline (logging, retry, cache, auth)
10
+ - Mock mode for development
11
+ - Dynamic token providers
12
+ - Response wrappers for consistent API envelopes
13
+ - Unified error system (RichError)
19
14
 
20
15
  ## Installation
21
16
 
22
- ```bash
23
17
  npm install @tahanabavi/typefetch
18
+
24
19
  # or
20
+
25
21
  yarn add @tahanabavi/typefetch
26
- ```
27
22
 
28
- ---
23
+ ## Key Features
24
+
25
+ 1. Type-Safe API Client
26
+ Define your API with Zod schemas and get full type safety for request and response types.
29
27
 
30
- ## What's New in v1.1.1
28
+ 2. Structured Request Support
29
+ Each request may contain:
31
30
 
32
- ### 🎯 Mock Data Support
31
+ - path: URL parameters (fills /users/:id)
32
+ - query: URL query string ?page=1&limit=10
33
+ - body: JSON payload for POST/PUT/PATCH
33
34
 
34
- - Add mock data to endpoints for development and testing
35
- - Configurable random delays to simulate network latency
36
- - Support for both static data and dynamic functions
37
- - Runtime toggle between mock and real API modes
35
+ The client automatically builds URLs and bodies correctly.
38
36
 
39
- ### 🔄 Response Wrapper
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.
40
39
 
41
- - Consistent API response format handling
42
- - Automatic validation of wrapped responses
43
- - Support for success/error response patterns
44
- - Seamless integration with existing contracts
40
+ 4. Middlewares
41
+ Middlewares allow modifying requests and responses.
42
+ Built-in middlewares include:
45
43
 
46
- ### 🚀 Enhanced Error Handling
44
+ - loggingMiddleware
45
+ - retryMiddleware
46
+ - authMiddleware
47
+ - cacheMiddleware
47
48
 
48
- - Better Zod error wrapping and reporting
49
- - Improved type safety for response wrappers
49
+ 5. Token Provider System
50
+ Provide tokens dynamically using:
50
51
 
51
- ### 🔧 Token Provider System
52
+ - token: static auth token
53
+ - tokenProvider: function or async function returning a token
52
54
 
53
- - Dynamic token resolution: Tokens resolved at request time, not initialization
54
- - Universal compatibility: Works seamlessly in both server and client environments
55
- - Async token providers: Support for asynchronous token retrieval
56
- - Multiple token sources: Flexible token sourcing from cookies, context, or external services
55
+ 6. Mock Mode
56
+ Provides mock responses based on endpoint.mockData, with configurable fake delays.
57
57
 
58
- ---
58
+ 7. Response Wrapper
59
+ For APIs that wrap responses:
59
60
 
60
- ## Defining Contracts
61
+ ```
62
+ {
63
+ "success": true,
64
+ "data": {...},
65
+ "timestamp": "...",
66
+ "requestId": "..."
67
+ }
68
+ ```
61
69
 
62
- Contracts are defined using the `Contracts` and `EndpointDef` types.
70
+ TypeFetch unwraps the response automatically.
63
71
 
64
- ```ts
72
+ ## Defining API Contracts
73
+
74
+ Example contract definition:
75
+
76
+ ```
65
77
  import { z } from "zod";
66
78
 
67
79
  const contracts = {
68
80
  user: {
69
81
  getUser: {
70
82
  method: "GET",
71
- path: "/user/:id",
83
+ path: "/users/:id",
72
84
  auth: true,
73
- request: z.object({ id: z.string() }),
74
- response: z.object({ id: z.string(), name: z.string() }),
75
- // Optional mock data
76
- mockData: { id: "1", name: "John Doe" },
85
+ request: z.object({
86
+ path: z.object({ id: z.string() }).optional(),
87
+ query: z.object({}).optional(),
88
+ body: z.never().optional(),
89
+ }),
90
+ response: z.object({
91
+ id: z.string(),
92
+ name: z.string(),
93
+ }),
94
+ mockData: { id: "1", name: "John Doe" }
77
95
  },
96
+
78
97
  createUser: {
79
98
  method: "POST",
80
- path: "/user",
81
- request: z.object({ name: z.string() }),
82
- response: z.object({ id: z.string(), name: z.string() }),
83
- // Dynamic mock data function
84
- mockData: () => ({ id: Math.random().toString(), name: "Dynamic User" }),
85
- },
86
- },
87
- } as const;
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
+ };
88
117
  ```
89
118
 
90
- ---
91
-
92
- ## Using `ApiClient`
119
+ ## Using ApiClient
93
120
 
94
- ```ts
95
- import { ApiClient, RichError } from "typefetch";
121
+ ```
122
+ import { ApiClient } from "@tahanabavi/typefetch";
96
123
 
97
124
  const client = new ApiClient(
98
125
  {
99
126
  baseUrl: "https://api.example.com",
100
- token: "your-auth-token",
101
- useMockData: true,
102
- mockDelay: { min: 100, max: 1000 },
127
+ tokenProvider: () => "dynamic-token",
128
+ useMockData: false
103
129
  },
104
130
  contracts
105
131
  );
106
132
 
107
133
  client.init();
108
134
 
109
- const { modules: api } = client;
135
+ const api = client.modules;
110
136
 
111
- const user = await api.user.getUser({ id: "123" });
112
- const newUser = await api.user.createUser({ name: "Taha" });
137
+ const user = await api.user.getUser({ path: { id: "123" } });
138
+ const created = await api.user.createUser({ body: { name: "Alice" } });
113
139
  ```
114
140
 
115
- ---
116
-
117
- ## Error Handling
141
+ ## Middlewares
118
142
 
119
- All errors are provided via the `RichError` class. You can define a custom error handler:
143
+ Example custom middleware:
120
144
 
121
- ```ts
122
- client.onError((error: RichError) => {
123
- console.error("API Error:", error.message, error.status, error.code);
145
+ ```
146
+ client.use(async (ctx, next) => {
147
+ console.log("Request to:", ctx.url);
148
+ const res = await next();
149
+ console.log("Response:", res.status);
150
+ return res;
124
151
  });
125
152
  ```
126
153
 
127
- ---
128
-
129
- ## Middlewares
130
-
131
- You can add custom behavior before or after requests. Middlewares work similarly to Express:
154
+ Built-in middlewares:
132
155
 
133
- ```ts
134
- client.use(
135
- async (ctx: MiddlewareContext, next: MiddlewareNext, options?: any) => {
136
- console.log("Request URL:", ctx.url);
137
- const response = await next();
138
- console.log("Response status:", response.status);
139
- return response;
140
- }
141
- );
142
156
  ```
143
-
144
- ### Built-in Middlewares
145
-
146
- This project provides some built-in middlewares:
147
-
148
- ```ts
149
157
  import {
150
- LoggingMiddleware,
151
- RetryMiddleware,
152
- AuthMiddleware,
153
- CacheMiddleware,
154
- } from "typefetch/middlewares";
155
-
156
- client.use(LoggingMiddleware);
157
- client.use(RetryMiddleware, { maxRetries: 3, delay: 100 });
158
- client.use(AuthMiddleware, { refreshToken: () => "your-auth-token" });
159
- client.use(CacheMiddleware, { ttl: 60 * 1000 });
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
+ });
160
170
  ```
161
171
 
162
- - `LoggingMiddleware` – Logs requests and responses
163
- - `RetryMiddleware` – Retries failed requests
164
- - `AuthMiddleware` Automatically adds Authorization headers
165
- - `CacheMiddleware` – Caches responses to reduce repeated requests
166
-
167
- ---
168
-
172
+ ## Mock Mode
173
+ ```
174
+ client.setMockMode(true, { min: 200, max: 1000 });
175
+ client.setMockMode(false);
176
+ ```
169
177
  ## Response Transformation
170
-
171
- You can transform the response format before returning it:
172
-
173
- ```ts
178
+ ```
174
179
  client.useResponseTransform((data) => {
175
- return { ...data, fetchedAt: new Date() };
180
+ return {
181
+ ...data,
182
+ transformedAt: new Date().toISOString()
183
+ };
176
184
  });
177
185
  ```
178
-
179
- ---
180
-
181
- ## Mock Data Features
182
-
183
- Static Mock Data:
184
-
185
- ```ts
186
- mockData: { id: "1", name: "Static User" }
187
- ```
188
-
189
- Dynamic Mock Data:
190
-
191
- ```ts
192
- mockData: () => ({ id: Math.random().toString(), name: `User-${Date.now()}` });
186
+ ## Response Wrapper Example
193
187
  ```
194
-
195
- Runtime Control:
196
-
197
- ```ts
198
- client.setMockMode(true, { min: 100, max: 2000 });
199
- client.setMockMode(false);
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);
200
206
  ```
201
-
202
- ---
203
-
204
- ## Response Wrapper Features
205
-
206
- ```ts
207
- const apiResponseWrapper = (successResponse: z.ZodTypeAny) =>
208
- z.union([
209
- z.object({
210
- success: z.literal(true),
211
- data: successResponse,
212
- timestamp: z.string(),
213
- requestId: z.string(),
214
- }),
215
- z.object({
216
- success: z.literal(false),
217
- message: z.string(),
218
- code: z.number(),
219
- timestamp: z.string(),
220
- requestId: z.string(),
221
- }),
222
- ]);
223
-
224
- client.setResponseWrapper(apiResponseWrapper);
207
+ ## Error Handling
225
208
  ```
226
-
227
- ---
228
-
229
- ## Important Notes
230
-
231
- - Always call `client.init()` before using endpoints.
232
- - Types are automatically inferred from Zod, making inputs and outputs type-safe.
233
- - Middleware execution order: first added middleware runs last, last added middleware runs first.
234
- - Mock data is used only when `useMockData` is true and mock data is defined.
235
- - Response wrapper automatically handles success/error patterns.
236
-
237
- ---
238
-
239
- ## Full Example
240
-
241
- ```ts
242
- import { z } from "zod";
243
- import { ApiClient, RichError } from "typefetch";
244
- import { LoggingMiddleware, RetryMiddleware } from "typefetch/middlewares";
245
-
246
- const contracts = {
247
- user: {
248
- getUser: {
249
- method: "GET",
250
- path: "/users/:id",
251
- auth: true,
252
- request: z.object({ id: z.string() }),
253
- response: z.object({
254
- id: z.string(),
255
- name: z.string(),
256
- email: z.string(),
257
- }),
258
- mockData: { id: "1", name: "John Doe", email: "john@example.com" },
259
- },
260
- },
261
- } as const;
262
-
263
- const apiResponseWrapper = (successResponse: z.ZodTypeAny) =>
264
- z.union([
265
- z.object({
266
- success: z.literal(true),
267
- data: successResponse,
268
- timestamp: z.string(),
269
- requestId: z.string(),
270
- }),
271
- z.object({
272
- success: z.literal(false),
273
- message: z.string(),
274
- code: z.number(),
275
- timestamp: z.string(),
276
- requestId: z.string(),
277
- }),
278
- ]);
279
-
280
- const client = new ApiClient(
281
- {
282
- baseUrl: "https://api.example.com",
283
- token: "abc123",
284
- useMockData: process.env.NODE_ENV === "development",
285
- },
286
- contracts
287
- );
288
-
289
- client.init();
290
- client.setResponseWrapper(apiResponseWrapper);
291
- client.use(LoggingMiddleware);
292
- client.use(RetryMiddleware, { maxRetries: 2 });
293
-
294
- client.onError((err: RichError) => {
295
- console.error("Error:", err.message);
209
+ client.onError((err) => {
210
+ console.error("API Error:", err.message, err.status, err.code);
296
211
  });
297
-
298
- const { modules: api } = client;
299
-
300
- (async () => {
301
- const user = await api.user.getUser({ id: "1" });
302
- console.log(user);
303
- })();
304
212
  ```
213
+ ## Notes
305
214
 
306
- ---
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.
307
220
 
308
221
  ## License
309
222
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { z } from 'zod';
2
2
 
3
- type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
3
+ /**
4
+ * Generic request schema:
5
+ *
6
+ * request: z.object({
7
+ * path: z.object({ ... }).optional(),
8
+ * query: z.object({ ... }).optional(),
9
+ * body: z.object({ ... }).optional(),
10
+ * })
11
+ */
12
+ type RequestSchema = z.ZodTypeAny;
13
+ type ResponseSchema = z.ZodTypeAny;
14
+ type EndpointDef<TReq extends RequestSchema, TRes extends ResponseSchema> = {
4
15
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
5
16
  path: string;
6
17
  auth?: boolean;
@@ -10,7 +21,7 @@ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
10
21
  };
11
22
  type Contracts = {
12
23
  [ModuleName: string]: {
13
- [EndpointName: string]: EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
24
+ [EndpointName: string]: EndpointDef<RequestSchema, ResponseSchema>;
14
25
  };
15
26
  };
16
27
  interface MiddlewareContext {
@@ -25,7 +36,14 @@ type ErrorLike = {
25
36
  code?: string;
26
37
  [key: string]: any;
27
38
  };
28
- type EndpointDefZ = EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
39
+ type EndpointDefZ = EndpointDef<RequestSchema, ResponseSchema>;
40
+ /**
41
+ * For each endpoint we expose a method:
42
+ * (input: z.infer<Endpoint["request"]>) => Promise<z.infer<Endpoint["response"]>>
43
+ *
44
+ * So VSCode will show you:
45
+ * { path?: { ... }, query?: { ... }, body?: { ... } }
46
+ */
29
47
  type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
30
48
  [K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
31
49
  };
@@ -41,6 +59,18 @@ declare class RichError extends Error implements ErrorLike {
41
59
  message: string;
42
60
  });
43
61
  }
62
+ /**
63
+ * 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
+ */
44
74
  declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
45
75
  private config;
46
76
  private contracts;
@@ -62,22 +92,95 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
62
92
  max: number;
63
93
  };
64
94
  }, contracts: C);
95
+ /**
96
+ * Builds the strongly-typed `modules` API from the provided contracts.
97
+ * Must be called once after constructing the client.
98
+ */
65
99
  init(): void;
100
+ /**
101
+ * Type-safe entrypoint for calling API endpoints.
102
+ * Populated by `init()` based on the `contracts` passed to the constructor.
103
+ */
66
104
  get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
105
+ /**
106
+ * Registers a middleware in the pipeline.
107
+ * Middlewares are executed in reverse order of registration.
108
+ */
67
109
  use<T>(middleware: Middleware<T>, options?: T): void;
110
+ /**
111
+ * Registers a global error handler.
112
+ * The handler is invoked for normalized errors before they are re-thrown.
113
+ */
68
114
  onError(handler: (error: E) => void): void;
115
+ /**
116
+ * Registers a transformation function applied to all successful responses
117
+ * after Zod parsing.
118
+ */
69
119
  useResponseTransform(fn: (data: any) => any): void;
120
+ /**
121
+ * Enables or disables mock mode. When enabled, endpoints with `mockData`
122
+ * return mocked responses instead of performing network requests.
123
+ */
70
124
  setMockMode(enabled: boolean, delay?: {
71
125
  min: number;
72
126
  max: number;
73
127
  }): void;
128
+ /**
129
+ * Registers a schema wrapper for APIs that wrap data in an envelope.
130
+ * Example: { success, data, message, code, ... }.
131
+ */
74
132
  setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny): void;
133
+ /**
134
+ * Sets or updates the token provider used for authenticated endpoints.
135
+ * Overrides any static token provided in the constructor.
136
+ */
75
137
  setTokenProvider(provider: TokenProvider): void;
138
+ /**
139
+ * Returns the current token, preferring the tokenProvider if present,
140
+ * otherwise falling back to the static token from the constructor.
141
+ */
76
142
  getCurrentToken(): Promise<string | undefined>;
143
+ /**
144
+ * Executes a single endpoint request.
145
+ *
146
+ * Expected request shape (new style):
147
+ * z.object({
148
+ * path: z.object({...}).optional(),
149
+ * query: z.object({...}).optional(),
150
+ * body: z.any().optional(),
151
+ * })
152
+ *
153
+ * If the parsed request does not contain `path`, `query` or `body`,
154
+ * the entire input is treated as the legacy flat request body.
155
+ */
77
156
  private request;
157
+ /**
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.
165
+ */
166
+ private buildUrlAndBody;
167
+ /**
168
+ * Returns a mocked response based on `endpoint.mockData`,
169
+ * respecting the configured mock delay and response wrapper.
170
+ */
78
171
  private handleMockRequest;
172
+ /**
173
+ * Returns a random delay in milliseconds within the current mock delay range.
174
+ */
79
175
  private getRandomDelay;
176
+ /**
177
+ * Creates a RichError instance from a partial error description.
178
+ */
80
179
  private createError;
180
+ /**
181
+ * Normalizes unknown errors into a RichError instance.
182
+ * Zod validation errors are converted into a standardized validation error.
183
+ */
81
184
  private normalizeError;
82
185
  }
83
186
 
@@ -104,4 +207,4 @@ type CacheOptions = {
104
207
  };
105
208
  declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
106
209
 
107
- 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 RetryOptions, RichError, type TokenProvider, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
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 };