wynkjs 1.0.4 → 1.0.7
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 +361 -315
- package/dist/cors.d.ts +28 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +121 -0
- package/dist/decorators/exception.decorators.d.ts +1 -0
- package/dist/decorators/exception.decorators.d.ts.map +1 -1
- package/dist/decorators/exception.decorators.js +20 -3
- 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 +8 -3
- 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 +2 -2
- package/dist/decorators/pipe.decorators.js +2 -2
- package/dist/factory.d.ts +62 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +191 -180
- 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 +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- 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 +10 -10
- package/dist/pipes/validation.pipe.js +4 -4
- package/dist/plugins/compression.d.ts +75 -0
- package/dist/plugins/compression.d.ts.map +1 -0
- package/dist/plugins/compression.js +125 -0
- 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,6 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
/**
|
|
3
|
-
* Parameter Decorators for
|
|
3
|
+
* Parameter Decorators for WynkJS Framework
|
|
4
4
|
* Extract data from request context
|
|
5
5
|
*/
|
|
6
6
|
export type ParamType = "body" | "param" | "query" | "headers" | "request" | "response" | "context" | "user" | "file" | "files";
|
|
@@ -80,7 +80,7 @@ export declare function Res(): ParameterDecorator;
|
|
|
80
80
|
*/
|
|
81
81
|
export declare const Response: typeof Res;
|
|
82
82
|
/**
|
|
83
|
-
* @Context decorator - Injects full
|
|
83
|
+
* @Context decorator - Injects full WynkJS context
|
|
84
84
|
* @example
|
|
85
85
|
* @Get()
|
|
86
86
|
* getData(@Context() ctx: any) {}
|
|
@@ -66,12 +66,12 @@ export declare class ValidationPipe implements WynkPipeTransform {
|
|
|
66
66
|
});
|
|
67
67
|
transform(value: any, metadata: ArgumentMetadata): Promise<any>;
|
|
68
68
|
/**
|
|
69
|
-
* Format
|
|
69
|
+
* Format WynkJS validation error
|
|
70
70
|
* Called by ValidationExceptionFilter
|
|
71
71
|
*/
|
|
72
72
|
formatError(exception: any): any;
|
|
73
73
|
/**
|
|
74
|
-
* Parse validation error from
|
|
74
|
+
* Parse validation error from WynkJS exception
|
|
75
75
|
*/
|
|
76
76
|
private parseValidationError;
|
|
77
77
|
/**
|
|
@@ -92,7 +92,7 @@ export class ValidationPipe {
|
|
|
92
92
|
return value;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* Format
|
|
95
|
+
* Format WynkJS validation error
|
|
96
96
|
* Called by ValidationExceptionFilter
|
|
97
97
|
*/
|
|
98
98
|
formatError(exception) {
|
|
@@ -105,7 +105,7 @@ export class ValidationPipe {
|
|
|
105
105
|
return this.defaultFormatError(validationError);
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
* Parse validation error from
|
|
108
|
+
* Parse validation error from WynkJS exception
|
|
109
109
|
*/
|
|
110
110
|
parseValidationError(exception) {
|
|
111
111
|
let validationData;
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
2
|
import "reflect-metadata";
|
|
3
3
|
import { ErrorFormatter } from "./decorators/formatter.decorators";
|
|
4
|
+
import { CorsOptions } from "./cors";
|
|
4
5
|
/**
|
|
5
6
|
* Application Factory for WynkJS Framework
|
|
6
7
|
* Creates and configures Elysia app with all decorators support
|
|
7
8
|
*/
|
|
8
9
|
export interface ApplicationOptions {
|
|
9
|
-
cors?: boolean |
|
|
10
|
+
cors?: boolean | CorsOptions;
|
|
10
11
|
globalPrefix?: string;
|
|
11
12
|
logger?: boolean;
|
|
12
13
|
validationErrorFormatter?: ErrorFormatter;
|
|
14
|
+
providers?: any[];
|
|
13
15
|
}
|
|
14
16
|
export declare class WynkFramework {
|
|
15
17
|
private app;
|
|
16
18
|
private controllers;
|
|
19
|
+
private providers;
|
|
17
20
|
private globalGuards;
|
|
18
21
|
private globalInterceptors;
|
|
19
22
|
private globalPipes;
|
|
20
23
|
private globalFilters;
|
|
21
24
|
private validationFormatter?;
|
|
25
|
+
private shutdownHandlersRegistered;
|
|
26
|
+
private globalPrefix?;
|
|
27
|
+
private isBuilt;
|
|
22
28
|
constructor(options?: ApplicationOptions);
|
|
23
29
|
/**
|
|
24
30
|
* Static convenience creator to align with documentation examples
|
|
25
31
|
*/
|
|
26
32
|
static create(options?: ApplicationOptions & {
|
|
27
33
|
controllers?: any[];
|
|
34
|
+
providers?: any[];
|
|
28
35
|
}): WynkFramework;
|
|
36
|
+
/**
|
|
37
|
+
* Register providers with the application
|
|
38
|
+
* Providers are singleton services that are initialized when the app starts
|
|
39
|
+
*/
|
|
40
|
+
registerProviders(...providers: any[]): this;
|
|
29
41
|
/**
|
|
30
42
|
* Register controllers with the application
|
|
31
43
|
*/
|
|
@@ -46,10 +58,53 @@ export declare class WynkFramework {
|
|
|
46
58
|
* Register global exception filters
|
|
47
59
|
*/
|
|
48
60
|
useGlobalFilters(...filters: any[]): this;
|
|
61
|
+
/**
|
|
62
|
+
* Initialize all registered providers
|
|
63
|
+
* Providers with onModuleInit() method will be called
|
|
64
|
+
*/
|
|
65
|
+
private initializeProviders;
|
|
49
66
|
/**
|
|
50
67
|
* Build the application - register all routes
|
|
51
68
|
*/
|
|
52
69
|
build(): Promise<Elysia>;
|
|
70
|
+
/**
|
|
71
|
+
* Cleanup all providers when app shuts down
|
|
72
|
+
* Providers with onModuleDestroy() method will be called
|
|
73
|
+
*/
|
|
74
|
+
private destroyProviders;
|
|
75
|
+
/**
|
|
76
|
+
* Use an Elysia plugin or middleware (fully compatible with Elysia.js ecosystem)
|
|
77
|
+
*
|
|
78
|
+
* This method directly proxies to Elysia's use() method, making all Elysia plugins
|
|
79
|
+
* and middleware work seamlessly with WynkJS.
|
|
80
|
+
*
|
|
81
|
+
* @param plugin - Any Elysia plugin or instance
|
|
82
|
+
* @returns this for method chaining
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { WynkFactory } from "wynkjs";
|
|
87
|
+
* import { compression } from "wynkjs";
|
|
88
|
+
* import { cors } from "@elysiajs/cors";
|
|
89
|
+
* import { swagger } from "@elysiajs/swagger";
|
|
90
|
+
* import { jwt } from "@elysiajs/jwt";
|
|
91
|
+
*
|
|
92
|
+
* const app = WynkFactory.create({
|
|
93
|
+
* controllers: [UserController],
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* // Use WynkJS built-in plugins
|
|
97
|
+
* app.use(compression({ threshold: 1024 }));
|
|
98
|
+
*
|
|
99
|
+
* // Use any Elysia plugin from npm
|
|
100
|
+
* app.use(cors());
|
|
101
|
+
* app.use(swagger());
|
|
102
|
+
* app.use(jwt({ name: 'jwt', secret: 'secret' }));
|
|
103
|
+
*
|
|
104
|
+
* await app.listen(3000);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
use(...plugins: any[]): this;
|
|
53
108
|
/**
|
|
54
109
|
* Start listening on a port
|
|
55
110
|
*/
|
|
@@ -58,6 +113,11 @@ export declare class WynkFramework {
|
|
|
58
113
|
* Get the underlying Elysia instance
|
|
59
114
|
*/
|
|
60
115
|
getApp(): Elysia;
|
|
116
|
+
/**
|
|
117
|
+
* Handle an HTTP request
|
|
118
|
+
* Automatically builds the app if not already built
|
|
119
|
+
*/
|
|
120
|
+
handle(request: Request): Promise<Response>;
|
|
61
121
|
/**
|
|
62
122
|
* Register a single controller
|
|
63
123
|
*/
|
|
@@ -73,6 +133,7 @@ export declare function createApp(options?: ApplicationOptions): WynkFramework;
|
|
|
73
133
|
export declare class WynkFactory {
|
|
74
134
|
static create(options?: ApplicationOptions & {
|
|
75
135
|
controllers?: any[];
|
|
136
|
+
providers?: any[];
|
|
76
137
|
}): WynkFramework;
|
|
77
138
|
}
|
|
78
139
|
export { WynkFramework as ElysiaFramework };
|
package/dist/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../core/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,kBAAkB,CAAC;AAc1B,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../core/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,kBAAkB,CAAC;AAc1B,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAa,MAAM,QAAQ,CAAC;AAOhD;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB,CAAC,EAAE,cAAc,CAAC;IAC1C,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;CACnB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,mBAAmB,CAAC,CAAiB;IAC7C,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,kBAAuB;IA2L5C;;OAEG;IACH,MAAM,CAAC,MAAM,CACX,OAAO,GAAE,kBAAkB,GAAG;QAC5B,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC;QACpB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;KACd,GACL,aAAa;IAahB;;;OAGG;IACH,iBAAiB,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI;IAK5C;;OAEG;IACH,mBAAmB,CAAC,GAAG,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI;IAKhD;;OAEG;IACH,eAAe,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI;IAKvC;;OAEG;IACH,qBAAqB,CAAC,GAAG,YAAY,EAAE,GAAG,EAAE,GAAG,IAAI;IAKnD;;OAEG;IACH,cAAc,CAAC,GAAG,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI;IAKrC;;OAEG;IACH,gBAAgB,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI;IAKzC;;;OAGG;YACW,mBAAmB;IAkCjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwD9B;;;OAGG;YACW,gBAAgB;IA0B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI;IAK5B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCzC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAOjD;;OAEG;YACW,kBAAkB;CAwLjC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,kBAAuB,GAAG,aAAa,CAEzE;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,MAAM,CACX,OAAO,GAAE,kBAAkB,GAAG;QAC5B,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC;QACpB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;KACd,GACL,aAAa;CAYjB;AAGD,OAAO,EAAE,aAAa,IAAI,eAAe,EAAE,CAAC"}
|
package/dist/factory.js
CHANGED
|
@@ -2,22 +2,38 @@ import { Elysia } from "elysia";
|
|
|
2
2
|
import "reflect-metadata";
|
|
3
3
|
import { container } from "tsyringe";
|
|
4
4
|
import { Value } from "@sinclair/typebox/value";
|
|
5
|
-
import {
|
|
6
|
-
import { executeInterceptors } from "./decorators/interceptor.decorators";
|
|
7
|
-
import { executePipes } from "./decorators/pipe.decorators";
|
|
8
|
-
import { executeExceptionFilters, HttpException, } from "./decorators/exception.decorators";
|
|
5
|
+
import { executeExceptionFilters, } from "./decorators/exception.decorators";
|
|
9
6
|
import { schemaRegistry } from "./schema-registry";
|
|
7
|
+
import { setupCors } from "./cors";
|
|
8
|
+
import { normalizePrefixPath } from "./global-prefix";
|
|
9
|
+
import { buildUltraOptimizedHandler, buildMiddlewareChain, } from "./ultra-optimized-handler";
|
|
10
10
|
export class WynkFramework {
|
|
11
11
|
app;
|
|
12
12
|
controllers = [];
|
|
13
|
+
providers = []; // Store registered providers
|
|
13
14
|
globalGuards = [];
|
|
14
15
|
globalInterceptors = [];
|
|
15
16
|
globalPipes = [];
|
|
16
17
|
globalFilters = [];
|
|
17
18
|
validationFormatter;
|
|
19
|
+
shutdownHandlersRegistered = false; // Prevent duplicate signal handlers
|
|
20
|
+
globalPrefix; // Store global prefix for route registration
|
|
21
|
+
isBuilt = false; // Track if build() has been called
|
|
18
22
|
constructor(options = {}) {
|
|
19
23
|
this.app = new Elysia();
|
|
20
24
|
this.validationFormatter = options.validationErrorFormatter;
|
|
25
|
+
// Store global prefix for later use
|
|
26
|
+
if (options.globalPrefix) {
|
|
27
|
+
this.globalPrefix = normalizePrefixPath(options.globalPrefix);
|
|
28
|
+
}
|
|
29
|
+
// Register providers if provided
|
|
30
|
+
if (options.providers && options.providers.length > 0) {
|
|
31
|
+
this.providers.push(...options.providers);
|
|
32
|
+
}
|
|
33
|
+
// Apply CORS configuration
|
|
34
|
+
if (options.cors) {
|
|
35
|
+
setupCors(this.app, options.cors);
|
|
36
|
+
}
|
|
21
37
|
// Configure Elysia's error handling for validation errors
|
|
22
38
|
this.app.onError(({ code, error, set, request }) => {
|
|
23
39
|
// Handle ValidationError from Elysia
|
|
@@ -137,13 +153,10 @@ export class WynkFramework {
|
|
|
137
153
|
error: err.name || "Error",
|
|
138
154
|
};
|
|
139
155
|
});
|
|
140
|
-
// Apply CORS if enabled
|
|
141
|
-
if (options.cors) {
|
|
142
|
-
// CORS configuration would go here
|
|
143
|
-
}
|
|
144
156
|
// Apply global prefix if specified
|
|
145
157
|
if (options.globalPrefix) {
|
|
146
|
-
// Global prefix
|
|
158
|
+
// Global prefix is handled during route registration
|
|
159
|
+
console.log(`✅ Global prefix configured: ${this.globalPrefix}`);
|
|
147
160
|
}
|
|
148
161
|
return this;
|
|
149
162
|
}
|
|
@@ -152,11 +165,21 @@ export class WynkFramework {
|
|
|
152
165
|
*/
|
|
153
166
|
static create(options = {}) {
|
|
154
167
|
const app = new WynkFramework(options);
|
|
168
|
+
// Don't re-register providers/controllers if they were already added in constructor
|
|
169
|
+
// The constructor already handles options.providers
|
|
155
170
|
if (options.controllers && options.controllers.length) {
|
|
156
171
|
app.registerControllers(...options.controllers);
|
|
157
172
|
}
|
|
158
173
|
return app;
|
|
159
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Register providers with the application
|
|
177
|
+
* Providers are singleton services that are initialized when the app starts
|
|
178
|
+
*/
|
|
179
|
+
registerProviders(...providers) {
|
|
180
|
+
this.providers.push(...providers);
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
160
183
|
/**
|
|
161
184
|
* Register controllers with the application
|
|
162
185
|
*/
|
|
@@ -192,10 +215,42 @@ export class WynkFramework {
|
|
|
192
215
|
this.globalFilters.push(...filters);
|
|
193
216
|
return this;
|
|
194
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Initialize all registered providers
|
|
220
|
+
* Providers with onModuleInit() method will be called
|
|
221
|
+
*/
|
|
222
|
+
async initializeProviders() {
|
|
223
|
+
console.log(`🔧 Initializing ${this.providers.length} providers...`);
|
|
224
|
+
for (const ProviderClass of this.providers) {
|
|
225
|
+
try {
|
|
226
|
+
console.log(` ⚙️ Initializing provider: ${ProviderClass.name}`);
|
|
227
|
+
// Resolve provider instance from DI container
|
|
228
|
+
const instance = container.resolve(ProviderClass);
|
|
229
|
+
// Check if provider has onModuleInit lifecycle hook
|
|
230
|
+
if (typeof instance.onModuleInit === "function") {
|
|
231
|
+
await instance.onModuleInit();
|
|
232
|
+
console.log(` ✅ ${ProviderClass.name} initialized successfully`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Just register in container for injection
|
|
236
|
+
console.log(` ✅ ${ProviderClass.name} registered in container`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error(` ❌ Failed to initialize provider ${ProviderClass.name}:`, error);
|
|
241
|
+
throw new Error(`Provider initialization failed for ${ProviderClass.name}: ${error.message}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
console.log(`✅ All providers initialized successfully\n`);
|
|
245
|
+
}
|
|
195
246
|
/**
|
|
196
247
|
* Build the application - register all routes
|
|
197
248
|
*/
|
|
198
249
|
async build() {
|
|
250
|
+
// Initialize providers first (database connections, etc.)
|
|
251
|
+
if (this.providers.length > 0) {
|
|
252
|
+
await this.initializeProviders();
|
|
253
|
+
}
|
|
199
254
|
// Register global error handler if filters exist
|
|
200
255
|
if (this.globalFilters.length > 0) {
|
|
201
256
|
this.app.onError(async ({ error, set, request }) => {
|
|
@@ -234,8 +289,69 @@ export class WynkFramework {
|
|
|
234
289
|
for (const ControllerClass of this.controllers) {
|
|
235
290
|
await this.registerController(ControllerClass);
|
|
236
291
|
}
|
|
292
|
+
this.isBuilt = true;
|
|
237
293
|
return this.app;
|
|
238
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Cleanup all providers when app shuts down
|
|
297
|
+
* Providers with onModuleDestroy() method will be called
|
|
298
|
+
*/
|
|
299
|
+
async destroyProviders() {
|
|
300
|
+
console.log(`\n🔧 Cleaning up ${this.providers.length} providers...`);
|
|
301
|
+
for (const ProviderClass of this.providers) {
|
|
302
|
+
try {
|
|
303
|
+
// Resolve provider instance from DI container
|
|
304
|
+
const instance = container.resolve(ProviderClass);
|
|
305
|
+
// Check if provider has onModuleDestroy lifecycle hook
|
|
306
|
+
if (typeof instance.onModuleDestroy === "function") {
|
|
307
|
+
console.log(` 🧹 Destroying provider: ${ProviderClass.name}`);
|
|
308
|
+
await instance.onModuleDestroy();
|
|
309
|
+
console.log(` ✅ ${ProviderClass.name} destroyed successfully`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
console.error(` ❌ Failed to destroy provider ${ProviderClass.name}:`, error);
|
|
314
|
+
// Continue cleanup even if one provider fails
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
console.log(`✅ All providers cleaned up\n`);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Use an Elysia plugin or middleware (fully compatible with Elysia.js ecosystem)
|
|
321
|
+
*
|
|
322
|
+
* This method directly proxies to Elysia's use() method, making all Elysia plugins
|
|
323
|
+
* and middleware work seamlessly with WynkJS.
|
|
324
|
+
*
|
|
325
|
+
* @param plugin - Any Elysia plugin or instance
|
|
326
|
+
* @returns this for method chaining
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```typescript
|
|
330
|
+
* import { WynkFactory } from "wynkjs";
|
|
331
|
+
* import { compression } from "wynkjs";
|
|
332
|
+
* import { cors } from "@elysiajs/cors";
|
|
333
|
+
* import { swagger } from "@elysiajs/swagger";
|
|
334
|
+
* import { jwt } from "@elysiajs/jwt";
|
|
335
|
+
*
|
|
336
|
+
* const app = WynkFactory.create({
|
|
337
|
+
* controllers: [UserController],
|
|
338
|
+
* });
|
|
339
|
+
*
|
|
340
|
+
* // Use WynkJS built-in plugins
|
|
341
|
+
* app.use(compression({ threshold: 1024 }));
|
|
342
|
+
*
|
|
343
|
+
* // Use any Elysia plugin from npm
|
|
344
|
+
* app.use(cors());
|
|
345
|
+
* app.use(swagger());
|
|
346
|
+
* app.use(jwt({ name: 'jwt', secret: 'secret' }));
|
|
347
|
+
*
|
|
348
|
+
* await app.listen(3000);
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
use(...plugins) {
|
|
352
|
+
this.app.use(...plugins);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
239
355
|
/**
|
|
240
356
|
* Start listening on a port
|
|
241
357
|
*/
|
|
@@ -243,6 +359,29 @@ export class WynkFramework {
|
|
|
243
359
|
await this.build();
|
|
244
360
|
this.app.listen(port);
|
|
245
361
|
console.log(`🚀 Application is running on http://localhost:${port}`);
|
|
362
|
+
// Register signal handlers only once to prevent memory leaks
|
|
363
|
+
if (!this.shutdownHandlersRegistered) {
|
|
364
|
+
this.shutdownHandlersRegistered = true;
|
|
365
|
+
// Setup graceful shutdown handlers
|
|
366
|
+
const gracefulShutdown = async (signal) => {
|
|
367
|
+
console.log(`\n📡 Received ${signal}, shutting down gracefully...`);
|
|
368
|
+
try {
|
|
369
|
+
// Cleanup providers (close database connections, etc.)
|
|
370
|
+
await this.destroyProviders();
|
|
371
|
+
// Stop the Elysia server
|
|
372
|
+
await this.app.stop();
|
|
373
|
+
console.log("👋 Application shut down successfully");
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
console.error("❌ Error during shutdown:", error);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
// Register signal handlers (only once)
|
|
382
|
+
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
383
|
+
process.once("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
384
|
+
}
|
|
246
385
|
}
|
|
247
386
|
/**
|
|
248
387
|
* Get the underlying Elysia instance
|
|
@@ -250,6 +389,16 @@ export class WynkFramework {
|
|
|
250
389
|
getApp() {
|
|
251
390
|
return this.app;
|
|
252
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Handle an HTTP request
|
|
394
|
+
* Automatically builds the app if not already built
|
|
395
|
+
*/
|
|
396
|
+
async handle(request) {
|
|
397
|
+
if (!this.isBuilt) {
|
|
398
|
+
await this.build();
|
|
399
|
+
}
|
|
400
|
+
return this.app.handle(request);
|
|
401
|
+
}
|
|
253
402
|
/**
|
|
254
403
|
* Register a single controller
|
|
255
404
|
*/
|
|
@@ -273,7 +422,11 @@ export class WynkFramework {
|
|
|
273
422
|
// Get @Use() middleware (simple pattern like user's working code)
|
|
274
423
|
const controllerUses = Reflect.getMetadata("uses", ControllerClass) || [];
|
|
275
424
|
for (const route of routes) {
|
|
276
|
-
|
|
425
|
+
// Apply global prefix to route path
|
|
426
|
+
let fullPath = basePath + route.path;
|
|
427
|
+
if (this.globalPrefix) {
|
|
428
|
+
fullPath = this.globalPrefix + fullPath;
|
|
429
|
+
}
|
|
277
430
|
const method = route.method.toLowerCase();
|
|
278
431
|
const methodName = route.methodName;
|
|
279
432
|
// Get method-specific metadata
|
|
@@ -284,19 +437,24 @@ export class WynkFramework {
|
|
|
284
437
|
// Get @Use() middleware for this method
|
|
285
438
|
const methodUses = Reflect.getMetadata("uses", instance, methodName) || [];
|
|
286
439
|
const params = Reflect.getMetadata("params", instance, methodName) || [];
|
|
440
|
+
// Sort params once during registration, not on every request
|
|
441
|
+
if (params.length > 0) {
|
|
442
|
+
params.sort((a, b) => a.index - b.index);
|
|
443
|
+
}
|
|
287
444
|
const httpCode = Reflect.getMetadata("route:httpCode", instance, methodName);
|
|
288
445
|
const headers = Reflect.getMetadata("route:headers", instance, methodName);
|
|
289
446
|
const redirect = Reflect.getMetadata("route:redirect", instance, methodName);
|
|
290
|
-
// Combine guards, interceptors, pipes, filters
|
|
447
|
+
// Combine guards, interceptors, pipes, filters
|
|
448
|
+
// Order: method -> controller -> global (method is innermost/closest to handler)
|
|
291
449
|
const allGuards = [
|
|
292
450
|
...this.globalGuards,
|
|
293
451
|
...controllerGuards,
|
|
294
452
|
...methodGuards,
|
|
295
453
|
];
|
|
296
454
|
const allInterceptors = [
|
|
297
|
-
...this.globalInterceptors,
|
|
298
|
-
...controllerInterceptors,
|
|
299
455
|
...methodInterceptors,
|
|
456
|
+
...controllerInterceptors,
|
|
457
|
+
...this.globalInterceptors,
|
|
300
458
|
];
|
|
301
459
|
const allPipes = [
|
|
302
460
|
...this.globalPipes,
|
|
@@ -314,178 +472,29 @@ export class WynkFramework {
|
|
|
314
472
|
if (bodySchema && !routeOptions.body) {
|
|
315
473
|
routeOptions.body = bodySchema;
|
|
316
474
|
}
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// No parameter decorators, pass full context
|
|
335
|
-
args.push(ctx);
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
// Sort params by index
|
|
339
|
-
params.sort((a, b) => a.index - b.index);
|
|
340
|
-
for (const param of params) {
|
|
341
|
-
let value;
|
|
342
|
-
// Extract value based on type
|
|
343
|
-
switch (param.type) {
|
|
344
|
-
case "body":
|
|
345
|
-
value = param.data ? ctx.body?.[param.data] : ctx.body;
|
|
346
|
-
break;
|
|
347
|
-
case "param":
|
|
348
|
-
value = param.data ? ctx.params?.[param.data] : ctx.params;
|
|
349
|
-
break;
|
|
350
|
-
case "query":
|
|
351
|
-
value = param.data ? ctx.query?.[param.data] : ctx.query;
|
|
352
|
-
break;
|
|
353
|
-
case "headers":
|
|
354
|
-
value = param.data
|
|
355
|
-
? ctx.headers?.get?.(param.data) ||
|
|
356
|
-
ctx.request?.headers?.get?.(param.data)
|
|
357
|
-
: ctx.headers || ctx.request?.headers;
|
|
358
|
-
break;
|
|
359
|
-
case "request":
|
|
360
|
-
value = ctx.request || ctx;
|
|
361
|
-
break;
|
|
362
|
-
case "response":
|
|
363
|
-
value = ctx.set || ctx.response;
|
|
364
|
-
break;
|
|
365
|
-
case "context":
|
|
366
|
-
if (param.data) {
|
|
367
|
-
// Access nested property like "session.userId"
|
|
368
|
-
const keys = param.data.split(".");
|
|
369
|
-
value = keys.reduce((obj, key) => obj?.[key], ctx);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
value = ctx;
|
|
373
|
-
}
|
|
374
|
-
break;
|
|
375
|
-
case "user":
|
|
376
|
-
value = param.data ? ctx.user?.[param.data] : ctx.user;
|
|
377
|
-
break;
|
|
378
|
-
case "file":
|
|
379
|
-
value = ctx.body?.file || ctx.file;
|
|
380
|
-
break;
|
|
381
|
-
case "files":
|
|
382
|
-
value = ctx.body?.files || ctx.files;
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
// Apply pipes if any
|
|
386
|
-
if (param.pipes && param.pipes.length > 0) {
|
|
387
|
-
const metadata = {
|
|
388
|
-
type: param.type,
|
|
389
|
-
data: param.data,
|
|
390
|
-
};
|
|
391
|
-
value = await executePipes(param.pipes, value, metadata);
|
|
392
|
-
}
|
|
393
|
-
// Apply global/controller/method pipes
|
|
394
|
-
if (allPipes.length > 0) {
|
|
395
|
-
const metadata = {
|
|
396
|
-
type: param.type,
|
|
397
|
-
data: param.data,
|
|
398
|
-
};
|
|
399
|
-
value = await executePipes(allPipes, value, metadata);
|
|
400
|
-
}
|
|
401
|
-
args[param.index] = value;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Call controller method
|
|
405
|
-
return await instance[methodName].apply(instance, args);
|
|
406
|
-
};
|
|
407
|
-
// Execute interceptors
|
|
408
|
-
let result;
|
|
409
|
-
if (allInterceptors.length > 0) {
|
|
410
|
-
result = await executeInterceptors(allInterceptors, executionContext, executeHandler);
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
result = await executeHandler();
|
|
414
|
-
}
|
|
415
|
-
// Handle redirect
|
|
416
|
-
if (redirect) {
|
|
417
|
-
ctx.set.redirect = redirect.url;
|
|
418
|
-
ctx.set.status = redirect.statusCode;
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
// Set custom HTTP code
|
|
422
|
-
if (httpCode) {
|
|
423
|
-
ctx.set.status = httpCode;
|
|
424
|
-
}
|
|
425
|
-
// Set custom headers
|
|
426
|
-
if (headers) {
|
|
427
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
428
|
-
ctx.set.headers[key] = value;
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
return result;
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
console.log("🔴 ERROR CAUGHT IN FACTORY");
|
|
435
|
-
console.log("allFilters.length:", allFilters.length);
|
|
436
|
-
console.log("Error:", error?.message);
|
|
437
|
-
// Execute exception filters
|
|
438
|
-
if (allFilters.length > 0) {
|
|
439
|
-
console.log("✅ Executing exception filters...");
|
|
440
|
-
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
441
|
-
try {
|
|
442
|
-
const result = await executeExceptionFilters(allFilters, error, executionContext);
|
|
443
|
-
if (result) {
|
|
444
|
-
if (result.statusCode) {
|
|
445
|
-
ctx.set.status = result.statusCode;
|
|
446
|
-
}
|
|
447
|
-
return result;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
catch (filterError) {
|
|
451
|
-
// If filter doesn't handle it, continue to default error handling
|
|
452
|
-
error = filterError;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
console.log("❌ No filters registered for this route");
|
|
457
|
-
}
|
|
458
|
-
// Default error handling
|
|
459
|
-
if (error instanceof HttpException) {
|
|
460
|
-
ctx.set.status = error.getStatus();
|
|
461
|
-
return error.getResponse();
|
|
462
|
-
}
|
|
463
|
-
// Unknown error
|
|
464
|
-
ctx.set.status = 500;
|
|
465
|
-
return {
|
|
466
|
-
statusCode: 500,
|
|
467
|
-
message: error.message || "Internal server error",
|
|
468
|
-
error: "Internal Server Error",
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
};
|
|
475
|
+
// ⚡ ULTRA-OPTIMIZED HANDLER - Use specialized builder
|
|
476
|
+
// This eliminates nested async/await and IIFEs for maximum performance
|
|
477
|
+
const handler = buildUltraOptimizedHandler({
|
|
478
|
+
instance,
|
|
479
|
+
methodName,
|
|
480
|
+
ControllerClass,
|
|
481
|
+
params,
|
|
482
|
+
allGuards,
|
|
483
|
+
allInterceptors,
|
|
484
|
+
allPipes,
|
|
485
|
+
allFilters,
|
|
486
|
+
httpCode,
|
|
487
|
+
headers,
|
|
488
|
+
redirect,
|
|
489
|
+
routePath: route.path,
|
|
490
|
+
routeMethod: method.toUpperCase(),
|
|
491
|
+
});
|
|
472
492
|
// Wrap handler with @Use() middleware if present
|
|
473
493
|
// Combine controller and method middleware
|
|
474
494
|
const allUses = [...controllerUses, ...methodUses];
|
|
475
495
|
let finalHandler = handler;
|
|
476
496
|
if (allUses.length > 0) {
|
|
477
|
-
|
|
478
|
-
console.log(` Middleware count: ${allUses.length}`);
|
|
479
|
-
allUses.forEach((m, i) => console.log(` [${i}] ${m.name || "anonymous"}`));
|
|
480
|
-
// Build middleware chain using reduce (O(n) complexity)
|
|
481
|
-
// Builds from right to left: handler <- middleware[n-1] <- ... <- middleware[0]
|
|
482
|
-
// Executes left to right: middleware[0] -> ... -> middleware[n-1] -> handler
|
|
483
|
-
finalHandler = allUses.reduceRight((next, middleware, index) => {
|
|
484
|
-
return async (ctx) => {
|
|
485
|
-
console.log(`▶️ Executing middleware [${index}]: ${middleware.name || "anonymous"}`);
|
|
486
|
-
return await middleware(ctx, () => next(ctx));
|
|
487
|
-
};
|
|
488
|
-
}, handler);
|
|
497
|
+
finalHandler = buildMiddlewareChain(handler, allUses);
|
|
489
498
|
}
|
|
490
499
|
// Register route with Elysia
|
|
491
500
|
const elysiaOptions = {};
|
|
@@ -536,6 +545,8 @@ export function createApp(options = {}) {
|
|
|
536
545
|
export class WynkFactory {
|
|
537
546
|
static create(options = {}) {
|
|
538
547
|
const app = new WynkFramework(options);
|
|
548
|
+
// Don't re-register providers if they were already added in constructor
|
|
549
|
+
// The constructor already handles options.providers
|
|
539
550
|
if (options.controllers) {
|
|
540
551
|
app.registerControllers(...options.controllers);
|
|
541
552
|
}
|