@tahanabavi/typefetch 1.1.0 → 1.2.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,302 +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.0
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
- ---
52
+ - token: static auth token
53
+ - tokenProvider: function or async function returning a token
52
54
 
53
- ## Defining Contracts
55
+ 6. Mock Mode
56
+ Provides mock responses based on endpoint.mockData, with configurable fake delays.
54
57
 
55
- Contracts are defined using the `Contracts` and `EndpointDef` types.
58
+ 7. Response Wrapper
59
+ For APIs that wrap responses:
56
60
 
57
- ```ts
61
+ ```
62
+ {
63
+ "success": true,
64
+ "data": {...},
65
+ "timestamp": "...",
66
+ "requestId": "..."
67
+ }
68
+ ```
69
+
70
+ TypeFetch unwraps the response automatically.
71
+
72
+ ## Defining API Contracts
73
+
74
+ Example contract definition:
75
+
76
+ ```
58
77
  import { z } from "zod";
59
78
 
60
79
  const contracts = {
61
80
  user: {
62
81
  getUser: {
63
82
  method: "GET",
64
- path: "/user/:id",
83
+ path: "/users/:id",
65
84
  auth: true,
66
- request: z.object({ id: z.string() }),
67
- response: z.object({ id: z.string(), name: z.string() }),
68
- // Optional mock data
69
- 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" }
70
95
  },
96
+
71
97
  createUser: {
72
98
  method: "POST",
73
- path: "/user",
74
- request: z.object({ name: z.string() }),
75
- response: z.object({ id: z.string(), name: z.string() }),
76
- // Dynamic mock data function
77
- mockData: () => ({ id: Math.random().toString(), name: "Dynamic User" }),
78
- },
79
- },
80
- } 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
+ };
81
117
  ```
82
118
 
83
- ---
84
-
85
- ## Using `ApiClient`
119
+ ## Using ApiClient
86
120
 
87
- ```ts
88
- import { ApiClient, RichError } from "typefetch";
121
+ ```
122
+ import { ApiClient } from "@tahanabavi/typefetch";
89
123
 
90
124
  const client = new ApiClient(
91
125
  {
92
126
  baseUrl: "https://api.example.com",
93
- token: "your-auth-token",
94
- useMockData: true,
95
- mockDelay: { min: 100, max: 1000 },
127
+ tokenProvider: () => "dynamic-token",
128
+ useMockData: false
96
129
  },
97
130
  contracts
98
131
  );
99
132
 
100
133
  client.init();
101
134
 
102
- const { modules: api } = client;
135
+ const api = client.modules;
103
136
 
104
- const user = await api.user.getUser({ id: "123" });
105
- 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" } });
106
139
  ```
107
140
 
108
- ---
109
-
110
- ## Error Handling
141
+ ## Middlewares
111
142
 
112
- All errors are provided via the `RichError` class. You can define a custom error handler:
143
+ Example custom middleware:
113
144
 
114
- ```ts
115
- client.onError((error: RichError) => {
116
- 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;
117
151
  });
118
152
  ```
119
153
 
120
- ---
121
-
122
- ## Middlewares
123
-
124
- You can add custom behavior before or after requests. Middlewares work similarly to Express:
154
+ Built-in middlewares:
125
155
 
126
- ```ts
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
- );
135
156
  ```
136
-
137
- ### Built-in Middlewares
138
-
139
- This project provides some built-in middlewares:
140
-
141
- ```ts
142
157
  import {
143
- LoggingMiddleware,
144
- RetryMiddleware,
145
- AuthMiddleware,
146
- CacheMiddleware,
147
- } from "typefetch/middlewares";
148
-
149
- client.use(LoggingMiddleware);
150
- client.use(RetryMiddleware, { maxRetries: 3, delay: 100 });
151
- client.use(AuthMiddleware, { refreshToken: () => "your-auth-token" });
152
- 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
+ });
153
170
  ```
154
171
 
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
159
-
160
- ---
161
-
172
+ ## Mock Mode
173
+ ```
174
+ client.setMockMode(true, { min: 200, max: 1000 });
175
+ client.setMockMode(false);
176
+ ```
162
177
  ## Response Transformation
163
-
164
- You can transform the response format before returning it:
165
-
166
- ```ts
178
+ ```
167
179
  client.useResponseTransform((data) => {
168
- return { ...data, fetchedAt: new Date() };
180
+ return {
181
+ ...data,
182
+ transformedAt: new Date().toISOString()
183
+ };
169
184
  });
