@tahanabavi/typefetch 1.0.2 → 1.1.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,17 +1,19 @@
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, and response transformation.
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.
4
4
 
5
5
  ---
6
6
 
7
7
  ## Features
8
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
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
15
17
 
16
18
  ---
17
19
 
@@ -25,9 +27,32 @@ yarn add @tahanabavi/typefetch
25
27
 
26
28
  ---
27
29
 
30
+ ## What's New in v1.1.0
31
+
32
+ ### 🎯 Mock Data Support
33
+
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
38
+
39
+ ### 🔄 Response Wrapper
40
+
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
45
+
46
+ ### 🚀 Enhanced Error Handling
47
+
48
+ - Better Zod error wrapping and reporting
49
+ - Improved type safety for response wrappers
50
+
51
+ ---
52
+
28
53
  ## Defining Contracts
29
54
 
30
- Contracts are defined using the `Contracts` and `EndpointDef` types
55
+ Contracts are defined using the `Contracts` and `EndpointDef` types.
31
56
 
32
57
  ```ts
33
58
  import { z } from "zod";
@@ -40,12 +65,16 @@ const contracts = {
40
65
  auth: true,
41
66
  request: z.object({ id: z.string() }),
42
67
  response: z.object({ id: z.string(), name: z.string() }),
68
+ // Optional mock data
69
+ mockData: { id: "1", name: "John Doe" },
43
70
  },
44
71
  createUser: {
45
72
  method: "POST",
46
73
  path: "/user",
47
74
  request: z.object({ name: z.string() }),
48
75
  response: z.object({ id: z.string(), name: z.string() }),
76
+ // Dynamic mock data function
77
+ mockData: () => ({ id: Math.random().toString(), name: "Dynamic User" }),
49
78
  },
50
79
  },
51
80
  } as const;
@@ -62,14 +91,18 @@ const client = new ApiClient(
62
91
  {
63
92
  baseUrl: "https://api.example.com",
64
93
  token: "your-auth-token",
94
+ useMockData: true,
95
+ mockDelay: { min: 100, max: 1000 },
65
96
  },
66
97
  contracts
67
98
  );
68
99
 
69
100
  client.init();
70
101
 
71
- const user = await client.user.getUser({ id: "123" });
72
- const newUser = await client.user.createUser({ name: "Taha" });
102
+ const { modules: api } = client;
103
+
104
+ const user = await api.user.getUser({ id: "123" });
105
+ const newUser = await api.user.createUser({ name: "Taha" });
73
106
  ```
74
107
 
75
108
  ---
@@ -80,7 +113,7 @@ All errors are provided via the `RichError` class. You can define a custom error
80
113
 
81
114
  ```ts
82
115
  client.onError((error: RichError) => {
83
- console.error("API Error:", error.message, error.status);
116
+ console.error("API Error:", error.message, error.status, error.code);
84
117
  });
85
118
  ```
86
119
 
