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.
Files changed (63) hide show
  1. package/README.md +528 -348
  2. package/dist/cors.d.ts +28 -0
  3. package/dist/cors.d.ts.map +1 -0
  4. package/dist/cors.js +121 -0
  5. package/dist/database.d.ts +1 -1
  6. package/dist/database.js +1 -1
  7. package/dist/decorators/exception.advanced.d.ts +286 -18
  8. package/dist/decorators/exception.advanced.d.ts.map +1 -1
  9. package/dist/decorators/exception.advanced.js +410 -17
  10. package/dist/decorators/exception.decorators.d.ts +93 -2
  11. package/dist/decorators/exception.decorators.d.ts.map +1 -1
  12. package/dist/decorators/exception.decorators.js +140 -8
  13. package/dist/decorators/formatter.decorators.d.ts +93 -0
  14. package/dist/decorators/formatter.decorators.d.ts.map +1 -0
  15. package/dist/decorators/formatter.decorators.js +131 -0
  16. package/dist/decorators/guard.decorators.d.ts +2 -2
  17. package/dist/decorators/guard.decorators.d.ts.map +1 -1
  18. package/dist/decorators/guard.decorators.js +11 -5
  19. package/dist/decorators/http.decorators.d.ts +10 -4
  20. package/dist/decorators/http.decorators.d.ts.map +1 -1
  21. package/dist/decorators/http.decorators.js +9 -2
  22. package/dist/decorators/interceptor.advanced.d.ts +9 -9
  23. package/dist/decorators/interceptor.advanced.d.ts.map +1 -1
  24. package/dist/decorators/interceptor.advanced.js +7 -7
  25. package/dist/decorators/interceptor.decorators.d.ts +9 -7
  26. package/dist/decorators/interceptor.decorators.d.ts.map +1 -1
  27. package/dist/decorators/interceptor.decorators.js +29 -18
  28. package/dist/decorators/param.decorators.d.ts +2 -2
  29. package/dist/decorators/param.decorators.js +1 -1
  30. package/dist/decorators/pipe.decorators.d.ts +4 -4
  31. package/dist/decorators/pipe.decorators.d.ts.map +1 -1
  32. package/dist/decorators/pipe.decorators.js +4 -4
  33. package/dist/dto.js +1 -1
  34. package/dist/factory.d.ts +30 -2
  35. package/dist/factory.d.ts.map +1 -1
  36. package/dist/factory.js +210 -186
  37. package/dist/filters/exception.filters.d.ts +124 -0
  38. package/dist/filters/exception.filters.d.ts.map +1 -0
  39. package/dist/filters/exception.filters.js +208 -0
  40. package/dist/global-prefix.d.ts +49 -0
  41. package/dist/global-prefix.d.ts.map +1 -0
  42. package/dist/global-prefix.js +155 -0
  43. package/dist/index.d.ts +7 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +6 -1
  46. package/dist/interfaces/interceptor.interface.d.ts +15 -0
  47. package/dist/interfaces/interceptor.interface.d.ts.map +1 -0
  48. package/dist/interfaces/interceptor.interface.js +1 -0
  49. package/dist/optimized-handler.d.ts +31 -0
  50. package/dist/optimized-handler.d.ts.map +1 -0
  51. package/dist/optimized-handler.js +180 -0
  52. package/dist/pipes/validation.pipe.d.ts +12 -12
  53. package/dist/pipes/validation.pipe.d.ts.map +1 -1
  54. package/dist/pipes/validation.pipe.js +43 -15
  55. package/dist/schema-registry.d.ts +51 -0
  56. package/dist/schema-registry.d.ts.map +1 -0
  57. package/dist/schema-registry.js +134 -0
  58. package/dist/testing/index.d.ts +2 -2
  59. package/dist/testing/index.js +2 -2
  60. package/dist/ultra-optimized-handler.d.ts +51 -0
  61. package/dist/ultra-optimized-handler.d.ts.map +1 -0
  62. package/dist/ultra-optimized-handler.js +302 -0
  63. package/package.json +17 -10
