@next-feature/client 0.1.1 → 0.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/components/api-error-boundary.d.ts +18 -0
- package/hooks/use-api-error.d.ts +10 -0
- package/{src/index.ts → index.d.ts} +6 -6
- package/index.js +4844 -0
- package/lib/client.d.ts +72 -0
- package/lib/config/index.d.ts +20 -0
- package/lib/error.d.ts +35 -0
- package/lib/types/client.d.ts +24 -0
- package/lib/types/index.d.ts +27 -0
- package/lib/utils/axios.d.ts +7 -0
- package/lib/utils/error.d.ts +19 -0
- package/lib/utils/zod.d.ts +10 -0
- package/package.json +1 -1
- package/server.d.ts +0 -0
- package/server.js +1 -0
- package/.babelrc +0 -12
- package/eslint.config.mjs +0 -12
- package/jest.config.cts +0 -10
- package/project.json +0 -32
- package/src/components/api-error-boundary.tsx +0 -58
- package/src/hooks/use-api-error.tsx +0 -39
- package/src/lib/client.ts +0 -431
- package/src/lib/error.ts +0 -169
- package/src/lib/types/index.ts +0 -13
- package/src/lib/utils/error.ts +0 -136
- package/src/lib/utils/helper.ts +0 -20
- package/tsconfig.json +0 -20
- package/tsconfig.lib.json +0 -29
- package/tsconfig.spec.json +0 -23
- package/vite.config.ts +0 -51
package/src/lib/client.ts
DELETED
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import axios, {
|
|
2
|
-
AxiosError,
|
|
3
|
-
AxiosInstance,
|
|
4
|
-
AxiosRequestConfig,
|
|
5
|
-
AxiosResponse,
|
|
6
|
-
InternalAxiosRequestConfig,
|
|
7
|
-
} from 'axios';
|
|
8
|
-
import { ApiError, ProblemDetail } from './error';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Configuration options for the API client
|
|
12
|
-
*/
|
|
13
|
-
export interface ApiClientConfig {
|
|
14
|
-
baseURL: string;
|
|
15
|
-
timeout?: number;
|
|
16
|
-
enableRefreshToken?: boolean;
|
|
17
|
-
maxRetries?: number;
|
|
18
|
-
retryDelay?: number;
|
|
19
|
-
onAuthenticated?: (
|
|
20
|
-
config: InternalAxiosRequestConfig
|
|
21
|
-
) => void | Promise<void>;
|
|
22
|
-
onUnauthorized?: () => void | Promise<void>;
|
|
23
|
-
onRefreshTokenExpired?: () => void | Promise<void>;
|
|
24
|
-
onRefreshToken?: () => string | Promise<string>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Pending request queue item
|
|
29
|
-
*/
|
|
30
|
-
interface PendingRequest {
|
|
31
|
-
resolve: (token: string) => void;
|
|
32
|
-
reject: (error: any) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Axios wrapper with JWT/Refresh token handling
|
|
37
|
-
*/
|
|
38
|
-
export class ApiClient {
|
|
39
|
-
private readonly instance: AxiosInstance;
|
|
40
|
-
private isRefreshing = false;
|
|
41
|
-
private pendingRequests: PendingRequest[] = [];
|
|
42
|
-
private config: Required<ApiClientConfig>;
|
|
43
|
-
|
|
44
|
-
constructor(config: ApiClientConfig) {
|
|
45
|
-
this.config = {
|
|
46
|
-
timeout: 30000,
|
|
47
|
-
enableRefreshToken: true,
|
|
48
|
-
maxRetries: 3,
|
|
49
|
-
retryDelay: 1000,
|
|
50
|
-
onUnauthorized: async () => {
|
|
51
|
-
// Redirect to login page
|
|
52
|
-
if (typeof window !== 'undefined') {
|
|
53
|
-
window.location.href = '/login';
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
onRefreshTokenExpired: async () => {
|
|
57
|
-
console.error('Session expired. Please login again.');
|
|
58
|
-
if (typeof window !== 'undefined') {
|
|
59
|
-
window.location.href = '/login?expired=true';
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
onAuthenticated: async (config) => {
|
|
63
|
-
console.log('Authenticated', config.data);
|
|
64
|
-
},
|
|
65
|
-
onRefreshToken: async () => {
|
|
66
|
-
return '';
|
|
67
|
-
},
|
|
68
|
-
...config,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
this.instance = axios.create({
|
|
72
|
-
baseURL: this.config.baseURL,
|
|
73
|
-
timeout: this.config.timeout,
|
|
74
|
-
headers: {
|
|
75
|
-
'Content-Type': 'application/json',
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
this.setupInterceptors();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Setup request and response interceptors
|
|
84
|
-
*/
|
|
85
|
-
private setupInterceptors(): void {
|
|
86
|
-
// Request interceptor
|
|
87
|
-
this.instance.interceptors.request.use(
|
|
88
|
-
this.handleRequestFulfilled.bind(this),
|
|
89
|
-
this.handleRequestRejected.bind(this)
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// Response interceptor
|
|
93
|
-
this.instance.interceptors.response.use(
|
|
94
|
-
this.handleResponseFulfilled.bind(this),
|
|
95
|
-
this.handleResponseRejected.bind(this)
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Attach JWT token to request headers
|
|
101
|
-
*/
|
|
102
|
-
private async handleRequestFulfilled(
|
|
103
|
-
config: InternalAxiosRequestConfig
|
|
104
|
-
): Promise<InternalAxiosRequestConfig> {
|
|
105
|
-
try {
|
|
106
|
-
if (this.config.onAuthenticated) {
|
|
107
|
-
this.config.onAuthenticated(config);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return config;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error('Error attaching token to request:', error);
|
|
113
|
-
return config;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Handle request errors
|
|
119
|
-
*/
|
|
120
|
-
private handleRequestRejected(error: any): Promise<never> {
|
|
121
|
-
console.error('Request configuration error:', error);
|
|
122
|
-
return Promise.reject(error);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Pass through successful responses
|
|
127
|
-
*/
|
|
128
|
-
private handleResponseFulfilled(response: AxiosResponse): AxiosResponse {
|
|
129
|
-
return response;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Handle response errors with retry logic and token refresh
|
|
134
|
-
*/
|
|
135
|
-
private async handleResponseRejected(error: AxiosError): Promise<any> {
|
|
136
|
-
const originalRequest = error.config as InternalAxiosRequestConfig & {
|
|
137
|
-
_retry?: boolean;
|
|
138
|
-
_retryCount?: number;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
if (!originalRequest) {
|
|
142
|
-
return Promise.reject(this.createApiError(error));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Handle 401 Unauthorized - attempt token refresh
|
|
146
|
-
if (error.response?.status === 401 && this.config.enableRefreshToken) {
|
|
147
|
-
return this.handleUnauthorizedError(error, originalRequest);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Handle network errors and 5xx errors with retry logic
|
|
151
|
-
if (this.shouldRetry(error, originalRequest)) {
|
|
152
|
-
return this.retryRequest(originalRequest);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Create and reject with custom ApiError
|
|
156
|
-
return Promise.reject(this.createApiError(error));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Handle 401 errors with token refresh
|
|
161
|
-
*/
|
|
162
|
-
private async handleUnauthorizedError(
|
|
163
|
-
error: AxiosError,
|
|
164
|
-
originalRequest: InternalAxiosRequestConfig & { _retry?: boolean }
|
|
165
|
-
): Promise<any> {
|
|
166
|
-
// Prevent infinite loops
|
|
167
|
-
if (originalRequest._retry) {
|
|
168
|
-
if (this.config.onRefreshTokenExpired) {
|
|
169
|
-
await this.config.onRefreshTokenExpired();
|
|
170
|
-
}
|
|
171
|
-
return Promise.reject(this.createApiError(error));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
originalRequest._retry = true;
|
|
175
|
-
|
|
176
|
-
// If already refreshing, queue the request
|
|
177
|
-
if (this.isRefreshing) {
|
|
178
|
-
return new Promise((resolve, reject) => {
|
|
179
|
-
this.pendingRequests.push({ resolve, reject });
|
|
180
|
-
})
|
|
181
|
-
.then((token) => {
|
|
182
|
-
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
183
|
-
return this.instance(originalRequest);
|
|
184
|
-
})
|
|
185
|
-
.catch((err) => {
|
|
186
|
-
return Promise.reject(err);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
this.isRefreshing = true;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const newToken = await this.refreshToken();
|
|
194
|
-
|
|
195
|
-
// Update the original request with new token
|
|
196
|
-
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
|
197
|
-
|
|
198
|
-
// Resolve all pending requests with new token
|
|
199
|
-
this.processPendingRequests(null, newToken);
|
|
200
|
-
|
|
201
|
-
// Retry the original request
|
|
202
|
-
return this.instance(originalRequest);
|
|
203
|
-
} catch (refreshError) {
|
|
204
|
-
// Reject all pending requests
|
|
205
|
-
this.processPendingRequests(refreshError, null);
|
|
206
|
-
|
|
207
|
-
if (this.config.onUnauthorized) {
|
|
208
|
-
await this.config.onUnauthorized();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return Promise.reject(this.createApiError(error));
|
|
212
|
-
} finally {
|
|
213
|
-
this.isRefreshing = false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Refresh the JWT token using the refresh token
|
|
219
|
-
*/
|
|
220
|
-
private async refreshToken(): Promise<string> {
|
|
221
|
-
try {
|
|
222
|
-
// const session = await auth();
|
|
223
|
-
//
|
|
224
|
-
// if (!session?.user || !('refreshToken' in session.user)) {
|
|
225
|
-
// throw new Error('No refresh token available');
|
|
226
|
-
// }
|
|
227
|
-
//
|
|
228
|
-
// const refreshToken = session.user.refreshToken as string;
|
|
229
|
-
const refreshToken = this.config.onRefreshToken();
|
|
230
|
-
|
|
231
|
-
// Call your refresh token endpoint
|
|
232
|
-
const response = await axios.post<{ jwtToken: string }>(
|
|
233
|
-
`${this.config.baseURL}/auth/refresh`,
|
|
234
|
-
{ refreshToken },
|
|
235
|
-
{
|
|
236
|
-
headers: {
|
|
237
|
-
'Content-Type': 'application/json',
|
|
238
|
-
},
|
|
239
|
-
}
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
const newToken = response.data.jwtToken;
|
|
243
|
-
|
|
244
|
-
// Update the session with the new token
|
|
245
|
-
// Note: You'll need to implement this based on your auth setup
|
|
246
|
-
// This might involve updating cookies or calling an API route
|
|
247
|
-
await this.updateSession(newToken);
|
|
248
|
-
|
|
249
|
-
return newToken;
|
|
250
|
-
} catch (error) {
|
|
251
|
-
console.error('Token refresh failed:', error);
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Update session with new token
|
|
258
|
-
* Implement this based on your Next.js auth setup
|
|
259
|
-
*/
|
|
260
|
-
private async updateSession(newToken: string): Promise<void> {
|
|
261
|
-
// Example implementation - adjust based on your auth setup
|
|
262
|
-
try {
|
|
263
|
-
await fetch('/api/auth/update-token', {
|
|
264
|
-
method: 'POST',
|
|
265
|
-
headers: {
|
|
266
|
-
'Content-Type': 'application/json',
|
|
267
|
-
},
|
|
268
|
-
body: JSON.stringify({ jwtToken: newToken }),
|
|
269
|
-
});
|
|
270
|
-
} catch (error) {
|
|
271
|
-
console.error('Failed to update session:', error);
|
|
272
|
-
throw error;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Process all pending requests after token refresh
|
|
278
|
-
*/
|
|
279
|
-
private processPendingRequests(error: any, token: string | null): void {
|
|
280
|
-
this.pendingRequests.forEach((request) => {
|
|
281
|
-
if (error) {
|
|
282
|
-
request.reject(error);
|
|
283
|
-
} else if (token) {
|
|
284
|
-
request.resolve(token);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
this.pendingRequests = [];
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Determine if request should be retried
|
|
293
|
-
*/
|
|
294
|
-
private shouldRetry(
|
|
295
|
-
error: AxiosError,
|
|
296
|
-
config: InternalAxiosRequestConfig & { _retryCount?: number }
|
|
297
|
-
): boolean {
|
|
298
|
-
const retryCount = config._retryCount || 0;
|
|
299
|
-
|
|
300
|
-
// Don't retry if max retries exceeded
|
|
301
|
-
if (retryCount >= this.config.maxRetries) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Retry on network errors
|
|
306
|
-
if (!error.response) {
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Retry on 5xx server errors (except 501)
|
|
311
|
-
const status = error.response.status;
|
|
312
|
-
if (status >= 500 && status !== 501) {
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Retry on 429 (Too Many Requests)
|
|
317
|
-
if (status === 429) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Retry failed request with exponential backoff
|
|
326
|
-
*/
|
|
327
|
-
private async retryRequest(
|
|
328
|
-
config: InternalAxiosRequestConfig & { _retryCount?: number }
|
|
329
|
-
): Promise<any> {
|
|
330
|
-
config._retryCount = (config._retryCount || 0) + 1;
|
|
331
|
-
|
|
332
|
-
const delay = this.config.retryDelay * Math.pow(2, config._retryCount - 1);
|
|
333
|
-
|
|
334
|
-
await this.sleep(delay);
|
|
335
|
-
|
|
336
|
-
console.log(
|
|
337
|
-
`Retrying request (attempt ${config._retryCount}):`,
|
|
338
|
-
config.url
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
return this.instance(config);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Sleep helper for retry delays
|
|
346
|
-
*/
|
|
347
|
-
private sleep(ms: number): Promise<void> {
|
|
348
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Create ApiError from AxiosError
|
|
353
|
-
*/
|
|
354
|
-
private createApiError(error: AxiosError): ApiError {
|
|
355
|
-
const status = error.response?.status || 0;
|
|
356
|
-
const problemDetail = this.extractProblemDetail(error);
|
|
357
|
-
|
|
358
|
-
return new ApiError(status, problemDetail, error);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Extract ProblemDetail from error response
|
|
363
|
-
*/
|
|
364
|
-
private extractProblemDetail(error: AxiosError): ProblemDetail | null {
|
|
365
|
-
if (!error.response?.data) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const data = error.response.data;
|
|
370
|
-
|
|
371
|
-
// Check if response matches ProblemDetail structure
|
|
372
|
-
if (
|
|
373
|
-
typeof data === 'object' &&
|
|
374
|
-
'type' in data &&
|
|
375
|
-
'title' in data &&
|
|
376
|
-
'status' in data
|
|
377
|
-
) {
|
|
378
|
-
return data as ProblemDetail;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* HTTP Methods with proper typing
|
|
386
|
-
*/
|
|
387
|
-
|
|
388
|
-
async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
389
|
-
const response = await this.instance.get<T>(url, config);
|
|
390
|
-
return response.data;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async post<T = any>(
|
|
394
|
-
url: string,
|
|
395
|
-
data?: any,
|
|
396
|
-
config?: AxiosRequestConfig
|
|
397
|
-
): Promise<T> {
|
|
398
|
-
const response = await this.instance.post<T>(url, data, config);
|
|
399
|
-
return response.data;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async put<T = any>(
|
|
403
|
-
url: string,
|
|
404
|
-
data?: any,
|
|
405
|
-
config?: AxiosRequestConfig
|
|
406
|
-
): Promise<T> {
|
|
407
|
-
const response = await this.instance.put<T>(url, data, config);
|
|
408
|
-
return response.data;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async patch<T = any>(
|
|
412
|
-
url: string,
|
|
413
|
-
data?: any,
|
|
414
|
-
config?: AxiosRequestConfig
|
|
415
|
-
): Promise<T> {
|
|
416
|
-
const response = await this.instance.patch<T>(url, data, config);
|
|
417
|
-
return response.data;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
421
|
-
const response = await this.instance.delete<T>(url, config);
|
|
422
|
-
return response.data;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Get the underlying Axios instance for advanced usage
|
|
427
|
-
*/
|
|
428
|
-
getAxiosInstance(): AxiosInstance {
|
|
429
|
-
return this.instance;
|
|
430
|
-
}
|
|
431
|
-
}
|
package/src/lib/error.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { AxiosError, HttpStatusCode } from 'axios';
|
|
2
|
-
import { ZodError } from 'zod';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Spring Boot ProblemDetail structure
|
|
6
|
-
*/
|
|
7
|
-
export interface ProblemDetail {
|
|
8
|
-
type: string;
|
|
9
|
-
title: string;
|
|
10
|
-
status: HttpStatusCode;
|
|
11
|
-
detail?: string;
|
|
12
|
-
instance?: string;
|
|
13
|
-
errors?: Record<string, unknown>;
|
|
14
|
-
[key: string]: unknown;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Custom error class for API errors
|
|
19
|
-
*/
|
|
20
|
-
export class ApiError extends Error {
|
|
21
|
-
constructor(
|
|
22
|
-
public status: HttpStatusCode,
|
|
23
|
-
public problemDetail: ProblemDetail | null,
|
|
24
|
-
public originalError?: Error,
|
|
25
|
-
message?: string
|
|
26
|
-
) {
|
|
27
|
-
super(
|
|
28
|
-
message ||
|
|
29
|
-
problemDetail?.detail ||
|
|
30
|
-
problemDetail?.title ||
|
|
31
|
-
originalError?.message ||
|
|
32
|
-
'An error occurred'
|
|
33
|
-
);
|
|
34
|
-
this.name = 'ApiError';
|
|
35
|
-
Object.setPrototypeOf(this, ApiError.prototype);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get isClientError(): boolean {
|
|
39
|
-
return this.status >= 400 && this.status < 500;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get isServerError(): boolean {
|
|
43
|
-
return this.status >= 500;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get isUnauthorized(): boolean {
|
|
47
|
-
return this.status === 401;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get isForbidden(): boolean {
|
|
51
|
-
return this.status === 403;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get isNotFound(): boolean {
|
|
55
|
-
return this.status === 404;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static builder<T>(): ApiErrorBuilder {
|
|
59
|
-
return new ApiErrorBuilder<T>();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
static of(error: Error): ApiError {
|
|
63
|
-
if (error instanceof ApiError) {
|
|
64
|
-
return error;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return ApiError.builder().originalError(error).build();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create ApiError from Zod validation error
|
|
72
|
-
*/
|
|
73
|
-
static fromZodError(zodError: ZodError) {
|
|
74
|
-
const errors: Record<string, string> = {};
|
|
75
|
-
|
|
76
|
-
zodError.errors.forEach((error) => {
|
|
77
|
-
error.path.forEach((path) => {
|
|
78
|
-
errors[path] = error.message;
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return ApiError.builder()
|
|
83
|
-
.originalError(zodError)
|
|
84
|
-
.status(HttpStatusCode.BadRequest)
|
|
85
|
-
.message('Validation error')
|
|
86
|
-
.detail('errors', errors)
|
|
87
|
-
.build();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export class ApiErrorBuilder<
|
|
92
|
-
AdditionalProblemDetails = Record<string, unknown>
|
|
93
|
-
> {
|
|
94
|
-
private _problemDetail: ProblemDetail;
|
|
95
|
-
private _status: HttpStatusCode;
|
|
96
|
-
private _originalError: Error;
|
|
97
|
-
private _message: string;
|
|
98
|
-
|
|
99
|
-
constructor() {
|
|
100
|
-
this._status = HttpStatusCode.InternalServerError;
|
|
101
|
-
this._problemDetail = {
|
|
102
|
-
status: this._status,
|
|
103
|
-
title: '',
|
|
104
|
-
type: 'about:blank',
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Set standard ProblemDetail fields
|
|
110
|
-
*/
|
|
111
|
-
detail<K extends keyof (ProblemDetail & AdditionalProblemDetails)>(
|
|
112
|
-
key: K,
|
|
113
|
-
value: K extends keyof ProblemDetail
|
|
114
|
-
? ProblemDetail[K]
|
|
115
|
-
: K extends keyof AdditionalProblemDetails
|
|
116
|
-
? AdditionalProblemDetails[K]
|
|
117
|
-
: unknown
|
|
118
|
-
): ApiErrorBuilder<AdditionalProblemDetails> {
|
|
119
|
-
(this._problemDetail as any)[key] = value;
|
|
120
|
-
return this;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Set standard ProblemDetail fields
|
|
125
|
-
*/
|
|
126
|
-
problemDetail(
|
|
127
|
-
problemDetail: ProblemDetail
|
|
128
|
-
): ApiErrorBuilder<AdditionalProblemDetails> {
|
|
129
|
-
if (problemDetail) {
|
|
130
|
-
this._problemDetail = problemDetail;
|
|
131
|
-
}
|
|
132
|
-
return this;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
originalError(error: Error): ApiErrorBuilder<AdditionalProblemDetails> {
|
|
136
|
-
this._originalError = error;
|
|
137
|
-
this._problemDetail.title = error.name;
|
|
138
|
-
|
|
139
|
-
if (error instanceof AxiosError) {
|
|
140
|
-
this.status(error.status);
|
|
141
|
-
}
|
|
142
|
-
if (error instanceof ZodError) {
|
|
143
|
-
this.status(HttpStatusCode.BadRequest);
|
|
144
|
-
this.message('Validation error');
|
|
145
|
-
}
|
|
146
|
-
return this;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
status(status: HttpStatusCode): ApiErrorBuilder<AdditionalProblemDetails> {
|
|
150
|
-
this._status = status;
|
|
151
|
-
this._problemDetail.status = status;
|
|
152
|
-
return this;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
message(msg: string): ApiErrorBuilder<AdditionalProblemDetails> {
|
|
156
|
-
this._message = msg;
|
|
157
|
-
this._problemDetail.detail = msg;
|
|
158
|
-
return this;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
build(): ApiError {
|
|
162
|
-
return new ApiError(
|
|
163
|
-
this._status,
|
|
164
|
-
this._problemDetail,
|
|
165
|
-
this._originalError,
|
|
166
|
-
this._message
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
package/src/lib/types/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ProblemDetail } from '../error';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* [api-response]
|
|
5
|
-
* next-feature@0.0.11-beta
|
|
6
|
-
* November 4th 2025, 6:37:27 pm
|
|
7
|
-
*/
|
|
8
|
-
export interface ApiResponse<Response> {
|
|
9
|
-
success?: boolean;
|
|
10
|
-
message?: string;
|
|
11
|
-
error?: ProblemDetail;
|
|
12
|
-
data: Response;
|
|
13
|
-
}
|