wynkjs 1.0.4 → 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 +296 -313
- 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 +29 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +155 -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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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/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 +10 -8
|
@@ -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,20 @@ 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;
|
|
53
75
|
/**
|
|
54
76
|
* Start listening on a port
|
|
55
77
|
*/
|
|
@@ -58,6 +80,11 @@ export declare class WynkFramework {
|
|
|
58
80
|
* Get the underlying Elysia instance
|
|
59
81
|
*/
|
|
60
82
|
getApp(): Elysia;
|
|
83
|
+
/**
|
|
84
|
+
* Handle an HTTP request
|
|
85
|
+
* Automatically builds the app if not already built
|
|
86
|
+
*/
|
|
87
|
+
handle(request: Request): Promise<Response>;
|
|
61
88
|
/**
|
|
62
89
|
* Register a single controller
|
|
63
90
|
*/
|
|
@@ -73,6 +100,7 @@ export declare function createApp(options?: ApplicationOptions): WynkFramework;
|
|
|
73
100
|
export declare class WynkFactory {
|
|
74
101
|
static create(options?: ApplicationOptions & {
|
|
75
102
|
controllers?: any[];
|
|
103
|
+
providers?: any[];
|
|
76
104
|
}): WynkFramework;
|
|
77
105
|
}
|
|
78
106
|
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;;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,33 @@ 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
|
+
}
|
|
239
319
|
/**
|
|
240
320
|
* Start listening on a port
|
|
241
321
|
*/
|
|
@@ -243,6 +323,29 @@ export class WynkFramework {
|
|
|
243
323
|
await this.build();
|
|
244
324
|
this.app.listen(port);
|
|
245
325
|
console.log(`🚀 Application is running on http://localhost:${port}`);
|
|
326
|
+
// Register signal handlers only once to prevent memory leaks
|
|
327
|
+
if (!this.shutdownHandlersRegistered) {
|
|
328
|
+
this.shutdownHandlersRegistered = true;
|
|
329
|
+
// Setup graceful shutdown handlers
|
|
330
|
+
const gracefulShutdown = async (signal) => {
|
|
331
|
+
console.log(`\n📡 Received ${signal}, shutting down gracefully...`);
|
|
332
|
+
try {
|
|
333
|
+
// Cleanup providers (close database connections, etc.)
|
|
334
|
+
await this.destroyProviders();
|
|
335
|
+
// Stop the Elysia server
|
|
336
|
+
await this.app.stop();
|
|
337
|
+
console.log("👋 Application shut down successfully");
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
console.error("❌ Error during shutdown:", error);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
// Register signal handlers (only once)
|
|
346
|
+
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
347
|
+
process.once("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
348
|
+
}
|
|
246
349
|
}
|
|
247
350
|
/**
|
|
248
351
|
* Get the underlying Elysia instance
|
|
@@ -250,6 +353,16 @@ export class WynkFramework {
|
|
|
250
353
|
getApp() {
|
|
251
354
|
return this.app;
|
|
252
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Handle an HTTP request
|
|
358
|
+
* Automatically builds the app if not already built
|
|
359
|
+
*/
|
|
360
|
+
async handle(request) {
|
|
361
|
+
if (!this.isBuilt) {
|
|
362
|
+
await this.build();
|
|
363
|
+
}
|
|
364
|
+
return this.app.handle(request);
|
|
365
|
+
}
|
|
253
366
|
/**
|
|
254
367
|
* Register a single controller
|
|
255
368
|
*/
|
|
@@ -273,7 +386,11 @@ export class WynkFramework {
|
|
|
273
386
|
// Get @Use() middleware (simple pattern like user's working code)
|
|
274
387
|
const controllerUses = Reflect.getMetadata("uses", ControllerClass) || [];
|
|
275
388
|
for (const route of routes) {
|
|
276
|
-
|
|
389
|
+
// Apply global prefix to route path
|
|
390
|
+
let fullPath = basePath + route.path;
|
|
391
|
+
if (this.globalPrefix) {
|
|
392
|
+
fullPath = this.globalPrefix + fullPath;
|
|
393
|
+
}
|
|
277
394
|
const method = route.method.toLowerCase();
|
|
278
395
|
const methodName = route.methodName;
|
|
279
396
|
// Get method-specific metadata
|
|
@@ -284,19 +401,24 @@ export class WynkFramework {
|
|
|
284
401
|
// Get @Use() middleware for this method
|
|
285
402
|
const methodUses = Reflect.getMetadata("uses", instance, methodName) || [];
|
|
286
403
|
const params = Reflect.getMetadata("params", instance, methodName) || [];
|
|
404
|
+
// Sort params once during registration, not on every request
|
|
405
|
+
if (params.length > 0) {
|
|
406
|
+
params.sort((a, b) => a.index - b.index);
|
|
407
|
+
}
|
|
287
408
|
const httpCode = Reflect.getMetadata("route:httpCode", instance, methodName);
|
|
288
409
|
const headers = Reflect.getMetadata("route:headers", instance, methodName);
|
|
289
410
|
const redirect = Reflect.getMetadata("route:redirect", instance, methodName);
|
|
290
|
-
// Combine guards, interceptors, pipes, filters
|
|
411
|
+
// Combine guards, interceptors, pipes, filters
|
|
412
|
+
// Order: method -> controller -> global (method is innermost/closest to handler)
|
|
291
413
|
const allGuards = [
|
|
292
414
|
...this.globalGuards,
|
|
293
415
|
...controllerGuards,
|
|
294
416
|
...methodGuards,
|
|
295
417
|
];
|
|
296
418
|
const allInterceptors = [
|
|
297
|
-
...this.globalInterceptors,
|
|
298
|
-
...controllerInterceptors,
|
|
299
419
|
...methodInterceptors,
|
|
420
|
+
...controllerInterceptors,
|
|
421
|
+
...this.globalInterceptors,
|
|
300
422
|
];
|
|
301
423
|
const allPipes = [
|
|
302
424
|
...this.globalPipes,
|
|
@@ -314,178 +436,29 @@ export class WynkFramework {
|
|
|
314
436
|
if (bodySchema && !routeOptions.body) {
|
|
315
437
|
routeOptions.body = bodySchema;
|
|
316
438
|
}
|
|
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
|
-
};
|
|
439
|
+
// ⚡ ULTRA-OPTIMIZED HANDLER - Use specialized builder
|
|
440
|
+
// This eliminates nested async/await and IIFEs for maximum performance
|
|
441
|
+
const handler = buildUltraOptimizedHandler({
|
|
442
|
+
instance,
|
|
443
|
+
methodName,
|
|
444
|
+
ControllerClass,
|
|
445
|
+
params,
|
|
446
|
+
allGuards,
|
|
447
|
+
allInterceptors,
|
|
448
|
+
allPipes,
|
|
449
|
+
allFilters,
|
|
450
|
+
httpCode,
|
|
451
|
+
headers,
|
|
452
|
+
redirect,
|
|
453
|
+
routePath: route.path,
|
|
454
|
+
routeMethod: method.toUpperCase(),
|
|
455
|
+
});
|
|
472
456
|
// Wrap handler with @Use() middleware if present
|
|
473
457
|
// Combine controller and method middleware
|
|
474
458
|
const allUses = [...controllerUses, ...methodUses];
|
|
475
459
|
let finalHandler = handler;
|
|
476
460
|
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);
|
|
461
|
+
finalHandler = buildMiddlewareChain(handler, allUses);
|
|
489
462
|
}
|
|
490
463
|
// Register route with Elysia
|
|
491
464
|
const elysiaOptions = {};
|
|
@@ -536,6 +509,8 @@ export function createApp(options = {}) {
|
|
|
536
509
|
export class WynkFactory {
|
|
537
510
|
static create(options = {}) {
|
|
538
511
|
const app = new WynkFramework(options);
|
|
512
|
+
// Don't re-register providers if they were already added in constructor
|
|
513
|
+
// The constructor already handles options.providers
|
|
539
514
|
if (options.controllers) {
|
|
540
515
|
app.registerControllers(...options.controllers);
|
|
541
516
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Elysia } from "elysia";
|
|
2
|
+
/**
|
|
3
|
+
* Global Prefix Module for WynkJS Framework
|
|
4
|
+
* Adds a prefix to all routes in the application
|
|
5
|
+
* Separated from factory.ts for better maintainability
|
|
6
|
+
*/
|
|
7
|
+
export interface GlobalPrefixOptions {
|
|
8
|
+
prefix: string;
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Apply global prefix to all routes
|
|
13
|
+
* @param app - Elysia instance
|
|
14
|
+
* @param prefix - Prefix string (e.g., '/api', '/v1')
|
|
15
|
+
* @param options - Additional options like route exclusions
|
|
16
|
+
* @returns Modified Elysia instance
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyGlobalPrefix(app: Elysia, prefix: string | GlobalPrefixOptions): any;
|
|
19
|
+
/**
|
|
20
|
+
* Normalize prefix path
|
|
21
|
+
* - Ensures it starts with /
|
|
22
|
+
* - Removes trailing /
|
|
23
|
+
* - Validates format
|
|
24
|
+
* @param prefix - Raw prefix string
|
|
25
|
+
* @returns Normalized prefix
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizePrefixPath(prefix: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a route should be excluded from global prefix
|
|
30
|
+
* @param path - Route path
|
|
31
|
+
* @param excludedRoutes - List of routes to exclude
|
|
32
|
+
* @returns true if route should be excluded
|
|
33
|
+
*/
|
|
34
|
+
export declare function isRouteExcluded(path: string, excludedRoutes: string[]): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Apply global prefix to an existing Elysia app by wrapping it
|
|
37
|
+
* This is useful when you want to add prefix to already configured app
|
|
38
|
+
* @param app - Existing Elysia instance
|
|
39
|
+
* @param prefix - Prefix to apply
|
|
40
|
+
* @returns New Elysia instance with prefix
|
|
41
|
+
*/
|
|
42
|
+
export declare function wrapWithPrefix(app: Elysia, prefix: string | GlobalPrefixOptions): any;
|
|
43
|
+
/**
|
|
44
|
+
* Validate global prefix configuration
|
|
45
|
+
* @param prefix - Prefix configuration
|
|
46
|
+
* @returns true if valid, throws error if invalid
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateGlobalPrefix(prefix: string | GlobalPrefixOptions): boolean;
|
|
49
|
+
//# sourceMappingURL=global-prefix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-prefix.d.ts","sourceRoot":"","sources":["../core/global-prefix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GAAG,mBAAmB,GACnC,GAAG,CAkCL;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0B1D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAuBT;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GAAG,mBAAmB,GACnC,GAAG,CA0BL;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,mBAAmB,GACnC,OAAO,CA2CT"}
|