package/dist/factory.js CHANGED
@@ -2,28 +2,51 @@ 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 { 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";
5
+ import { executeExceptionFilters, } from "./decorators/exception.decorators";
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";
9
10
  export class WynkFramework {
10
11
  app;
11
12
  controllers = [];
13
+ providers = []; // Store registered providers
12
14
  globalGuards = [];
13
15
  globalInterceptors = [];
14
16
  globalPipes = [];
15
17
  globalFilters = [];
16
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
17
22
  constructor(options = {}) {
18
23
  this.app = new Elysia();
19
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
+ }
20
37
  // Configure Elysia's error handling for validation errors
21
- this.app.onError(({ code, error, set }) => {
38
+ this.app.onError(({ code, error, set, request }) => {
22
39
  // Handle ValidationError from Elysia
23
40
  if (code === "VALIDATION" ||
24
41
  error?.constructor?.name === "ValidationError") {
25
42
  const validationError = error;
26
43
  set.status = 400;
44
+ // Get the validation type (body, query, params)
45
+ const validationType = validationError.on || "body";
46
+ // Try to find the schema key for this route
47
+ const method = request.method;
48
+ const path = new URL(request.url).pathname;
49
+ const schemaKey = schemaRegistry.getSchemaKeyForRoute(method, path, validationType);
27
50
  // Try to collect all validation errors using TypeBox
28
51
  const allErrors = {};
29
52
  // Check if we have the validator and value to collect all errors
@@ -38,7 +61,15 @@ export class WynkFramework {
38
61
  if (!allErrors[field]) {
39
62
  allErrors[field] = [];
40
63
  }
41
- allErrors[field].push(err.message || "Validation failed");
64
+ // Try to get custom error message from schema registry
65
+ let message = err.message || "Validation failed";
66
+ if (schemaKey) {
67
+ const customMessage = schemaRegistry.getErrorMessage(schemaKey, field);
68
+ if (customMessage) {
69
+ message = customMessage;
70
+ }
71
+ }
72
+ allErrors[field].push(message);
42
73
  });
43
74
  }
44
75
  else {
@@ -46,9 +77,16 @@ export class WynkFramework {
46
77
  const field = validationError.valueError?.path?.replace(/^\//, "") ||
47
78
  validationError.on ||
48
79
  "body";
49
- const message = validationError.customError ||
80
+ let message = validationError.customError ||
50
81
  validationError.valueError?.message ||
51
82
  "Validation failed";
83
+ // Try to get custom error message
84
+ if (schemaKey) {
85
+ const customMessage = schemaRegistry.getErrorMessage(schemaKey, field);
86
+ if (customMessage) {
87
+ message = customMessage;
88
+ }
89
+ }
52
90
  allErrors[field] = [message];
53
91
  }
54
92
  }
@@ -57,9 +95,16 @@ export class WynkFramework {
57
95
  const field = validationError.valueError?.path?.replace(/^\//, "") ||
58
96
  validationError.on ||
59
97
  "body";
60
- const message = validationError.customError ||
98
+ let message = validationError.customError ||
61
99
  validationError.valueError?.message ||
62
100
  "Validation failed";
101
+ // Try to get custom error message
102
+ if (schemaKey) {
103
+ const customMessage = schemaRegistry.getErrorMessage(schemaKey, field);
104
+ if (customMessage) {
105
+ message = customMessage;
106
+ }
107
+ }
63
108
  allErrors[field] = [message];
64
109
  }
65
110
  }
@@ -68,9 +113,16 @@ export class WynkFramework {
68
113
  const field = validationError.valueError?.path?.replace(/^\//, "") ||
69
114
  validationError.on ||
70
115
  "body";
71
- const message = validationError.customError ||
116
+ let message = validationError.customError ||
72
117
  validationError.valueError?.message ||
73
118
  "Validation failed";
119
+ // Try to get custom error message
120
+ if (schemaKey) {
121
+ const customMessage = schemaRegistry.getErrorMessage(schemaKey, field);
122
+ if (customMessage) {
123
+ message = customMessage;
124
+ }
125
+ }
74
126
  allErrors[field] = [message];
75
127
  }
76
128
  // If a custom formatter is provided, use it
@@ -101,13 +153,10 @@ export class WynkFramework {
101
153
  error: err.name || "Error",
102
154
  };
103
155
  });
104
- // Apply CORS if enabled
105
- if (options.cors) {
106
- // CORS configuration would go here
107
- }
108
156
  // Apply global prefix if specified
109
157
  if (options.globalPrefix) {
110
- // Global prefix handling
158
+ // Global prefix is handled during route registration
159
+ console.log(`✅ Global prefix configured: ${this.globalPrefix}`);
111
160
  }
112
161
  return this;
113
162
  }
@@ -116,11 +165,21 @@ export class WynkFramework {
116
165
  */
117
166
  static create(options = {}) {
118
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
119
170
  if (options.controllers && options.controllers.length) {
120
171
  app.registerControllers(...options.controllers);
121
172
  }
122
173
  return app;
123
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
+ }
124
183
  /**
125
184
  * Register controllers with the application
126
185
  */
@@ -156,10 +215,42 @@ export class WynkFramework {
156
215
  this.globalFilters.push(...filters);
157
216
  return this;
158
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
+ }
159
246
  /**
160
247
  * Build the application - register all routes
161
248
  */
162
249
  async build() {
250
+ // Initialize providers first (database connections, etc.)
251
+ if (this.providers.length > 0) {
252
+ await this.initializeProviders();
253
+ }
163
254
  // Register global error handler if filters exist
164
255
  if (this.globalFilters.length > 0) {
165
256
  this.app.onError(async ({ error, set, request }) => {
@@ -198,8 +289,33 @@ export class WynkFramework {
198
289
  for (const ControllerClass of this.controllers) {
199
290
  await this.registerController(ControllerClass);
200
291
  }
292
+ this.isBuilt = true;
201
293
  return this.app;
202
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
+ }
203
319
  /**
204
320
  * Start listening on a port
205
321
  */
@@ -207,6 +323,29 @@ export class WynkFramework {
207
323
  await this.build();
208
324
  this.app.listen(port);
209
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
+ }
210
349
  }
211
350
  /**
212
351
  * Get the underlying Elysia instance
@@ -214,6 +353,16 @@ export class WynkFramework {
214
353
  getApp() {
215
354
  return this.app;
216
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
+ }
217
366
  /**
218
367
  * Register a single controller
219
368
  */
@@ -237,7 +386,11 @@ export class WynkFramework {
237
386
  // Get @Use() middleware (simple pattern like user's working code)
238
387
  const controllerUses = Reflect.getMetadata("uses", ControllerClass) || [];
239
388
  for (const route of routes) {
240
- const fullPath = basePath + route.path;
389
+ // Apply global prefix to route path
390
+ let fullPath = basePath + route.path;
391
+ if (this.globalPrefix) {
392
+ fullPath = this.globalPrefix + fullPath;
393
+ }
241
394
  const method = route.method.toLowerCase();
242
395
  const methodName = route.methodName;
243
396
  // Get method-specific metadata
@@ -248,19 +401,24 @@ export class WynkFramework {
248
401
  // Get @Use() middleware for this method
249
402
  const methodUses = Reflect.getMetadata("uses", instance, methodName) || [];
250
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
+ }
251
408
  const httpCode = Reflect.getMetadata("route:httpCode", instance, methodName);
252
409
  const headers = Reflect.getMetadata("route:headers", instance, methodName);
253
410
  const redirect = Reflect.getMetadata("route:redirect", instance, methodName);
254
- // Combine guards, interceptors, pipes, filters (global -> controller -> method)
411
+ // Combine guards, interceptors, pipes, filters
412
+ // Order: method -> controller -> global (method is innermost/closest to handler)
255
413
  const allGuards = [
256
414
  ...this.globalGuards,
257
415
  ...controllerGuards,
258
416
  ...methodGuards,
259
417
  ];
260
418
  const allInterceptors = [
261
- ...this.globalInterceptors,
262
- ...controllerInterceptors,
263
419
  ...methodInterceptors,
420
+ ...controllerInterceptors,
421
+ ...this.globalInterceptors,
264
422
  ];
265
423
  const allPipes = [
266
424
  ...this.globalPipes,
@@ -278,189 +436,53 @@ export class WynkFramework {
278
436
  if (bodySchema && !routeOptions.body) {
279
437
  routeOptions.body = bodySchema;
280
438
  }
281
- // Create route handler
282
- const handler = async (ctx) => {
283
- try {
284
- // Create execution context
285
- const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
286
- // Execute guards
287
- if (allGuards.length > 0) {
288
- const canActivate = await executeGuards(allGuards, executionContext);
289
- if (!canActivate) {
290
- throw new HttpException("Forbidden", 403, "Access denied");
291
- }
292
- }
293
- // Prepare handler with parameters and pipes
294
- const executeHandler = async () => {
295
- // Build arguments for the controller method
296
- const args = [];
297
- if (params.length === 0) {
298
- // No parameter decorators, pass full context
299
- args.push(ctx);
300
- }
301
- else {
302
- // Sort params by index
303
- params.sort((a, b) => a.index - b.index);
304
- for (const param of params) {
305
- let value;
306
- // Extract value based on type
307
- switch (param.type) {
308
- case "body":
309
- value = param.data ? ctx.body?.[param.data] : ctx.body;
310
- break;
311
- case "param":
312
- value = param.data ? ctx.params?.[param.data] : ctx.params;
313
- break;
314
- case "query":
315
- value = param.data ? ctx.query?.[param.data] : ctx.query;
316
- break;
317
- case "headers":
318
- value = param.data
319
- ? ctx.headers?.get?.(param.data) ||
320
- ctx.request?.headers?.get?.(param.data)
321
- : ctx.headers || ctx.request?.headers;
322
- break;
323
- case "request":
324
- value = ctx.request || ctx;
325
- break;
326
- case "response":
327
- value = ctx.set || ctx.response;
328
- break;
329
- case "context":
330
- if (param.data) {
331
- // Access nested property like "session.userId"
332
- const keys = param.data.split(".");
333
- value = keys.reduce((obj, key) => obj?.[key], ctx);
334
- }
335
- else {
336
- value = ctx;
337
- }
338
- break;
339
- case "user":
340
- value = param.data ? ctx.user?.[param.data] : ctx.user;
341
- break;
342
- case "file":
343
- value = ctx.body?.file || ctx.file;
344
- break;
345
- case "files":
346
- value = ctx.body?.files || ctx.files;
347
- break;
348
- }
349
- // Apply pipes if any
350
- if (param.pipes && param.pipes.length > 0) {
351
- const metadata = {
352
- type: param.type,
353
- data: param.data,
354
- };
355
- value = await executePipes(param.pipes, value, metadata);
356
- }
357
- // Apply global/controller/method pipes
358
- if (allPipes.length > 0) {
359
- const metadata = {
360
- type: param.type,
361
- data: param.data,
362
- };
363
- value = await executePipes(allPipes, value, metadata);
364
- }
365
- args[param.index] = value;
366
- }
367
- }
368
- // Call controller method
369
- return await instance[methodName].apply(instance, args);
370
- };
371
- // Execute interceptors
372
- let result;
373
- if (allInterceptors.length > 0) {
374
- result = await executeInterceptors(allInterceptors, executionContext, executeHandler);
375
- }
376
- else {
377
- result = await executeHandler();
378
- }
379
- // Handle redirect
380
- if (redirect) {
381
- ctx.set.redirect = redirect.url;
382
- ctx.set.status = redirect.statusCode;
383
- return;
384
- }
385
- // Set custom HTTP code
386
- if (httpCode) {
387
- ctx.set.status = httpCode;
388
- }
389
- // Set custom headers
390
- if (headers) {
391
- Object.entries(headers).forEach(([key, value]) => {
392
- ctx.set.headers[key] = value;
393
- });
394
- }
395
- return result;
396
- }
397
- catch (error) {
398
- console.log("🔴 ERROR CAUGHT IN FACTORY");
399
- console.log("allFilters.length:", allFilters.length);
400
- console.log("Error:", error?.message);
401
- // Execute exception filters
402
- if (allFilters.length > 0) {
403
- console.log("✅ Executing exception filters...");
404
- const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
405
- try {
406
- const result = await executeExceptionFilters(allFilters, error, executionContext);
407
- if (result) {
408
- if (result.statusCode) {
409
- ctx.set.status = result.statusCode;
410
- }
411
- return result;
412
- }
413
- }
414
- catch (filterError) {
415
- // If filter doesn't handle it, continue to default error handling
416
- error = filterError;
417
- }
418
- }
419
- else {
420
- console.log("❌ No filters registered for this route");
421
- }
422
- // Default error handling
423
- if (error instanceof HttpException) {
424
- ctx.set.status = error.getStatus();
425
- return error.getResponse();
426
- }
427
- // Unknown error
428
- ctx.set.status = 500;
429
- return {
430
- statusCode: 500,
431
- message: error.message || "Internal server error",
432
- error: "Internal Server Error",
433
- };
434
- }
435
- };
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
+ });
436
456
  // Wrap handler with @Use() middleware if present
437
457
  // Combine controller and method middleware
438
458
  const allUses = [...controllerUses, ...methodUses];
439
459
  let finalHandler = handler;
440
460
  if (allUses.length > 0) {
441
- console.log(`🔗 Building middleware chain for ${method} ${fullPath}:`);
442
- console.log(` Middleware count: ${allUses.length}`);
443
- allUses.forEach((m, i) => console.log(` [${i}] ${m.name || "anonymous"}`));
444
- // Build middleware chain using reduce (O(n) complexity)
445
- // Builds from right to left: handler <- middleware[n-1] <- ... <- middleware[0]
446
- // Executes left to right: middleware[0] -> ... -> middleware[n-1] -> handler
447
- finalHandler = allUses.reduceRight((next, middleware, index) => {
448
- return async (ctx) => {
449
- console.log(`▶️ Executing middleware [${index}]: ${middleware.name || "anonymous"}`);
450
- return await middleware(ctx, () => next(ctx));
451
- };
452
- }, handler);
461
+ finalHandler = buildMiddlewareChain(handler, allUses);
453
462
  }
454
463
  // Register route with Elysia
455
464
  const elysiaOptions = {};
456
465
  if (routeOptions.body || bodySchema) {
457
- elysiaOptions.body = routeOptions.body || bodySchema;
466
+ const schema = routeOptions.body || bodySchema;
467
+ elysiaOptions.body = schema;
468
+ // Register schema with custom error messages
469
+ const schemaKey = `${ControllerClass.name}.${methodName}.body`;
470
+ schemaRegistry.registerSchema(schemaKey, schema);
471
+ schemaRegistry.registerRoute(method, fullPath, schemaKey, "body");
458
472
  }
459
473
  if (routeOptions.query) {
460
474
  elysiaOptions.query = routeOptions.query;
475
+ // Register query schema
476
+ const schemaKey = `${ControllerClass.name}.${methodName}.query`;
477
+ schemaRegistry.registerSchema(schemaKey, routeOptions.query);
478
+ schemaRegistry.registerRoute(method, fullPath, schemaKey, "query");
461
479
  }
462
480
  if (routeOptions.params) {
463
481
  elysiaOptions.params = routeOptions.params;
482
+ // Register params schema
483
+ const schemaKey = `${ControllerClass.name}.${methodName}.params`;
484
+ schemaRegistry.registerSchema(schemaKey, routeOptions.params);
485
+ schemaRegistry.registerRoute(method, fullPath, schemaKey, "params");
464
486
  }
465
487
  if (routeOptions.headers) {
466
488
  elysiaOptions.headers = routeOptions.headers;
@@ -487,6 +509,8 @@ export function createApp(options = {}) {
487
509
  export class WynkFactory {
488
510
  static create(options = {}) {
489
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
490
514
  if (options.controllers) {
491
515
  app.registerControllers(...options.controllers);
492
516
  }
@@ -0,0 +1,124 @@
1
+ import { NotFoundException, WynkExceptionFilter } from "../decorators/exception.decorators";
2
+ import { ExecutionContext } from "../decorators/guard.decorators";
3
+ /**
4
+ * Database Exception Filter - Handles database errors ONLY (not HttpExceptions)
5
+ *
6
+ * This filter catches actual database errors (like unique constraint violations,
7
+ * foreign key errors, etc.) and converts them to user-friendly messages.
8
+ *
9
+ * It will NOT catch HttpException or its subclasses (ConflictException, etc.)
10
+ * that you throw manually - those will pass through to be handled correctly.
11
+ *
12
+ * @example
13
+ * // Use as global filter
14
+ * app.useGlobalFilters(new DatabaseExceptionFilter());
15
+ *
16
+ * // Use on specific controller
17
+ * @UseFilters(DatabaseExceptionFilter)
18
+ * @Controller('/users')
19
+ * export class UserController {
20
+ * @Post()
21
+ * async create(@Body() data: any) {
22
+ * // If you throw manually, it passes through:
23
+ * if (await this.userExists(data.email)) {
24
+ * throw new ConflictException('User with this email already exists'); // ✅ Works correctly
25
+ * }
26
+ *
27
+ * // If database throws error, filter catches it:
28
+ * return await this.db.insert(users).values(data); // ❌ DB unique constraint error → caught by filter
29
+ * }
30
+ * }
31
+ *
32
+ * Handles these database error codes:
33
+ * - 23505: Unique constraint violation → 409 Conflict
34
+ * - 23503: Foreign key constraint violation → 400 Bad Request
35
+ * - 23502: Not null constraint violation → 400 Bad Request
36
+ */
37
+ export declare class DatabaseExceptionFilter implements WynkExceptionFilter {
38
+ catch(exception: any, context: ExecutionContext): {
39
+ statusCode: number;
40
+ error: string;
41
+ message: string;
42
+ timestamp: string;
43
+ path: any;
44
+ };
45
+ }
46
+ /**
47
+ * Not Found Exception Filter - Handles 404 errors with smart detection
48
+ *
49
+ * This filter is SMART - it only handles NotFoundExceptionFilter if:
50
+ * 1. The exception is NotFoundException, AND
51
+ * 2. No response data has been set (empty, null, empty array, or empty object)
52
+ *
53
+ * This allows it to be used globally without breaking routes that return
54
+ * legitimate empty responses or have their own error handling.
55
+ *
56
+ * @example
57
+ * // ✅ Can be used globally - smart filtering
58
+ * app.useGlobalFilters(
59
+ * new NotFoundExceptionFilter(), // Safe to use globally now!
60
+ * new GlobalExceptionFilter()
61
+ * );
62
+ *
63
+ */
64
+ export declare class NotFoundExceptionFilter implements WynkExceptionFilter<NotFoundException> {
65
+ catch(exception: NotFoundException, context: ExecutionContext): {
66
+ statusCode: number;
67
+ error: string;
68
+ message: string;
69
+ timestamp: string;
70
+ path: any;
71
+ suggestion: string;
72
+ };
73
+ /**
74
+ * Check if response has meaningful data
75
+ * Returns false for: null, undefined, {}, [], ""
76
+ * Returns true for: anything else
77
+ */
78
+ private hasResponseData;
79
+ }
80
+ /**
81
+ * File Upload Exception Filter - Handles file upload errors
82
+ * @example
83
+ * @UseFilters(FileUploadExceptionFilter)
84
+ * @Post('/upload')
85
+ * async upload(@UploadedFile() file: any) {}
86
+ */
87
+ export declare class FileUploadExceptionFilter implements WynkExceptionFilter {
88
+ catch(exception: any, context: ExecutionContext): {
89
+ statusCode: number;
90
+ error: string;
91
+ message: string;
92
+ timestamp: string;
93
+ path: any;
94
+ };
95
+ }
96
+ /**
97
+ * Global Exception Filter - Catches all unhandled exceptions
98
+ * @example
99
+ * app.useGlobalFilters(new GlobalExceptionFilter());
100
+ */
101
+ export declare class GlobalExceptionFilter implements WynkExceptionFilter {
102
+ catch(exception: any, context: ExecutionContext): {
103
+ stack?: any;
104
+ statusCode: any;
105
+ error: any;
106
+ message: any;
107
+ timestamp: string;
108
+ path: any;
109
+ };
110
+ }
111
+ /**
112
+ // ============================================================================
113
+ // KEY TAKEAWAYS
114
+ // ============================================================================
115
+ //
116
+ // 1. Order matters: Specific → General
117
+ // 2. Filters can re-throw exceptions they don't handle
118
+ // 3. HttpException and subclasses should pass through specialized filters
119
+ // 4. Global filters catch everything not handled by controller/method filters
120
+ // 5. Always have a GlobalExceptionFilter as the last filter (catch-all)
121
+ //
122
+ // ============================================================================
123
+ */
124
+ //# sourceMappingURL=exception.filters.d.ts.map