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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimized Handler Builder for WynkJS
|
|
3
|
+
* Builds route handlers conditionally - only adds overhead when features are actually used
|
|
4
|
+
*/
|
|
5
|
+
import { createExecutionContext, executeGuards, } from "./decorators/guard.decorators";
|
|
6
|
+
import { executeInterceptors } from "./decorators/interceptor.decorators";
|
|
7
|
+
import { executePipes } from "./decorators/pipe.decorators";
|
|
8
|
+
import { executeExceptionFilters, HttpException, } from "./decorators/exception.decorators";
|
|
9
|
+
/**
|
|
10
|
+
* Build optimized handler - only include overhead when features are used
|
|
11
|
+
* This eliminates conditional checks on every request for unused features
|
|
12
|
+
*/
|
|
13
|
+
export function buildOptimizedHandler(options) {
|
|
14
|
+
const { instance, methodName, ControllerClass, params, allGuards, allInterceptors, allPipes, allFilters, httpCode, headers, redirect, } = options;
|
|
15
|
+
// Pre-sort params once during registration (not on every request)
|
|
16
|
+
if (params.length > 0) {
|
|
17
|
+
params.sort((a, b) => a.index - b.index);
|
|
18
|
+
}
|
|
19
|
+
// Base parameter extractor and method executor
|
|
20
|
+
const extractParamsAndExecute = async (ctx) => {
|
|
21
|
+
if (params.length === 0) {
|
|
22
|
+
// No parameter decorators - pass full context
|
|
23
|
+
return await instance[methodName](ctx);
|
|
24
|
+
}
|
|
25
|
+
// Build arguments array
|
|
26
|
+
const args = new Array(params.length);
|
|
27
|
+
for (const param of params) {
|
|
28
|
+
let value;
|
|
29
|
+
// Extract value based on type (optimized switch)
|
|
30
|
+
switch (param.type) {
|
|
31
|
+
case "body":
|
|
32
|
+
value = param.data ? ctx.body?.[param.data] : ctx.body;
|
|
33
|
+
break;
|
|
34
|
+
case "param":
|
|
35
|
+
value = param.data ? ctx.params?.[param.data] : ctx.params;
|
|
36
|
+
break;
|
|
37
|
+
case "query":
|
|
38
|
+
value = param.data ? ctx.query?.[param.data] : ctx.query;
|
|
39
|
+
break;
|
|
40
|
+
case "headers":
|
|
41
|
+
value = param.data
|
|
42
|
+
? ctx.headers?.get?.(param.data) ||
|
|
43
|
+
ctx.request?.headers?.get?.(param.data)
|
|
44
|
+
: ctx.headers || ctx.request?.headers;
|
|
45
|
+
break;
|
|
46
|
+
case "request":
|
|
47
|
+
value = ctx.request || ctx;
|
|
48
|
+
break;
|
|
49
|
+
case "response":
|
|
50
|
+
value = ctx.set || ctx.response;
|
|
51
|
+
break;
|
|
52
|
+
case "context":
|
|
53
|
+
if (param.data) {
|
|
54
|
+
const keys = param.data.split(".");
|
|
55
|
+
value = keys.reduce((obj, key) => obj?.[key], ctx);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
value = ctx;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case "user":
|
|
62
|
+
value = param.data ? ctx.user?.[param.data] : ctx.user;
|
|
63
|
+
break;
|
|
64
|
+
case "file":
|
|
65
|
+
value = ctx.body?.file || ctx.file;
|
|
66
|
+
break;
|
|
67
|
+
case "files":
|
|
68
|
+
value = ctx.body?.files || ctx.files;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
// Apply parameter-specific pipes
|
|
72
|
+
if (param.pipes && param.pipes.length > 0) {
|
|
73
|
+
const metadata = {
|
|
74
|
+
type: param.type,
|
|
75
|
+
data: param.data,
|
|
76
|
+
};
|
|
77
|
+
value = await executePipes(param.pipes, value, metadata);
|
|
78
|
+
}
|
|
79
|
+
// Apply global/controller/method pipes
|
|
80
|
+
if (allPipes.length > 0) {
|
|
81
|
+
const metadata = {
|
|
82
|
+
type: param.type,
|
|
83
|
+
data: param.data,
|
|
84
|
+
};
|
|
85
|
+
value = await executePipes(allPipes, value, metadata);
|
|
86
|
+
}
|
|
87
|
+
args[param.index] = value;
|
|
88
|
+
}
|
|
89
|
+
return await instance[methodName].apply(instance, args);
|
|
90
|
+
};
|
|
91
|
+
// Build handler layers conditionally
|
|
92
|
+
let handler = extractParamsAndExecute;
|
|
93
|
+
// Wrap with interceptors (only if they exist)
|
|
94
|
+
if (allInterceptors.length > 0) {
|
|
95
|
+
const prevHandler = handler;
|
|
96
|
+
handler = async (ctx) => {
|
|
97
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
98
|
+
return await executeInterceptors(allInterceptors, executionContext, () => prevHandler(ctx));
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Wrap with guards (only if they exist)
|
|
102
|
+
if (allGuards.length > 0) {
|
|
103
|
+
const prevHandler = handler;
|
|
104
|
+
handler = async (ctx) => {
|
|
105
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
106
|
+
const canActivate = await executeGuards(allGuards, executionContext);
|
|
107
|
+
if (!canActivate) {
|
|
108
|
+
throw new HttpException("Forbidden", 403, "Access denied");
|
|
109
|
+
}
|
|
110
|
+
return await prevHandler(ctx);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Final handler with response modifications
|
|
114
|
+
const finalHandler = async (ctx) => {
|
|
115
|
+
try {
|
|
116
|
+
const result = await handler(ctx);
|
|
117
|
+
// Handle redirect (if configured)
|
|
118
|
+
if (redirect) {
|
|
119
|
+
ctx.set.redirect = redirect.url;
|
|
120
|
+
ctx.set.status = redirect.statusCode;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Set custom HTTP code (if configured)
|
|
124
|
+
if (httpCode) {
|
|
125
|
+
ctx.set.status = httpCode;
|
|
126
|
+
}
|
|
127
|
+
// Set custom headers (if configured)
|
|
128
|
+
if (headers) {
|
|
129
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
130
|
+
ctx.set.headers[key] = value;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
// Execute exception filters (only if they exist)
|
|
137
|
+
if (allFilters.length > 0) {
|
|
138
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
139
|
+
try {
|
|
140
|
+
const result = await executeExceptionFilters(allFilters, error, executionContext);
|
|
141
|
+
if (result) {
|
|
142
|
+
if (result.statusCode) {
|
|
143
|
+
ctx.set.status = result.statusCode;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (filterError) {
|
|
149
|
+
error = filterError;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Default error handling
|
|
153
|
+
if (error instanceof HttpException) {
|
|
154
|
+
ctx.set.status = error.getStatus();
|
|
155
|
+
return error.getResponse();
|
|
156
|
+
}
|
|
157
|
+
ctx.set.status = 500;
|
|
158
|
+
return {
|
|
159
|
+
statusCode: 500,
|
|
160
|
+
message: error.message || "Internal server error",
|
|
161
|
+
error: "Internal Server Error",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return finalHandler;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Build middleware chain - only if middleware exists
|
|
169
|
+
*/
|
|
170
|
+
export function buildMiddlewareChain(handler, middlewares) {
|
|
171
|
+
if (middlewares.length === 0) {
|
|
172
|
+
return handler;
|
|
173
|
+
}
|
|
174
|
+
// Build middleware chain (removed console.log for performance)
|
|
175
|
+
return middlewares.reduceRight((next, middleware) => {
|
|
176
|
+
return async (ctx) => {
|
|
177
|
+
return await middleware(ctx, () => next(ctx));
|
|
178
|
+
};
|
|
179
|
+
}, handler);
|
|
180
|
+
}
|
|
@@ -15,9 +15,9 @@ export interface ArgumentMetadata {
|
|
|
15
15
|
data?: string;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Validation Error structure (
|
|
18
|
+
* Validation Error structure (WynkJS format)
|
|
19
19
|
*/
|
|
20
|
-
export interface
|
|
20
|
+
export interface WynkJSValidationError {
|
|
21
21
|
type: "validation";
|
|
22
22
|
on: "body" | "params" | "query" | "headers";
|
|
23
23
|
property: string;
|
|
@@ -36,39 +36,39 @@ export interface ElysiaValidationError {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Base Validation Pipe
|
|
39
|
-
* Handles
|
|
39
|
+
* Handles WynkJS validation errors and formats them
|
|
40
40
|
*/
|
|
41
41
|
export declare class ValidationPipe implements ValidationPipeTransform {
|
|
42
42
|
protected options: {
|
|
43
|
-
exceptionFactory?: (errors:
|
|
43
|
+
exceptionFactory?: (errors: WynkJSValidationError) => any;
|
|
44
44
|
transform?: boolean;
|
|
45
45
|
whitelist?: boolean;
|
|
46
46
|
};
|
|
47
47
|
constructor(options?: {
|
|
48
|
-
exceptionFactory?: (errors:
|
|
48
|
+
exceptionFactory?: (errors: WynkJSValidationError) => any;
|
|
49
49
|
transform?: boolean;
|
|
50
50
|
whitelist?: boolean;
|
|
51
51
|
});
|
|
52
52
|
/**
|
|
53
|
-
* Transform method (not used for
|
|
53
|
+
* Transform method (not used for WynkJS validation, but required by interface)
|
|
54
54
|
*/
|
|
55
55
|
transform(value: any, metadata: ArgumentMetadata): any;
|
|
56
56
|
/**
|
|
57
|
-
* Format
|
|
57
|
+
* Format WynkJS validation error
|
|
58
58
|
* This is called by the exception filter
|
|
59
59
|
*/
|
|
60
60
|
formatError(exception: any): any;
|
|
61
61
|
/**
|
|
62
|
-
* Parse
|
|
62
|
+
* Parse WynkJS validation error from exception
|
|
63
63
|
*/
|
|
64
|
-
protected parseValidationError(exception: any):
|
|
64
|
+
protected parseValidationError(exception: any): WynkJSValidationError;
|
|
65
65
|
/**
|
|
66
|
-
* Default error formatting
|
|
66
|
+
* Default error formatting with custom errorMessage support
|
|
67
67
|
*/
|
|
68
|
-
protected defaultFormatError(error:
|
|
68
|
+
protected defaultFormatError(error: WynkJSValidationError, 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,7 +1,8 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
+
import { schemaRegistry } from "../schema-registry";
|
|
2
3
|
/**
|
|
3
4
|
* Base Validation Pipe
|
|
4
|
-
* Handles
|
|
5
|
+
* Handles WynkJS validation errors and formats them
|
|
5
6
|
*/
|
|
6
7
|
export class ValidationPipe {
|
|
7
8
|
options;
|
|
@@ -9,13 +10,13 @@ export class ValidationPipe {
|
|
|
9
10
|
this.options = options || {};
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
|
-
* Transform method (not used for
|
|
13
|
+
* Transform method (not used for WynkJS validation, but required by interface)
|
|
13
14
|
*/
|
|
14
15
|
transform(value, metadata) {
|
|
15
16
|
return value;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
|
-
* Format
|
|
19
|
+
* Format WynkJS validation error
|
|
19
20
|
* This is called by the exception filter
|
|
20
21
|
*/
|
|
21
22
|
formatError(exception) {
|
|
@@ -29,7 +30,7 @@ export class ValidationPipe {
|
|
|
29
30
|
return this.defaultFormatError(validationError);
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
|
-
* Parse
|
|
33
|
+
* Parse WynkJS validation error from exception
|
|
33
34
|
*/
|
|
34
35
|
parseValidationError(exception) {
|
|
35
36
|
let validationData;
|
|
@@ -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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
class SchemaRegistry {
|
|
6
|
+
static instance;
|
|
7
|
+
errorMessages = {};
|
|
8
|
+
routeSchemas = {};
|
|
9
|
+
constructor() { }
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!SchemaRegistry.instance) {
|
|
12
|
+
SchemaRegistry.instance = new SchemaRegistry();
|
|
13
|
+
}
|
|
14
|
+
return SchemaRegistry.instance;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register custom error messages for a schema
|
|
18
|
+
* @param schemaKey Unique key for the schema (e.g., class name + method)
|
|
19
|
+
* @param schema The TypeBox schema object
|
|
20
|
+
*/
|
|
21
|
+
registerSchema(schemaKey, schema) {
|
|
22
|
+
if (!schema || typeof schema !== "object")
|
|
23
|
+
return;
|
|
24
|
+
const messages = {};
|
|
25
|
+
this.extractErrorMessages(schema, "", messages);
|
|
26
|
+
if (Object.keys(messages).length > 0) {
|
|
27
|
+
this.errorMessages[schemaKey] = messages;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register route-to-schema mapping
|
|
32
|
+
* @param method HTTP method (GET, POST, etc.)
|
|
33
|
+
* @param path Route path
|
|
34
|
+
* @param schemaKey The schema key
|
|
35
|
+
* @param validationType Type of validation (body, query, params)
|
|
36
|
+
*/
|
|
37
|
+
registerRoute(method, path, schemaKey, validationType) {
|
|
38
|
+
const routeKey = `${method.toUpperCase()}:${path}`;
|
|
39
|
+
if (!this.routeSchemas[routeKey]) {
|
|
40
|
+
this.routeSchemas[routeKey] = [];
|
|
41
|
+
}
|
|
42
|
+
this.routeSchemas[routeKey].push({ schemaKey, validationType });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get schema key for a route and validation type
|
|
46
|
+
* @param method HTTP method
|
|
47
|
+
* @param path Route path (actual request path with values)
|
|
48
|
+
* @param validationType Type of validation
|
|
49
|
+
* @returns Schema key or undefined
|
|
50
|
+
*/
|
|
51
|
+
getSchemaKeyForRoute(method, path, validationType) {
|
|
52
|
+
// First try exact match
|
|
53
|
+
const exactKey = `${method.toUpperCase()}:${path}`;
|
|
54
|
+
if (this.routeSchemas[exactKey]) {
|
|
55
|
+
const found = this.routeSchemas[exactKey].find((s) => s.validationType === validationType);
|
|
56
|
+
if (found)
|
|
57
|
+
return found.schemaKey;
|
|
58
|
+
}
|
|
59
|
+
// If no exact match, try to match patterns
|
|
60
|
+
// e.g., request path "/users/10/100" should match pattern "/users/:id1/:id2"
|
|
61
|
+
for (const [routeKey, schemas] of Object.entries(this.routeSchemas)) {
|
|
62
|
+
// Split only on the first colon to separate method from path
|
|
63
|
+
const colonIndex = routeKey.indexOf(":");
|
|
64
|
+
const routeMethod = routeKey.substring(0, colonIndex);
|
|
65
|
+
const routePath = routeKey.substring(colonIndex + 1);
|
|
66
|
+
if (routeMethod !== method.toUpperCase())
|
|
67
|
+
continue;
|
|
68
|
+
// Check if the routePath is a pattern (contains :param)
|
|
69
|
+
if (routePath.includes(":")) {
|
|
70
|
+
// Convert pattern to regex
|
|
71
|
+
const pattern = routePath
|
|
72
|
+
.replace(/:[^/]+/g, "([^/]+)") // Replace :param with regex group
|
|
73
|
+
.replace(/\//g, "\\/"); // Escape slashes
|
|
74
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
75
|
+
if (regex.test(path)) {
|
|
76
|
+
const found = schemas.find((s) => s.validationType === validationType);
|
|
77
|
+
if (found)
|
|
78
|
+
return found.schemaKey;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Recursively extract error messages from schema
|
|
86
|
+
*/
|
|
87
|
+
extractErrorMessages(schema, path, messages) {
|
|
88
|
+
// Check for custom error message at current level
|
|
89
|
+
if (schema.error || schema.errorMessage) {
|
|
90
|
+
messages[path || "root"] = schema.error || schema.errorMessage;
|
|
91
|
+
}
|
|
92
|
+
// Recurse into object properties
|
|
93
|
+
if (schema.type === "object" && schema.properties) {
|
|
94
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
95
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
96
|
+
this.extractErrorMessages(value, newPath, messages);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Recurse into array items
|
|
100
|
+
if (schema.type === "array" && schema.items) {
|
|
101
|
+
const newPath = path ? `${path}[]` : "[]";
|
|
102
|
+
this.extractErrorMessages(schema.items, newPath, messages);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get custom error message for a field path
|
|
107
|
+
* @param schemaKey The schema key
|
|
108
|
+
* @param fieldPath The field path (e.g., "user.email")
|
|
109
|
+
* @returns Custom error message or undefined
|
|
110
|
+
*/
|
|
111
|
+
getErrorMessage(schemaKey, fieldPath) {
|
|
112
|
+
const schemaMessages = this.errorMessages[schemaKey];
|
|
113
|
+
if (!schemaMessages)
|
|
114
|
+
return undefined;
|
|
115
|
+
// Try exact match first
|
|
116
|
+
if (schemaMessages[fieldPath]) {
|
|
117
|
+
return schemaMessages[fieldPath];
|
|
118
|
+
}
|
|
119
|
+
// Try without array indices (e.g., "items.0.name" -> "items[].name")
|
|
120
|
+
const normalizedPath = fieldPath.replace(/\.\d+\./g, "[].");
|
|
121
|
+
if (schemaMessages[normalizedPath]) {
|
|
122
|
+
return schemaMessages[normalizedPath];
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Clear all registered schemas (useful for testing)
|
|
128
|
+
*/
|
|
129
|
+
clear() {
|
|
130
|
+
this.errorMessages = {};
|
|
131
|
+
this.routeSchemas = {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export const schemaRegistry = SchemaRegistry.getInstance();
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WynkJS Testing Module
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Built-in testing utilities for WynkJS applications
|
|
4
|
+
* Works with Bun's native test runner
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Test class for creating isolated testing modules
|
package/dist/testing/index.js
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ULTRA-OPTIMIZED Handler Builder for WynkJS
|
|
3
|
+
*
|
|
4
|
+
* KEY OPTIMIZATION: Eliminate nested async/await and IIFEs
|
|
5
|
+
*
|
|
6
|
+
* Performance findings:
|
|
7
|
+
* - Direct sync: 32ms per 1M calls
|
|
8
|
+
* - Single async: 42ms per 1M calls
|
|
9
|
+
* - Nested async + IIFE: 232ms per 1M calls (5.7x SLOWER!)
|
|
10
|
+
*
|
|
11
|
+
* Root cause: The current pattern creates an IIFE for every request:
|
|
12
|
+
* return (async () => { try { ... } catch { ... } })();
|
|
13
|
+
*
|
|
14
|
+
* This creates massive overhead because:
|
|
15
|
+
* 1. Creates a new Promise on EVERY request
|
|
16
|
+
* 2. Wraps it in a try-catch block
|
|
17
|
+
* 3. Immediately invokes it (IIFE pattern)
|
|
18
|
+
* 4. Then awaits the result
|
|
19
|
+
*
|
|
20
|
+
* Solution: Build a SINGLE async function at registration time,
|
|
21
|
+
* not nested functions that get called on every request.
|
|
22
|
+
*/
|
|
23
|
+
import { ParamMetadata } from "./decorators/param.decorators";
|
|
24
|
+
export interface HandlerBuildOptions {
|
|
25
|
+
instance: any;
|
|
26
|
+
methodName: string;
|
|
27
|
+
ControllerClass: any;
|
|
28
|
+
params: ParamMetadata[];
|
|
29
|
+
allGuards: any[];
|
|
30
|
+
allInterceptors: any[];
|
|
31
|
+
allPipes: any[];
|
|
32
|
+
allFilters: any[];
|
|
33
|
+
httpCode?: number;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
redirect?: {
|
|
36
|
+
url: string;
|
|
37
|
+
statusCode: number;
|
|
38
|
+
};
|
|
39
|
+
routePath: string;
|
|
40
|
+
routeMethod: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ultra-optimized handler builder
|
|
44
|
+
* Builds a SINGLE async function, not nested closures
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildUltraOptimizedHandler(options: HandlerBuildOptions): (ctx: any) => Promise<any>;
|
|
47
|
+
/**
|
|
48
|
+
* Build middleware chain - same as before
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildMiddlewareChain(handler: (ctx: any) => Promise<any>, middlewares: any[]): (ctx: any) => Promise<any>;
|
|
51
|
+
//# sourceMappingURL=ultra-optimized-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ultra-optimized-handler.d.ts","sourceRoot":"","sources":["../core/ultra-optimized-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,GAAG,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,GAAG,CAAC;IACrB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,eAAe,EAAE,GAAG,EAAE,CAAC;IACvB,QAAQ,EAAE,GAAG,EAAE,CAAC;IAChB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,mBAAmB,GAC3B,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAiU5B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EACnC,WAAW,EAAE,GAAG,EAAE,GACjB,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAU5B"}
|