@tahanabavi/typefetch 1.0.3 → 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,6 +1,6 @@
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
 
@@ -12,6 +12,8 @@ TypeFetch is a type-safe client for working with APIs, built with TypeScript and
12
12
  - Error handling with the `RichError` class
13
13
  - Ability to transform responses using a response transformer
14
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,6 +91,8 @@ 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
  );
@@ -82,7 +113,7 @@ All errors are provided via the `RichError` class. You can define a custom error
82
113
 
83
114
  ```ts
84
115
  client.onError((error: RichError) => {
85
- console.error("API Error:", error.message, error.status);
116
+ console.error("API Error:", error.message, error.status, error.code);
86
117
  });
87
118
  ```
88
119
 
@@ -140,11 +171,61 @@ client.useResponseTransform((data) => {
140
171
 
141
172
  ---
142
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
+
143
222
  ## Important Notes
144
223
 
145
224
  - Always call `client.init()` before using endpoints.
146
225
  - Types are automatically inferred from Zod, making inputs and outputs type-safe.
147
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.
148
229
 
149
230
  ---
150
231
 
@@ -156,23 +237,50 @@ import { ApiClient, RichError } from "typefetch";
156
237
  import { LoggingMiddleware, RetryMiddleware } from "typefetch/middlewares";
157
238
 
158
239
  const contracts = {
159
- post: {
160
- getPost: {
240
+ user: {
241
+ getUser: {
161
242
  method: "GET",
162
- path: "/posts/:id",
243
+ path: "/users/:id",
163
244
  auth: true,
164
245
  request: z.object({ id: z.string() }),
165
- 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" },
166
252
  },
167
253
  },
168
254
  } as const;
169
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
+
170
273
  const client = new ApiClient(
171
- { 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
+ },
172
279
  contracts
173
280
  );
174
281
 
175
282
  client.init();
283
+ client.setResponseWrapper(apiResponseWrapper);
176
284
  client.use(LoggingMiddleware);
177
285
  client.use(RetryMiddleware, { maxRetries: 2 });
178
286
 
@@ -183,7 +291,13 @@ client.onError((err: RichError) => {
183
291
  const { modules: api } = client;
184
292
 
185
293
  (async () => {
186
- const post = await api.post.getPost({ id: "1" });
187
- console.log(post);
294
+ const user = await api.user.getUser({ id: "1" });
295
+ console.log(user);
188
296
  })();
189
297
  ```
298
+
299
+ ---
300
+
301
+ ## License
302
+
303
+ MIT
package/dist/index.d.mts CHANGED
@@ -6,6 +6,7 @@ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
6
6
  auth?: boolean;
7
7
  request: TReq;
8
8
  response: TRes;
9
+ mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
9
10
  };
10
11
  type Contracts = {
11
12
  [ModuleName: string]: {
@@ -45,17 +46,32 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
45
46
  private middlewares;
46
47
  private errorHandler?;
47
48
  private responseTransform;
49
+ private useMockData;
50
+ private mockDelay;
51
+ private responseWrapper?;
48
52
  private _modules;
49
53
  constructor(config: {
50
54
  baseUrl: string;
51
55
  token?: string;
56
+ useMockData?: boolean;
57
+ mockDelay?: {
58
+ min: number;
59
+ max: number;
60
+ };
52
61
  }, contracts: C);
53
62
  init(): void;
54
63
  get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
55
64
  use<T>(middleware: Middleware<T>, options?: T): void;
56
65
  onError(handler: (error: E) => void): void;
57
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;
58
72
  private request;
73
+ private handleMockRequest;
74
+ private getRandomDelay;
59
75
  private createError;
60
76
  private normalizeError;
61
77
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ type EndpointDef<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny> = {
6
6
  auth?: boolean;
7
7
  request: TReq;
8
8
  response: TRes;
9
+ mockData?: (() => z.infer<TRes>) | z.infer<TRes>;
9
10
  };
10
11
  type Contracts = {
11
12
  [ModuleName: string]: {
@@ -45,17 +46,32 @@ declare class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {
45
46
  private middlewares;
46
47
  private errorHandler?;
47
48
  private responseTransform;
49
+ private useMockData;
50
+ private mockDelay;
51
+ private responseWrapper?;
48
52
  private _modules;
49
53
  constructor(config: {
50
54
  baseUrl: string;
51
55
  token?: string;
56
+ useMockData?: boolean;
57
+ mockDelay?: {
58
+ min: number;
59
+ max: number;
60
+ };
52
61
  }, contracts: C);
53
62
  init(): void;
54
63
  get modules(): { [M in keyof C]: EndpointMethods<C[M]>; };
55
64
  use<T>(middleware: Middleware<T>, options?: T): void;
56
65
  onError(handler: (error: E) => void): void;
57
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;
58
72
  private request;
73
+ private handleMockRequest;
74
+ private getRandomDelay;
59
75
  private createError;
60
76
  private normalizeError;
61
77
  }
package/dist/index.js CHANGED
@@ -79,6 +79,10 @@ var ApiClient = class {
79
79
  this.contracts = contracts;
80
80
  this.middlewares = [];
81
81
  this.responseTransform = (d) => d;
82
+ this.useMockData = false;
83
+ this.mockDelay = { min: 100, max: 1e3 };
84
+ this.useMockData = config.useMockData || false;
85
+ this.mockDelay = config.mockDelay || { min: 100, max: 1e3 };
82
86
  }
83
87
  init() {
84
88
  const modules = {};
@@ -104,10 +108,22 @@ var ApiClient = class {
104
108
  useResponseTransform(fn) {
105
109
  this.responseTransform = fn;
106
110
  }
111
+ setMockMode(enabled, delay) {
112
+ this.useMockData = enabled;
113
+ if (delay) {
114
+ this.mockDelay = delay;
115
+ }
116
+ }
117
+ setResponseWrapper(wrapper) {
118
+ this.responseWrapper = wrapper;
119
+ }
107
120
  request(endpoint, input) {
108
121
  return __async(this, null, function* () {
109
- var _a, _b, _c;
122
+ var _a, _b, _c, _d;
110
123
  endpoint.request.parse(input);
124
+ if (this.useMockData && endpoint.mockData) {
125
+ return this.handleMockRequest(endpoint);
126
+ }
111
127
  if (endpoint.auth && !this.config.token) {
112
128
  const error = this.createError({
113
129
  message: `Missing token for ${endpoint.path}`,
@@ -134,28 +150,73 @@ var ApiClient = class {
134
150
  );
135
151
  try {
136
152
  const res = yield runner();
153
+ const json = yield res.json();
154
+ let responseData = json;
155
+ if (this.responseWrapper) {
156
+ const wrappedSchema = this.responseWrapper(endpoint.response);
157
+ const parsedResponse = wrappedSchema.parse(json);
158
+ if (parsedResponse.success === false) {
159
+ const error = this.createError({
160
+ message: parsedResponse.message || "Request failed",
161
+ status: parsedResponse.code || res.status,
162
+ code: `API_ERROR_${parsedResponse.code}`
163
+ });
164
+ (_b = this.errorHandler) == null ? void 0 : _b.call(this, error);
165
+ throw error;
166
+ }
167
+ responseData = parsedResponse.data;
168
+ }
137
169
  if (!res.ok) {
138
- const errorData = yield res.json().catch(() => ({}));
139
170
  const error = this.createError({
140
- message: errorData.message || res.statusText,
171
+ message: json.message || res.statusText,
141
172
  status: res.status,
142
- code: errorData.code,
143
- title: errorData.title,
144
- detail: errorData.detail,
145
- errors: errorData.errors
173
+ code: json.code,
174
+ title: json.title,
175
+ detail: json.detail,
176
+ errors: json.errors
146
177
  });
147
- (_b = this.errorHandler) == null ? void 0 : _b.call(this, error);
178
+ (_c = this.errorHandler) == null ? void 0 : _c.call(this, error);
148
179
  throw error;
149
180
  }
150
- const json = yield res.json();
151
- return this.responseTransform(endpoint.response.parse(json));
181
+ return this.responseTransform(endpoint.response.parse(responseData));
152
182
  } catch (err) {
153
183
  const error = this.normalizeError(err);
154
- (_c = this.errorHandler) == null ? void 0 : _c.call(this, error);
184
+ (_d = this.errorHandler) == null ? void 0 : _d.call(this, error);
155
185
  throw error;
156
186
  }
157
187
  });
158
188
  }
189
+ handleMockRequest(endpoint) {
190
+ return __async(this, null, function* () {
191
+ const delay = this.getRandomDelay();
192
+ yield new Promise((resolve) => setTimeout(resolve, delay));
193
+ let mockData;
194
+ if (typeof endpoint.mockData === "function") {
195
+ mockData = endpoint.mockData();
196
+ } else {
197
+ mockData = endpoint.mockData;
198
+ }
199
+ if (this.responseWrapper) {
200
+ const wrappedSchema = this.responseWrapper(endpoint.response);
201
+ const mockWrappedResponse = {
202
+ success: true,
203
+ data: mockData,
204
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
205
+ requestId: `mock-${Math.random().toString(36).substr(2, 9)}`
206
+ };
207
+ const parsedWrappedResponse = wrappedSchema.parse(mockWrappedResponse);
208
+ return this.responseTransform(
209
+ endpoint.response.parse(parsedWrappedResponse.data)
210
+ );
211
+ }
212
+ return this.responseTransform(endpoint.response.parse(mockData));
213
+ });
214
+ }
215
+ getRandomDelay() {
216
+ return Math.floor(
217
+ Math.random() * (this.mockDelay.max - this.mockDelay.min + 1)
218
+ ) + this.mockDelay.min;
219
+ }
159
220
  createError(error) {
160
221
  return new RichError(error);
161
222
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/middlewares/logging.ts","../src/middlewares/retry.ts","../src/middlewares/auth.ts","../src/middlewares/cache.ts"],"sourcesContent":["export * from \"./types\";\nexport * from \"./client\";\nexport * from \"./middlewares/logging\";\nexport * from \"./middlewares/retry\";\nexport * from \"./middlewares/auth\";\nexport * from \"./middlewares/cache\";\n","import {\n Contracts,\n EndpointDef,\n EndpointDefZ,\n Middleware,\n ErrorLike,\n EndpointMethods,\n} from \"@/types\";\nimport { z } from \"zod\";\n\nexport class RichError extends Error implements ErrorLike {\n status?: number;\n code?: string;\n title?: string;\n detail?: string;\n errors?: Record<string, string[]>;\n\n constructor(error: Partial<ErrorLike> & { message: string }) {\n super(error.message);\n Object.assign(this, error);\n }\n}\n\nexport class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {\n private middlewares: Array<{ fn: Middleware; options?: any }> = [];\n private errorHandler?: (error: E) => void;\n private responseTransform: (data: any) => any = (d) => d;\n\n private _modules!: {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n constructor(\n private config: { baseUrl: string; token?: string },\n private contracts: C\n ) {}\n\n init() {\n const modules = {} as {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n for (const moduleName in this.contracts) {\n const module = this.contracts[moduleName];\n (modules as any)[moduleName] = {} as EndpointMethods<typeof module>;\n\n for (const endpointName in module) {\n const endpoint = module[endpointName] as EndpointDefZ;\n\n (modules as any)[moduleName][endpointName] = (\n input: z.infer<(typeof endpoint)[\"request\"]>\n ) => this.request(endpoint, input);\n }\n }\n\n this._modules = modules;\n }\n\n get modules() {\n return this._modules;\n }\n\n use<T>(middleware: Middleware<T>, options?: T) {\n this.middlewares.push({ fn: middleware, options });\n }\n\n onError(handler: (error: E) => void) {\n this.errorHandler = handler;\n }\n\n useResponseTransform(fn: (data: any) => any) {\n this.responseTransform = fn;\n }\n\n private async request<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny>(\n endpoint: EndpointDef<TReq, TRes>,\n input: z.infer<TReq>\n ): Promise<z.infer<TRes>> {\n endpoint.request.parse(input);\n\n if (endpoint.auth && !this.config.token) {\n const error = this.createError({\n message: `Missing token for ${endpoint.path}`,\n status: 401,\n code: \"NO_TOKEN\",\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (endpoint.auth && this.config.token)\n headers[\"Authorization\"] = `Bearer ${this.config.token}`;\n\n const ctx = {\n url: this.config.baseUrl + endpoint.path,\n init: {\n method: endpoint.method,\n headers,\n body: endpoint.method !== \"GET\" ? JSON.stringify(input) : undefined,\n },\n };\n\n const runner = this.middlewares.reduceRight(\n (next, mw) => () => mw.fn(ctx, next, mw.options),\n () => fetch(ctx.url, ctx.init)\n );\n\n try {\n const res = await runner();\n if (!res.ok) {\n const errorData = await res.json().catch(() => ({}));\n const error = this.createError({\n message: errorData.message || res.statusText,\n status: res.status,\n code: errorData.code,\n title: errorData.title,\n detail: errorData.detail,\n errors: errorData.errors,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const json = await res.json();\n return this.responseTransform(endpoint.response.parse(json));\n } catch (err: any) {\n const error = this.normalizeError(err);\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n }\n\n private createError(error: Partial<RichError> & { message: string }) {\n return new RichError(error);\n }\n\n private normalizeError(err: any) {\n if (err instanceof RichError) return err;\n return this.createError({ message: err.message || \"Unknown error\" });\n }\n}\n","import { Middleware } from \"@/types\";\n\nexport type LoggingOptions = {\n logRequest?: boolean;\n logResponse?: boolean;\n debug?: boolean;\n};\n\nexport const loggingMiddleware: Middleware<LoggingOptions> = async (\n ctx,\n next,\n options\n) => {\n const { logRequest = true, logResponse = true, debug = true } = options || {};\n\n if (debug && logRequest) console.log(\"➡️ Request:\", ctx.url, ctx.init);\n\n const res = await next();\n\n if (debug && logResponse) console.log(\"⬅️ Response:\", res.status);\n\n return res;\n};\n","import { Middleware } from \"@/types\";\n\n\nexport type RetryOptions = {\n maxRetries?: number;\n delay?: number; // ms\n};\n\nexport const retryMiddleware = (options?: RetryOptions): Middleware => {\n const { maxRetries = 3, delay = 500 } = options || {};\n\n const middleware: Middleware = async (ctx, next) => {\n let attempt = 0;\n while (true) {\n try {\n return await next();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n attempt++;\n await new Promise((r) => setTimeout(r, delay * 2 ** attempt));\n }\n }\n };\n\n return middleware;\n};\n","import { Middleware } from \"@/types\";\n\nexport type AuthOptions = {\n refreshToken?: () => Promise<string>;\n};\n\nexport const authMiddleware: Middleware<AuthOptions> = async (\n ctx,\n next,\n options\n) => {\n if (options?.refreshToken) {\n try {\n const newToken = await options.refreshToken();\n ctx.init.headers = {\n ...ctx.init.headers,\n Authorization: `Bearer ${newToken}`,\n };\n } catch {}\n }\n\n return next();\n};\n","import { MiddlewareContext, MiddlewareNext } from \"@/types\";\n\nexport type CacheOptions = { ttl?: number };\n\nexport const cacheMiddleware = (options: CacheOptions = {}) => {\n const { ttl = 60000 } = options;\n const cache = new Map<string, { data: any; expires: number }>();\n\n return async (ctx: MiddlewareContext, next: MiddlewareNext) => {\n if (ctx.init.method === \"GET\") {\n const cached = cache.get(ctx.url);\n const now = Date.now();\n if (cached && cached.expires > now)\n return new Response(JSON.stringify(cached.data));\n\n const res = await next();\n const data = await res\n .clone()\n .json()\n .catch(() => null);\n if (data) cache.set(ctx.url, { data, expires: now + ttl });\n return res;\n }\n return next();\n };\n};\n"],"mappings":"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","names":["module"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/middlewares/logging.ts","../src/middlewares/retry.ts","../src/middlewares/auth.ts","../src/middlewares/cache.ts"],"sourcesContent":["export * from \"./types\";\nexport * from \"./client\";\nexport * from \"./middlewares/logging\";\nexport * from \"./middlewares/retry\";\nexport * from \"./middlewares/auth\";\nexport * from \"./middlewares/cache\";\n","import {\n Contracts,\n EndpointDef,\n EndpointDefZ,\n Middleware,\n ErrorLike,\n EndpointMethods,\n} from \"@/types\";\nimport { z } from \"zod\";\n\nexport class RichError extends Error implements ErrorLike {\n status?: number;\n code?: string;\n title?: string;\n detail?: string;\n errors?: Record<string, string[]>;\n\n constructor(error: Partial<ErrorLike> & { message: string }) {\n super(error.message);\n Object.assign(this, error);\n }\n}\n\nexport class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {\n private middlewares: Array<{ fn: Middleware; options?: any }> = [];\n private errorHandler?: (error: E) => void;\n private responseTransform: (data: any) => any = (d) => d;\n private useMockData: boolean = false;\n private mockDelay: { min: number; max: number } = { min: 100, max: 1000 };\n private responseWrapper?: (successResponse: z.ZodTypeAny) => z.ZodTypeAny;\n\n private _modules!: {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n constructor(\n private config: {\n baseUrl: string;\n token?: string;\n useMockData?: boolean;\n mockDelay?: { min: number; max: number };\n },\n private contracts: C\n ) {\n this.useMockData = config.useMockData || false;\n this.mockDelay = config.mockDelay || { min: 100, max: 1000 };\n }\n\n init() {\n const modules = {} as {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n for (const moduleName in this.contracts) {\n const module = this.contracts[moduleName];\n (modules as any)[moduleName] = {} as EndpointMethods<typeof module>;\n\n for (const endpointName in module) {\n const endpoint = module[endpointName] as EndpointDefZ;\n\n (modules as any)[moduleName][endpointName] = (\n input: z.infer<(typeof endpoint)[\"request\"]>\n ) => this.request(endpoint, input);\n }\n }\n\n this._modules = modules;\n }\n\n get modules() {\n return this._modules;\n }\n\n use<T>(middleware: Middleware<T>, options?: T) {\n this.middlewares.push({ fn: middleware, options });\n }\n\n onError(handler: (error: E) => void) {\n this.errorHandler = handler;\n }\n\n useResponseTransform(fn: (data: any) => any) {\n this.responseTransform = fn;\n }\n\n setMockMode(enabled: boolean, delay?: { min: number; max: number }) {\n this.useMockData = enabled;\n if (delay) {\n this.mockDelay = delay;\n }\n }\n\n setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny) {\n this.responseWrapper = wrapper;\n }\n\n private async request<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny>(\n endpoint: EndpointDef<TReq, TRes>,\n input: z.infer<TReq>\n ): Promise<z.infer<TRes>> {\n endpoint.request.parse(input);\n\n if (this.useMockData && endpoint.mockData) {\n return this.handleMockRequest(endpoint);\n }\n\n if (endpoint.auth && !this.config.token) {\n const error = this.createError({\n message: `Missing token for ${endpoint.path}`,\n status: 401,\n code: \"NO_TOKEN\",\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (endpoint.auth && this.config.token)\n headers[\"Authorization\"] = `Bearer ${this.config.token}`;\n\n const ctx = {\n url: this.config.baseUrl + endpoint.path,\n init: {\n method: endpoint.method,\n headers,\n body: endpoint.method !== \"GET\" ? JSON.stringify(input) : undefined,\n },\n };\n\n const runner = this.middlewares.reduceRight(\n (next, mw) => () => mw.fn(ctx, next, mw.options),\n () => fetch(ctx.url, ctx.init)\n );\n\n try {\n const res = await runner();\n const json = await res.json();\n\n let responseData = json;\n if (this.responseWrapper) {\n const wrappedSchema = this.responseWrapper(endpoint.response);\n const parsedResponse = wrappedSchema.parse(json);\n\n if (parsedResponse.success === false) {\n const error = this.createError({\n message: parsedResponse.message || \"Request failed\",\n status: parsedResponse.code || res.status,\n code: `API_ERROR_${parsedResponse.code}`,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n responseData = parsedResponse.data;\n }\n\n if (!res.ok) {\n const error = this.createError({\n message: json.message || res.statusText,\n status: res.status,\n code: json.code,\n title: json.title,\n detail: json.detail,\n errors: json.errors,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n return this.responseTransform(endpoint.response.parse(responseData));\n } catch (err: any) {\n const error = this.normalizeError(err);\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n }\n\n private async handleMockRequest<\n TReq extends z.ZodTypeAny,\n TRes extends z.ZodTypeAny\n >(endpoint: EndpointDef<TReq, TRes>): Promise<z.infer<TRes>> {\n const delay = this.getRandomDelay();\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n let mockData: z.infer<TRes>;\n if (typeof endpoint.mockData === \"function\") {\n mockData = (endpoint.mockData as () => z.infer<TRes>)();\n } else {\n mockData = endpoint.mockData as z.infer<TRes>;\n }\n\n if (this.responseWrapper) {\n const wrappedSchema = this.responseWrapper(endpoint.response);\n\n const mockWrappedResponse = {\n success: true,\n data: mockData,\n timestamp: new Date().toISOString(),\n requestId: `mock-${Math.random().toString(36).substr(2, 9)}`,\n };\n\n const parsedWrappedResponse = wrappedSchema.parse(mockWrappedResponse);\n\n return this.responseTransform(\n endpoint.response.parse(parsedWrappedResponse.data)\n );\n }\n\n return this.responseTransform(endpoint.response.parse(mockData));\n }\n\n private getRandomDelay(): number {\n return (\n Math.floor(\n Math.random() * (this.mockDelay.max - this.mockDelay.min + 1)\n ) + this.mockDelay.min\n );\n }\n\n private createError(error: Partial<RichError> & { message: string }) {\n return new RichError(error);\n }\n\n private normalizeError(err: any) {\n if (err instanceof RichError) return err;\n return this.createError({ message: err.message || \"Unknown error\" });\n }\n}\n","import { Middleware } from \"@/types\";\n\nexport type LoggingOptions = {\n logRequest?: boolean;\n logResponse?: boolean;\n debug?: boolean;\n};\n\nexport const loggingMiddleware: Middleware<LoggingOptions> = async (\n ctx,\n next,\n options\n) => {\n const { logRequest = true, logResponse = true, debug = true } = options || {};\n\n if (debug && logRequest) console.log(\"➡️ Request:\", ctx.url, ctx.init);\n\n const res = await next();\n\n if (debug && logResponse) console.log(\"⬅️ Response:\", res.status);\n\n return res;\n};\n","import { Middleware } from \"@/types\";\n\n\nexport type RetryOptions = {\n maxRetries?: number;\n delay?: number; // ms\n};\n\nexport const retryMiddleware = (options?: RetryOptions): Middleware => {\n const { maxRetries = 3, delay = 500 } = options || {};\n\n const middleware: Middleware = async (ctx, next) => {\n let attempt = 0;\n while (true) {\n try {\n return await next();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n attempt++;\n await new Promise((r) => setTimeout(r, delay * 2 ** attempt));\n }\n }\n };\n\n return middleware;\n};\n","import { Middleware } from \"@/types\";\n\nexport type AuthOptions = {\n refreshToken?: () => Promise<string>;\n};\n\nexport const authMiddleware: Middleware<AuthOptions> = async (\n ctx,\n next,\n options\n) => {\n if (options?.refreshToken) {\n try {\n const newToken = await options.refreshToken();\n ctx.init.headers = {\n ...ctx.init.headers,\n Authorization: `Bearer ${newToken}`,\n };\n } catch {}\n }\n\n return next();\n};\n","import { MiddlewareContext, MiddlewareNext } from \"@/types\";\n\nexport type CacheOptions = { ttl?: number };\n\nexport const cacheMiddleware = (options: CacheOptions = {}) => {\n const { ttl = 60000 } = options;\n const cache = new Map<string, { data: any; expires: number }>();\n\n return async (ctx: MiddlewareContext, next: MiddlewareNext) => {\n if (ctx.init.method === \"GET\") {\n const cached = cache.get(ctx.url);\n const now = Date.now();\n if (cached && cached.expires > now)\n return new Response(JSON.stringify(cached.data));\n\n const res = await next();\n const data = await res\n .clone()\n .json()\n .catch(() => null);\n if (data) cache.set(ctx.url, { data, expires: now + ttl });\n return res;\n }\n return next();\n };\n};\n"],"mappings":"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","names":["module"]}
package/dist/index.mjs CHANGED
@@ -52,6 +52,10 @@ var ApiClient = class {
52
52
  this.contracts = contracts;
53
53
  this.middlewares = [];
54
54
  this.responseTransform = (d) => d;
55
+ this.useMockData = false;
56
+ this.mockDelay = { min: 100, max: 1e3 };
57
+ this.useMockData = config.useMockData || false;
58
+ this.mockDelay = config.mockDelay || { min: 100, max: 1e3 };
55
59
  }
56
60
  init() {
57
61
  const modules = {};
@@ -77,10 +81,22 @@ var ApiClient = class {
77
81
  useResponseTransform(fn) {
78
82
  this.responseTransform = fn;
79
83
  }
84
+ setMockMode(enabled, delay) {
85
+ this.useMockData = enabled;
86
+ if (delay) {
87
+ this.mockDelay = delay;
88
+ }
89
+ }
90
+ setResponseWrapper(wrapper) {
91
+ this.responseWrapper = wrapper;
92
+ }
80
93
  request(endpoint, input) {
81
94
  return __async(this, null, function* () {
82
- var _a, _b, _c;
95
+ var _a, _b, _c, _d;
83
96
  endpoint.request.parse(input);
97
+ if (this.useMockData && endpoint.mockData) {
98
+ return this.handleMockRequest(endpoint);
99
+ }
84
100
  if (endpoint.auth && !this.config.token) {
85
101
  const error = this.createError({
86
102
  message: `Missing token for ${endpoint.path}`,
@@ -107,28 +123,73 @@ var ApiClient = class {
107
123
  );
108
124
  try {
109
125
  const res = yield runner();
126
+ const json = yield res.json();
127
+ let responseData = json;
128
+ if (this.responseWrapper) {
129
+ const wrappedSchema = this.responseWrapper(endpoint.response);
130
+ const parsedResponse = wrappedSchema.parse(json);
131
+ if (parsedResponse.success === false) {
132
+ const error = this.createError({
133
+ message: parsedResponse.message || "Request failed",
134
+ status: parsedResponse.code || res.status,
135
+ code: `API_ERROR_${parsedResponse.code}`
136
+ });
137
+ (_b = this.errorHandler) == null ? void 0 : _b.call(this, error);
138
+ throw error;
139
+ }
140
+ responseData = parsedResponse.data;
141
+ }
110
142
  if (!res.ok) {
111
- const errorData = yield res.json().catch(() => ({}));
112
143
  const error = this.createError({
113
- message: errorData.message || res.statusText,
144
+ message: json.message || res.statusText,
114
145
  status: res.status,
115
- code: errorData.code,
116
- title: errorData.title,
117
- detail: errorData.detail,
118
- errors: errorData.errors
146
+ code: json.code,
147
+ title: json.title,
148
+ detail: json.detail,
149
+ errors: json.errors
119
150
  });
120
- (_b = this.errorHandler) == null ? void 0 : _b.call(this, error);
151
+ (_c = this.errorHandler) == null ? void 0 : _c.call(this, error);
121
152
  throw error;
122
153
  }
123
- const json = yield res.json();
124
- return this.responseTransform(endpoint.response.parse(json));
154
+ return this.responseTransform(endpoint.response.parse(responseData));
125
155
  } catch (err) {
126
156
  const error = this.normalizeError(err);
127
- (_c = this.errorHandler) == null ? void 0 : _c.call(this, error);
157
+ (_d = this.errorHandler) == null ? void 0 : _d.call(this, error);
128
158
  throw error;
129
159
  }
130
160
  });
131
161
  }
162
+ handleMockRequest(endpoint) {
163
+ return __async(this, null, function* () {
164
+ const delay = this.getRandomDelay();
165
+ yield new Promise((resolve) => setTimeout(resolve, delay));
166
+ let mockData;
167
+ if (typeof endpoint.mockData === "function") {
168
+ mockData = endpoint.mockData();
169
+ } else {
170
+ mockData = endpoint.mockData;
171
+ }
172
+ if (this.responseWrapper) {
173
+ const wrappedSchema = this.responseWrapper(endpoint.response);
174
+ const mockWrappedResponse = {
175
+ success: true,
176
+ data: mockData,
177
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
178
+ requestId: `mock-${Math.random().toString(36).substr(2, 9)}`
179
+ };
180
+ const parsedWrappedResponse = wrappedSchema.parse(mockWrappedResponse);
181
+ return this.responseTransform(
182
+ endpoint.response.parse(parsedWrappedResponse.data)
183
+ );
184
+ }
185
+ return this.responseTransform(endpoint.response.parse(mockData));
186
+ });
187
+ }
188
+ getRandomDelay() {
189
+ return Math.floor(
190
+ Math.random() * (this.mockDelay.max - this.mockDelay.min + 1)
191
+ ) + this.mockDelay.min;
192
+ }
132
193
  createError(error) {
133
194
  return new RichError(error);
134
195
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/middlewares/logging.ts","../src/middlewares/retry.ts","../src/middlewares/auth.ts","../src/middlewares/cache.ts"],"sourcesContent":["import {\n Contracts,\n EndpointDef,\n EndpointDefZ,\n Middleware,\n ErrorLike,\n EndpointMethods,\n} from \"@/types\";\nimport { z } from \"zod\";\n\nexport class RichError extends Error implements ErrorLike {\n status?: number;\n code?: string;\n title?: string;\n detail?: string;\n errors?: Record<string, string[]>;\n\n constructor(error: Partial<ErrorLike> & { message: string }) {\n super(error.message);\n Object.assign(this, error);\n }\n}\n\nexport class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {\n private middlewares: Array<{ fn: Middleware; options?: any }> = [];\n private errorHandler?: (error: E) => void;\n private responseTransform: (data: any) => any = (d) => d;\n\n private _modules!: {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n constructor(\n private config: { baseUrl: string; token?: string },\n private contracts: C\n ) {}\n\n init() {\n const modules = {} as {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n for (const moduleName in this.contracts) {\n const module = this.contracts[moduleName];\n (modules as any)[moduleName] = {} as EndpointMethods<typeof module>;\n\n for (const endpointName in module) {\n const endpoint = module[endpointName] as EndpointDefZ;\n\n (modules as any)[moduleName][endpointName] = (\n input: z.infer<(typeof endpoint)[\"request\"]>\n ) => this.request(endpoint, input);\n }\n }\n\n this._modules = modules;\n }\n\n get modules() {\n return this._modules;\n }\n\n use<T>(middleware: Middleware<T>, options?: T) {\n this.middlewares.push({ fn: middleware, options });\n }\n\n onError(handler: (error: E) => void) {\n this.errorHandler = handler;\n }\n\n useResponseTransform(fn: (data: any) => any) {\n this.responseTransform = fn;\n }\n\n private async request<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny>(\n endpoint: EndpointDef<TReq, TRes>,\n input: z.infer<TReq>\n ): Promise<z.infer<TRes>> {\n endpoint.request.parse(input);\n\n if (endpoint.auth && !this.config.token) {\n const error = this.createError({\n message: `Missing token for ${endpoint.path}`,\n status: 401,\n code: \"NO_TOKEN\",\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (endpoint.auth && this.config.token)\n headers[\"Authorization\"] = `Bearer ${this.config.token}`;\n\n const ctx = {\n url: this.config.baseUrl + endpoint.path,\n init: {\n method: endpoint.method,\n headers,\n body: endpoint.method !== \"GET\" ? JSON.stringify(input) : undefined,\n },\n };\n\n const runner = this.middlewares.reduceRight(\n (next, mw) => () => mw.fn(ctx, next, mw.options),\n () => fetch(ctx.url, ctx.init)\n );\n\n try {\n const res = await runner();\n if (!res.ok) {\n const errorData = await res.json().catch(() => ({}));\n const error = this.createError({\n message: errorData.message || res.statusText,\n status: res.status,\n code: errorData.code,\n title: errorData.title,\n detail: errorData.detail,\n errors: errorData.errors,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const json = await res.json();\n return this.responseTransform(endpoint.response.parse(json));\n } catch (err: any) {\n const error = this.normalizeError(err);\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n }\n\n private createError(error: Partial<RichError> & { message: string }) {\n return new RichError(error);\n }\n\n private normalizeError(err: any) {\n if (err instanceof RichError) return err;\n return this.createError({ message: err.message || \"Unknown error\" });\n }\n}\n","import { Middleware } from \"@/types\";\n\nexport type LoggingOptions = {\n logRequest?: boolean;\n logResponse?: boolean;\n debug?: boolean;\n};\n\nexport const loggingMiddleware: Middleware<LoggingOptions> = async (\n ctx,\n next,\n options\n) => {\n const { logRequest = true, logResponse = true, debug = true } = options || {};\n\n if (debug && logRequest) console.log(\"➡️ Request:\", ctx.url, ctx.init);\n\n const res = await next();\n\n if (debug && logResponse) console.log(\"⬅️ Response:\", res.status);\n\n return res;\n};\n","import { Middleware } from \"@/types\";\n\n\nexport type RetryOptions = {\n maxRetries?: number;\n delay?: number; // ms\n};\n\nexport const retryMiddleware = (options?: RetryOptions): Middleware => {\n const { maxRetries = 3, delay = 500 } = options || {};\n\n const middleware: Middleware = async (ctx, next) => {\n let attempt = 0;\n while (true) {\n try {\n return await next();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n attempt++;\n await new Promise((r) => setTimeout(r, delay * 2 ** attempt));\n }\n }\n };\n\n return middleware;\n};\n","import { Middleware } from \"@/types\";\n\nexport type AuthOptions = {\n refreshToken?: () => Promise<string>;\n};\n\nexport const authMiddleware: Middleware<AuthOptions> = async (\n ctx,\n next,\n options\n) => {\n if (options?.refreshToken) {\n try {\n const newToken = await options.refreshToken();\n ctx.init.headers = {\n ...ctx.init.headers,\n Authorization: `Bearer ${newToken}`,\n };\n } catch {}\n }\n\n return next();\n};\n","import { MiddlewareContext, MiddlewareNext } from \"@/types\";\n\nexport type CacheOptions = { ttl?: number };\n\nexport const cacheMiddleware = (options: CacheOptions = {}) => {\n const { ttl = 60000 } = options;\n const cache = new Map<string, { data: any; expires: number }>();\n\n return async (ctx: MiddlewareContext, next: MiddlewareNext) => {\n if (ctx.init.method === \"GET\") {\n const cached = cache.get(ctx.url);\n const now = Date.now();\n if (cached && cached.expires > now)\n return new Response(JSON.stringify(cached.data));\n\n const res = await next();\n const data = await res\n .clone()\n .json()\n .catch(() => null);\n if (data) cache.set(ctx.url, { data, expires: now + ttl });\n return res;\n }\n return next();\n };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/middlewares/logging.ts","../src/middlewares/retry.ts","../src/middlewares/auth.ts","../src/middlewares/cache.ts"],"sourcesContent":["import {\n Contracts,\n EndpointDef,\n EndpointDefZ,\n Middleware,\n ErrorLike,\n EndpointMethods,\n} from \"@/types\";\nimport { z } from \"zod\";\n\nexport class RichError extends Error implements ErrorLike {\n status?: number;\n code?: string;\n title?: string;\n detail?: string;\n errors?: Record<string, string[]>;\n\n constructor(error: Partial<ErrorLike> & { message: string }) {\n super(error.message);\n Object.assign(this, error);\n }\n}\n\nexport class ApiClient<C extends Contracts, E extends ErrorLike = RichError> {\n private middlewares: Array<{ fn: Middleware; options?: any }> = [];\n private errorHandler?: (error: E) => void;\n private responseTransform: (data: any) => any = (d) => d;\n private useMockData: boolean = false;\n private mockDelay: { min: number; max: number } = { min: 100, max: 1000 };\n private responseWrapper?: (successResponse: z.ZodTypeAny) => z.ZodTypeAny;\n\n private _modules!: {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n constructor(\n private config: {\n baseUrl: string;\n token?: string;\n useMockData?: boolean;\n mockDelay?: { min: number; max: number };\n },\n private contracts: C\n ) {\n this.useMockData = config.useMockData || false;\n this.mockDelay = config.mockDelay || { min: 100, max: 1000 };\n }\n\n init() {\n const modules = {} as {\n [M in keyof C]: EndpointMethods<C[M]>;\n };\n\n for (const moduleName in this.contracts) {\n const module = this.contracts[moduleName];\n (modules as any)[moduleName] = {} as EndpointMethods<typeof module>;\n\n for (const endpointName in module) {\n const endpoint = module[endpointName] as EndpointDefZ;\n\n (modules as any)[moduleName][endpointName] = (\n input: z.infer<(typeof endpoint)[\"request\"]>\n ) => this.request(endpoint, input);\n }\n }\n\n this._modules = modules;\n }\n\n get modules() {\n return this._modules;\n }\n\n use<T>(middleware: Middleware<T>, options?: T) {\n this.middlewares.push({ fn: middleware, options });\n }\n\n onError(handler: (error: E) => void) {\n this.errorHandler = handler;\n }\n\n useResponseTransform(fn: (data: any) => any) {\n this.responseTransform = fn;\n }\n\n setMockMode(enabled: boolean, delay?: { min: number; max: number }) {\n this.useMockData = enabled;\n if (delay) {\n this.mockDelay = delay;\n }\n }\n\n setResponseWrapper(wrapper: (successResponse: z.ZodTypeAny) => z.ZodTypeAny) {\n this.responseWrapper = wrapper;\n }\n\n private async request<TReq extends z.ZodTypeAny, TRes extends z.ZodTypeAny>(\n endpoint: EndpointDef<TReq, TRes>,\n input: z.infer<TReq>\n ): Promise<z.infer<TRes>> {\n endpoint.request.parse(input);\n\n if (this.useMockData && endpoint.mockData) {\n return this.handleMockRequest(endpoint);\n }\n\n if (endpoint.auth && !this.config.token) {\n const error = this.createError({\n message: `Missing token for ${endpoint.path}`,\n status: 401,\n code: \"NO_TOKEN\",\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (endpoint.auth && this.config.token)\n headers[\"Authorization\"] = `Bearer ${this.config.token}`;\n\n const ctx = {\n url: this.config.baseUrl + endpoint.path,\n init: {\n method: endpoint.method,\n headers,\n body: endpoint.method !== \"GET\" ? JSON.stringify(input) : undefined,\n },\n };\n\n const runner = this.middlewares.reduceRight(\n (next, mw) => () => mw.fn(ctx, next, mw.options),\n () => fetch(ctx.url, ctx.init)\n );\n\n try {\n const res = await runner();\n const json = await res.json();\n\n let responseData = json;\n if (this.responseWrapper) {\n const wrappedSchema = this.responseWrapper(endpoint.response);\n const parsedResponse = wrappedSchema.parse(json);\n\n if (parsedResponse.success === false) {\n const error = this.createError({\n message: parsedResponse.message || \"Request failed\",\n status: parsedResponse.code || res.status,\n code: `API_ERROR_${parsedResponse.code}`,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n responseData = parsedResponse.data;\n }\n\n if (!res.ok) {\n const error = this.createError({\n message: json.message || res.statusText,\n status: res.status,\n code: json.code,\n title: json.title,\n detail: json.detail,\n errors: json.errors,\n });\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n\n return this.responseTransform(endpoint.response.parse(responseData));\n } catch (err: any) {\n const error = this.normalizeError(err);\n this.errorHandler?.(error as unknown as E);\n throw error;\n }\n }\n\n private async handleMockRequest<\n TReq extends z.ZodTypeAny,\n TRes extends z.ZodTypeAny\n >(endpoint: EndpointDef<TReq, TRes>): Promise<z.infer<TRes>> {\n const delay = this.getRandomDelay();\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n let mockData: z.infer<TRes>;\n if (typeof endpoint.mockData === \"function\") {\n mockData = (endpoint.mockData as () => z.infer<TRes>)();\n } else {\n mockData = endpoint.mockData as z.infer<TRes>;\n }\n\n if (this.responseWrapper) {\n const wrappedSchema = this.responseWrapper(endpoint.response);\n\n const mockWrappedResponse = {\n success: true,\n data: mockData,\n timestamp: new Date().toISOString(),\n requestId: `mock-${Math.random().toString(36).substr(2, 9)}`,\n };\n\n const parsedWrappedResponse = wrappedSchema.parse(mockWrappedResponse);\n\n return this.responseTransform(\n endpoint.response.parse(parsedWrappedResponse.data)\n );\n }\n\n return this.responseTransform(endpoint.response.parse(mockData));\n }\n\n private getRandomDelay(): number {\n return (\n Math.floor(\n Math.random() * (this.mockDelay.max - this.mockDelay.min + 1)\n ) + this.mockDelay.min\n );\n }\n\n private createError(error: Partial<RichError> & { message: string }) {\n return new RichError(error);\n }\n\n private normalizeError(err: any) {\n if (err instanceof RichError) return err;\n return this.createError({ message: err.message || \"Unknown error\" });\n }\n}\n","import { Middleware } from \"@/types\";\n\nexport type LoggingOptions = {\n logRequest?: boolean;\n logResponse?: boolean;\n debug?: boolean;\n};\n\nexport const loggingMiddleware: Middleware<LoggingOptions> = async (\n ctx,\n next,\n options\n) => {\n const { logRequest = true, logResponse = true, debug = true } = options || {};\n\n if (debug && logRequest) console.log(\"➡️ Request:\", ctx.url, ctx.init);\n\n const res = await next();\n\n if (debug && logResponse) console.log(\"⬅️ Response:\", res.status);\n\n return res;\n};\n","import { Middleware } from \"@/types\";\n\n\nexport type RetryOptions = {\n maxRetries?: number;\n delay?: number; // ms\n};\n\nexport const retryMiddleware = (options?: RetryOptions): Middleware => {\n const { maxRetries = 3, delay = 500 } = options || {};\n\n const middleware: Middleware = async (ctx, next) => {\n let attempt = 0;\n while (true) {\n try {\n return await next();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n attempt++;\n await new Promise((r) => setTimeout(r, delay * 2 ** attempt));\n }\n }\n };\n\n return middleware;\n};\n","import { Middleware } from \"@/types\";\n\nexport type AuthOptions = {\n refreshToken?: () => Promise<string>;\n};\n\nexport const authMiddleware: Middleware<AuthOptions> = async (\n ctx,\n next,\n options\n) => {\n if (options?.refreshToken) {\n try {\n const newToken = await options.refreshToken();\n ctx.init.headers = {\n ...ctx.init.headers,\n Authorization: `Bearer ${newToken}`,\n };\n } catch {}\n }\n\n return next();\n};\n","import { MiddlewareContext, MiddlewareNext } from \"@/types\";\n\nexport type CacheOptions = { ttl?: number };\n\nexport const cacheMiddleware = (options: CacheOptions = {}) => {\n const { ttl = 60000 } = options;\n const cache = new Map<string, { data: any; expires: number }>();\n\n return async (ctx: MiddlewareContext, next: MiddlewareNext) => {\n if (ctx.init.method === \"GET\") {\n const cached = cache.get(ctx.url);\n const now = Date.now();\n if (cached && cached.expires > now)\n return new Response(JSON.stringify(cached.data));\n\n const res = await next();\n const data = await res\n .clone()\n .json()\n .catch(() => null);\n if (data) cache.set(ctx.url, { data, expires: now + ttl });\n return res;\n }\n return next();\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUO,IAAM,YAAN,cAAwB,MAA2B;AAAA,EAOxD,YAAY,OAAiD;AAC3D,UAAM,MAAM,OAAO;AACnB,WAAO,OAAO,MAAM,KAAK;AAAA,EAC3B;AACF;AAEO,IAAM,YAAN,MAAsE;AAAA,EAY3E,YACU,QAMA,WACR;AAPQ;AAMA;AAlBV,SAAQ,cAAwD,CAAC;AAEjE,SAAQ,oBAAwC,CAAC,MAAM;AACvD,SAAQ,cAAuB;AAC/B,SAAQ,YAA0C,EAAE,KAAK,KAAK,KAAK,IAAK;AAgBtE,SAAK,cAAc,OAAO,eAAe;AACzC,SAAK,YAAY,OAAO,aAAa,EAAE,KAAK,KAAK,KAAK,IAAK;AAAA,EAC7D;AAAA,EAEA,OAAO;AACL,UAAM,UAAU,CAAC;AAIjB,eAAW,cAAc,KAAK,WAAW;AACvC,YAAM,SAAS,KAAK,UAAU,UAAU;AACxC,MAAC,QAAgB,UAAU,IAAI,CAAC;AAEhC,iBAAW,gBAAgB,QAAQ;AACjC,cAAM,WAAW,OAAO,YAAY;AAEpC,QAAC,QAAgB,UAAU,EAAE,YAAY,IAAI,CAC3C,UACG,KAAK,QAAQ,UAAU,KAAK;AAAA,MACnC;AAAA,IACF;AAEA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAI,UAAU;AACZ,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAO,YAA2B,SAAa;AAC7C,SAAK,YAAY,KAAK,EAAE,IAAI,YAAY,QAAQ,CAAC;AAAA,EACnD;AAAA,EAEA,QAAQ,SAA6B;AACnC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,qBAAqB,IAAwB;AAC3C,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,YAAY,SAAkB,OAAsC;AAClE,SAAK,cAAc;AACnB,QAAI,OAAO;AACT,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,mBAAmB,SAA0D;AAC3E,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEc,QACZ,UACA,OACwB;AAAA;AAnG5B;AAoGI,eAAS,QAAQ,MAAM,KAAK;AAE5B,UAAI,KAAK,eAAe,SAAS,UAAU;AACzC,eAAO,KAAK,kBAAkB,QAAQ;AAAA,MACxC;AAEA,UAAI,SAAS,QAAQ,CAAC,KAAK,OAAO,OAAO;AACvC,cAAM,QAAQ,KAAK,YAAY;AAAA,UAC7B,SAAS,qBAAqB,SAAS,IAAI;AAAA,UAC3C,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AACD,mBAAK,iBAAL,8BAAoB;AACpB,cAAM;AAAA,MACR;AAEA,YAAM,UAAuB,EAAE,gBAAgB,mBAAmB;AAClE,UAAI,SAAS,QAAQ,KAAK,OAAO;AAC/B,gBAAQ,eAAe,IAAI,UAAU,KAAK,OAAO,KAAK;AAExD,YAAM,MAAM;AAAA,QACV,KAAK,KAAK,OAAO,UAAU,SAAS;AAAA,QACpC,MAAM;AAAA,UACJ,QAAQ,SAAS;AAAA,UACjB;AAAA,UACA,MAAM,SAAS,WAAW,QAAQ,KAAK,UAAU,KAAK,IAAI;AAAA,QAC5D;AAAA,MACF;AAEA,YAAM,SAAS,KAAK,YAAY;AAAA,QAC9B,CAAC,MAAM,OAAO,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,OAAO;AAAA,QAC/C,MAAM,MAAM,IAAI,KAAK,IAAI,IAAI;AAAA,MAC/B;AAEA,UAAI;AACF,cAAM,MAAM,MAAM,OAAO;AACzB,cAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,YAAI,eAAe;AACnB,YAAI,KAAK,iBAAiB;AACxB,gBAAM,gBAAgB,KAAK,gBAAgB,SAAS,QAAQ;AAC5D,gBAAM,iBAAiB,cAAc,MAAM,IAAI;AAE/C,cAAI,eAAe,YAAY,OAAO;AACpC,kBAAM,QAAQ,KAAK,YAAY;AAAA,cAC7B,SAAS,eAAe,WAAW;AAAA,cACnC,QAAQ,eAAe,QAAQ,IAAI;AAAA,cACnC,MAAM,aAAa,eAAe,IAAI;AAAA,YACxC,CAAC;AACD,uBAAK,iBAAL,8BAAoB;AACpB,kBAAM;AAAA,UACR;AAEA,yBAAe,eAAe;AAAA,QAChC;AAEA,YAAI,CAAC,IAAI,IAAI;AACX,gBAAM,QAAQ,KAAK,YAAY;AAAA,YAC7B,SAAS,KAAK,WAAW,IAAI;AAAA,YAC7B,QAAQ,IAAI;AAAA,YACZ,MAAM,KAAK;AAAA,YACX,OAAO,KAAK;AAAA,YACZ,QAAQ,KAAK;AAAA,YACb,QAAQ,KAAK;AAAA,UACf,CAAC;AACD,qBAAK,iBAAL,8BAAoB;AACpB,gBAAM;AAAA,QACR;AAEA,eAAO,KAAK,kBAAkB,SAAS,SAAS,MAAM,YAAY,CAAC;AAAA,MACrE,SAAS,KAAU;AACjB,cAAM,QAAQ,KAAK,eAAe,GAAG;AACrC,mBAAK,iBAAL,8BAAoB;AACpB,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA,EAEc,kBAGZ,UAA2D;AAAA;AAC3D,YAAM,QAAQ,KAAK,eAAe;AAClC,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAEzD,UAAI;AACJ,UAAI,OAAO,SAAS,aAAa,YAAY;AAC3C,mBAAY,SAAS,SAAiC;AAAA,MACxD,OAAO;AACL,mBAAW,SAAS;AAAA,MACtB;AAEA,UAAI,KAAK,iBAAiB;AACxB,cAAM,gBAAgB,KAAK,gBAAgB,SAAS,QAAQ;AAE5D,cAAM,sBAAsB;AAAA,UAC1B,SAAS;AAAA,UACT,MAAM;AAAA,UACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,WAAW,QAAQ,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,QAC5D;AAEA,cAAM,wBAAwB,cAAc,MAAM,mBAAmB;AAErE,eAAO,KAAK;AAAA,UACV,SAAS,SAAS,MAAM,sBAAsB,IAAI;AAAA,QACpD;AAAA,MACF;AAEA,aAAO,KAAK,kBAAkB,SAAS,SAAS,MAAM,QAAQ,CAAC;AAAA,IACjE;AAAA;AAAA,EAEQ,iBAAyB;AAC/B,WACE,KAAK;AAAA,MACH,KAAK,OAAO,KAAK,KAAK,UAAU,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7D,IAAI,KAAK,UAAU;AAAA,EAEvB;AAAA,EAEQ,YAAY,OAAiD;AACnE,WAAO,IAAI,UAAU,KAAK;AAAA,EAC5B;AAAA,EAEQ,eAAe,KAAU;AAC/B,QAAI,eAAe,UAAW,QAAO;AACrC,WAAO,KAAK,YAAY,EAAE,SAAS,IAAI,WAAW,gBAAgB,CAAC;AAAA,EACrE;AACF;;;AC3NO,IAAM,oBAAgD,CAC3D,KACA,MACA,YACG;AACH,QAAM,EAAE,aAAa,MAAM,cAAc,MAAM,QAAQ,KAAK,IAAI,WAAW,CAAC;AAE5E,MAAI,SAAS,WAAY,SAAQ,IAAI,yBAAe,IAAI,KAAK,IAAI,IAAI;AAErE,QAAM,MAAM,MAAM,KAAK;AAEvB,MAAI,SAAS,YAAa,SAAQ,IAAI,0BAAgB,IAAI,MAAM;AAEhE,SAAO;AACT;;;ACdO,IAAM,kBAAkB,CAAC,YAAuC;AACrE,QAAM,EAAE,aAAa,GAAG,QAAQ,IAAI,IAAI,WAAW,CAAC;AAEpD,QAAM,aAAyB,CAAO,KAAK,SAAS;AAClD,QAAI,UAAU;AACd,WAAO,MAAM;AACX,UAAI;AACF,eAAO,MAAM,KAAK;AAAA,MACpB,SAAS,KAAK;AACZ,YAAI,WAAW,WAAY,OAAM;AACjC;AACA,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,SAAK,QAAO,CAAC;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ACnBO,IAAM,iBAA0C,CACrD,KACA,MACA,YACG;AACH,MAAI,mCAAS,cAAc;AACzB,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,aAAa;AAC5C,UAAI,KAAK,UAAU,iCACd,IAAI,KAAK,UADK;AAAA,QAEjB,eAAe,UAAU,QAAQ;AAAA,MACnC;AAAA,IACF,SAAQ;AAAA,IAAC;AAAA,EACX;AAEA,SAAO,KAAK;AACd;;;AClBO,IAAM,kBAAkB,CAAC,UAAwB,CAAC,MAAM;AAC7D,QAAM,EAAE,MAAM,IAAM,IAAI;AACxB,QAAM,QAAQ,oBAAI,IAA4C;AAE9D,SAAO,CAAO,KAAwB,SAAyB;AAC7D,QAAI,IAAI,KAAK,WAAW,OAAO;AAC7B,YAAM,SAAS,MAAM,IAAI,IAAI,GAAG;AAChC,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,UAAU,OAAO,UAAU;AAC7B,eAAO,IAAI,SAAS,KAAK,UAAU,OAAO,IAAI,CAAC;AAEjD,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,OAAO,MAAM,IAChB,MAAM,EACN,KAAK,EACL,MAAM,MAAM,IAAI;AACnB,UAAI,KAAM,OAAM,IAAI,IAAI,KAAK,EAAE,MAAM,SAAS,MAAM,IAAI,CAAC;AACzD,aAAO;AAAA,IACT;AACA,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tahanabavi/typefetch",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "A fully type-safe, extensible API client for TypeScript projects, featuring global error handling, configurable middleware, automatic retries, auth refresh, response transforms, and seamless contract integration. Designed for large-scale applications and developer-friendly API interactions.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",