@dismissible/nestjs-validation 0.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 ADDED
@@ -0,0 +1,206 @@
1
+ # @dismissible/nestjs-validation
2
+
3
+ A validation service for NestJS applications using class-validator and class-transformer.
4
+
5
+ > **Part of the Dismissible API** - This library is part of the [Dismissible API](https://dismissible.io) ecosystem. Visit [dismissible.io](https://dismissible.io) for more information and documentation.
6
+
7
+ ## Overview
8
+
9
+ This library provides a `ValidationService` that wraps `class-validator` and `class-transformer` to provide a consistent validation API for DTOs and class instances in NestJS applications.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @dismissible/nestjs-validation
15
+ ```
16
+
17
+ You'll also need to install the peer dependencies:
18
+
19
+ ```bash
20
+ npm install class-validator class-transformer
21
+ ```
22
+
23
+ ## Getting Started
24
+
25
+ ### Basic Setup
26
+
27
+ ```typescript
28
+ import { Module } from '@nestjs/common';
29
+ import { ValidationModule } from '@dismissible/nestjs-validation';
30
+
31
+ @Module({
32
+ imports: [ValidationModule],
33
+ })
34
+ export class AppModule {}
35
+ ```
36
+
37
+ ### Validating DTOs
38
+
39
+ The `ValidationService` can validate plain objects against DTO classes:
40
+
41
+ ```typescript
42
+ import { Injectable } from '@nestjs/common';
43
+ import { ValidationService } from '@dismissible/nestjs-validation';
44
+ import { IsString, IsEmail, IsOptional } from 'class-validator';
45
+
46
+ class CreateUserDto {
47
+ @IsString()
48
+ name!: string;
49
+
50
+ @IsEmail()
51
+ email!: string;
52
+
53
+ @IsOptional()
54
+ @IsString()
55
+ phone?: string;
56
+ }
57
+
58
+ @Injectable()
59
+ export class UserService {
60
+ constructor(private readonly validationService: ValidationService) {}
61
+
62
+ async createUser(data: unknown) {
63
+ // Validates and transforms the data, throws BadRequestException if invalid
64
+ const dto = await this.validationService.validateDto(CreateUserDto, data);
65
+
66
+ // dto is now a validated instance of CreateUserDto
67
+ // Use dto.name, dto.email, etc.
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Validating Existing Instances
73
+
74
+ You can also validate class instances that have already been created:
75
+
76
+ ```typescript
77
+ import { Injectable } from '@nestjs/common';
78
+ import { ValidationService } from '@dismissible/nestjs-validation';
79
+ import { IsString, IsDate } from 'class-validator';
80
+
81
+ class MyDto {
82
+ @IsString()
83
+ name!: string;
84
+
85
+ @IsDate()
86
+ createdAt!: Date;
87
+ }
88
+
89
+ @Injectable()
90
+ export class MyService {
91
+ constructor(private readonly validationService: ValidationService) {}
92
+
93
+ async validateInstance(dto: MyDto) {
94
+ // Validates the instance, throws BadRequestException if invalid
95
+ await this.validationService.validateInstance(dto);
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Error Handling
101
+
102
+ The validation service throws `BadRequestException` with formatted error messages when validation fails:
103
+
104
+ ```typescript
105
+ import { BadRequestException } from '@nestjs/common';
106
+ import { ValidationService } from '@dismissible/nestjs-validation';
107
+
108
+ @Injectable()
109
+ export class MyService {
110
+ constructor(private readonly validationService: ValidationService) {}
111
+
112
+ async handleRequest(data: unknown) {
113
+ try {
114
+ const dto = await this.validationService.validateDto(MyDto, data);
115
+ // Process valid data
116
+ } catch (error) {
117
+ if (error instanceof BadRequestException) {
118
+ // Handle validation errors
119
+ console.error('Validation failed:', error.message);
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## API Reference
128
+
129
+ ### ValidationService
130
+
131
+ #### `validateDto<T>(dtoClass, data): Promise<T>`
132
+
133
+ Validates and transforms plain data into a DTO instance.
134
+
135
+ **Parameters:**
136
+
137
+ - `dtoClass: ClassConstructor<T>` - The DTO class to validate against
138
+ - `data: unknown` - The data to validate
139
+
140
+ **Returns:** `Promise<T>` - A validated instance of the DTO class
141
+
142
+ **Throws:** `BadRequestException` if validation fails
143
+
144
+ #### `validateInstance<T>(instance): Promise<void>`
145
+
146
+ Validates an existing class instance.
147
+
148
+ **Parameters:**
149
+
150
+ - `instance: T` - The instance to validate
151
+
152
+ **Throws:** `BadRequestException` if validation fails
153
+
154
+ ### ValidationModule
155
+
156
+ A NestJS module that provides `ValidationService` as a singleton.
157
+
158
+ **Exports:**
159
+
160
+ - `ValidationService` - The validation service
161
+
162
+ ## Features
163
+
164
+ - Automatic transformation using `class-transformer`
165
+ - Validation using `class-validator` decorators
166
+ - Nested validation error extraction
167
+ - Formatted error messages
168
+ - Type-safe DTOs with TypeScript generics
169
+
170
+ ## Example: Using in a Controller
171
+
172
+ ```typescript
173
+ import { Controller, Post, Body } from '@nestjs/common';
174
+ import { ValidationService } from '@dismissible/nestjs-validation';
175
+ import { IsString, IsEmail } from 'class-validator';
176
+
177
+ class CreateUserDto {
178
+ @IsString()
179
+ name!: string;
180
+
181
+ @IsEmail()
182
+ email!: string;
183
+ }
184
+
185
+ @Controller('users')
186
+ export class UserController {
187
+ constructor(private readonly validationService: ValidationService) {}
188
+
189
+ @Post()
190
+ async create(@Body() body: unknown) {
191
+ const dto = await this.validationService.validateDto(CreateUserDto, body);
192
+ // Use validated dto
193
+ return { message: `Creating user: ${dto.name}` };
194
+ }
195
+ }
196
+ ```
197
+
198
+ ## Related Packages
199
+
200
+ This validation service is used by:
201
+
202
+ - `@dismissible/nestjs-dismissible` - Validates dismissible items
203
+
204
+ ## License
205
+
206
+ MIT
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@dismissible/nestjs-validation",
3
+ "version": "0.0.1",
4
+ "description": "Validation service module for NestJS applications using class-validator and class-transformer",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.mjs",
10
+ "require": "./src/index.js",
11
+ "types": "./src/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {},
19
+ "peerDependencies": {
20
+ "@nestjs/common": "^11.0.0",
21
+ "class-validator": "^0.14.0",
22
+ "class-transformer": "^0.5.0",
23
+ "reflect-metadata": "^0.2.2"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "@nestjs/common": {
27
+ "optional": false
28
+ },
29
+ "class-validator": {
30
+ "optional": false
31
+ },
32
+ "class-transformer": {
33
+ "optional": false
34
+ },
35
+ "reflect-metadata": {
36
+ "optional": false
37
+ }
38
+ },
39
+ "keywords": [
40
+ "nestjs",
41
+ "validation",
42
+ "class-validator",
43
+ "class-transformer",
44
+ "dto"
45
+ ],
46
+ "author": "",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/DismissibleIo/dismissible-api"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "type": "commonjs"
56
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './validation.service';
2
+ export * from './validation.module';
3
+ export * from './transform-boolean.decorator';
4
+ export * from './transform-comma-separated.decorator';
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./validation.service"), exports);
5
+ tslib_1.__exportStar(require("./validation.module"), exports);
6
+ tslib_1.__exportStar(require("./transform-boolean.decorator"), exports);
7
+ tslib_1.__exportStar(require("./transform-comma-separated.decorator"), exports);
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/validation/src/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqC;AACrC,8DAAoC;AACpC,wEAA8C;AAC9C,gFAAsD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Transforms string values to boolean, preserving existing boolean values.
3
+ * Useful for environment variable configuration where boolean values may be passed as strings.
4
+ *
5
+ * @param defaultValue - Optional default value to return if the value is not a boolean or string.
6
+ * If not provided, the original value is returned unchanged.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * class Config {
11
+ * @IsBoolean()
12
+ * @TransformBoolean()
13
+ * enabled!: boolean;
14
+ * }
15
+ * ```
16
+ */
17
+ export declare function TransformBoolean(defaultValue?: boolean): PropertyDecorator;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransformBoolean = TransformBoolean;
4
+ const class_transformer_1 = require("class-transformer");
5
+ /**
6
+ * Transforms string values to boolean, preserving existing boolean values.
7
+ * Useful for environment variable configuration where boolean values may be passed as strings.
8
+ *
9
+ * @param defaultValue - Optional default value to return if the value is not a boolean or string.
10
+ * If not provided, the original value is returned unchanged.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * class Config {
15
+ * @IsBoolean()
16
+ * @TransformBoolean()
17
+ * enabled!: boolean;
18
+ * }
19
+ * ```
20
+ */
21
+ function TransformBoolean(defaultValue) {
22
+ return (0, class_transformer_1.Transform)(({ value }) => {
23
+ if (typeof value === 'boolean') {
24
+ return value;
25
+ }
26
+ if (typeof value === 'string') {
27
+ return value.toLowerCase() === 'true';
28
+ }
29
+ return defaultValue !== undefined ? defaultValue : value;
30
+ });
31
+ }
32
+ //# sourceMappingURL=transform-boolean.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-boolean.decorator.js","sourceRoot":"","sources":["../../../../libs/validation/src/transform-boolean.decorator.ts"],"names":[],"mappings":";;AAkBA,4CAUC;AA5BD,yDAA8C;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,gBAAgB,CAAC,YAAsB;IACrD,OAAO,IAAA,6BAAS,EAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QAC7B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QACxC,CAAC;QACD,OAAO,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Transforms a comma-separated string into an array of trimmed strings.
3
+ * If the value is already an array, it is returned as-is.
4
+ *
5
+ * @example
6
+ * // Input: "GET,POST,DELETE" → Output: ["GET", "POST", "DELETE"]
7
+ * // Input: "a , b , c" → Output: ["a", "b", "c"]
8
+ * // Input: ["a", "b"] → Output: ["a", "b"]
9
+ */
10
+ export declare function TransformCommaSeparated(): PropertyDecorator;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransformCommaSeparated = TransformCommaSeparated;
4
+ const class_transformer_1 = require("class-transformer");
5
+ /**
6
+ * Transforms a comma-separated string into an array of trimmed strings.
7
+ * If the value is already an array, it is returned as-is.
8
+ *
9
+ * @example
10
+ * // Input: "GET,POST,DELETE" → Output: ["GET", "POST", "DELETE"]
11
+ * // Input: "a , b , c" → Output: ["a", "b", "c"]
12
+ * // Input: ["a", "b"] → Output: ["a", "b"]
13
+ */
14
+ function TransformCommaSeparated() {
15
+ return (0, class_transformer_1.Transform)(({ value }) => typeof value === 'string' ? value.split(',').map((s) => s.trim()) : value);
16
+ }
17
+ //# sourceMappingURL=transform-comma-separated.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-comma-separated.decorator.js","sourceRoot":"","sources":["../../../../libs/validation/src/transform-comma-separated.decorator.ts"],"names":[],"mappings":";;AAWA,0DAIC;AAfD,yDAA8C;AAE9C;;;;;;;;GAQG;AACH,SAAgB,uBAAuB;IACrC,OAAO,IAAA,6BAAS,EAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAC7B,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAC1E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare class ValidationModule {
2
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidationModule = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const validation_service_1 = require("./validation.service");
7
+ let ValidationModule = class ValidationModule {
8
+ };
9
+ exports.ValidationModule = ValidationModule;
10
+ exports.ValidationModule = ValidationModule = tslib_1.__decorate([
11
+ (0, common_1.Module)({
12
+ providers: [validation_service_1.ValidationService],
13
+ exports: [validation_service_1.ValidationService],
14
+ })
15
+ ], ValidationModule);
16
+ //# sourceMappingURL=validation.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.module.js","sourceRoot":"","sources":["../../../../libs/validation/src/validation.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,6DAAyD;AAMlD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAAG,CAAA;AAAnB,4CAAgB;2BAAhB,gBAAgB;IAJ5B,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,sCAAiB,CAAC;QAC9B,OAAO,EAAE,CAAC,sCAAiB,CAAC;KAC7B,CAAC;GACW,gBAAgB,CAAG"}
@@ -0,0 +1,7 @@
1
+ import { ClassConstructor } from 'class-transformer';
2
+ export declare class ValidationService {
3
+ validateDto<T extends object>(dtoClass: ClassConstructor<T>, data: unknown): Promise<T>;
4
+ validateInstance<T extends object>(instance: T): Promise<void>;
5
+ private formatValidationErrors;
6
+ private extractErrorMessage;
7
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidationService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const class_validator_1 = require("class-validator");
7
+ const class_transformer_1 = require("class-transformer");
8
+ let ValidationService = class ValidationService {
9
+ async validateDto(dtoClass, data) {
10
+ if (data === null || data === undefined) {
11
+ throw new common_1.BadRequestException('Data cannot be null or undefined');
12
+ }
13
+ const dtoInstance = (0, class_transformer_1.plainToInstance)(dtoClass, data);
14
+ const validationErrors = await (0, class_validator_1.validate)(dtoInstance);
15
+ if (validationErrors.length > 0) {
16
+ const errorMessages = this.formatValidationErrors(validationErrors);
17
+ throw new common_1.BadRequestException(errorMessages);
18
+ }
19
+ return dtoInstance;
20
+ }
21
+ async validateInstance(instance) {
22
+ const validationErrors = await (0, class_validator_1.validate)(instance);
23
+ if (validationErrors.length > 0) {
24
+ const errorMessages = this.formatValidationErrors(validationErrors);
25
+ throw new common_1.BadRequestException(errorMessages);
26
+ }
27
+ }
28
+ formatValidationErrors(errors) {
29
+ return errors
30
+ .map((error) => this.extractErrorMessage(error))
31
+ .filter((message) => message.length > 0)
32
+ .join('; ');
33
+ }
34
+ extractErrorMessage(error) {
35
+ const messages = [];
36
+ if (error.constraints) {
37
+ messages.push(...Object.values(error.constraints));
38
+ }
39
+ if (error.children && error.children.length > 0) {
40
+ const childMessages = error.children
41
+ .map((child) => this.extractErrorMessage(child))
42
+ .filter((message) => message.length > 0);
43
+ messages.push(...childMessages);
44
+ }
45
+ return messages.join(', ');
46
+ }
47
+ };
48
+ exports.ValidationService = ValidationService;
49
+ exports.ValidationService = ValidationService = tslib_1.__decorate([
50
+ (0, common_1.Injectable)()
51
+ ], ValidationService);
52
+ //# sourceMappingURL=validation.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.service.js","sourceRoot":"","sources":["../../../../libs/validation/src/validation.service.ts"],"names":[],"mappings":";;;;AAAA,2CAAiE;AACjE,qDAA4D;AAC5D,yDAAsE;AAG/D,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,KAAK,CAAC,WAAW,CAAmB,QAA6B,EAAE,IAAa;QAC9E,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,IAAI,4BAAmB,CAAC,kCAAkC,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,WAAW,GAAG,IAAA,mCAAe,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,gBAAgB,GAAG,MAAM,IAAA,0BAAQ,EAAC,WAAqB,CAAC,CAAC;QAE/D,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;YACpE,MAAM,IAAI,4BAAmB,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAmB,QAAW;QAClD,MAAM,gBAAgB,GAAG,MAAM,IAAA,0BAAQ,EAAC,QAAkB,CAAC,CAAC;QAE5D,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;YACpE,MAAM,IAAI,4BAAmB,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAEO,sBAAsB,CAAC,MAAyB;QACtD,OAAO,MAAM;aACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;aAC/C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;aACvC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAEO,mBAAmB,CAAC,KAAsB;QAChD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ;iBACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;iBAC/C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC;QAClC,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF,CAAA;AAjDY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;GACA,iBAAiB,CAiD7B"}