@rotyro-tools/nestjs-typeorm-validator 1.0.0

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 rotyro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # NestJS TypeORM Validator
2
+
3
+ [![Publish to npm](https://github.com/rotyro-tools/nestjs-typeorm-validator/actions/workflows/publish-to-npm.yml/badge.svg)](https://github.com/rotyro-tools/nestjs-typeorm-validator/actions/workflows/publish-to-npm.yml)
4
+ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
5
+
6
+ Lightweight NestJS decorators for validating values against TypeORM entities.
7
+
8
+ This package provides several decorators (more to come):
9
+
10
+ | Decorator | Description |
11
+ | ----------- | ------------------------------------------------------------- |
12
+ | `@ExistsIn` | Ensures a value exists in a specified entity/table column. |
13
+ | `@UniqueIn` | Ensures a value is unique in a specified entity/table column. |
14
+
15
+ It integrates with TypeORM DataSource instances, enabling database-aware validation in DTOs.
16
+
17
+ ---
18
+
19
+ ## Key features
20
+
21
+ - Works with TypeORM entity classes or table name strings.
22
+ - Supports multiple (named) TypeORM DataSource registrations.
23
+ - Supports class-validator options (for example `each` for array validation, or `message` for custom message).
24
+ - Minimal runtime dependency surface — relies on TypeORM and class-validator as peer dependencies.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ Install the package:
31
+
32
+ ### npm
33
+
34
+ ```bash
35
+ npm install @rotyro-tools/nestjs-typeorm-validator
36
+ ```
37
+
38
+ ### yarn
39
+
40
+ ```bash
41
+ yarn add @rotyro-tools/nestjs-typeorm-validator
42
+ ```
43
+
44
+ ### pnpm
45
+
46
+ ```bash
47
+ pnpm add @rotyro-tools/nestjs-typeorm-validator
48
+ ```
49
+
50
+ ### bun
51
+
52
+ ```bash
53
+ bun add @rotyro-tools/nestjs-typeorm-validator
54
+ ```
55
+
56
+ ## Peer dependencies
57
+
58
+ This package declares the following peer dependencies that consumers must install at compatible versions:
59
+
60
+ - [class-validator](https://github.com/typestack/class-validator)
61
+ - [typeorm](https://github.com/typeorm/typeorm)
62
+
63
+ Install them:
64
+
65
+ ### npm
66
+
67
+ ```bash
68
+ npm install --save class-validator typeorm
69
+ ```
70
+
71
+ ### yarn
72
+
73
+ ```bash
74
+ yarn add class-validator typeorm
75
+ ```
76
+
77
+ ### pnpm
78
+
79
+ ```bash
80
+ pnpm add class-validator typeorm
81
+ ```
82
+
83
+ ### bun
84
+
85
+ ```bash
86
+ bun add class-validator typeorm
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Getting started
92
+
93
+ 1. Register one or more TypeORM DataSources to be used by the validators:
94
+
95
+ ```typescript
96
+ import { registerDataSourceForValidation } from '@rotyro-tools/nestjs-typeorm-validator';
97
+ import { DataSource } from 'typeorm';
98
+
99
+ const dataSource = new DataSource({
100
+ type: 'mysql',
101
+ host: 'localhost',
102
+ port: 3306,
103
+ username: 'user',
104
+ password: 'pass',
105
+ database: 'db',
106
+ entities: [
107
+ /* ... */
108
+ ],
109
+ synchronize: false,
110
+ });
111
+
112
+ // If you initialize the DataSource yourself, that's fine. If you don't, registerDataSourceForValidation will attempt to initialize it for you.
113
+ await dataSource.initialize();
114
+
115
+ // Register your DataSource
116
+ registerDataSourceForValidation(dataSource); // Registers as 'default'
117
+
118
+ // Second 'replica' DataSource
119
+ const replicaDataSource = new DataSource({
120
+ type: 'mysql',
121
+ host: 'replica-host',
122
+ port: 3306,
123
+ username: 'replica_user',
124
+ password: 'replica_pass',
125
+ database: 'db_replica',
126
+ entities: [
127
+ /* ... */
128
+ ],
129
+ synchronize: false,
130
+ });
131
+
132
+ // Optionally initialize the replica DataSource as well
133
+ await replicaDataSource.initialize();
134
+
135
+ // Register your DataSource
136
+ registerDataSourceForValidation(replicaDataSource, 'replica');
137
+ ```
138
+
139
+ 2. Use decorators in your DTOs:
140
+
141
+ ```typescript
142
+ import { ExistsIn, UniqueIn } from '@rotyro-tools/nestjs-typeorm-validator';
143
+
144
+ import { User } from '@/modules/users/entities/user.entity';
145
+
146
+ class CreatePostDto {
147
+ // Ensure the given authorId exists in the User entity's id column
148
+ // in the 'default' DataSource
149
+ @ExistsIn(User, 'id', null, { message: 'Author not found' })
150
+ authorId: number;
151
+
152
+ // Ensure the title is unique in the posts table (using a table name)
153
+ // in the 'replica' DataSource
154
+ @UniqueIn('post', 'title', 'replica', { message: 'Title already taken' })
155
+ title: string;
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Examples
162
+
163
+ Checking existence (minimal setup):
164
+
165
+ ```typescript
166
+ import { ExistsIn } from '@rotyro-tools/nestjs-typeorm-validator';
167
+
168
+ class CreateCommentDto {
169
+ @ExistsIn('user', 'id')
170
+ userId: number;
171
+ }
172
+ ```
173
+
174
+ Unique constraint (entity usage and custom message):
175
+
176
+ ```typescript
177
+ import { UniqueIn } from '@rotyro-tools/nestjs-typeorm-validator';
178
+
179
+ import { User } from '@/modules/users/entities/user.entity';
180
+
181
+ class RegisterUserDto {
182
+ @UniqueIn(User, 'email', null, { message: 'Email already in use' })
183
+ email: string;
184
+ }
185
+ ```
186
+
187
+ Validating arrays:
188
+
189
+ ```typescript
190
+ import { ExistsIn } from '@rotyro-tools/nestjs-typeorm-validator';
191
+
192
+ class BulkCreateDto {
193
+ @ExistsIn('tag', 'name', 'replica', {
194
+ each: true,
195
+ message: 'Tag does not exist',
196
+ })
197
+ tagNames: string[];
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Development
204
+
205
+ Install dependencies:
206
+
207
+ ```bash
208
+ npm install
209
+ ```
210
+
211
+ Build:
212
+
213
+ ```bash
214
+ npm run build
215
+ ```
216
+
217
+ Lint:
218
+
219
+ ```bash
220
+ npm run lint
221
+ ```
222
+
223
+ Format:
224
+
225
+ ```bash
226
+ npm run format
227
+ ```
228
+
229
+ Run tests:
230
+
231
+ ```bash
232
+ npm run test
233
+ ```
234
+
235
+ Commit (with Commitizen):
236
+
237
+ ```bash
238
+ npm run cm
239
+ ```
@@ -0,0 +1,157 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ import { EntityTarget } from 'typeorm';
3
+
4
+ /**
5
+ * Interface representing the minimal DataSource contract needed for validation.
6
+ * This allows compatibility with different TypeORM versions.
7
+ */
8
+ interface DataSourceLike {
9
+ isInitialized: boolean;
10
+ initialize(): Promise<this>;
11
+ getRepository(target: string | (new (...args: unknown[]) => unknown)): unknown;
12
+ }
13
+
14
+ /**
15
+ * Register a DataSource instance to be used for validation.
16
+ * @param dataSourceInstance - TypeORM DataSource instance
17
+ * @param dataSourceName - Optional DataSource name (defaults to 'default'). If `null`, `undefined`, or an empty string is passed,
18
+ * the registration will use the 'default' name — the same name used when registering without specifying one.
19
+ * @throws {DataSourceInvalidError} if the instance is invalid
20
+ */
21
+ declare function registerDataSourceForValidation(dataSourceInstance: DataSourceLike, dataSourceName?: string): void;
22
+
23
+ /**
24
+ * Validates that the decorated property's value **exists in** a specific database column.
25
+ *
26
+ * @description
27
+ * The `@ExistsIn` decorator performs a database lookup (via TypeORM) to verify that the
28
+ * provided value **exists** in the specified table column.
29
+ * It supports both:
30
+ * - **Entity classes** (e.g., `User`, `Post`)
31
+ * - **Table name strings** (e.g., `'user'`, `'post'`)
32
+ *
33
+ *
34
+ * You can also specify which registered data source to use by passing the dataSourceName
35
+ * (the same name you used when calling `registerDataSourceForValidation(dataSourceInstance, 'yourName')`).
36
+ * If you pass `null` or `undefined` (or leave the parameter omitted), the decorator will use the
37
+ * 'default' data source — the same default used when registering a data source without specifying a name.
38
+ *
39
+ * The `validationOptions` parameter accepts the same ValidationOptions used by `class-validator`.
40
+ * Therefore options such as `each: true` (to validate array elements) and `message` (to provide a custom error message)
41
+ * behave exactly as they do in `class-validator`.
42
+ *
43
+ * @example
44
+ * **Using with an Entity class:**
45
+ * ```typescript
46
+ * class CreatePostDto {
47
+ * @ExistsIn(User, 'id')
48
+ * authorId: number;
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * **Using with a table name:**
54
+ * ```typescript
55
+ * class CreatePostDto {
56
+ * @ExistsIn('user', 'id')
57
+ * authorId: number;
58
+ * }
59
+ * ```
60
+ *
61
+ * @example
62
+ * **Using with a specific data source (registered as 'secondary'):**
63
+ * ```typescript
64
+ * class CreatePostDto {
65
+ * @ExistsIn(User, 'id', 'secondary')
66
+ * authorId: number;
67
+ * }
68
+ * ```
69
+ *
70
+ * @example
71
+ * **Using a default data source and validating arrays with custom message:**
72
+ * ```typescript
73
+ * class CreateMultiplePostsDto {
74
+ * @ExistsIn('user', 'id', null, { each: true, message: 'Every authorId must exist' })
75
+ * authorIds: number[];
76
+ * }
77
+ * ```
78
+ *
79
+ * @param entityOrTableName - The entity class or table name to check against.
80
+ * @param columnName - The column name in which to look for the value.
81
+ * @param dataSourceName - Optional name of the data source to use for validation (must match a registered name).
82
+ * Passing `null`/`undefined` (or omitting this argument) will use the 'default' data source.
83
+ * @param validationOptions - Optional validation settings from `class-validator` (use `{ each: true }` for arrays and `message` for custom text).
84
+ * @returns A property decorator function used by `class-validator`.
85
+ *
86
+ * @public
87
+ */
88
+ declare function ExistsIn<T extends EntityTarget<unknown> | string>(entityOrTableName: T, columnName: T extends string ? string : T extends EntityTarget<infer E> ? keyof E & string : string, dataSourceName?: string, validationOptions?: ValidationOptions): PropertyDecorator;
89
+
90
+ /**
91
+ * Validates that the decorated property's value is **unique** within a specified database column.
92
+ *
93
+ * @description
94
+ * The `@UniqueIn` decorator performs a database lookup (via TypeORM) to ensure that the provided
95
+ * value does **not already exist** in the specified table column.
96
+ * It supports both:
97
+ * - **Entity class references** (e.g., `User`, `Post`)
98
+ * - **Direct table name strings** (e.g., `'user'`, `'post'`)
99
+ *
100
+ *
101
+ * You can also specify which registered data source to use by passing the dataSourceName
102
+ * (the same name you used when calling `registerDataSourceForValidation(dataSourceInstance, 'yourName')`).
103
+ * If you pass `null` or `undefined` (or leave the parameter omitted), the decorator will use the
104
+ * 'default' data source — the same default used when registering a data source without specifying a name.
105
+ *
106
+ * The `validationOptions` parameter accepts the same ValidationOptions used by `class-validator`.
107
+ * Therefore options such as `each: true` (to validate array elements) and `message` (to provide a custom error message)
108
+ * behave exactly as they do in `class-validator`.
109
+ *
110
+ * @example
111
+ * **Using with an Entity class:**
112
+ * ```typescript
113
+ * class CreateUserDto {
114
+ * @UniqueIn(User, 'email')
115
+ * email: string;
116
+ * }
117
+ * ```
118
+ *
119
+ * @example
120
+ * **Using with a table name:**
121
+ * ```typescript
122
+ * class CreateUserDto {
123
+ * @UniqueIn('user', 'email')
124
+ * email: string;
125
+ * }
126
+ * ```
127
+ *
128
+ * @example
129
+ * **Using with a specific data source (registered as 'secondary'):**
130
+ * ```typescript
131
+ * class CreateUserDto {
132
+ * @UniqueIn(User, 'email', 'secondary')
133
+ * email: string;
134
+ * }
135
+ * ```
136
+ *
137
+ * @example
138
+ * **Using a default data source and validating arrays with custom message:**
139
+ * ```typescript
140
+ * class CreateUsersDto {
141
+ * @UniqueIn('user', 'email', null, { each: true, message: 'Each user email must be unique' })
142
+ * emails: string[];
143
+ * }
144
+ * ```
145
+ *
146
+ * @param entityOrTableName - The entity class or table name to check against.
147
+ * @param columnName - The column name to validate uniqueness in.
148
+ * @param dataSourceName - Optional name of the data source to use for validation (must match a registered name).
149
+ * Passing `null`/`undefined` (or omitting this argument) will use the 'default' data source.
150
+ * @param validationOptions - Optional validation settings from `class-validator` (use `{ each: true }` for arrays and `message` for custom text).
151
+ * @returns A property decorator function used by `class-validator`.
152
+ *
153
+ * @public
154
+ */
155
+ declare function UniqueIn<T extends EntityTarget<unknown> | string>(entityOrTableName: T, columnName: T extends string ? string : T extends EntityTarget<infer E> ? keyof E & string : string, dataSourceName?: string, validationOptions?: ValidationOptions): PropertyDecorator;
156
+
157
+ export { ExistsIn, UniqueIn, registerDataSourceForValidation };
@@ -0,0 +1,157 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ import { EntityTarget } from 'typeorm';
3
+
4
+ /**
5
+ * Interface representing the minimal DataSource contract needed for validation.
6
+ * This allows compatibility with different TypeORM versions.
7
+ */
8
+ interface DataSourceLike {
9
+ isInitialized: boolean;
10
+ initialize(): Promise<this>;
11
+ getRepository(target: string | (new (...args: unknown[]) => unknown)): unknown;
12
+ }
13
+
14
+ /**
15
+ * Register a DataSource instance to be used for validation.
16
+ * @param dataSourceInstance - TypeORM DataSource instance
17
+ * @param dataSourceName - Optional DataSource name (defaults to 'default'). If `null`, `undefined`, or an empty string is passed,
18
+ * the registration will use the 'default' name — the same name used when registering without specifying one.
19
+ * @throws {DataSourceInvalidError} if the instance is invalid
20
+ */
21
+ declare function registerDataSourceForValidation(dataSourceInstance: DataSourceLike, dataSourceName?: string): void;
22
+
23
+ /**
24
+ * Validates that the decorated property's value **exists in** a specific database column.
25
+ *
26
+ * @description
27
+ * The `@ExistsIn` decorator performs a database lookup (via TypeORM) to verify that the
28
+ * provided value **exists** in the specified table column.
29
+ * It supports both:
30
+ * - **Entity classes** (e.g., `User`, `Post`)
31
+ * - **Table name strings** (e.g., `'user'`, `'post'`)
32
+ *
33
+ *
34
+ * You can also specify which registered data source to use by passing the dataSourceName
35
+ * (the same name you used when calling `registerDataSourceForValidation(dataSourceInstance, 'yourName')`).
36
+ * If you pass `null` or `undefined` (or leave the parameter omitted), the decorator will use the
37
+ * 'default' data source — the same default used when registering a data source without specifying a name.
38
+ *
39
+ * The `validationOptions` parameter accepts the same ValidationOptions used by `class-validator`.
40
+ * Therefore options such as `each: true` (to validate array elements) and `message` (to provide a custom error message)
41
+ * behave exactly as they do in `class-validator`.
42
+ *
43
+ * @example
44
+ * **Using with an Entity class:**
45
+ * ```typescript
46
+ * class CreatePostDto {
47
+ * @ExistsIn(User, 'id')
48
+ * authorId: number;
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * **Using with a table name:**
54
+ * ```typescript
55
+ * class CreatePostDto {
56
+ * @ExistsIn('user', 'id')
57
+ * authorId: number;
58
+ * }
59
+ * ```
60
+ *
61
+ * @example
62
+ * **Using with a specific data source (registered as 'secondary'):**
63
+ * ```typescript
64
+ * class CreatePostDto {
65
+ * @ExistsIn(User, 'id', 'secondary')
66
+ * authorId: number;
67
+ * }
68
+ * ```
69
+ *
70
+ * @example
71
+ * **Using a default data source and validating arrays with custom message:**
72
+ * ```typescript
73
+ * class CreateMultiplePostsDto {
74
+ * @ExistsIn('user', 'id', null, { each: true, message: 'Every authorId must exist' })
75
+ * authorIds: number[];
76
+ * }
77
+ * ```
78
+ *
79
+ * @param entityOrTableName - The entity class or table name to check against.
80
+ * @param columnName - The column name in which to look for the value.
81
+ * @param dataSourceName - Optional name of the data source to use for validation (must match a registered name).
82
+ * Passing `null`/`undefined` (or omitting this argument) will use the 'default' data source.
83
+ * @param validationOptions - Optional validation settings from `class-validator` (use `{ each: true }` for arrays and `message` for custom text).
84
+ * @returns A property decorator function used by `class-validator`.
85
+ *
86
+ * @public
87
+ */
88
+ declare function ExistsIn<T extends EntityTarget<unknown> | string>(entityOrTableName: T, columnName: T extends string ? string : T extends EntityTarget<infer E> ? keyof E & string : string, dataSourceName?: string, validationOptions?: ValidationOptions): PropertyDecorator;
89
+
90
+ /**
91
+ * Validates that the decorated property's value is **unique** within a specified database column.
92
+ *
93
+ * @description
94
+ * The `@UniqueIn` decorator performs a database lookup (via TypeORM) to ensure that the provided
95
+ * value does **not already exist** in the specified table column.
96
+ * It supports both:
97
+ * - **Entity class references** (e.g., `User`, `Post`)
98
+ * - **Direct table name strings** (e.g., `'user'`, `'post'`)
99
+ *
100
+ *
101
+ * You can also specify which registered data source to use by passing the dataSourceName
102
+ * (the same name you used when calling `registerDataSourceForValidation(dataSourceInstance, 'yourName')`).
103
+ * If you pass `null` or `undefined` (or leave the parameter omitted), the decorator will use the
104
+ * 'default' data source — the same default used when registering a data source without specifying a name.
105
+ *
106
+ * The `validationOptions` parameter accepts the same ValidationOptions used by `class-validator`.
107
+ * Therefore options such as `each: true` (to validate array elements) and `message` (to provide a custom error message)
108
+ * behave exactly as they do in `class-validator`.
109
+ *
110
+ * @example
111
+ * **Using with an Entity class:**
112
+ * ```typescript
113
+ * class CreateUserDto {
114
+ * @UniqueIn(User, 'email')
115
+ * email: string;
116
+ * }
117
+ * ```
118
+ *
119
+ * @example
120
+ * **Using with a table name:**
121
+ * ```typescript
122
+ * class CreateUserDto {
123
+ * @UniqueIn('user', 'email')
124
+ * email: string;
125
+ * }
126
+ * ```
127
+ *
128
+ * @example
129
+ * **Using with a specific data source (registered as 'secondary'):**
130
+ * ```typescript
131
+ * class CreateUserDto {
132
+ * @UniqueIn(User, 'email', 'secondary')
133
+ * email: string;
134
+ * }
135
+ * ```
136
+ *
137
+ * @example
138
+ * **Using a default data source and validating arrays with custom message:**
139
+ * ```typescript
140
+ * class CreateUsersDto {
141
+ * @UniqueIn('user', 'email', null, { each: true, message: 'Each user email must be unique' })
142
+ * emails: string[];
143
+ * }
144
+ * ```
145
+ *
146
+ * @param entityOrTableName - The entity class or table name to check against.
147
+ * @param columnName - The column name to validate uniqueness in.
148
+ * @param dataSourceName - Optional name of the data source to use for validation (must match a registered name).
149
+ * Passing `null`/`undefined` (or omitting this argument) will use the 'default' data source.
150
+ * @param validationOptions - Optional validation settings from `class-validator` (use `{ each: true }` for arrays and `message` for custom text).
151
+ * @returns A property decorator function used by `class-validator`.
152
+ *
153
+ * @public
154
+ */
155
+ declare function UniqueIn<T extends EntityTarget<unknown> | string>(entityOrTableName: T, columnName: T extends string ? string : T extends EntityTarget<infer E> ? keyof E & string : string, dataSourceName?: string, validationOptions?: ValidationOptions): PropertyDecorator;
156
+
157
+ export { ExistsIn, UniqueIn, registerDataSourceForValidation };