wynkjs 1.0.3 → 1.0.4
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 +252 -55
- 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 +92 -2
- package/dist/decorators/exception.decorators.d.ts.map +1 -1
- package/dist/decorators/exception.decorators.js +120 -5
- 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/http.decorators.d.ts +3 -2
- package/dist/decorators/http.decorators.d.ts.map +1 -1
- package/dist/decorators/pipe.decorators.d.ts +2 -2
- package/dist/decorators/pipe.decorators.d.ts.map +1 -1
- package/dist/decorators/pipe.decorators.js +2 -2
- package/dist/dto.js +1 -1
- package/dist/factory.d.ts +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +55 -6
- 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/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/pipes/validation.pipe.d.ts +3 -3
- package/dist/pipes/validation.pipe.d.ts.map +1 -1
- package/dist/pipes/validation.pipe.js +39 -11
- 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/package.json +8 -3
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { NotFoundException, WynkExceptionFilter } from "../decorators/exception.decorators";
|
|
2
|
+
import { ExecutionContext } from "../decorators/guard.decorators";
|
|
3
|
+
/**
|
|
4
|
+
* Database Exception Filter - Handles database errors ONLY (not HttpExceptions)
|
|
5
|
+
*
|
|
6
|
+
* This filter catches actual database errors (like unique constraint violations,
|
|
7
|
+
* foreign key errors, etc.) and converts them to user-friendly messages.
|
|
8
|
+
*
|
|
9
|
+
* It will NOT catch HttpException or its subclasses (ConflictException, etc.)
|
|
10
|
+
* that you throw manually - those will pass through to be handled correctly.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Use as global filter
|
|
14
|
+
* app.useGlobalFilters(new DatabaseExceptionFilter());
|
|
15
|
+
*
|
|
16
|
+
* // Use on specific controller
|
|
17
|
+
* @UseFilters(DatabaseExceptionFilter)
|
|
18
|
+
* @Controller('/users')
|
|
19
|
+
* export class UserController {
|
|
20
|
+
* @Post()
|
|
21
|
+
* async create(@Body() data: any) {
|
|
22
|
+
* // If you throw manually, it passes through:
|
|
23
|
+
* if (await this.userExists(data.email)) {
|
|
24
|
+
* throw new ConflictException('User with this email already exists'); // ✅ Works correctly
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // If database throws error, filter catches it:
|
|
28
|
+
* return await this.db.insert(users).values(data); // ❌ DB unique constraint error → caught by filter
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* Handles these database error codes:
|
|
33
|
+
* - 23505: Unique constraint violation → 409 Conflict
|
|
34
|
+
* - 23503: Foreign key constraint violation → 400 Bad Request
|
|
35
|
+
* - 23502: Not null constraint violation → 400 Bad Request
|
|
36
|
+
*/
|
|
37
|
+
export declare class DatabaseExceptionFilter implements WynkExceptionFilter {
|
|
38
|
+
catch(exception: any, context: ExecutionContext): {
|
|
39
|
+
statusCode: number;
|
|
40
|
+
error: string;
|
|
41
|
+
message: string;
|
|
42
|
+
timestamp: string;
|
|
43
|
+
path: any;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Not Found Exception Filter - Handles 404 errors with smart detection
|
|
48
|
+
*
|
|
49
|
+
* This filter is SMART - it only handles NotFoundExceptionFilter if:
|
|
50
|
+
* 1. The exception is NotFoundException, AND
|
|
51
|
+
* 2. No response data has been set (empty, null, empty array, or empty object)
|
|
52
|
+
*
|
|
53
|
+
* This allows it to be used globally without breaking routes that return
|
|
54
|
+
* legitimate empty responses or have their own error handling.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // ✅ Can be used globally - smart filtering
|
|
58
|
+
* app.useGlobalFilters(
|
|
59
|
+
* new NotFoundExceptionFilter(), // Safe to use globally now!
|
|
60
|
+
* new GlobalExceptionFilter()
|
|
61
|
+
* );
|
|
62
|
+
*
|
|
63
|
+
*/
|
|
64
|
+
export declare class NotFoundExceptionFilter implements WynkExceptionFilter<NotFoundException> {
|
|
65
|
+
catch(exception: NotFoundException, context: ExecutionContext): {
|
|
66
|
+
statusCode: number;
|
|
67
|
+
error: string;
|
|
68
|
+
message: string;
|
|
69
|
+
timestamp: string;
|
|
70
|
+
path: any;
|
|
71
|
+
suggestion: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Check if response has meaningful data
|
|
75
|
+
* Returns false for: null, undefined, {}, [], ""
|
|
76
|
+
* Returns true for: anything else
|
|
77
|
+
*/
|
|
78
|
+
private hasResponseData;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* File Upload Exception Filter - Handles file upload errors
|
|
82
|
+
* @example
|
|
83
|
+
* @UseFilters(FileUploadExceptionFilter)
|
|
84
|
+
* @Post('/upload')
|
|
85
|
+
* async upload(@UploadedFile() file: any) {}
|
|
86
|
+
*/
|
|
87
|
+
export declare class FileUploadExceptionFilter implements WynkExceptionFilter {
|
|
88
|
+
catch(exception: any, context: ExecutionContext): {
|
|
89
|
+
statusCode: number;
|
|
90
|
+
error: string;
|
|
91
|
+
message: string;
|
|
92
|
+
timestamp: string;
|
|
93
|
+
path: any;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Global Exception Filter - Catches all unhandled exceptions
|
|
98
|
+
* @example
|
|
99
|
+
* app.useGlobalFilters(new GlobalExceptionFilter());
|
|
100
|
+
*/
|
|
101
|
+
export declare class GlobalExceptionFilter implements WynkExceptionFilter {
|
|
102
|
+
catch(exception: any, context: ExecutionContext): {
|
|
103
|
+
stack?: any;
|
|
104
|
+
statusCode: any;
|
|
105
|
+
error: any;
|
|
106
|
+
message: any;
|
|
107
|
+
timestamp: string;
|
|
108
|
+
path: any;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// KEY TAKEAWAYS
|
|
114
|
+
// ============================================================================
|
|
115
|
+
//
|
|
116
|
+
// 1. Order matters: Specific → General
|
|
117
|
+
// 2. Filters can re-throw exceptions they don't handle
|
|
118
|
+
// 3. HttpException and subclasses should pass through specialized filters
|
|
119
|
+
// 4. Global filters catch everything not handled by controller/method filters
|
|
120
|
+
// 5. Always have a GlobalExceptionFilter as the last filter (catch-all)
|
|
121
|
+
//
|
|
122
|
+
// ============================================================================
|
|
123
|
+
*/
|
|
124
|
+
//# sourceMappingURL=exception.filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception.filters.d.ts","sourceRoot":"","sources":["../../core/filters/exception.filters.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;CAuChD;AAED;;;;;;;;;;;;;;;;;GAiBG;AAEH,qBAAa,uBACX,YAAW,mBAAmB,CAAC,iBAAiB,CAAC;IAEjD,KAAK,CAAC,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;IAmB7D;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAuBxB;AAED;;;;;;GAMG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;CA2BhD;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,mBAAmB;IAC/D,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;CA2BhD;AAED;;;;;;;;;;;;EAYE"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { HttpException, } from "../decorators/exception.decorators";
|
|
2
|
+
/**
|
|
3
|
+
* Database Exception Filter - Handles database errors ONLY (not HttpExceptions)
|
|
4
|
+
*
|
|
5
|
+
* This filter catches actual database errors (like unique constraint violations,
|
|
6
|
+
* foreign key errors, etc.) and converts them to user-friendly messages.
|
|
7
|
+
*
|
|
8
|
+
* It will NOT catch HttpException or its subclasses (ConflictException, etc.)
|
|
9
|
+
* that you throw manually - those will pass through to be handled correctly.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Use as global filter
|
|
13
|
+
* app.useGlobalFilters(new DatabaseExceptionFilter());
|
|
14
|
+
*
|
|
15
|
+
* // Use on specific controller
|
|
16
|
+
* @UseFilters(DatabaseExceptionFilter)
|
|
17
|
+
* @Controller('/users')
|
|
18
|
+
* export class UserController {
|
|
19
|
+
* @Post()
|
|
20
|
+
* async create(@Body() data: any) {
|
|
21
|
+
* // If you throw manually, it passes through:
|
|
22
|
+
* if (await this.userExists(data.email)) {
|
|
23
|
+
* throw new ConflictException('User with this email already exists'); // ✅ Works correctly
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* // If database throws error, filter catches it:
|
|
27
|
+
* return await this.db.insert(users).values(data); // ❌ DB unique constraint error → caught by filter
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* Handles these database error codes:
|
|
32
|
+
* - 23505: Unique constraint violation → 409 Conflict
|
|
33
|
+
* - 23503: Foreign key constraint violation → 400 Bad Request
|
|
34
|
+
* - 23502: Not null constraint violation → 400 Bad Request
|
|
35
|
+
*/
|
|
36
|
+
export class DatabaseExceptionFilter {
|
|
37
|
+
catch(exception, context) {
|
|
38
|
+
const response = context.getResponse();
|
|
39
|
+
const request = context.getRequest();
|
|
40
|
+
// Don't catch HttpException or its subclasses (like ConflictException)
|
|
41
|
+
// These are intentionally thrown by the user
|
|
42
|
+
if (exception instanceof HttpException) {
|
|
43
|
+
throw exception;
|
|
44
|
+
}
|
|
45
|
+
// Check for common database errors
|
|
46
|
+
let message = "Database error occurred";
|
|
47
|
+
let statusCode = 500;
|
|
48
|
+
if (exception.code === "23505" || exception.message?.includes("unique")) {
|
|
49
|
+
message = "Resource already exists";
|
|
50
|
+
statusCode = 409; // Conflict
|
|
51
|
+
}
|
|
52
|
+
else if (exception.code === "23503" ||
|
|
53
|
+
exception.message?.includes("foreign key")) {
|
|
54
|
+
message = "Referenced resource does not exist";
|
|
55
|
+
statusCode = 400;
|
|
56
|
+
}
|
|
57
|
+
else if (exception.code === "23502" ||
|
|
58
|
+
exception.message?.includes("not null")) {
|
|
59
|
+
message = "Required field is missing";
|
|
60
|
+
statusCode = 400;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
statusCode,
|
|
64
|
+
error: "Database Error",
|
|
65
|
+
message,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
path: request.url,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Not Found Exception Filter - Handles 404 errors with smart detection
|
|
73
|
+
*
|
|
74
|
+
* This filter is SMART - it only handles NotFoundExceptionFilter if:
|
|
75
|
+
* 1. The exception is NotFoundException, AND
|
|
76
|
+
* 2. No response data has been set (empty, null, empty array, or empty object)
|
|
77
|
+
*
|
|
78
|
+
* This allows it to be used globally without breaking routes that return
|
|
79
|
+
* legitimate empty responses or have their own error handling.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // ✅ Can be used globally - smart filtering
|
|
83
|
+
* app.useGlobalFilters(
|
|
84
|
+
* new NotFoundExceptionFilter(), // Safe to use globally now!
|
|
85
|
+
* new GlobalExceptionFilter()
|
|
86
|
+
* );
|
|
87
|
+
*
|
|
88
|
+
*/
|
|
89
|
+
export class NotFoundExceptionFilter {
|
|
90
|
+
catch(exception, context) {
|
|
91
|
+
const response = context.getResponse();
|
|
92
|
+
const request = context.getRequest();
|
|
93
|
+
const hasResponseData = this.hasResponseData(response);
|
|
94
|
+
if (hasResponseData) {
|
|
95
|
+
throw exception;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
statusCode: exception.statusCode,
|
|
99
|
+
error: "Not Found",
|
|
100
|
+
message: exception.message || "Resource not found",
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
path: request.url,
|
|
103
|
+
suggestion: "Please check the resource ID or URL",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if response has meaningful data
|
|
108
|
+
* Returns false for: null, undefined, {}, [], ""
|
|
109
|
+
* Returns true for: anything else
|
|
110
|
+
*/
|
|
111
|
+
hasResponseData(response) {
|
|
112
|
+
if (response === null || response === undefined) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Check for empty object
|
|
116
|
+
if (typeof response === "object" && !Array.isArray(response)) {
|
|
117
|
+
return Object.keys(response).length > 0;
|
|
118
|
+
}
|
|
119
|
+
// Check for empty array
|
|
120
|
+
if (Array.isArray(response)) {
|
|
121
|
+
return response.length > 0;
|
|
122
|
+
}
|
|
123
|
+
// Check for empty string
|
|
124
|
+
if (typeof response === "string") {
|
|
125
|
+
return response.length > 0;
|
|
126
|
+
}
|
|
127
|
+
// For numbers, booleans, etc. - consider them as having data
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* File Upload Exception Filter - Handles file upload errors
|
|
133
|
+
* @example
|
|
134
|
+
* @UseFilters(FileUploadExceptionFilter)
|
|
135
|
+
* @Post('/upload')
|
|
136
|
+
* async upload(@UploadedFile() file: any) {}
|
|
137
|
+
*/
|
|
138
|
+
export class FileUploadExceptionFilter {
|
|
139
|
+
catch(exception, context) {
|
|
140
|
+
const response = context.getResponse();
|
|
141
|
+
const request = context.getRequest();
|
|
142
|
+
// Don't catch HttpException or its subclasses
|
|
143
|
+
if (exception instanceof HttpException) {
|
|
144
|
+
throw exception;
|
|
145
|
+
}
|
|
146
|
+
let message = "File upload failed";
|
|
147
|
+
if (exception.message?.includes("size")) {
|
|
148
|
+
message = "File size exceeds limit";
|
|
149
|
+
}
|
|
150
|
+
else if (exception.message?.includes("type")) {
|
|
151
|
+
message = "Invalid file type";
|
|
152
|
+
}
|
|
153
|
+
else if (exception.message?.includes("required")) {
|
|
154
|
+
message = "File is required";
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
statusCode: 400,
|
|
158
|
+
error: "File Upload Error",
|
|
159
|
+
message,
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
path: request.url,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Global Exception Filter - Catches all unhandled exceptions
|
|
167
|
+
* @example
|
|
168
|
+
* app.useGlobalFilters(new GlobalExceptionFilter());
|
|
169
|
+
*/
|
|
170
|
+
export class GlobalExceptionFilter {
|
|
171
|
+
catch(exception, context) {
|
|
172
|
+
const response = context.getResponse();
|
|
173
|
+
const request = context.getRequest();
|
|
174
|
+
const statusCode = exception.statusCode || 500;
|
|
175
|
+
const message = exception.message || "Internal server error";
|
|
176
|
+
// Log the error for debugging
|
|
177
|
+
console.error("❌ Unhandled exception:", {
|
|
178
|
+
statusCode,
|
|
179
|
+
message,
|
|
180
|
+
path: request.url,
|
|
181
|
+
method: request.method,
|
|
182
|
+
stack: exception.stack,
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
statusCode,
|
|
186
|
+
error: exception.name || "Error",
|
|
187
|
+
message,
|
|
188
|
+
timestamp: new Date().toISOString(),
|
|
189
|
+
path: request.url,
|
|
190
|
+
...(process.env.NODE_ENV === "development" && {
|
|
191
|
+
stack: exception.stack,
|
|
192
|
+
}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// KEY TAKEAWAYS
|
|
199
|
+
// ============================================================================
|
|
200
|
+
//
|
|
201
|
+
// 1. Order matters: Specific → General
|
|
202
|
+
// 2. Filters can re-throw exceptions they don't handle
|
|
203
|
+
// 3. HttpException and subclasses should pass through specialized filters
|
|
204
|
+
// 4. Global filters catch everything not handled by controller/method filters
|
|
205
|
+
// 5. Always have a GlobalExceptionFilter as the last filter (catch-all)
|
|
206
|
+
//
|
|
207
|
+
// ============================================================================
|
|
208
|
+
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,11 @@ export * from "./decorators/interceptor.advanced";
|
|
|
17
17
|
export * from "./decorators/pipe.decorators";
|
|
18
18
|
export * from "./decorators/pipe.advanced";
|
|
19
19
|
export * from "./decorators/exception.decorators";
|
|
20
|
-
export * from "./decorators/
|
|
20
|
+
export * from "./decorators/formatter.decorators";
|
|
21
|
+
export * from "./filters/exception.filters";
|
|
21
22
|
export * from "./decorators/database.decorators";
|
|
22
23
|
export * from "./dto";
|
|
24
|
+
export { schemaRegistry } from "./schema-registry";
|
|
23
25
|
export * from "./factory";
|
|
24
26
|
export * from "./testing";
|
|
25
27
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,kBAAkB,CAAC;AAI1B,OAAO,EACL,UAAU,EACV,MAAM,EACN,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,EACL,UAAU,IAAI,UAAU,EACxB,MAAM,IAAI,MAAM,EAChB,SAAS,IAAI,SAAS,EACtB,cAAc,IAAI,cAAc,EAChC,QAAQ,IAAI,QAAQ,EACpB,SAAS,IAAI,SAAS,GACvB,MAAM,UAAU,CAAC;AAGlB,cAAc,8BAA8B,CAAC;AAG7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAI3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,kBAAkB,CAAC;AAI1B,OAAO,EACL,UAAU,EACV,MAAM,EACN,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,EACL,UAAU,IAAI,UAAU,EACxB,MAAM,IAAI,MAAM,EAChB,SAAS,IAAI,SAAS,EACtB,cAAc,IAAI,cAAc,EAChC,QAAQ,IAAI,QAAQ,EACpB,SAAS,IAAI,SAAS,GACvB,MAAM,UAAU,CAAC;AAGlB,cAAc,8BAA8B,CAAC;AAG7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAI3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,6BAA6B,CAAC;AAI5C,cAAc,kCAAkC,CAAC;AASjD,cAAc,OAAO,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,cAAc,WAAW,CAAC;AAG1B,cAAc,WAAW,CAAC;AAE1B;;GAEG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,8 @@ export * from "./decorators/pipe.advanced";
|
|
|
28
28
|
// Note: validation.pipe.ts is deprecated, use pipe.decorators.ts instead
|
|
29
29
|
// Exception Filters
|
|
30
30
|
export * from "./decorators/exception.decorators";
|
|
31
|
-
export * from "./decorators/
|
|
31
|
+
export * from "./decorators/formatter.decorators";
|
|
32
|
+
export * from "./filters/exception.filters";
|
|
32
33
|
// Database Registry (General-Purpose)
|
|
33
34
|
// Works with ANY ORM/ODM: Drizzle, Mongoose, Prisma, TypeORM, custom Database classes
|
|
34
35
|
export * from "./decorators/database.decorators";
|
|
@@ -39,6 +40,8 @@ export * from "./decorators/database.decorators";
|
|
|
39
40
|
// See: plugins/drizzle and plugins/mongoose
|
|
40
41
|
// DTO Utilities
|
|
41
42
|
export * from "./dto";
|
|
43
|
+
// Schema Registry for custom error messages
|
|
44
|
+
export { schemaRegistry } from "./schema-registry";
|
|
42
45
|
// Application Factory
|
|
43
46
|
export * from "./factory";
|
|
44
47
|
// Testing Module
|
|
@@ -63,12 +63,12 @@ export declare class ValidationPipe implements ValidationPipeTransform {
|
|
|
63
63
|
*/
|
|
64
64
|
protected parseValidationError(exception: any): ElysiaValidationError;
|
|
65
65
|
/**
|
|
66
|
-
* Default error formatting
|
|
66
|
+
* Default error formatting with custom errorMessage support
|
|
67
67
|
*/
|
|
68
|
-
protected defaultFormatError(error: ElysiaValidationError): any;
|
|
68
|
+
protected defaultFormatError(error: ElysiaValidationError, schemaKey?: string): any;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Custom Format Error Pipe
|
|
71
|
+
* Custom Format Error Pipe
|
|
72
72
|
* Formats validation errors as { [field]: [messages] }
|
|
73
73
|
*/
|
|
74
74
|
export declare class FormatErrorPipe extends ValidationPipe {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.pipe.d.ts","sourceRoot":"","sources":["../../core/pipes/validation.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.pipe.d.ts","sourceRoot":"","sources":["../../core/pipes/validation.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,MAAM,WAAW,uBAAuB,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG;IACvD,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,GAAG,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,GAAG,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,uBAAuB;IAC5D,SAAS,CAAC,OAAO,EAAE;QACjB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,GAAG,CAAC;QAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;gBAEU,OAAO,CAAC,EAAE;QACpB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,GAAG,CAAC;QAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB;IAID;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,GAAG,GAAG;IAItD;;;OAGG;IACH,WAAW,CAAC,SAAS,EAAE,GAAG,GAAG,GAAG;IAahC;;OAEG;IACH,SAAS,CAAC,oBAAoB,CAAC,SAAS,EAAE,GAAG,GAAG,qBAAqB;IAgBrE;;OAEG;IACH,SAAS,CAAC,kBAAkB,CAC1B,KAAK,EAAE,qBAAqB,EAC5B,SAAS,CAAC,EAAE,MAAM,GACjB,GAAG;CA2CP;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,cAAc;;CAiClD;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,cAAc;;CA4BlD;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,cAAc;;CAoCpD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
+
import { schemaRegistry } from "../schema-registry";
|
|
2
3
|
/**
|
|
3
4
|
* Base Validation Pipe
|
|
4
5
|
* Handles Elysia validation errors and formats them
|
|
@@ -47,23 +48,35 @@ export class ValidationPipe {
|
|
|
47
48
|
return validationData;
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
|
-
* Default error formatting
|
|
51
|
+
* Default error formatting with custom errorMessage support
|
|
51
52
|
*/
|
|
52
|
-
defaultFormatError(error) {
|
|
53
|
+
defaultFormatError(error, schemaKey) {
|
|
53
54
|
const errors = [];
|
|
54
55
|
if (error.errors && error.errors.length > 0) {
|
|
55
56
|
error.errors.forEach((err) => {
|
|
57
|
+
const fieldPath = err.path?.replace(/^\//, "") || "unknown";
|
|
58
|
+
// Try to get custom message from schema registry first
|
|
59
|
+
let message = err.summary || err.message;
|
|
60
|
+
if (schemaKey) {
|
|
61
|
+
const customMessage = schemaRegistry.getErrorMessage(schemaKey, fieldPath);
|
|
62
|
+
if (customMessage) {
|
|
63
|
+
message = customMessage;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
56
66
|
errors.push({
|
|
57
|
-
field:
|
|
58
|
-
message:
|
|
67
|
+
field: fieldPath,
|
|
68
|
+
message: message,
|
|
59
69
|
value: err.value,
|
|
60
70
|
});
|
|
61
71
|
});
|
|
62
72
|
}
|
|
63
73
|
else {
|
|
74
|
+
// Check schema for custom errorMessage
|
|
75
|
+
const customMessage = error.schema?.errorMessage;
|
|
76
|
+
const message = customMessage || error.summary || error.message;
|
|
64
77
|
errors.push({
|
|
65
78
|
field: error.property?.replace(/^\//, "") || "unknown",
|
|
66
|
-
message:
|
|
79
|
+
message: message,
|
|
67
80
|
value: error.value,
|
|
68
81
|
});
|
|
69
82
|
}
|
|
@@ -75,7 +88,7 @@ export class ValidationPipe {
|
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
/**
|
|
78
|
-
* Custom Format Error Pipe
|
|
91
|
+
* Custom Format Error Pipe
|
|
79
92
|
* Formats validation errors as { [field]: [messages] }
|
|
80
93
|
*/
|
|
81
94
|
export class FormatErrorPipe extends ValidationPipe {
|
|
@@ -89,12 +102,18 @@ export class FormatErrorPipe extends ValidationPipe {
|
|
|
89
102
|
if (!formattedErrors[field]) {
|
|
90
103
|
formattedErrors[field] = [];
|
|
91
104
|
}
|
|
92
|
-
|
|
105
|
+
// Use custom errorMessage if available
|
|
106
|
+
const customMessage = err.schema?.errorMessage;
|
|
107
|
+
const message = customMessage || err.summary || err.message;
|
|
108
|
+
formattedErrors[field].push(message);
|
|
93
109
|
});
|
|
94
110
|
}
|
|
95
111
|
else {
|
|
96
112
|
const field = error.property?.replace(/^\//, "") || "unknown";
|
|
97
|
-
|
|
113
|
+
// Use custom errorMessage if available
|
|
114
|
+
const customMessage = error.schema?.errorMessage;
|
|
115
|
+
const message = customMessage || error.summary || error.message;
|
|
116
|
+
formattedErrors[field] = [message];
|
|
98
117
|
}
|
|
99
118
|
return {
|
|
100
119
|
statusCode: 400,
|
|
@@ -116,11 +135,17 @@ export class SimpleErrorPipe extends ValidationPipe {
|
|
|
116
135
|
const messages = [];
|
|
117
136
|
if (error.errors && error.errors.length > 0) {
|
|
118
137
|
error.errors.forEach((err) => {
|
|
119
|
-
|
|
138
|
+
// Use custom errorMessage if available
|
|
139
|
+
const customMessage = err.schema?.errorMessage;
|
|
140
|
+
const message = customMessage || err.summary || err.message;
|
|
141
|
+
messages.push(message);
|
|
120
142
|
});
|
|
121
143
|
}
|
|
122
144
|
else {
|
|
123
|
-
|
|
145
|
+
// Use custom errorMessage if available
|
|
146
|
+
const customMessage = error.schema?.errorMessage;
|
|
147
|
+
const message = customMessage || error.summary || error.message;
|
|
148
|
+
messages.push(message);
|
|
124
149
|
}
|
|
125
150
|
return {
|
|
126
151
|
statusCode: 400,
|
|
@@ -142,9 +167,12 @@ export class DetailedErrorPipe extends ValidationPipe {
|
|
|
142
167
|
const errors = [];
|
|
143
168
|
if (error.errors && error.errors.length > 0) {
|
|
144
169
|
error.errors.forEach((err) => {
|
|
170
|
+
// Use custom errorMessage if available
|
|
171
|
+
const customMessage = err.schema?.errorMessage;
|
|
172
|
+
const message = customMessage || err.summary || err.message;
|
|
145
173
|
errors.push({
|
|
146
174
|
field: err.path?.replace(/^\//, "") || "unknown",
|
|
147
|
-
message:
|
|
175
|
+
message: message,
|
|
148
176
|
value: err.value,
|
|
149
177
|
expected: err.schema?.format
|
|
150
178
|
? `${err.schema.type} (format: ${err.schema.format})`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Registry for storing and retrieving custom error messages
|
|
3
|
+
* This allows validation pipes to look up custom error messages from schemas
|
|
4
|
+
*/
|
|
5
|
+
declare class SchemaRegistry {
|
|
6
|
+
private static instance;
|
|
7
|
+
private errorMessages;
|
|
8
|
+
private routeSchemas;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): SchemaRegistry;
|
|
11
|
+
/**
|
|
12
|
+
* Register custom error messages for a schema
|
|
13
|
+
* @param schemaKey Unique key for the schema (e.g., class name + method)
|
|
14
|
+
* @param schema The TypeBox schema object
|
|
15
|
+
*/
|
|
16
|
+
registerSchema(schemaKey: string, schema: any): void;
|
|
17
|
+
/**
|
|
18
|
+
* Register route-to-schema mapping
|
|
19
|
+
* @param method HTTP method (GET, POST, etc.)
|
|
20
|
+
* @param path Route path
|
|
21
|
+
* @param schemaKey The schema key
|
|
22
|
+
* @param validationType Type of validation (body, query, params)
|
|
23
|
+
*/
|
|
24
|
+
registerRoute(method: string, path: string, schemaKey: string, validationType: "body" | "query" | "params"): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get schema key for a route and validation type
|
|
27
|
+
* @param method HTTP method
|
|
28
|
+
* @param path Route path (actual request path with values)
|
|
29
|
+
* @param validationType Type of validation
|
|
30
|
+
* @returns Schema key or undefined
|
|
31
|
+
*/
|
|
32
|
+
getSchemaKeyForRoute(method: string, path: string, validationType: "body" | "query" | "params"): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Recursively extract error messages from schema
|
|
35
|
+
*/
|
|
36
|
+
private extractErrorMessages;
|
|
37
|
+
/**
|
|
38
|
+
* Get custom error message for a field path
|
|
39
|
+
* @param schemaKey The schema key
|
|
40
|
+
* @param fieldPath The field path (e.g., "user.email")
|
|
41
|
+
* @returns Custom error message or undefined
|
|
42
|
+
*/
|
|
43
|
+
getErrorMessage(schemaKey: string, fieldPath: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Clear all registered schemas (useful for testing)
|
|
46
|
+
*/
|
|
47
|
+
clear(): void;
|
|
48
|
+
}
|
|
49
|
+
export declare const schemaRegistry: SchemaRegistry;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=schema-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-registry.d.ts","sourceRoot":"","sources":["../core/schema-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,cAAM,cAAc;IAClB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiB;IACxC,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,cAAc;IAOpC;;;;OAIG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;IAWpD;;;;;;OAMG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAC1C,IAAI;IAUP;;;;;;OAMG;IACH,oBAAoB,CAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAC1C,MAAM,GAAG,SAAS;IAyCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAyB5B;;;;;OAKG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAkBzE;;OAEG;IACH,KAAK,IAAI,IAAI;CAId;AAED,eAAO,MAAM,cAAc,gBAA+B,CAAC"}
|