wynkjs 1.0.0 → 1.0.1
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 +68 -26
- package/dist/decorators/exception.advanced.d.ts +43 -14
- package/dist/decorators/exception.advanced.d.ts.map +1 -1
- package/dist/decorators/exception.advanced.js +132 -15
- package/dist/decorators/http.decorators.d.ts +14 -12
- package/dist/decorators/http.decorators.d.ts.map +1 -1
- package/dist/decorators/http.decorators.js +34 -16
- package/dist/dto.d.ts +2 -1
- package/dist/dto.d.ts.map +1 -1
- package/dist/dto.js +33 -1
- package/dist/factory.d.ts +3 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +87 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,24 +30,25 @@ WynkJS combines the **speed of Elysia** with the **elegant decorator syntax of N
|
|
|
30
30
|
|
|
31
31
|
- 🚀 **20x Faster** - Built on Elysia, one of the fastest web frameworks
|
|
32
32
|
- 🎨 **Decorator-Based** - Familiar NestJS-style decorators
|
|
33
|
-
- 💉 **Dependency Injection** - Built-in DI
|
|
33
|
+
- 💉 **Dependency Injection** - Built-in DI (no need to import reflect-metadata!)
|
|
34
34
|
- 🔒 **Type-Safe** - Full TypeScript support
|
|
35
35
|
- 🎯 **Simple & Clean** - Easy to learn, powerful to use
|
|
36
36
|
- 🔌 **Middleware Support** - Guards, interceptors, pipes, filters
|
|
37
37
|
- ⚡ **Hot Reload** - Fast development with Bun
|
|
38
|
+
- 📦 **Single Import** - Everything from `wynkjs` (Injectable, Controller, Get, etc.)
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
41
42
|
## 📦 Installation
|
|
42
43
|
|
|
43
44
|
```bash
|
|
44
|
-
npm install wynkjs elysia
|
|
45
|
+
npm install wynkjs elysia
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
Or with Bun:
|
|
48
49
|
|
|
49
50
|
```bash
|
|
50
|
-
bun add wynkjs elysia
|
|
51
|
+
bun add wynkjs elysia
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
---
|
|
@@ -57,10 +58,9 @@ bun add wynkjs elysia reflect-metadata
|
|
|
57
58
|
### 1. Create Your First Controller
|
|
58
59
|
|
|
59
60
|
```typescript
|
|
60
|
-
import { Controller, Get, Post, Body } from "wynkjs";
|
|
61
|
-
import { injectable } from "tsyringe";
|
|
61
|
+
import { Controller, Get, Post, Body, Param, Injectable } from "wynkjs";
|
|
62
62
|
|
|
63
|
-
@
|
|
63
|
+
@Injectable()
|
|
64
64
|
@Controller("/users")
|
|
65
65
|
export class UserController {
|
|
66
66
|
@Get("/")
|
|
@@ -83,11 +83,10 @@ export class UserController {
|
|
|
83
83
|
### 2. Create Your Application
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
|
-
import "
|
|
87
|
-
import { WynkFramework } from "wynkjs";
|
|
86
|
+
import { WynkFactory } from "wynkjs";
|
|
88
87
|
import { UserController } from "./controllers/user.controller";
|
|
89
88
|
|
|
90
|
-
const app =
|
|
89
|
+
const app = WynkFactory.create({
|
|
91
90
|
controllers: [UserController],
|
|
92
91
|
});
|
|
93
92
|
|
|
@@ -96,6 +95,8 @@ await app.listen(3000);
|
|
|
96
95
|
console.log("🚀 Server running on http://localhost:3000");
|
|
97
96
|
```
|
|
98
97
|
|
|
98
|
+
**Note**: No need to import `reflect-metadata` - WynkJS handles it automatically! ✨
|
|
99
|
+
|
|
99
100
|
### 3. Run Your Server
|
|
100
101
|
|
|
101
102
|
```bash
|
|
@@ -211,19 +212,20 @@ export class AdminController {
|
|
|
211
212
|
### 💉 Dependency Injection
|
|
212
213
|
|
|
213
214
|
```typescript
|
|
214
|
-
|
|
215
|
+
// Option 1: Capital-cased (recommended for consistency)
|
|
216
|
+
import { Injectable, Inject, Controller, Get } from "wynkjs";
|
|
215
217
|
|
|
216
|
-
@
|
|
218
|
+
@Injectable()
|
|
217
219
|
export class UserService {
|
|
218
220
|
async findAll() {
|
|
219
221
|
return [{ id: 1, name: "Alice" }];
|
|
220
222
|
}
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
@
|
|
225
|
+
@Injectable()
|
|
224
226
|
@Controller("/users")
|
|
225
227
|
export class UserController {
|
|
226
|
-
constructor(
|
|
228
|
+
constructor(private userService: UserService) {}
|
|
227
229
|
|
|
228
230
|
@Get("/")
|
|
229
231
|
async list() {
|
|
@@ -233,6 +235,22 @@ export class UserController {
|
|
|
233
235
|
}
|
|
234
236
|
```
|
|
235
237
|
|
|
238
|
+
```typescript
|
|
239
|
+
// Option 2: Lowercase (tsyringe convention)
|
|
240
|
+
import { Injectable, Inject, Controller, Get } from "wynkjs";
|
|
241
|
+
|
|
242
|
+
@Injectable()
|
|
243
|
+
export class UserService {
|
|
244
|
+
async findAll() {
|
|
245
|
+
return [{ id: 1, name: "Alice" }];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Available DI decorators**:
|
|
251
|
+
|
|
252
|
+
- Capital: `Injectable`, `Inject`, `Singleton`, `AutoInjectable`, `Container`
|
|
253
|
+
|
|
236
254
|
### 🗃️ Database Integration (Drizzle ORM)
|
|
237
255
|
|
|
238
256
|
```typescript
|
|
@@ -250,7 +268,7 @@ const userTable = pgTable("users", {
|
|
|
250
268
|
// Register tables
|
|
251
269
|
registerTables({ userTable });
|
|
252
270
|
|
|
253
|
-
@
|
|
271
|
+
@Injectable()
|
|
254
272
|
export class UserService {
|
|
255
273
|
private db = drizzle(process.env.DATABASE_URL);
|
|
256
274
|
|
|
@@ -264,14 +282,16 @@ export class UserService {
|
|
|
264
282
|
|
|
265
283
|
### 📝 Request Validation
|
|
266
284
|
|
|
285
|
+
WynkJS provides automatic request validation with customizable error formats:
|
|
286
|
+
|
|
267
287
|
```typescript
|
|
268
|
-
import { Post, Body,
|
|
269
|
-
import { t } from "elysia";
|
|
288
|
+
import { Post, Body, DTO, WynkFactory, FormatErrorFormatter } from "wynkjs";
|
|
270
289
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
290
|
+
// Define your DTO with validation rules
|
|
291
|
+
const CreateUserDTO = DTO.Strict({
|
|
292
|
+
name: DTO.String({ minLength: 2 }),
|
|
293
|
+
email: DTO.String({ format: "email", minLength: 5 }),
|
|
294
|
+
age: DTO.Number({ minimum: 18 }),
|
|
275
295
|
});
|
|
276
296
|
|
|
277
297
|
@Controller("/users")
|
|
@@ -285,8 +305,21 @@ export class UserController {
|
|
|
285
305
|
return { message: "User created", data: body };
|
|
286
306
|
}
|
|
287
307
|
}
|
|
308
|
+
|
|
309
|
+
// Choose your validation error format
|
|
310
|
+
const app = WynkFactory.create({
|
|
311
|
+
controllers: [UserController],
|
|
312
|
+
// Option 1: Default format (recommended)
|
|
313
|
+
// validationErrorFormatter: new FormatErrorFormatter(), // NestJS-style
|
|
314
|
+
// Option 2: Simple array format
|
|
315
|
+
// validationErrorFormatter: new SimpleErrorFormatter(),
|
|
316
|
+
// Option 3: Detailed format with field info
|
|
317
|
+
// validationErrorFormatter: new DetailedErrorFormatter(),
|
|
318
|
+
});
|
|
288
319
|
```
|
|
289
320
|
|
|
321
|
+
**See [VALIDATION_FORMATTERS.md](./docs/VALIDATION_FORMATTERS.md) for all available error formats**
|
|
322
|
+
|
|
290
323
|
### 🔄 Multiple Middleware
|
|
291
324
|
|
|
292
325
|
```typescript
|
|
@@ -357,16 +390,26 @@ console.log("🚀 Server running on http://localhost:3000");
|
|
|
357
390
|
|
|
358
391
|
```typescript
|
|
359
392
|
// controllers/user.controller.ts
|
|
360
|
-
import {
|
|
361
|
-
|
|
393
|
+
import {
|
|
394
|
+
Injectable,
|
|
395
|
+
Inject,
|
|
396
|
+
Controller,
|
|
397
|
+
Get,
|
|
398
|
+
Post,
|
|
399
|
+
Put,
|
|
400
|
+
Delete,
|
|
401
|
+
Body,
|
|
402
|
+
Param,
|
|
403
|
+
Use,
|
|
404
|
+
} from "wynkjs";
|
|
362
405
|
import { UserService } from "../services/user.service";
|
|
363
406
|
import { jwtGuard } from "../middleware/jwt.guard";
|
|
364
407
|
|
|
365
|
-
@
|
|
408
|
+
@Injectable()
|
|
366
409
|
@Controller("/users")
|
|
367
410
|
@Use(jwtGuard)
|
|
368
411
|
export class UserController {
|
|
369
|
-
constructor(@
|
|
412
|
+
constructor(@Inject(UserService) private userService: UserService) {}
|
|
370
413
|
|
|
371
414
|
@Get("/")
|
|
372
415
|
async list() {
|
|
@@ -402,9 +445,8 @@ export class UserController {
|
|
|
402
445
|
|
|
403
446
|
```typescript
|
|
404
447
|
// services/user.service.ts
|
|
405
|
-
import { injectable } from "tsyringe";
|
|
406
448
|
|
|
407
|
-
@
|
|
449
|
+
@Injectable()
|
|
408
450
|
export class UserService {
|
|
409
451
|
private users = [
|
|
410
452
|
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
@@ -1,27 +1,56 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { WynkExceptionFilter } from "./exception.decorators";
|
|
3
3
|
import { ExecutionContext } from "./guard.decorators";
|
|
4
|
-
import { NotFoundException,
|
|
4
|
+
import { NotFoundException, UnauthorizedException, ForbiddenException } from "./exception.decorators";
|
|
5
5
|
/**
|
|
6
6
|
* Advanced Exception Filters for WynkJS Framework
|
|
7
7
|
* Specialized filters for different error scenarios
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Error Formatter Interface
|
|
11
|
+
* Used by ValidationExceptionFilter to format validation errors
|
|
12
|
+
*/
|
|
13
|
+
export interface ErrorFormatter {
|
|
14
|
+
format(validationError: any): any;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* FormatErrorFormatter - Formats as { field: [messages] } like NestJS
|
|
18
|
+
*/
|
|
19
|
+
export declare class FormatErrorFormatter implements ErrorFormatter {
|
|
20
|
+
format(error: any): any;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* SimpleErrorFormatter - Formats as simple array of messages
|
|
24
|
+
*/
|
|
25
|
+
export declare class SimpleErrorFormatter implements ErrorFormatter {
|
|
26
|
+
format(error: any): any;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* DetailedErrorFormatter - Formats with detailed field info
|
|
30
|
+
*/
|
|
31
|
+
export declare class DetailedErrorFormatter implements ErrorFormatter {
|
|
32
|
+
format(error: any): any;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validation Exception Filter - Handles validation errors with customizable formatting
|
|
11
36
|
* @example
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
37
|
+
* // With FormatErrorFormatter (NestJS-style)
|
|
38
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new FormatErrorFormatter()));
|
|
39
|
+
*
|
|
40
|
+
* // With SimpleErrorFormatter
|
|
41
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new SimpleErrorFormatter()));
|
|
42
|
+
*
|
|
43
|
+
* // With DetailedErrorFormatter
|
|
44
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new DetailedErrorFormatter()));
|
|
45
|
+
*
|
|
46
|
+
* // Without formatter (default detailed format)
|
|
47
|
+
* app.useGlobalFilters(new ValidationExceptionFilter());
|
|
15
48
|
*/
|
|
16
|
-
export declare class ValidationExceptionFilter implements WynkExceptionFilter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
errors: any;
|
|
22
|
-
timestamp: string;
|
|
23
|
-
path: any;
|
|
24
|
-
};
|
|
49
|
+
export declare class ValidationExceptionFilter implements WynkExceptionFilter {
|
|
50
|
+
private formatter;
|
|
51
|
+
constructor(formatter?: ErrorFormatter);
|
|
52
|
+
catch(exception: any, context: ExecutionContext): any;
|
|
53
|
+
private isValidationError;
|
|
25
54
|
}
|
|
26
55
|
/**
|
|
27
56
|
* Database Exception Filter - Handles database errors
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exception.advanced.d.ts","sourceRoot":"","sources":["../../core/decorators/exception.advanced.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAEL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"exception.advanced.d.ts","sourceRoot":"","sources":["../../core/decorators/exception.advanced.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAEL,iBAAiB,EAEjB,qBAAqB,EACrB,kBAAkB,EAEnB,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,eAAe,EAAE,GAAG,GAAG,GAAG,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IACzD,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG;CAsBxB;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IACzD,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG;CAiBxB;AAED;;GAEG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG;CAgCxB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,OAAO,CAAC,SAAS,CAA+B;gBAEpC,SAAS,CAAC,EAAE,cAAc;IAItC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;IAgC/C,OAAO,CAAC,iBAAiB;CAiB1B;AAED;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;CAiChD;AAED;;;;;;GAMG;AACH,qBAAa,6BACX,YAAW,mBAAmB,CAAC,qBAAqB,CAAC;IAErD,KAAK,CAAC,SAAS,EAAE,qBAAqB,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;CAalE;AAED;;;;;;GAMG;AACH,qBAAa,4BACX,YAAW,mBAAmB,CAAC,kBAAkB,CAAC;IAElD,KAAK,CAAC,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;CAe/D;AAED;;;;;;GAMG;AACH,qBAAa,uBACX,YAAW,mBAAmB,CAAC,iBAAiB,CAAC;IAEjD,KAAK,CAAC,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;CAa9D;AAED;;;;;;GAMG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAClE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;;CAchD;AAED;;;;;;GAMG;AACH,qBAAa,4BAA6B,YAAW,mBAAmB;IACtE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;;CAchD;AAED;;;;;;GAMG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;CAsBhD;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,mBAAmB;IAC/D,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB;;;;;;;;CA2BhD"}
|
|
@@ -1,27 +1,144 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Specialized filters for different error scenarios
|
|
3
|
+
* FormatErrorFormatter - Formats as { field: [messages] } like NestJS
|
|
5
4
|
*/
|
|
5
|
+
export class FormatErrorFormatter {
|
|
6
|
+
format(error) {
|
|
7
|
+
const formattedErrors = {};
|
|
8
|
+
if (error.errors && error.errors.length > 0) {
|
|
9
|
+
error.errors.forEach((err) => {
|
|
10
|
+
const field = err.path?.replace(/^\//, "") || "unknown";
|
|
11
|
+
if (!formattedErrors[field]) {
|
|
12
|
+
formattedErrors[field] = [];
|
|
13
|
+
}
|
|
14
|
+
formattedErrors[field].push(err.summary || err.message);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const field = error.property?.replace(/^\//, "") || "unknown";
|
|
19
|
+
formattedErrors[field] = [error.summary || error.message];
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
statusCode: 400,
|
|
23
|
+
message: "Validation failed",
|
|
24
|
+
errors: formattedErrors,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* SimpleErrorFormatter - Formats as simple array of messages
|
|
30
|
+
*/
|
|
31
|
+
export class SimpleErrorFormatter {
|
|
32
|
+
format(error) {
|
|
33
|
+
const messages = [];
|
|
34
|
+
if (error.errors && error.errors.length > 0) {
|
|
35
|
+
error.errors.forEach((err) => {
|
|
36
|
+
messages.push(err.summary || err.message);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
messages.push(error.summary || error.message);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
statusCode: 400,
|
|
44
|
+
message: "Validation failed",
|
|
45
|
+
errors: messages,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
6
49
|
/**
|
|
7
|
-
*
|
|
50
|
+
* DetailedErrorFormatter - Formats with detailed field info
|
|
51
|
+
*/
|
|
52
|
+
export class DetailedErrorFormatter {
|
|
53
|
+
format(error) {
|
|
54
|
+
const errors = [];
|
|
55
|
+
if (error.errors && error.errors.length > 0) {
|
|
56
|
+
error.errors.forEach((err) => {
|
|
57
|
+
errors.push({
|
|
58
|
+
field: err.path?.replace(/^\//, "") || "unknown",
|
|
59
|
+
message: err.summary || err.message,
|
|
60
|
+
value: err.value,
|
|
61
|
+
expected: err.schema,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
errors.push({
|
|
67
|
+
field: error.property?.replace(/^\//, "") || "unknown",
|
|
68
|
+
message: error.summary || error.message,
|
|
69
|
+
value: error.found,
|
|
70
|
+
expected: error.expected,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
statusCode: 400,
|
|
75
|
+
message: "Validation failed",
|
|
76
|
+
errors,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validation Exception Filter - Handles validation errors with customizable formatting
|
|
8
82
|
* @example
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
83
|
+
* // With FormatErrorFormatter (NestJS-style)
|
|
84
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new FormatErrorFormatter()));
|
|
85
|
+
*
|
|
86
|
+
* // With SimpleErrorFormatter
|
|
87
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new SimpleErrorFormatter()));
|
|
88
|
+
*
|
|
89
|
+
* // With DetailedErrorFormatter
|
|
90
|
+
* app.useGlobalFilters(new ValidationExceptionFilter(new DetailedErrorFormatter()));
|
|
91
|
+
*
|
|
92
|
+
* // Without formatter (default detailed format)
|
|
93
|
+
* app.useGlobalFilters(new ValidationExceptionFilter());
|
|
12
94
|
*/
|
|
13
95
|
export class ValidationExceptionFilter {
|
|
96
|
+
formatter = null;
|
|
97
|
+
constructor(formatter) {
|
|
98
|
+
this.formatter = formatter || null;
|
|
99
|
+
}
|
|
14
100
|
catch(exception, context) {
|
|
15
|
-
const response = context.getResponse();
|
|
16
101
|
const request = context.getRequest();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
102
|
+
// Check if this is a validation error from Elysia
|
|
103
|
+
const isValidationError = this.isValidationError(exception);
|
|
104
|
+
if (!isValidationError) {
|
|
105
|
+
// Not a validation error, re-throw to let other filters handle it
|
|
106
|
+
throw exception;
|
|
107
|
+
}
|
|
108
|
+
// Parse the validation error
|
|
109
|
+
let validationError;
|
|
110
|
+
if (typeof exception.message === "string") {
|
|
111
|
+
try {
|
|
112
|
+
validationError = JSON.parse(exception.message);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
validationError = { message: exception.message };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
validationError = exception;
|
|
120
|
+
}
|
|
121
|
+
// Format the error using the provided formatter
|
|
122
|
+
if (this.formatter) {
|
|
123
|
+
return this.formatter.format(validationError);
|
|
124
|
+
}
|
|
125
|
+
// Default format (detailed)
|
|
126
|
+
return new DetailedErrorFormatter().format(validationError);
|
|
127
|
+
}
|
|
128
|
+
isValidationError(exception) {
|
|
129
|
+
if (!exception)
|
|
130
|
+
return false;
|
|
131
|
+
// Check if it's a validation error by looking at the error structure
|
|
132
|
+
if (exception.message && typeof exception.message === "string") {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(exception.message);
|
|
135
|
+
return parsed.type === "validation";
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return (exception.type === "validation" || exception.code === "VALIDATION_ERROR");
|
|
25
142
|
}
|
|
26
143
|
}
|
|
27
144
|
/**
|
|
@@ -21,13 +21,15 @@ export interface RouteOptions {
|
|
|
21
21
|
export declare function Controller(path?: string): ClassDecorator;
|
|
22
22
|
/**
|
|
23
23
|
* HTTP GET decorator
|
|
24
|
-
* @param
|
|
25
|
-
* @param options Route configuration options
|
|
24
|
+
* @param pathOrOptions Route path or options with DTO
|
|
26
25
|
* @example
|
|
27
26
|
* @Get('/profile')
|
|
28
27
|
* async getProfile() {}
|
|
28
|
+
*
|
|
29
|
+
* @Get({ path: '/:id', params: UserIdDTO, query: QueryDTO })
|
|
30
|
+
* async findOne(@Param('id') id: string, @Query() query: any) {}
|
|
29
31
|
*/
|
|
30
|
-
export declare function Get(
|
|
32
|
+
export declare function Get(pathOrOptions?: string | RouteOptions): MethodDecorator;
|
|
31
33
|
/**
|
|
32
34
|
* @Post decorator - Define a POST route
|
|
33
35
|
* @param pathOrOptions Optional route path or options with DTO
|
|
@@ -66,25 +68,25 @@ export declare function Put(pathOrOptions?: string | RouteOptions): MethodDecora
|
|
|
66
68
|
export declare function Patch(pathOrOptions?: string | RouteOptions): MethodDecorator;
|
|
67
69
|
/**
|
|
68
70
|
* HTTP DELETE decorator
|
|
69
|
-
* @param
|
|
70
|
-
* @param options Route configuration options
|
|
71
|
+
* @param pathOrOptions Route path or options with DTO
|
|
71
72
|
* @example
|
|
72
73
|
* @Delete('/:id')
|
|
73
74
|
* async remove(@Param('id') id: string) {}
|
|
75
|
+
*
|
|
76
|
+
* @Delete({ path: '/:id', params: UserIdDTO })
|
|
77
|
+
* async remove(@Param('id') id: string) {}
|
|
74
78
|
*/
|
|
75
|
-
export declare function Delete(
|
|
79
|
+
export declare function Delete(pathOrOptions?: string | RouteOptions): MethodDecorator;
|
|
76
80
|
/**
|
|
77
81
|
* HTTP OPTIONS decorator
|
|
78
|
-
* @param
|
|
79
|
-
* @param options Route configuration options
|
|
82
|
+
* @param pathOrOptions Route path or options with DTO
|
|
80
83
|
*/
|
|
81
|
-
export declare function Options(
|
|
84
|
+
export declare function Options(pathOrOptions?: string | RouteOptions): MethodDecorator;
|
|
82
85
|
/**
|
|
83
86
|
* HTTP HEAD decorator
|
|
84
|
-
* @param
|
|
85
|
-
* @param options Route configuration options
|
|
87
|
+
* @param pathOrOptions Route path or options with DTO
|
|
86
88
|
*/
|
|
87
|
-
export declare function Head(
|
|
89
|
+
export declare function Head(pathOrOptions?: string | RouteOptions): MethodDecorator;
|
|
88
90
|
/**
|
|
89
91
|
* Set custom HTTP status code for a route
|
|
90
92
|
* @param code HTTP status code
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.decorators.d.ts","sourceRoot":"","sources":["../../core/decorators/http.decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAW,GAAG,cAAc,CAK5D;AAED
|
|
1
|
+
{"version":3,"file":"http.decorators.d.ts","sourceRoot":"","sources":["../../core/decorators/http.decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAW,GAAG,cAAc,CAK5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,GAAG,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,GAAG,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM7E;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GACpC,eAAe,CAMjB;AAED;;;GAGG;AACH,wBAAgB,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,eAAe,CAM3E;AAmDD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAStD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAYnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAY,GACvB,eAAe,CAcjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,EAAE,GAAG,cAAc,GAAG,eAAe,CAuB3E"}
|
|
@@ -14,14 +14,20 @@ export function Controller(path = "") {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* HTTP GET decorator
|
|
17
|
-
* @param
|
|
18
|
-
* @param options Route configuration options
|
|
17
|
+
* @param pathOrOptions Route path or options with DTO
|
|
19
18
|
* @example
|
|
20
19
|
* @Get('/profile')
|
|
21
20
|
* async getProfile() {}
|
|
21
|
+
*
|
|
22
|
+
* @Get({ path: '/:id', params: UserIdDTO, query: QueryDTO })
|
|
23
|
+
* async findOne(@Param('id') id: string, @Query() query: any) {}
|
|
22
24
|
*/
|
|
23
|
-
export function Get(
|
|
24
|
-
|
|
25
|
+
export function Get(pathOrOptions) {
|
|
26
|
+
if (typeof pathOrOptions === "string") {
|
|
27
|
+
return createRouteDecorator("GET", pathOrOptions);
|
|
28
|
+
}
|
|
29
|
+
const options = pathOrOptions || {};
|
|
30
|
+
return createRouteDecorator("GET", options.path || "", options);
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
27
33
|
* @Post decorator - Define a POST route
|
|
@@ -79,30 +85,42 @@ export function Patch(pathOrOptions) {
|
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
81
87
|
* HTTP DELETE decorator
|
|
82
|
-
* @param
|
|
83
|
-
* @param options Route configuration options
|
|
88
|
+
* @param pathOrOptions Route path or options with DTO
|
|
84
89
|
* @example
|
|
85
90
|
* @Delete('/:id')
|
|
86
91
|
* async remove(@Param('id') id: string) {}
|
|
92
|
+
*
|
|
93
|
+
* @Delete({ path: '/:id', params: UserIdDTO })
|
|
94
|
+
* async remove(@Param('id') id: string) {}
|
|
87
95
|
*/
|
|
88
|
-
export function Delete(
|
|
89
|
-
|
|
96
|
+
export function Delete(pathOrOptions) {
|
|
97
|
+
if (typeof pathOrOptions === "string") {
|
|
98
|
+
return createRouteDecorator("DELETE", pathOrOptions);
|
|
99
|
+
}
|
|
100
|
+
const options = pathOrOptions || {};
|
|
101
|
+
return createRouteDecorator("DELETE", options.path || "", options);
|
|
90
102
|
}
|
|
91
103
|
/**
|
|
92
104
|
* HTTP OPTIONS decorator
|
|
93
|
-
* @param
|
|
94
|
-
* @param options Route configuration options
|
|
105
|
+
* @param pathOrOptions Route path or options with DTO
|
|
95
106
|
*/
|
|
96
|
-
export function Options(
|
|
97
|
-
|
|
107
|
+
export function Options(pathOrOptions) {
|
|
108
|
+
if (typeof pathOrOptions === "string") {
|
|
109
|
+
return createRouteDecorator("OPTIONS", pathOrOptions);
|
|
110
|
+
}
|
|
111
|
+
const options = pathOrOptions || {};
|
|
112
|
+
return createRouteDecorator("OPTIONS", options.path || "", options);
|
|
98
113
|
}
|
|
99
114
|
/**
|
|
100
115
|
* HTTP HEAD decorator
|
|
101
|
-
* @param
|
|
102
|
-
* @param options Route configuration options
|
|
116
|
+
* @param pathOrOptions Route path or options with DTO
|
|
103
117
|
*/
|
|
104
|
-
export function Head(
|
|
105
|
-
|
|
118
|
+
export function Head(pathOrOptions) {
|
|
119
|
+
if (typeof pathOrOptions === "string") {
|
|
120
|
+
return createRouteDecorator("HEAD", pathOrOptions);
|
|
121
|
+
}
|
|
122
|
+
const options = pathOrOptions || {};
|
|
123
|
+
return createRouteDecorator("HEAD", options.path || "", options);
|
|
106
124
|
}
|
|
107
125
|
/**
|
|
108
126
|
* Helper function to create route decorators
|
package/dist/dto.d.ts
CHANGED
|
@@ -35,8 +35,9 @@ export declare const CommonDTO: {
|
|
|
35
35
|
Name: (options?: {}) => import("@sinclair/typebox").TString;
|
|
36
36
|
/**
|
|
37
37
|
* Email validation
|
|
38
|
+
* Uses format: email which validates basic email structure
|
|
38
39
|
*/
|
|
39
|
-
Email: (options?:
|
|
40
|
+
Email: (options?: any) => import("@sinclair/typebox").TString;
|
|
40
41
|
/**
|
|
41
42
|
* Password validation (min 6 characters)
|
|
42
43
|
*/
|
package/dist/dto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../core/dto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,eAAO,MAAM,GAAG,EAAE,GAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../core/dto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,eAAO,MAAM,GAAG,EAAE,GAAO,CAAC;AAiC1B;;GAEG;AACH,eAAO,MAAM,SAAS;IACpB;;OAEG;;IAGH;;;OAGG;sBACc,GAAG;IAOpB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAQH;;OAEG;;IAGH;;OAEG;;;;;IAOH;;OAEG;mBACY,MAAM,EAAE;;;;CAKxB,CAAC;AAEF;;GAEG;AACH,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/dto.js
CHANGED
|
@@ -31,6 +31,33 @@ import { t } from "elysia";
|
|
|
31
31
|
// Consumers still get runtime validation; for static typing, prefer using
|
|
32
32
|
// `Static<TSchema>` from Elysia which we re-export below.
|
|
33
33
|
export const DTO = t;
|
|
34
|
+
/**
|
|
35
|
+
* Helper to attach strict validation to a DTO Object schema
|
|
36
|
+
* Sets additionalProperties to false which makes Elysia strip unknown fields
|
|
37
|
+
*
|
|
38
|
+
* NOTE: Elysia's default behavior is to STRIP additional properties, not throw errors.
|
|
39
|
+
* This is secure by default. If you need to throw errors on unknown properties,
|
|
40
|
+
* use the approach documented in STRICT_VALIDATION.md
|
|
41
|
+
*
|
|
42
|
+
* @param properties - Object properties schema
|
|
43
|
+
* @param options - Additional schema options
|
|
44
|
+
* @returns TypeBox schema with additionalProperties: false
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* export const UserDTO = DTO.Strict({
|
|
48
|
+
* email: DTO.String(),
|
|
49
|
+
* age: DTO.Number()
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // Request: { email: "test@test.com", age: 25, extra: "field" }
|
|
53
|
+
* // Result: { email: "test@test.com", age: 25 } ← extra stripped (secure)
|
|
54
|
+
*/
|
|
55
|
+
DTO.Strict = (properties, options = {}) => {
|
|
56
|
+
return t.Object(properties, {
|
|
57
|
+
...options,
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
34
61
|
/**
|
|
35
62
|
* Common DTO patterns for quick use
|
|
36
63
|
*/
|
|
@@ -41,8 +68,13 @@ export const CommonDTO = {
|
|
|
41
68
|
Name: (options = {}) => t.String({ minLength: 2, maxLength: 50, ...options }),
|
|
42
69
|
/**
|
|
43
70
|
* Email validation
|
|
71
|
+
* Uses format: email which validates basic email structure
|
|
44
72
|
*/
|
|
45
|
-
Email: (options = {}) => t.String({
|
|
73
|
+
Email: (options = {}) => t.String({
|
|
74
|
+
format: "email",
|
|
75
|
+
error: "Invalid email address",
|
|
76
|
+
...options,
|
|
77
|
+
}),
|
|
46
78
|
/**
|
|
47
79
|
* Password validation (min 6 characters)
|
|
48
80
|
*/
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
2
|
import "reflect-metadata";
|
|
3
|
+
import { ErrorFormatter } from "./decorators/exception.advanced";
|
|
3
4
|
/**
|
|
4
5
|
* Application Factory for WynkJS Framework
|
|
5
6
|
* Creates and configures Elysia app with all decorators support
|
|
@@ -8,6 +9,7 @@ export interface ApplicationOptions {
|
|
|
8
9
|
cors?: boolean | any;
|
|
9
10
|
globalPrefix?: string;
|
|
10
11
|
logger?: boolean;
|
|
12
|
+
validationErrorFormatter?: ErrorFormatter;
|
|
11
13
|
}
|
|
12
14
|
export declare class WynkFramework {
|
|
13
15
|
private app;
|
|
@@ -16,6 +18,7 @@ export declare class WynkFramework {
|
|
|
16
18
|
private globalInterceptors;
|
|
17
19
|
private globalPipes;
|
|
18
20
|
private globalFilters;
|
|
21
|
+
private validationFormatter?;
|
|
19
22
|
constructor(options?: ApplicationOptions);
|
|
20
23
|
/**
|
|
21
24
|
* Static convenience creator to align with documentation examples
|
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;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,GAAG,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,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,iCAAiC,CAAC;AAEjE;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,GAAG,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB,CAAC,EAAE,cAAc,CAAC;CAC3C;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,WAAW,CAAa;IAChC,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;gBAEjC,OAAO,GAAE,kBAAuB;IAmH5C;;OAEG;IACH,MAAM,CAAC,MAAM,CACX,OAAO,GAAE,kBAAkB,GAAG;QAAE,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;KAAO,GACzD,aAAa;IAQhB;;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;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAkD9B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;YACW,kBAAkB;CAuVjC;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;QAAE,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;KAAO,GACzD,aAAa;CASjB;AAGD,OAAO,EAAE,aAAa,IAAI,eAAe,EAAE,CAAC"}
|
package/dist/factory.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
2
|
import "reflect-metadata";
|
|
3
3
|
import { container } from "tsyringe";
|
|
4
|
+
import { Value } from "@sinclair/typebox/value";
|
|
4
5
|
import { createExecutionContext, executeGuards, } from "./decorators/guard.decorators";
|
|
5
6
|
import { executeInterceptors } from "./decorators/interceptor.decorators";
|
|
6
7
|
import { executePipes } from "./decorators/pipe.decorators";
|
|
@@ -12,8 +13,94 @@ export class WynkFramework {
|
|
|
12
13
|
globalInterceptors = [];
|
|
13
14
|
globalPipes = [];
|
|
14
15
|
globalFilters = [];
|
|
16
|
+
validationFormatter;
|
|
15
17
|
constructor(options = {}) {
|
|
16
18
|
this.app = new Elysia();
|
|
19
|
+
this.validationFormatter = options.validationErrorFormatter;
|
|
20
|
+
// Configure Elysia's error handling for validation errors
|
|
21
|
+
this.app.onError(({ code, error, set }) => {
|
|
22
|
+
// Handle ValidationError from Elysia
|
|
23
|
+
if (code === "VALIDATION" ||
|
|
24
|
+
error?.constructor?.name === "ValidationError") {
|
|
25
|
+
const validationError = error;
|
|
26
|
+
set.status = 400;
|
|
27
|
+
// Try to collect all validation errors using TypeBox
|
|
28
|
+
const allErrors = {};
|
|
29
|
+
// Check if we have the validator and value to collect all errors
|
|
30
|
+
if (validationError.validator && validationError.value) {
|
|
31
|
+
const schema = validationError.validator.schema || validationError.validator;
|
|
32
|
+
// Use TypeBox's Errors iterator to collect ALL validation errors
|
|
33
|
+
try {
|
|
34
|
+
const errors = [...Value.Errors(schema, validationError.value)];
|
|
35
|
+
if (errors.length > 0) {
|
|
36
|
+
errors.forEach((err) => {
|
|
37
|
+
const field = err.path?.replace(/^\//, "") || "unknown";
|
|
38
|
+
if (!allErrors[field]) {
|
|
39
|
+
allErrors[field] = [];
|
|
40
|
+
}
|
|
41
|
+
allErrors[field].push(err.message || "Validation failed");
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Fallback to single error
|
|
46
|
+
const field = validationError.valueError?.path?.replace(/^\//, "") ||
|
|
47
|
+
validationError.on ||
|
|
48
|
+
"body";
|
|
49
|
+
const message = validationError.customError ||
|
|
50
|
+
validationError.valueError?.message ||
|
|
51
|
+
"Validation failed";
|
|
52
|
+
allErrors[field] = [message];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
// Fallback to single error if TypeBox iteration fails
|
|
57
|
+
const field = validationError.valueError?.path?.replace(/^\//, "") ||
|
|
58
|
+
validationError.on ||
|
|
59
|
+
"body";
|
|
60
|
+
const message = validationError.customError ||
|
|
61
|
+
validationError.valueError?.message ||
|
|
62
|
+
"Validation failed";
|
|
63
|
+
allErrors[field] = [message];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Fallback to single error
|
|
68
|
+
const field = validationError.valueError?.path?.replace(/^\//, "") ||
|
|
69
|
+
validationError.on ||
|
|
70
|
+
"body";
|
|
71
|
+
const message = validationError.customError ||
|
|
72
|
+
validationError.valueError?.message ||
|
|
73
|
+
"Validation failed";
|
|
74
|
+
allErrors[field] = [message];
|
|
75
|
+
}
|
|
76
|
+
// If a custom formatter is provided, use it
|
|
77
|
+
if (this.validationFormatter) {
|
|
78
|
+
// Convert allErrors to the format expected by formatters
|
|
79
|
+
const formattedError = {
|
|
80
|
+
errors: Object.entries(allErrors).map(([field, messages]) => ({
|
|
81
|
+
path: `/${field}`,
|
|
82
|
+
summary: messages[0],
|
|
83
|
+
message: messages.join(", "),
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
return this.validationFormatter.format(formattedError);
|
|
87
|
+
}
|
|
88
|
+
// Default format: { statusCode, message, errors: { field: [messages] } }
|
|
89
|
+
return {
|
|
90
|
+
statusCode: 400,
|
|
91
|
+
message: "Validation failed",
|
|
92
|
+
errors: allErrors,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Default error handling
|
|
96
|
+
const err = error;
|
|
97
|
+
set.status = err.status || 500;
|
|
98
|
+
return {
|
|
99
|
+
statusCode: err.status || 500,
|
|
100
|
+
message: err.message || "Internal server error",
|
|
101
|
+
error: err.name || "Error",
|
|
102
|
+
};
|
|
103
|
+
});
|
|
17
104
|
// Apply CORS if enabled
|
|
18
105
|
if (options.cors) {
|
|
19
106
|
// CORS configuration would go here
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* @author WynkJS Team
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
|
+
import "reflect-metadata";
|
|
9
|
+
export { injectable, inject, singleton, autoInjectable, registry, container, } from "tsyringe";
|
|
10
|
+
export type { DependencyContainer } from "tsyringe";
|
|
11
|
+
export { injectable as Injectable, inject as Inject, singleton as Singleton, autoInjectable as AutoInjectable, registry as Registry, container as Container, } from "tsyringe";
|
|
8
12
|
export * from "./decorators/http.decorators";
|
|
9
13
|
export * from "./decorators/param.decorators";
|
|
10
14
|
export * from "./decorators/guard.decorators";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,kBAAkB,CAAC;AAI1B,OAAO,EACL,UAAU,EACV,MAAM,EACN,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,OAAO,EACL,UAAU,IAAI,UAAU,EACxB,MAAM,IAAI,MAAM,EAChB,SAAS,IAAI,SAAS,EACtB,cAAc,IAAI,cAAc,EAChC,QAAQ,IAAI,QAAQ,EACpB,SAAS,IAAI,SAAS,GACvB,MAAM,UAAU,CAAC;AAGlB,cAAc,8BAA8B,CAAC;AAG7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAI3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,iCAAiC,CAAC;AAIhD,cAAc,kCAAkC,CAAC;AASjD,cAAc,OAAO,CAAC;AAGtB,cAAc,WAAW,CAAC;AAE1B;;GAEG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
* @author WynkJS Team
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
|
+
// Import reflect-metadata at the top of the framework
|
|
9
|
+
// This ensures users don't need to import it manually
|
|
10
|
+
import "reflect-metadata";
|
|
11
|
+
// Re-export tsyringe decorators for dependency injection
|
|
12
|
+
// Users can import these from 'wynkjs' instead of 'tsyringe'
|
|
13
|
+
export { injectable, inject, singleton, autoInjectable, registry, container, } from "tsyringe";
|
|
14
|
+
// Capital-cased aliases for consistency with WynkJS naming convention
|
|
15
|
+
export { injectable as Injectable, inject as Inject, singleton as Singleton, autoInjectable as AutoInjectable, registry as Registry, container as Container, } from "tsyringe";
|
|
8
16
|
// HTTP Method Decorators
|
|
9
17
|
export * from "./decorators/http.decorators";
|
|
10
18
|
// Parameter Decorators
|
package/package.json
CHANGED