170
185
  ```
171
-
172
- ---
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
+ ## Response Wrapper Example
186
187
  ```
187
-
188
- Runtime Control:
189
-
190
- ```ts
191
- client.setMockMode(true, { min: 100, max: 2000 });
192
- 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);
193
206
  ```
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);
207
+ ## Error Handling
218
208
  ```
219
-
220
- ---
221
-
222
- ## Important Notes
223
-
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.
229
-
230
- ---
231
-
232
- ## Full Example
233
-
234
- ```ts
235
- import { z } from "zod";
236
- import { ApiClient, RichError } from "typefetch";
237
- import { LoggingMiddleware, RetryMiddleware } from "typefetch/middlewares";
238
-
239
- const contracts = {
240
- user: {
241
- getUser: {
242
- method: "GET",
243
- path: "/users/:id",
244
- auth: true,
245
- request: z.object({ id: 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" },
252
- },
253
- },
254
- } as const;
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
-
273
- const client = new ApiClient(
274
- {
275
- baseUrl: "https://api.example.com",
276
- token: "abc123",
277
- useMockData: process.env.NODE_ENV === "development",
278
- },
279
- contracts
280
- );
281
-
282
- client.init();
283
- client.setResponseWrapper(apiResponseWrapper);
284
- client.use(LoggingMiddleware);
285
- client.use(RetryMiddleware, { maxRetries: 2 });
286
-
287
- client.onError((err: RichError) => {
288
- console.error("Error:", err.message);
209
+ client.onError((err) => {
210
+ console.error("API Error:", err.message, err.status, err.code);
289
211
  });
290
-
291
- const { modules: api } = client;
292
-
293
- (async () => {
294
- const user = await api.user.getUser({ id: "1" });
295
- console.log(user);
296
- })();
297
212
  ```
213
+ ## Notes
298
214
 
299
- ---
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.
300
220
 
301
221
  ## License
302
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,10 +36,18 @@ 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
  };
50
+ type TokenProvider = () => string | Promise<string>;
32
51
 
33
52
  declare class RichError extends Error implements ErrorLike {
34
53
  status?: number;
@@ -40,6 +59,18 @@ declare class RichError extends Error implements ErrorLike {
40
59
  message: string;
41
60
  });
42
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
+ */
43
74
  declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
44
75
  private config;
45
76
  private contracts;
@@ -49,30 +80,107 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
49
80
  private useMockData;
50
81
  private mockDelay;
51
82
  private responseWrapper?;
83
+ private tokenProvider?;
52
84
  private _modules;
53
85
  constructor(config: {
54
86
  baseUrl: string;
55
87
  token?: string;
88
+ tokenProvider?: TokenProvider;
56
89
  useMockData?: boolean;
57
90
  mockDelay?: {
58
91
  min: number;
59
92
  max: number;
60
93
  };
61
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
+ */
62
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
+ */
63
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
+ */
64
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
+ */
65
114
  onError(handler: (error: E) => void): void;
115
+ /**
116
+ * Registers a transformation function applied to all successful responses
117
+ * after Zod parsing.
118
+ */
66
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
+ */
67
124
  setMockMode(enabled: boolean, delay?: {
68
125
  min: number;
69
126
  max: number;
70
127
  }): void;
128
+ /**
129
+ * Registers a schema wrapper for APIs that wrap data in an envelope.
130
+ * Example: { success, data, message, code, ... }.
131
+ */
71
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
+ */
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
+ */
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
+ */
72
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
+ */
73
171
  private handleMockRequest;
172
+ /**
173
+ * Returns a random delay in milliseconds within the current mock delay range.
174
+ */
74
175
  private getRandomDelay;
176
+ /**
177
+ * Creates a RichError instance from a partial error description.
178
+ */
75
179
  private createError;
180
+ /**
181
+ * Normalizes unknown errors into a RichError instance.
182
+ * Zod validation errors are converted into a standardized validation error.
183
+ */
76
184
  private normalizeError;
77
185
  }
78
186
 
@@ -99,4 +207,4 @@ type CacheOptions = {
99
207
  };
100
208
  declare const cacheMiddleware: (options?: CacheOptions) => (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
101
209
 
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 };
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 };