@@ -91,12 +124,14 @@ client.onError((error: RichError) => {
91
124
  You can add custom behavior before or after requests. Middlewares work similarly to Express:
92
125
 
93
126
  ```ts
94
- client.use(async (ctx, next, options) => {
95
- console.log("Request URL:", ctx.url);
96
- const response = await next();
97
- console.log("Response status:", response.status);
98
- return response;
99
- });
127
+ client.use(
128
+ async (ctx: MiddlewareContext, next: MiddlewareNext, options?: any) => {
129
+ console.log("Request URL:", ctx.url);
130
+ const response = await next();
131
+ console.log("Response status:", response.status);
132
+ return response;
133
+ }
134
+ );
100
135
  ```
101
136
 
102
137
  ### Built-in Middlewares
@@ -117,10 +152,10 @@ client.use(AuthMiddleware, { refreshToken: () => "your-auth-token" });
117
152
  client.use(CacheMiddleware, { ttl: 60 * 1000 });
118
153
  ```
119
154
 
120
- * `LoggingMiddleware` – Logs requests and responses
121
- * `RetryMiddleware` – Retries failed requests
122
- * `AuthMiddleware` – Automatically adds Authorization headers
123
- * `CacheMiddleware` – Caches responses to reduce repeated requests
155
+ - `LoggingMiddleware` – Logs requests and responses
156
+ - `RetryMiddleware` – Retries failed requests
157
+ - `AuthMiddleware` – Automatically adds Authorization headers
158
+ - `CacheMiddleware` – Caches responses to reduce repeated requests
124
159
 
125
160
  ---
126
161
 
@@ -136,11 +171,61 @@ client.useResponseTransform((data) => {
136
171
 
137
172
  ---
138
173
 
174
+ ## Mock Data Features
175
+
176
+ Static Mock Data:
177
+
178
+ ```ts
179
+ mockData: { id: "1", name: "Static User" }
180
+ ```
181
+
182
+ Dynamic Mock Data:
183
+
184
+ ```ts
185
+ mockData: () => ({ id: Math.random().toString(), name: `User-${Date.now()}` });
186
+ ```
187
+
188
+ Runtime Control:
189
+
190
+ ```ts
191
+ client.setMockMode(true, { min: 100, max: 2000 });
192
+ client.setMockMode(false);
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Response Wrapper Features
198
+
199
+ ```ts
200
+ const apiResponseWrapper = (successResponse: z.ZodTypeAny) =>
201
+ z.union([
202
+ z.object({
203
+ success: z.literal(true),
204
+ data: successResponse,
205
+ timestamp: z.string(),
206
+ requestId: z.string(),
207
+ }),
208
+ z.object({
209
+ success: z.literal(false),
210
+ message: z.string(),
211
+ code: z.number(),
212
+ timestamp: z.string(),
213
+ requestId: z.string(),
214
+ }),
215
+ ]);
216
+
217
+ client.setResponseWrapper(apiResponseWrapper);
218
+ ```
219
+
220
+ ---
221
+
139
222
  ## Important Notes
140
223
 
141
- * Always call `client.init()` before using endpoints.
142
- * Types are automatically inferred from Zod, making inputs and outputs type-safe.
143
- * Middleware execution order: first added middleware runs last, last added middleware runs first.
224
+ - Always call `client.init()` before using endpoints.
225
+ - Types are automatically inferred from Zod, making inputs and outputs type-safe.
226
+ - Middleware execution order: first added middleware runs last, last added middleware runs first.
227
+ - Mock data is used only when `useMockData` is true and mock data is defined.
228
+ - Response wrapper automatically handles success/error patterns.
144
229
 
145
230
  ---
146
231
 
@@ -152,23 +237,50 @@ import { ApiClient, RichError } from "typefetch";
152
237
  import { LoggingMiddleware, RetryMiddleware } from "typefetch/middlewares";
153
238
 
154
239
  const contracts = {
155
- post: {
156
- getPost: {
240
+ user: {
241
+ getUser: {
157
242
  method: "GET",
158
- path: "/posts/:id",
243
+ path: "/users/:id",
159
244
  auth: true,
160
245
  request: z.object({ id: z.string() }),
161
- response: z.object({ id: z.string(), title: z.string() }),
246
+ response: z.object({
247
+ id: z.string(),
248
+ name: z.string(),
249
+ email: z.string(),
250
+ }),
251
+ mockData: { id: "1", name: "John Doe", email: "john@example.com" },
162
252
  },
163
253
  },
164
254
  } as const;
165
255
 
256
+ const apiResponseWrapper = (successResponse: z.ZodTypeAny) =>
257
+ z.union([
258
+ z.object({
259
+ success: z.literal(true),
260
+ data: successResponse,
261
+ timestamp: z.string(),
262
+ requestId: z.string(),
263
+ }),
264
+ z.object({
265
+ success: z.literal(false),
266
+ message: z.string(),
267
+ code: z.number(),
268
+ timestamp: z.string(),
269
+ requestId: z.string(),
270
+ }),
271
+ ]);
272
+
166
273
  const client = new ApiClient(
167
- { baseUrl: "https://api.example.com", token: "abc123" },
274
+ {
275
+ baseUrl: "https://api.example.com",
276
+ token: "abc123",
277
+ useMockData: process.env.NODE_ENV === "development",
278
+ },
168
279
  contracts
169
280
  );
170
281
 
171
282
  client.init();
283
+ client.setResponseWrapper(apiResponseWrapper);
172
284
  client.use(LoggingMiddleware);
173
285
  client.use(RetryMiddleware, { maxRetries: 2 });
174
286
 
@@ -176,8 +288,16 @@ client.onError((err: RichError) => {
176
288
  console.error("Error:", err.message);
177
289
  });
178
290
 
291
+ const { modules: api } = client;
292
+
179
293
  (async () => {
180
- const post = await client.post.getPost({ id: "1" });
181
- console.log(post);
294
+ const user = await api.user.getUser({ id: "1" });
295
+ console.log(user);
182
296
  })();
183
- ```
297
+ ```
298
+
299
+ ---
300
+
301
+ ## License
302
+
303
+ MIT
@@ -0,0 +1,102 @@
1
+ import { z } from 'zod';
2
+
3
+ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
4
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
5
+ path: string;
6
+ auth?: boolean;
7
+ request: TReq;
8
+ response: TRes;
9
+ mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
10
+ };
11
+ type Contracts = {
12
+ [ModuleName: string]: {
13
+ [EndpointName: string]: EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
14
+ };
15
+ };
16
+ interface MiddlewareContext {
17
+ url: string;
18
+ init: RequestInit;
19
+ }
20
+ type MiddlewareNext = () => Promise<Response>;
21
+ type Middleware<Options = any> = (ctx: MiddlewareContext, next: MiddlewareNext, options?: Options) => Promise<Response>;
22
+ type ErrorLike = {
23
+ message: string;
24
+ status?: number;
25
+ code?: string;
26
+ [key: string]: any;
27
+ };
28
+ type EndpointDefZ = EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
29
+ type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
30
+ [K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
31
+ };
32
+
33
+ declare class RichError extends Error implements ErrorLike {
34
+ status?: number;
35
+ code?: string;
36
+ title?: string;
37
+ detail?: string;
38
+ errors?: Record<string, string[]>;
39
+ constructor(error: Partial<ErrorLike> & {
40
+ message: string;
41
+ });
42
+ }
43
+ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
44
+ private config;
45
+ private contracts;
46
+ private middlewares;
47
+ private errorHandler?;
48
+ private responseTransform;
49
+ private useMockData;
50
+ private mockDelay;
51
+ private responseWrapper?;
52
+ private _modules;
53
+ constructor(config: {
54
+ baseUrl: string;
55
+ token?: string;
56
+ useMockData?: boolean;
57
+ mockDelay?: {
58
+ min: number;
59
+ max: number;
60
+ };
61
+ }, contracts: C);
62
+ init(): void;
63
+ get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
64
+ use<T>(middleware: Middleware<T>, options?: T): void;
65
+ onError(handler: (error: E) => void): void;
66
+ useResponseTransform(fn: (data: any) => any): void;
67
+ setMockMode(enabled: boolean, delay?: {
68
+ min: number;
69
+ max: number;
70
+ }): void;
71
+ setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny): void;
72
+ private request;
73
+ private handleMockRequest;
74
+ private getRandomDelay;
75
+ private createError;
76
+ private normalizeError;
77
+ }
78
+
79
+ type LoggingOptions = {
80
+ logRequest?: boolean;
81
+ logResponse?: boolean;
82
+ debug?: boolean;
83
+ };
84
+ declare const loggingMiddleware: Middleware<LoggingOptions>;
85
+
86
+ type RetryOptions = {
87
+ maxRetries?: number;
88
+ delay?: number;
89
+ };
90
+ declare const retryMiddleware: (options?: RetryOptions) => Middleware;
91
+
92
+ type AuthOptions = {
93
+ refreshToken?: () => Promise<string>;
94
+ };
95
+ declare const authMiddleware: Middleware<AuthOptions>;
96
+
97
+ type CacheOptions = {
98
+ ttl?: number;
99
+ };
100
+ declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
101
+
102
+ 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, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,102 @@
1
- export * from "./types";
2
- export * from "./client";
3
- export * from "./middlewares/logging";
4
- export * from "./middlewares/retry";
5
- export * from "./middlewares/auth";
6
- export * from "./middlewares/cache";
1
+ import { z } from 'zod';
2
+
3
+ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
4
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
5
+ path: string;
6
+ auth?: boolean;
7
+ request: TReq;
8
+ response: TRes;
9
+ mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
10
+ };
11
+ type Contracts = {
12
+ [ModuleName: string]: {
13
+ [EndpointName: string]: EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
14
+ };
15
+ };
16
+ interface MiddlewareContext {
17
+ url: string;
18
+ init: RequestInit;
19
+ }
20
+ type MiddlewareNext = () => Promise<Response>;
21
+ type Middleware<Options = any> = (ctx: MiddlewareContext, next: MiddlewareNext, options?: Options) => Promise<Response>;
22
+ type ErrorLike = {
23
+ message: string;
24
+ status?: number;
25
+ code?: string;
26
+ [key: string]: any;
27
+ };
28
+ type EndpointDefZ = EndpointDef<z.ZodTypeAny, z.ZodTypeAny>;
29
+ type EndpointMethods<M extends Record<string, EndpointDefZ>> = {
30
+ [K in keyof M]: (input: z.infer<M[K]["request"]>) => Promise<z.infer<M[K]["response"]>>;
31
+ };
32
+
33
+ declare class RichError extends Error implements ErrorLike {
34
+ status?: number;
35
+ code?: string;
36
+ title?: string;
37
+ detail?: string;
38
+ errors?: Record<string, string[]>;
39
+ constructor(error: Partial<ErrorLike> & {
40
+ message: string;
41
+ });
42
+ }
43
+ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
44
+ private config;
45
+ private contracts;
46
+ private middlewares;
47
+ private errorHandler?;
48
+ private responseTransform;
49
+ private useMockData;
50
+ private mockDelay;
51
+ private responseWrapper?;
52
+ private _modules;
53
+ constructor(config: {
54
+ baseUrl: string;
55
+ token?: string;
56
+ useMockData?: boolean;
57
+ mockDelay?: {
58
+ min: number;
59
+ max: number;
60
+ };
61
+ }, contracts: C);
62
+ init(): void;
63
+ get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
64
+ use<T>(middleware: Middleware<T>, options?: T): void;
65
+ onError(handler: (error: E) => void): void;
66
+ useResponseTransform(fn: (data: any) => any): void;
67
+ setMockMode(enabled: boolean, delay?: {
68
+ min: number;
69
+ max: number;
70
+ }): void;
71
+ setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny): void;
72
+ private request;
73
+ private handleMockRequest;
74
+ private getRandomDelay;
75
+ private createError;
76
+ private normalizeError;
77
+ }
78
+
79
+ type LoggingOptions = {
80
+ logRequest?: boolean;
81
+ logResponse?: boolean;
82
+ debug?: boolean;
83
+ };
84
+ declare const loggingMiddleware: Middleware<LoggingOptions>;
85
+
86
+ type RetryOptions = {
87
+ maxRetries?: number;
88
+ delay?: number;
89
+ };
90
+ declare const retryMiddleware: (options?: RetryOptions) => Middleware;
91
+
92
+ type AuthOptions = {
93
+ refreshToken?: () => Promise<string>;
94
+ };
95
+ declare const authMiddleware: Middleware<AuthOptions>;
96
+
97
+ type CacheOptions = {
98
+ ttl?: number;
99
+ };
100
+ declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
101
+
102
+ 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, authMiddleware, cacheMiddleware, loggingMiddleware, retryMiddleware };