wynkjs 1.0.3 → 1.0.6
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 +528 -348
- package/dist/cors.d.ts +28 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +121 -0
- package/dist/database.d.ts +1 -1
- package/dist/database.js +1 -1
- package/dist/decorators/exception.advanced.d.ts +286 -18
- package/dist/decorators/exception.advanced.d.ts.map +1 -1
- package/dist/decorators/exception.advanced.js +410 -17
- package/dist/decorators/exception.decorators.d.ts +93 -2
- package/dist/decorators/exception.decorators.d.ts.map +1 -1
- package/dist/decorators/exception.decorators.js +140 -8
- package/dist/decorators/formatter.decorators.d.ts +93 -0
- package/dist/decorators/formatter.decorators.d.ts.map +1 -0
- package/dist/decorators/formatter.decorators.js +131 -0
- package/dist/decorators/guard.decorators.d.ts +2 -2
- package/dist/decorators/guard.decorators.d.ts.map +1 -1
- package/dist/decorators/guard.decorators.js +11 -5
- package/dist/decorators/http.decorators.d.ts +10 -4
- package/dist/decorators/http.decorators.d.ts.map +1 -1
- package/dist/decorators/http.decorators.js +9 -2
- package/dist/decorators/interceptor.advanced.d.ts +9 -9
- package/dist/decorators/interceptor.advanced.d.ts.map +1 -1
- package/dist/decorators/interceptor.advanced.js +7 -7
- package/dist/decorators/interceptor.decorators.d.ts +9 -7
- package/dist/decorators/interceptor.decorators.d.ts.map +1 -1
- package/dist/decorators/interceptor.decorators.js +29 -18
- package/dist/decorators/param.decorators.d.ts +2 -2
- package/dist/decorators/param.decorators.js +1 -1
- package/dist/decorators/pipe.decorators.d.ts +4 -4
- package/dist/decorators/pipe.decorators.d.ts.map +1 -1
- package/dist/decorators/pipe.decorators.js +4 -4
- package/dist/dto.js +1 -1
- package/dist/factory.d.ts +30 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +210 -186
- package/dist/filters/exception.filters.d.ts +124 -0
- package/dist/filters/exception.filters.d.ts.map +1 -0
- package/dist/filters/exception.filters.js +208 -0
- package/dist/global-prefix.d.ts +49 -0
- package/dist/global-prefix.d.ts.map +1 -0
- package/dist/global-prefix.js +155 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/interfaces/interceptor.interface.d.ts +15 -0
- package/dist/interfaces/interceptor.interface.d.ts.map +1 -0
- package/dist/interfaces/interceptor.interface.js +1 -0
- package/dist/optimized-handler.d.ts +31 -0
- package/dist/optimized-handler.d.ts.map +1 -0
- package/dist/optimized-handler.js +180 -0
- package/dist/pipes/validation.pipe.d.ts +12 -12
- package/dist/pipes/validation.pipe.d.ts.map +1 -1
- package/dist/pipes/validation.pipe.js +43 -15
- package/dist/schema-registry.d.ts +51 -0
- package/dist/schema-registry.d.ts.map +1 -0
- package/dist/schema-registry.js +134 -0
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/ultra-optimized-handler.d.ts +51 -0
- package/dist/ultra-optimized-handler.d.ts.map +1 -0
- package/dist/ultra-optimized-handler.js +302 -0
- package/package.json +17 -10
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
+
import { HttpException, } from "./exception.decorators";
|
|
2
3
|
/**
|
|
3
4
|
* FormatErrorFormatter - Formats as { field: [messages] } like NestJS
|
|
5
|
+
*
|
|
6
|
+
* Output example:
|
|
7
|
+
* {
|
|
8
|
+
* "statusCode": 400,
|
|
9
|
+
* "message": "Validation failed",
|
|
10
|
+
* "errors": {
|
|
11
|
+
* "email": ["Invalid email address"],
|
|
12
|
+
* "age": ["Must be at least 18"]
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const app = WynkFactory.create({
|
|
18
|
+
* controllers: [UserController],
|
|
19
|
+
* validationErrorFormatter: new FormatErrorFormatter(),
|
|
20
|
+
* });
|
|
4
21
|
*/
|
|
5
22
|
export class FormatErrorFormatter {
|
|
6
23
|
format(error) {
|
|
@@ -27,6 +44,22 @@ export class FormatErrorFormatter {
|
|
|
27
44
|
}
|
|
28
45
|
/**
|
|
29
46
|
* SimpleErrorFormatter - Formats as simple array of messages
|
|
47
|
+
*
|
|
48
|
+
* Output example:
|
|
49
|
+
* {
|
|
50
|
+
* "statusCode": 400,
|
|
51
|
+
* "message": "Validation failed",
|
|
52
|
+
* "errors": [
|
|
53
|
+
* "Invalid email address",
|
|
54
|
+
* "Must be at least 18"
|
|
55
|
+
* ]
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const app = WynkFactory.create({
|
|
60
|
+
* controllers: [UserController],
|
|
61
|
+
* validationErrorFormatter: new SimpleErrorFormatter(),
|
|
62
|
+
* });
|
|
30
63
|
*/
|
|
31
64
|
export class SimpleErrorFormatter {
|
|
32
65
|
format(error) {
|
|
@@ -48,6 +81,26 @@ export class SimpleErrorFormatter {
|
|
|
48
81
|
}
|
|
49
82
|
/**
|
|
50
83
|
* DetailedErrorFormatter - Formats with detailed field info
|
|
84
|
+
*
|
|
85
|
+
* Output example:
|
|
86
|
+
* {
|
|
87
|
+
* "statusCode": 400,
|
|
88
|
+
* "message": "Validation failed",
|
|
89
|
+
* "errors": [
|
|
90
|
+
* {
|
|
91
|
+
* "field": "email",
|
|
92
|
+
* "message": "Invalid email address",
|
|
93
|
+
* "value": "invalid-email",
|
|
94
|
+
* "expected": {...schema...}
|
|
95
|
+
* }
|
|
96
|
+
* ]
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const app = WynkFactory.create({
|
|
101
|
+
* controllers: [UserController],
|
|
102
|
+
* validationErrorFormatter: new DetailedErrorFormatter(),
|
|
103
|
+
* });
|
|
51
104
|
*/
|
|
52
105
|
export class DetailedErrorFormatter {
|
|
53
106
|
format(error) {
|
|
@@ -78,19 +131,25 @@ export class DetailedErrorFormatter {
|
|
|
78
131
|
}
|
|
79
132
|
}
|
|
80
133
|
/**
|
|
81
|
-
* Validation Exception Filter -
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* app.useGlobalFilters(new ValidationExceptionFilter(new FormatErrorFormatter()));
|
|
134
|
+
* Validation Exception Filter - DEPRECATED / NOT RECOMMENDED
|
|
135
|
+
*
|
|
136
|
+
* ⚠️ WARNING: This filter is NOT used by WynkJS for validation errors!
|
|
85
137
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
138
|
+
* Validation errors are handled directly in the factory's onError hook.
|
|
139
|
+
* To format validation errors, use the validationErrorFormatter option:
|
|
88
140
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
141
|
+
* @example
|
|
142
|
+
* // ✅ CORRECT: Use validationErrorFormatter in factory options
|
|
143
|
+
* const app = WynkFactory.create({
|
|
144
|
+
* controllers: [UserController],
|
|
145
|
+
* validationErrorFormatter: new FormatErrorFormatter(), // This works!
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* // ❌ WRONG: This does nothing for validation errors
|
|
149
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new FormatErrorFormatter()));
|
|
91
150
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
151
|
+
* This class is kept for backward compatibility and custom use cases,
|
|
152
|
+
* but it's not part of the standard validation error flow.
|
|
94
153
|
*/
|
|
95
154
|
export class ValidationExceptionFilter {
|
|
96
155
|
formatter = null;
|
|
@@ -142,16 +201,48 @@ export class ValidationExceptionFilter {
|
|
|
142
201
|
}
|
|
143
202
|
}
|
|
144
203
|
/**
|
|
145
|
-
* Database Exception Filter - Handles database errors
|
|
204
|
+
* Database Exception Filter - Handles database errors ONLY (not HttpExceptions)
|
|
205
|
+
*
|
|
206
|
+
* This filter catches actual database errors (like unique constraint violations,
|
|
207
|
+
* foreign key errors, etc.) and converts them to user-friendly messages.
|
|
208
|
+
*
|
|
209
|
+
* It will NOT catch HttpException or its subclasses (ConflictException, etc.)
|
|
210
|
+
* that you throw manually - those will pass through to be handled correctly.
|
|
211
|
+
*
|
|
146
212
|
* @example
|
|
213
|
+
* // Use as global filter
|
|
214
|
+
* app.useGlobalFilters(new DatabaseExceptionFilter());
|
|
215
|
+
*
|
|
216
|
+
* // Use on specific controller
|
|
147
217
|
* @UseFilters(DatabaseExceptionFilter)
|
|
148
218
|
* @Controller('/users')
|
|
149
|
-
* export class UserController {
|
|
219
|
+
* export class UserController {
|
|
220
|
+
* @Post()
|
|
221
|
+
* async create(@Body() data: any) {
|
|
222
|
+
* // If you throw manually, it passes through:
|
|
223
|
+
* if (await this.userExists(data.email)) {
|
|
224
|
+
* throw new ConflictException('User with this email already exists'); // ✅ Works correctly
|
|
225
|
+
* }
|
|
226
|
+
*
|
|
227
|
+
* // If database throws error, filter catches it:
|
|
228
|
+
* return await this.db.insert(users).values(data); // ❌ DB unique constraint error → caught by filter
|
|
229
|
+
* }
|
|
230
|
+
* }
|
|
231
|
+
*
|
|
232
|
+
* Handles these database error codes:
|
|
233
|
+
* - 23505: Unique constraint violation → 409 Conflict
|
|
234
|
+
* - 23503: Foreign key constraint violation → 400 Bad Request
|
|
235
|
+
* - 23502: Not null constraint violation → 400 Bad Request
|
|
150
236
|
*/
|
|
151
237
|
export class DatabaseExceptionFilter {
|
|
152
238
|
catch(exception, context) {
|
|
153
239
|
const response = context.getResponse();
|
|
154
240
|
const request = context.getRequest();
|
|
241
|
+
// Don't catch HttpException or its subclasses (like ConflictException)
|
|
242
|
+
// These are intentionally thrown by the user
|
|
243
|
+
if (exception instanceof HttpException) {
|
|
244
|
+
throw exception;
|
|
245
|
+
}
|
|
155
246
|
// Check for common database errors
|
|
156
247
|
let message = "Database error occurred";
|
|
157
248
|
let statusCode = 500;
|
|
@@ -180,10 +271,20 @@ export class DatabaseExceptionFilter {
|
|
|
180
271
|
}
|
|
181
272
|
/**
|
|
182
273
|
* Authentication Exception Filter - Handles auth errors
|
|
274
|
+
*
|
|
275
|
+
* ⚠️ IMPORTANT: This catches ALL UnauthorizedException instances!
|
|
276
|
+
* Use on specific routes/controllers, not globally.
|
|
277
|
+
*
|
|
183
278
|
* @example
|
|
279
|
+
* // ✅ GOOD: Use on auth-protected controller
|
|
184
280
|
* @UseFilters(AuthenticationExceptionFilter)
|
|
185
281
|
* @Controller('/auth')
|
|
186
|
-
* export class AuthController {
|
|
282
|
+
* export class AuthController {
|
|
283
|
+
* @Post('/login')
|
|
284
|
+
* async login() {
|
|
285
|
+
* throw new UnauthorizedException('Invalid credentials');
|
|
286
|
+
* }
|
|
287
|
+
* }
|
|
187
288
|
*/
|
|
188
289
|
export class AuthenticationExceptionFilter {
|
|
189
290
|
catch(exception, context) {
|
|
@@ -201,10 +302,20 @@ export class AuthenticationExceptionFilter {
|
|
|
201
302
|
}
|
|
202
303
|
/**
|
|
203
304
|
* Authorization Exception Filter - Handles permission errors
|
|
305
|
+
*
|
|
306
|
+
* ⚠️ IMPORTANT: This catches ALL ForbiddenException instances!
|
|
307
|
+
* Use on specific routes/controllers, not globally.
|
|
308
|
+
*
|
|
204
309
|
* @example
|
|
310
|
+
* // ✅ GOOD: Use on admin-only controller
|
|
205
311
|
* @UseFilters(AuthorizationExceptionFilter)
|
|
206
312
|
* @Controller('/admin')
|
|
207
|
-
* export class AdminController {
|
|
313
|
+
* export class AdminController {
|
|
314
|
+
* @Get('/users')
|
|
315
|
+
* async getAllUsers() {
|
|
316
|
+
* throw new ForbiddenException('Admin access required');
|
|
317
|
+
* }
|
|
318
|
+
* }
|
|
208
319
|
*/
|
|
209
320
|
export class AuthorizationExceptionFilter {
|
|
210
321
|
catch(exception, context) {
|
|
@@ -222,16 +333,64 @@ export class AuthorizationExceptionFilter {
|
|
|
222
333
|
}
|
|
223
334
|
}
|
|
224
335
|
/**
|
|
225
|
-
* Not Found Exception Filter - Handles 404 errors
|
|
336
|
+
* Not Found Exception Filter - Handles 404 errors with smart detection
|
|
337
|
+
*
|
|
338
|
+
* This filter is SMART - it only handles NotFoundExceptionFilter if:
|
|
339
|
+
* 1. The exception is NotFoundException, AND
|
|
340
|
+
* 2. No response data has been set (empty, null, empty array, or empty object)
|
|
341
|
+
*
|
|
342
|
+
* This allows it to be used globally without breaking routes that return
|
|
343
|
+
* legitimate empty responses or have their own error handling.
|
|
344
|
+
*
|
|
226
345
|
* @example
|
|
227
|
-
*
|
|
346
|
+
* // ✅ Can be used globally - smart filtering
|
|
347
|
+
* app.useGlobalFilters(
|
|
348
|
+
* new NotFoundExceptionFilter(), // Safe to use globally now!
|
|
349
|
+
* new GlobalExceptionFilter()
|
|
350
|
+
* );
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* // Works correctly in all scenarios:
|
|
354
|
+
*
|
|
355
|
+
* // Scenario 1: Empty response with NotFoundException
|
|
228
356
|
* @Get('/:id')
|
|
229
|
-
* async findOne(@Param('id') id: string) {
|
|
357
|
+
* async findOne(@Param('id') id: string) {
|
|
358
|
+
* const item = await this.service.findOne(id);
|
|
359
|
+
* if (!item) {
|
|
360
|
+
* throw new NotFoundException('Item not found'); // ✅ Caught and formatted by NotFoundExceptionFilter
|
|
361
|
+
* }
|
|
362
|
+
* return item;
|
|
363
|
+
* }
|
|
364
|
+
*
|
|
365
|
+
* // Scenario 2: Valid empty array response
|
|
366
|
+
* @Get('/search')
|
|
367
|
+
* async search(@Query('q') query: string) {
|
|
368
|
+
* const results = await this.service.search(query);
|
|
369
|
+
* return results; // ✅ Returns [] without triggering NotFoundExceptionFilter (no NotFoundException thrown)
|
|
370
|
+
* }
|
|
371
|
+
*
|
|
372
|
+
* // Scenario 3: NotFoundException with existing response data
|
|
373
|
+
* @Get('/custom')
|
|
374
|
+
* async custom() {
|
|
375
|
+
* const data = { message: 'Custom data' };
|
|
376
|
+
* throw new NotFoundException('Not found'); // ✅ Passes through (has response data)
|
|
377
|
+
* }
|
|
230
378
|
*/
|
|
231
379
|
export class NotFoundExceptionFilter {
|
|
232
380
|
catch(exception, context) {
|
|
233
381
|
const response = context.getResponse();
|
|
234
382
|
const request = context.getRequest();
|
|
383
|
+
// Check if there's any meaningful response data already set
|
|
384
|
+
// If there is, this might be an intentional NotFoundException with custom handling
|
|
385
|
+
// So we pass it through to the next filter
|
|
386
|
+
const hasResponseData = this.hasResponseData(response);
|
|
387
|
+
if (hasResponseData) {
|
|
388
|
+
// There's already response data, don't override it
|
|
389
|
+
// Pass to next filter
|
|
390
|
+
throw exception;
|
|
391
|
+
}
|
|
392
|
+
// No response data and we have a NotFoundException
|
|
393
|
+
// This is likely a "resource not found" scenario
|
|
235
394
|
return {
|
|
236
395
|
statusCode: exception.statusCode,
|
|
237
396
|
error: "Not Found",
|
|
@@ -241,6 +400,30 @@ export class NotFoundExceptionFilter {
|
|
|
241
400
|
suggestion: "Please check the resource ID or URL",
|
|
242
401
|
};
|
|
243
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Check if response has meaningful data
|
|
405
|
+
* Returns false for: null, undefined, {}, [], ""
|
|
406
|
+
* Returns true for: anything else
|
|
407
|
+
*/
|
|
408
|
+
hasResponseData(response) {
|
|
409
|
+
if (response === null || response === undefined) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
// Check for empty object
|
|
413
|
+
if (typeof response === 'object' && !Array.isArray(response)) {
|
|
414
|
+
return Object.keys(response).length > 0;
|
|
415
|
+
}
|
|
416
|
+
// Check for empty array
|
|
417
|
+
if (Array.isArray(response)) {
|
|
418
|
+
return response.length > 0;
|
|
419
|
+
}
|
|
420
|
+
// Check for empty string
|
|
421
|
+
if (typeof response === 'string') {
|
|
422
|
+
return response.length > 0;
|
|
423
|
+
}
|
|
424
|
+
// For numbers, booleans, etc. - consider them as having data
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
244
427
|
}
|
|
245
428
|
/**
|
|
246
429
|
* Rate Limit Exception Filter - Handles rate limit errors
|
|
@@ -253,6 +436,10 @@ export class RateLimitExceptionFilter {
|
|
|
253
436
|
catch(exception, context) {
|
|
254
437
|
const response = context.getResponse();
|
|
255
438
|
const request = context.getRequest();
|
|
439
|
+
// Don't catch HttpException or its subclasses
|
|
440
|
+
if (exception instanceof HttpException) {
|
|
441
|
+
throw exception;
|
|
442
|
+
}
|
|
256
443
|
return {
|
|
257
444
|
statusCode: 429,
|
|
258
445
|
error: "Too Many Requests",
|
|
@@ -275,6 +462,10 @@ export class BusinessLogicExceptionFilter {
|
|
|
275
462
|
catch(exception, context) {
|
|
276
463
|
const response = context.getResponse();
|
|
277
464
|
const request = context.getRequest();
|
|
465
|
+
// Don't catch HttpException or its subclasses
|
|
466
|
+
if (exception instanceof HttpException) {
|
|
467
|
+
throw exception;
|
|
468
|
+
}
|
|
278
469
|
return {
|
|
279
470
|
statusCode: exception.statusCode || 422,
|
|
280
471
|
error: "Business Rule Violation",
|
|
@@ -297,6 +488,10 @@ export class FileUploadExceptionFilter {
|
|
|
297
488
|
catch(exception, context) {
|
|
298
489
|
const response = context.getResponse();
|
|
299
490
|
const request = context.getRequest();
|
|
491
|
+
// Don't catch HttpException or its subclasses
|
|
492
|
+
if (exception instanceof HttpException) {
|
|
493
|
+
throw exception;
|
|
494
|
+
}
|
|
300
495
|
let message = "File upload failed";
|
|
301
496
|
if (exception.message?.includes("size")) {
|
|
302
497
|
message = "File size exceeds limit";
|
|
@@ -347,3 +542,201 @@ export class GlobalExceptionFilter {
|
|
|
347
542
|
};
|
|
348
543
|
}
|
|
349
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* ============================================================================
|
|
547
|
+
* COMPLETE EXAMPLE: Using Multiple Exception Filters
|
|
548
|
+
* ============================================================================
|
|
549
|
+
*
|
|
550
|
+
* Here's a complete example showing how to properly use multiple exception
|
|
551
|
+
* filters in a WynkJS application with the correct order and behavior.
|
|
552
|
+
*/
|
|
553
|
+
/*
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Example 1: Validation Error Formatting (CORRECT WAY)
|
|
556
|
+
// ============================================================================
|
|
557
|
+
|
|
558
|
+
import { WynkFactory, FormatErrorFormatter, DetailedErrorFormatter } from 'wynkjs';
|
|
559
|
+
|
|
560
|
+
// ✅ CORRECT: Pass formatter to factory options
|
|
561
|
+
const app = WynkFactory.create({
|
|
562
|
+
controllers: [UserController, ProductController],
|
|
563
|
+
validationErrorFormatter: new FormatErrorFormatter(), // Formats validation errors
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// ❌ WRONG: Don't use ValidationExceptionFilter in useGlobalFilters
|
|
567
|
+
// app.useGlobalFilters(new ValidationExceptionFilter(new FormatErrorFormatter()));
|
|
568
|
+
// This does nothing because validation is handled in factory's onError hook
|
|
569
|
+
|
|
570
|
+
// ============================================================================
|
|
571
|
+
// Example 2: Exception Filters (For non-validation errors)
|
|
572
|
+
// ============================================================================
|
|
573
|
+
|
|
574
|
+
import { WynkFactory } from 'wynkjs';
|
|
575
|
+
import {
|
|
576
|
+
DatabaseExceptionFilter,
|
|
577
|
+
GlobalExceptionFilter,
|
|
578
|
+
} from 'wynkjs';
|
|
579
|
+
|
|
580
|
+
const app = WynkFactory.create({
|
|
581
|
+
controllers: [UserController, ProductController],
|
|
582
|
+
validationErrorFormatter: new FormatErrorFormatter(), // For validation errors
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Register filters for OTHER types of errors (DB errors, unexpected errors, etc.)
|
|
586
|
+
app.useGlobalFilters(
|
|
587
|
+
new DatabaseExceptionFilter(), // Handles DB errors
|
|
588
|
+
new GlobalExceptionFilter() // Catches everything else
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
await app.listen(3000);
|
|
592
|
+
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// Example 3: Controller-Level Filters
|
|
595
|
+
// ============================================================================
|
|
596
|
+
|
|
597
|
+
import { Controller, Post, Body, UseFilters } from 'wynkjs';
|
|
598
|
+
import { DatabaseExceptionFilter, ConflictException } from 'wynkjs';
|
|
599
|
+
|
|
600
|
+
@UseFilters(DatabaseExceptionFilter) // Only for this controller
|
|
601
|
+
@Controller('/users')
|
|
602
|
+
export class UserController {
|
|
603
|
+
|
|
604
|
+
@Post()
|
|
605
|
+
async create(@Body() data: any) {
|
|
606
|
+
// Check if user exists
|
|
607
|
+
const exists = await this.checkUserExists(data.email);
|
|
608
|
+
|
|
609
|
+
if (exists) {
|
|
610
|
+
// ✅ This ConflictException will NOT be caught by DatabaseExceptionFilter
|
|
611
|
+
// It will pass through and be handled by the default HttpException handler
|
|
612
|
+
throw new ConflictException('User with this email already exists');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// If database throws a unique constraint error, DatabaseExceptionFilter will catch it
|
|
616
|
+
return await this.db.insert(users).values(data);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// Example 4: Method-Level Filters (Most Specific)
|
|
622
|
+
// ============================================================================
|
|
623
|
+
|
|
624
|
+
import { Controller, Post, Get, UseFilters, Param } from 'wynkjs';
|
|
625
|
+
import { NotFoundExceptionFilter, NotFoundException } from 'wynkjs';
|
|
626
|
+
|
|
627
|
+
@Controller('/products')
|
|
628
|
+
export class ProductController {
|
|
629
|
+
|
|
630
|
+
@Get('/:id')
|
|
631
|
+
@UseFilters(NotFoundExceptionFilter) // Only for this route
|
|
632
|
+
async findOne(@Param('id') id: string) {
|
|
633
|
+
const product = await this.db.findOne(id);
|
|
634
|
+
|
|
635
|
+
if (!product) {
|
|
636
|
+
// ✅ Caught by NotFoundExceptionFilter and formatted nicely
|
|
637
|
+
throw new NotFoundException(`Product with ID ${id} not found`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return product;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
@Post()
|
|
644
|
+
async create(@Body() data: any) {
|
|
645
|
+
// This route doesn't have NotFoundExceptionFilter
|
|
646
|
+
return await this.db.insert(products).values(data);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ============================================================================
|
|
651
|
+
// Example 5: Understanding the Flow
|
|
652
|
+
// ============================================================================
|
|
653
|
+
|
|
654
|
+
// Given this setup:
|
|
655
|
+
const app = WynkFactory.create({
|
|
656
|
+
controllers: [UserController],
|
|
657
|
+
validationErrorFormatter: new FormatErrorFormatter(), // For validation errors
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
app.useGlobalFilters(
|
|
661
|
+
new DatabaseExceptionFilter(),
|
|
662
|
+
new GlobalExceptionFilter()
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
// SCENARIO 1: Validation Error
|
|
666
|
+
// ──────────────────────────────
|
|
667
|
+
POST /users with invalid data
|
|
668
|
+
→ Handled by factory's onError hook (NOT by ValidationExceptionFilter)
|
|
669
|
+
→ Formatted using validationErrorFormatter option
|
|
670
|
+
→ Returns formatted validation error
|
|
671
|
+
|
|
672
|
+
// SCENARIO 2: Manual ConflictException
|
|
673
|
+
// ──────────────────────────────────────
|
|
674
|
+
throw new ConflictException('User exists');
|
|
675
|
+
→ DatabaseExceptionFilter checks: It's an HttpException, re-throws
|
|
676
|
+
→ GlobalExceptionFilter catches it
|
|
677
|
+
→ Returns the ConflictException response
|
|
678
|
+
|
|
679
|
+
// SCENARIO 3: Database Unique Constraint Error
|
|
680
|
+
// ──────────────────────────────────────────────
|
|
681
|
+
Database throws: { code: '23505', message: 'unique constraint' }
|
|
682
|
+
→ DatabaseExceptionFilter catches it (not an HttpException)
|
|
683
|
+
→ Converts to user-friendly message
|
|
684
|
+
→ Returns: { statusCode: 409, message: 'Resource already exists' }
|
|
685
|
+
|
|
686
|
+
// SCENARIO 4: Unexpected Error
|
|
687
|
+
// ──────────────────────────────
|
|
688
|
+
throw new Error('Something went wrong');
|
|
689
|
+
→ DatabaseExceptionFilter checks: Not a DB error, re-throws
|
|
690
|
+
→ GlobalExceptionFilter catches it
|
|
691
|
+
→ Logs error and returns generic 500 response
|
|
692
|
+
|
|
693
|
+
// ============================================================================
|
|
694
|
+
// Example 6: Custom Exception Filter
|
|
695
|
+
// ============================================================================
|
|
696
|
+
|
|
697
|
+
import { WynkExceptionFilter, ExecutionContext, HttpException } from 'wynkjs';
|
|
698
|
+
|
|
699
|
+
export class CustomBusinessFilter implements WynkExceptionFilter {
|
|
700
|
+
catch(exception: any, context: ExecutionContext) {
|
|
701
|
+
const request = context.getRequest();
|
|
702
|
+
|
|
703
|
+
// Don't catch HttpExceptions (let them pass through)
|
|
704
|
+
if (exception instanceof HttpException) {
|
|
705
|
+
throw exception;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Only catch errors with 'BUSINESS_RULE_VIOLATION' code
|
|
709
|
+
if (exception.code === 'BUSINESS_RULE_VIOLATION') {
|
|
710
|
+
return {
|
|
711
|
+
statusCode: 422,
|
|
712
|
+
error: 'Business Rule Violation',
|
|
713
|
+
message: exception.message,
|
|
714
|
+
rule: exception.rule,
|
|
715
|
+
timestamp: new Date().toISOString(),
|
|
716
|
+
path: request.url,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Not our error type, re-throw it
|
|
721
|
+
throw exception;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Use it:
|
|
726
|
+
app.useGlobalFilters(
|
|
727
|
+
new CustomBusinessFilter(),
|
|
728
|
+
new GlobalExceptionFilter()
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
// ============================================================================
|
|
732
|
+
// KEY TAKEAWAYS
|
|
733
|
+
// ============================================================================
|
|
734
|
+
//
|
|
735
|
+
// 1. Order matters: Specific → General
|
|
736
|
+
// 2. Filters can re-throw exceptions they don't handle
|
|
737
|
+
// 3. HttpException and subclasses should pass through specialized filters
|
|
738
|
+
// 4. Global filters catch everything not handled by controller/method filters
|
|
739
|
+
// 5. Always have a GlobalExceptionFilter as the last filter (catch-all)
|
|
740
|
+
//
|
|
741
|
+
// ============================================================================
|
|
742
|
+
*/
|
|
@@ -18,7 +18,7 @@ export interface WynkExceptionFilter<T = any> {
|
|
|
18
18
|
* export class HttpWynkExceptionFilter implements WynkExceptionFilter {}
|
|
19
19
|
*
|
|
20
20
|
* @Catch() // Catches all exceptions
|
|
21
|
-
* export class
|
|
21
|
+
* export class AllExceptions implements WynkExceptionFilter {}
|
|
22
22
|
*/
|
|
23
23
|
export declare function Catch(...exceptions: any[]): ClassDecorator;
|
|
24
24
|
/**
|
|
@@ -42,6 +42,7 @@ export declare class HttpException extends Error {
|
|
|
42
42
|
readonly statusCode: number;
|
|
43
43
|
readonly error?: string | undefined;
|
|
44
44
|
constructor(message: string, statusCode: number, error?: string | undefined);
|
|
45
|
+
get status(): number;
|
|
45
46
|
getStatus(): number;
|
|
46
47
|
getResponse(): any;
|
|
47
48
|
}
|
|
@@ -115,7 +116,97 @@ export declare class HttpWynkExceptionFilter implements WynkExceptionFilter<Http
|
|
|
115
116
|
/**
|
|
116
117
|
* All exceptions filter - catches everything
|
|
117
118
|
*/
|
|
118
|
-
export declare class
|
|
119
|
+
export declare class AllExceptions implements WynkExceptionFilter {
|
|
119
120
|
catch(exception: any, context: ExecutionContext): any;
|
|
120
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Authentication Exception Filter - Handles auth errors
|
|
124
|
+
*
|
|
125
|
+
* ⚠️ IMPORTANT: This catches ALL UnauthorizedException instances!
|
|
126
|
+
* Use on specific routes/controllers, not globally.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // ✅ GOOD: Use on auth-protected controller
|
|
130
|
+
* @UseFilters(AuthenticationExceptionFilter)
|
|
131
|
+
* @Controller('/auth')
|
|
132
|
+
* export class AuthController {
|
|
133
|
+
* @Post('/login')
|
|
134
|
+
* async login() {
|
|
135
|
+
* throw new UnauthorizedException('Invalid credentials');
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
*/
|
|
139
|
+
export declare class AuthenticationException implements WynkExceptionFilter<UnauthorizedException> {
|
|
140
|
+
catch(exception: UnauthorizedException, context: ExecutionContext): {
|
|
141
|
+
statusCode: number;
|
|
142
|
+
error: string;
|
|
143
|
+
message: string;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
path: any;
|
|
146
|
+
hint: string;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Authorization Exception Filter - Handles permission errors
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ IMPORTANT: This catches ALL ForbiddenException instances!
|
|
153
|
+
* Use on specific routes/controllers, not globally.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // ✅ GOOD: Use on admin-only controller
|
|
157
|
+
* @UseFilters(AuthorizationExceptionFilter)
|
|
158
|
+
* @Controller('/admin')
|
|
159
|
+
* export class AdminController {
|
|
160
|
+
* @Get('/users')
|
|
161
|
+
* async getAllUsers() {
|
|
162
|
+
* throw new ForbiddenException('Admin access required');
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
*/
|
|
166
|
+
export declare class AuthorizationException implements WynkExceptionFilter<ForbiddenException> {
|
|
167
|
+
catch(exception: ForbiddenException, context: ExecutionContext): {
|
|
168
|
+
statusCode: number;
|
|
169
|
+
error: string;
|
|
170
|
+
message: string;
|
|
171
|
+
timestamp: string;
|
|
172
|
+
path: any;
|
|
173
|
+
requiredPermissions: any;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Rate Limit Exception Filter - Handles rate limit errors
|
|
178
|
+
* @example
|
|
179
|
+
* @UseFilters(RateLimitExceptionFilter)
|
|
180
|
+
* @Post()
|
|
181
|
+
* async create() {}
|
|
182
|
+
*/
|
|
183
|
+
export declare class RateLimitException implements WynkExceptionFilter {
|
|
184
|
+
catch(exception: any, context: ExecutionContext): {
|
|
185
|
+
statusCode: number;
|
|
186
|
+
error: string;
|
|
187
|
+
message: any;
|
|
188
|
+
timestamp: string;
|
|
189
|
+
path: any;
|
|
190
|
+
retryAfter: any;
|
|
191
|
+
hint: string;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Business Logic Exception Filter - Handles business rule violations
|
|
196
|
+
* @example
|
|
197
|
+
* @UseFilters(BusinessLogicExceptionFilter)
|
|
198
|
+
* @Post('/transfer')
|
|
199
|
+
* async transfer(@Body() data: any) {}
|
|
200
|
+
*/
|
|
201
|
+
export declare class BusinessLogicException implements WynkExceptionFilter {
|
|
202
|
+
catch(exception: any, context: ExecutionContext): {
|
|
203
|
+
statusCode: any;
|
|
204
|
+
error: string;
|
|
205
|
+
message: any;
|
|
206
|
+
timestamp: string;
|
|
207
|
+
path: any;
|
|
208
|
+
rule: any;
|
|
209
|
+
details: any;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
121
212
|
//# sourceMappingURL=exception.decorators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exception.decorators.d.ts","sourceRoot":"","sources":["../../core/decorators/exception.decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,GAAG;IAC1C,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,gBAAgB,GAAG,GAAG,CAAC;CACrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,cAAc,CAK1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,GAAG,OAAO,EAAE,CAAC,QAAQ,GAAG,mBAAmB,CAAC,EAAE,GAC7C,eAAe,GAAG,cAAc,CAwBlC;AAED;;GAEG;AAEH,qBAAa,aAAc,SAAQ,KAAK;aAEpB,OAAO,EAAE,MAAM;aACf,UAAU,EAAE,MAAM;aAClB,KAAK,CAAC,EAAE,MAAM;gBAFd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,MAAM,YAAA;IAMhC,SAAS,IAAI,MAAM;IAInB,WAAW,IAAI,GAAG;CAOnB;AAED,qBAAa,mBAAoB,SAAQ,aAAa;gBACxC,OAAO,GAAE,MAAsB;CAG5C;AAED,qBAAa,qBAAsB,SAAQ,aAAa;gBAC1C,OAAO,GAAE,MAAuB;CAG7C;AAED,qBAAa,kBAAmB,SAAQ,aAAa;gBACvC,OAAO,GAAE,MAAoB;CAG1C;AAED,qBAAa,iBAAkB,SAAQ,aAAa;gBACtC,OAAO,GAAE,MAAoB;CAG1C;AAED,qBAAa,yBAA0B,SAAQ,aAAa;gBAC9C,OAAO,GAAE,MAA6B;CAGnD;AAED,qBAAa,sBAAuB,SAAQ,aAAa;gBAC3C,OAAO,GAAE,MAAyB;CAG/C;AAED,qBAAa,uBAAwB,SAAQ,aAAa;gBAC5C,OAAO,GAAE,MAA0B;CAGhD;AAED,qBAAa,iBAAkB,SAAQ,aAAa;gBACtC,OAAO,GAAE,MAAmB;CAGzC;AAED,qBAAa,sBAAuB,SAAQ,aAAa;gBAC3C,OAAO,GAAE,MAAkC;CAGxD;AAED,qBAAa,aAAc,SAAQ,aAAa;gBAClC,OAAO,GAAE,MAAe;CAGrC;AAED,qBAAa,wBAAyB,SAAQ,aAAa;gBAC7C,OAAO,GAAE,MAA4B;CAGlD;AAED,qBAAa,6BAA8B,SAAQ,aAAa;gBAClD,OAAO,GAAE,MAAiC;CAGvD;AAED,qBAAa,4BAA6B,SAAQ,aAAa;gBACjD,OAAO,GAAE,MAA+B;CAGrD;AAED,qBAAa,4BAA6B,SAAQ,aAAa;gBACjD,OAAO,GAAE,MAAgC;CAGtD;AAED,qBAAa,uBAAwB,SAAQ,aAAa;gBAC5C,OAAO,GAAE,MAA0B;CAGhD;AAED,qBAAa,mBAAoB,SAAQ,aAAa;gBACxC,OAAO,GAAE,MAAsB;CAG5C;AAED,qBAAa,2BAA4B,SAAQ,aAAa;gBAChD,OAAO,GAAE,MAA8B;CAGpD;AAED,qBAAa,uBAAwB,SAAQ,aAAa;gBAC5C,OAAO,GAAE,MAA0B;CAGhD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,CAAC,QAAQ,GAAG,mBAAmB,CAAC,EAAE,EAC3C,SAAS,EAAE,GAAG,EACd,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"exception.decorators.d.ts","sourceRoot":"","sources":["../../core/decorators/exception.decorators.ts"],"names":[],"mappings":"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"}
|