ts-class-to-openapi 1.0.2 → 1.0.4

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 CHANGED
@@ -4,12 +4,17 @@
4
4
 
5
5
  A powerful library that automatically converts your TypeScript classes into OpenAPI-compatible schemas. Works with **pure TypeScript classes** and **class-validator decorated classes**, with zero runtime dependencies.
6
6
 
7
- > **🎯 New Feature**: Now supports **pure TypeScript classes** without requiring any decorators or external dependencies! Perfect for transforming existing codebases instantly.
7
+ > **🎯 Latest Features**:
8
+ >
9
+ > - **Pure TypeScript classes** without requiring any decorators or external dependencies!
10
+ > - **Full enum support** with `@IsEnum` decorator for string, numeric, and object enums
11
+ > - Perfect for transforming existing codebases instantly.
8
12
 
9
13
  ## 🚀 Key Features
10
14
 
11
15
  - ✅ **Pure TypeScript Support** - Transform any TypeScript class without decorators
12
16
  - ✅ **class-validator Compatible** - Enhanced schemas with validation decorators
17
+ - ✅ **Enum Support** - Full support for TypeScript enums and object enums with @IsEnum
13
18
  - ✅ **CommonJS & ESM Compatible** - Works in any Node.js project
14
19
  - ✅ **Zero Runtime Dependencies** - No `reflect-metadata` or `emitDecoratorMetadata` required
15
20
  - ✅ **OpenAPI 3.1.0** - Industry-standard schema generation
@@ -81,7 +86,12 @@ This library is **100% compatible with both CommonJS and ESM**, allowing you to
81
86
  ```typescript
82
87
  // ESM import
83
88
  import { transform } from 'ts-class-to-openapi'
84
- import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
89
+ import { IsString, IsEmail, IsNotEmpty, IsEnum } from 'class-validator'
90
+
91
+ enum UserType {
92
+ ADMIN = 'admin',
93
+ USER = 'user',
94
+ }
85
95
 
86
96
  class User {
87
97
  @IsString()
@@ -90,6 +100,9 @@ class User {
90
100
 
91
101
  @IsEmail()
92
102
  email: string
103
+
104
+ @IsEnum(UserType)
105
+ type: UserType
93
106
  }
94
107
 
95
108
  const schema = transform(User)
@@ -101,7 +114,12 @@ console.log(JSON.stringify(schema, null, 2))
101
114
  ```typescript
102
115
  // TypeScript with CommonJS configuration
103
116
  import { transform } from 'ts-class-to-openapi'
104
- import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
117
+ import { IsString, IsEmail, IsNotEmpty, IsEnum } from 'class-validator'
118
+
119
+ enum UserType {
120
+ ADMIN = 'admin',
121
+ USER = 'user',
122
+ }
105
123
 
106
124
  class User {
107
125
  @IsString()
@@ -110,6 +128,9 @@ class User {
110
128
 
111
129
  @IsEmail()
112
130
  email: string
131
+
132
+ @IsEnum(UserType)
133
+ type: UserType
113
134
  }
114
135
 
115
136
  const schema = transform(User)
@@ -173,8 +194,22 @@ import {
173
194
  IsPositive,
174
195
  IsArray,
175
196
  IsNotEmpty,
197
+ IsEnum,
176
198
  } from 'class-validator'
177
199
 
200
+ // Define enums for better API contracts
201
+ enum ProductStatus {
202
+ DRAFT = 'draft',
203
+ PUBLISHED = 'published',
204
+ ARCHIVED = 'archived',
205
+ }
206
+
207
+ enum Priority {
208
+ LOW = 1,
209
+ MEDIUM = 2,
210
+ HIGH = 3,
211
+ }
212
+
178
213
  // Enhanced with validation decorators
179
214
  class Product {
180
215
  @IsNumber()
@@ -189,6 +224,13 @@ class Product {
189
224
  @IsPositive()
190
225
  price: number
191
226
 
227
+ @IsEnum(ProductStatus)
228
+ @IsNotEmpty()
229
+ status: ProductStatus
230
+
231
+ @IsEnum(Priority)
232
+ priority?: Priority
233
+
192
234
  @IsArray()
193
235
  categories: string[]
194
236
  }
@@ -204,6 +246,7 @@ const schema = transform(Product)
204
246
  - ✅ String length constraints
205
247
  - ✅ Number range validation
206
248
  - ✅ Array size validation
249
+ - ✅ Enum value constraints with automatic type detection
207
250
 
208
251
  ## 🚀 Quick Start
209
252
 
@@ -264,7 +307,7 @@ console.log(JSON.stringify(result, null, 2))
264
307
  "format": "date-time"
265
308
  }
266
309
  },
267
- "required": []
310
+ "required": ["id", "name", "email", "age", "isActive", "tags", "createdAt"]
268
311
  }
269
312
  }
270
313
  ```
@@ -319,7 +362,7 @@ console.log(JSON.stringify(result, null, 2))
319
362
  "maximum": 100
320
363
  }
321
364
  },
322
- "required": ["name"]
365
+ "required": ["name", "email", "age"]
323
366
  }
324
367
  }
325
368
  ```
@@ -750,7 +793,7 @@ const schema = transform(User)
750
793
  "format": "binary"
751
794
  }
752
795
  },
753
- "required": ["id", "tags", "role", "avatar"]
796
+ "required": ["id", "name", "email", "tags", "createdAt", "role", "files", "avatar"]
754
797
  }
755
798
  }
756
799
  ```
@@ -765,7 +808,7 @@ const schema = transform(User)
765
808
  | **Configuration** | None | `experimentalDecorators: true` |
766
809
  | **Type Detection** | Automatic | Automatic + Decorators |
767
810
  | **Validation Rules** | Basic types only | Rich validation constraints |
768
- | **Required Fields** | None (all optional) | Specified by decorators |
811
+ | **Required Fields** | Based on TypeScript optional operator (`?`) | TypeScript optional operator + decorators |
769
812
  | **String Constraints** | None | Min/max length, patterns |
770
813
  | **Number Constraints** | None | Min/max values, positive |
771
814
  | **Array Constraints** | None | Min/max items, non-empty |
@@ -779,11 +822,11 @@ const schema = transform(User)
779
822
 
780
823
  ```typescript
781
824
  class User {
782
- name: string
783
- email: string
784
- age: number
825
+ name: string // Required (no ? operator)
826
+ email: string // Required (no ? operator)
827
+ age: number // Required (no ? operator)
785
828
  }
786
- // Generates: All properties optional, basic types
829
+ // Generates: All properties required (no ? operator), basic types
787
830
  ```
788
831
 
789
832
  **Enhanced Class:**
@@ -801,21 +844,126 @@ class User {
801
844
  @Min(18)
802
845
  age: number
803
846
  }
804
- // Generates: name required, email format validation, age minimum 18
847
+ // Generates: All properties required, email format validation, age minimum 18
805
848
  ```
806
849
 
807
850
  ## 🎨 Supported Decorators Reference
808
851
 
809
852
  ### Type Validation Decorators
810
853
 
811
- | Decorator | Generated Schema Property | Description |
812
- | -------------- | ------------------------------------- | --------------------------- |
813
- | `@IsString()` | `type: "string"` | String type validation |
814
- | `@IsInt()` | `type: "integer", format: "int32"` | Integer type validation |
815
- | `@IsNumber()` | `type: "number", format: "double"` | Number type validation |
816
- | `@IsBoolean()` | `type: "boolean"` | Boolean type validation |
817
- | `@IsEmail()` | `type: "string", format: "email"` | Email format validation |
818
- | `@IsDate()` | `type: "string", format: "date-time"` | Date-time format validation |
854
+ | Decorator | Generated Schema Property | Description |
855
+ | --------------------- | ----------------------------------------------- | --------------------------- |
856
+ | `@IsString()` | `type: "string"` | String type validation |
857
+ | `@IsInt()` | `type: "integer", format: "int32"` | Integer type validation |
858
+ | `@IsNumber()` | `type: "number", format: "double"` | Number type validation |
859
+ | `@IsBoolean()` | `type: "boolean"` | Boolean type validation |
860
+ | `@IsEmail()` | `type: "string", format: "email"` | Email format validation |
861
+ | `@IsDate()` | `type: "string", format: "date-time"` | Date-time format validation |
862
+ | `@IsEnum(enumObject)` | `type: "string/number/boolean", enum: [values]` | Enum constraint validation |
863
+
864
+ ### Enum Validation with @IsEnum
865
+
866
+ The `@IsEnum()` decorator provides powerful enum validation with automatic type detection and value extraction:
867
+
868
+ ```typescript
869
+ import { transform } from 'ts-class-to-openapi'
870
+ import { IsEnum, IsNotEmpty, IsArray } from 'class-validator'
871
+
872
+ // String enum
873
+ enum UserRole {
874
+ ADMIN = 'admin',
875
+ USER = 'user',
876
+ MODERATOR = 'moderator',
877
+ }
878
+
879
+ // Numeric enum
880
+ enum Priority {
881
+ LOW = 1,
882
+ MEDIUM = 2,
883
+ HIGH = 3,
884
+ }
885
+
886
+ // Auto-incremented enum
887
+ enum Size {
888
+ SMALL, // 0
889
+ MEDIUM, // 1
890
+ LARGE, // 2
891
+ }
892
+
893
+ // Object enum with 'as const'
894
+ const Status = {
895
+ ACTIVE: 'active',
896
+ INACTIVE: 'inactive',
897
+ PENDING: 'pending',
898
+ } as const
899
+
900
+ class TaskEntity {
901
+ @IsEnum(UserRole)
902
+ @IsNotEmpty()
903
+ assignedRole: UserRole // Required enum
904
+
905
+ @IsEnum(Priority)
906
+ priority?: Priority // Optional enum
907
+
908
+ @IsEnum(Size)
909
+ size?: Size // Auto-incremented enum
910
+
911
+ @IsEnum(Status)
912
+ status?: keyof typeof Status // Object enum
913
+
914
+ @IsEnum(UserRole)
915
+ @IsArray()
916
+ allowedRoles?: UserRole[] // Array of enums
917
+ }
918
+
919
+ const result = transform(TaskEntity)
920
+ ```
921
+
922
+ **Generated Output:**
923
+
924
+ ```json
925
+ {
926
+ "name": "TaskEntity",
927
+ "schema": {
928
+ "type": "object",
929
+ "properties": {
930
+ "assignedRole": {
931
+ "type": "string",
932
+ "enum": ["admin", "user", "moderator"]
933
+ },
934
+ "priority": {
935
+ "type": "number",
936
+ "enum": [1, 2, 3]
937
+ },
938
+ "size": {
939
+ "type": "number",
940
+ "enum": [0, 1, 2]
941
+ },
942
+ "status": {
943
+ "type": "string",
944
+ "enum": ["active", "inactive", "pending"]
945
+ },
946
+ "allowedRoles": {
947
+ "type": "array",
948
+ "items": {
949
+ "type": "string",
950
+ "enum": ["admin", "user", "moderator"]
951
+ }
952
+ }
953
+ },
954
+ "required": ["assignedRole"]
955
+ }
956
+ }
957
+ ```
958
+
959
+ **Supported Enum Types:**
960
+
961
+ - ✅ **String enums**: `enum Color { RED = 'red' }`
962
+ - ✅ **Numeric enums**: `enum Status { ACTIVE = 1 }`
963
+ - ✅ **Auto-incremented enums**: `enum Size { SMALL, MEDIUM }`
964
+ - ✅ **Object enums**: `const Colors = { RED: 'red' } as const`
965
+ - ✅ **Mixed enums**: `enum Mixed { NUM = 1, STR = 'text' }`
966
+ - ✅ **Array of enums**: `roles: UserRole[]`
819
967
 
820
968
  ### String Validation Decorators
821
969
 
@@ -933,7 +1081,7 @@ console.log(JSON.stringify(schema, null, 2))
933
1081
  "format": "binary"
934
1082
  }
935
1083
  },
936
- "required": ["document"]
1084
+ "required": ["document", "attachments"]
937
1085
  }
938
1086
  }
939
1087
  ```
@@ -981,6 +1129,75 @@ console.log(result.name) // "User"
981
1129
  console.log(result.schema) // OpenAPI schema object
982
1130
  ```
983
1131
 
1132
+ ## 🔍 Required Properties Rules
1133
+
1134
+ Understanding how properties are marked as required is crucial for generating accurate schemas:
1135
+
1136
+ ### TypeScript Optional Operator (`?`)
1137
+
1138
+ The presence or absence of the TypeScript optional operator (`?`) determines if a property is required:
1139
+
1140
+ ```typescript
1141
+ class User {
1142
+ name: string // ✅ REQUIRED (no ? operator)
1143
+ email: string // ✅ REQUIRED (no ? operator)
1144
+ age?: number // ❌ OPTIONAL (has ? operator)
1145
+ bio?: string // ❌ OPTIONAL (has ? operator)
1146
+ }
1147
+
1148
+ // Generated schema:
1149
+ // "required": ["name", "email"]
1150
+ ```
1151
+
1152
+ ### class-validator Decorators Override
1153
+
1154
+ When using class-validator decorators, they can override the TypeScript optional behavior:
1155
+
1156
+ ```typescript
1157
+ import { IsNotEmpty, IsOptional } from 'class-validator'
1158
+
1159
+ class User {
1160
+ @IsNotEmpty()
1161
+ requiredField?: string // ✅ REQUIRED (@IsNotEmpty overrides ?)
1162
+
1163
+ @IsOptional()
1164
+ optionalField: string // ❌ OPTIONAL (@IsOptional overrides no ?)
1165
+
1166
+ normalField: string // ✅ REQUIRED (no ? operator)
1167
+ normalOptional?: string // ❌ OPTIONAL (has ? operator)
1168
+ }
1169
+
1170
+ // Generated schema:
1171
+ // "required": ["requiredField", "normalField"]
1172
+ ```
1173
+
1174
+ ### Array Properties
1175
+
1176
+ Array properties follow the same rules, with additional decorators:
1177
+
1178
+ ```typescript
1179
+ import { ArrayNotEmpty } from 'class-validator'
1180
+
1181
+ class User {
1182
+ tags: string[] // ✅ REQUIRED (no ? operator)
1183
+
1184
+ @ArrayNotEmpty()
1185
+ categories?: string[] // ✅ REQUIRED (@ArrayNotEmpty overrides ?)
1186
+
1187
+ optionalTags?: string[] // ❌ OPTIONAL (has ? operator)
1188
+ }
1189
+
1190
+ // Generated schema:
1191
+ // "required": ["tags", "categories"]
1192
+ ```
1193
+
1194
+ ### Summary of Rules
1195
+
1196
+ 1. **No `?` operator** → Property is **REQUIRED**
1197
+ 2. **Has `?` operator** → Property is **OPTIONAL**
1198
+ 3. **`@IsNotEmpty()` or `@ArrayNotEmpty()`** → Forces **REQUIRED** (overrides `?`)
1199
+ 4. **`@IsOptional()`** → Forces **OPTIONAL** (overrides no `?`)
1200
+
984
1201
  ## 🌟 Advanced Features
985
1202
 
986
1203
  - ✅ **Pure TypeScript Support** - Works with any TypeScript class, no decorators required
@@ -0,0 +1,33 @@
1
+ export type ApiResponse<T> = {
2
+ data: T;
3
+ message: string;
4
+ success: boolean;
5
+ };
6
+ export type PaginatedResponse<T> = {
7
+ items: T[];
8
+ total: number;
9
+ page: number;
10
+ };
11
+ export declare class Product {
12
+ id: number;
13
+ name: string;
14
+ price: number;
15
+ }
16
+ export declare class UserEntity {
17
+ id: number;
18
+ username: string;
19
+ email: string;
20
+ }
21
+ export declare class ProductResponseDto {
22
+ response: ApiResponse<Product>;
23
+ }
24
+ export declare class UserListDto {
25
+ users: PaginatedResponse<UserEntity>;
26
+ }
27
+ export type KeyValuePair<K, V> = {
28
+ key: K;
29
+ value: V;
30
+ };
31
+ export declare class ConfigDto {
32
+ setting: KeyValuePair<string, number>;
33
+ }
@@ -0,0 +1,23 @@
1
+ declare enum Color {
2
+ RED = "red",
3
+ GREEN = "green",
4
+ BLUE = "blue"
5
+ }
6
+ declare enum Size {
7
+ SMALL = 0,
8
+ MEDIUM = 1,
9
+ LARGE = 2
10
+ }
11
+ declare enum HttpStatus {
12
+ OK = 200,
13
+ NOT_FOUND = 404,
14
+ ERROR = "server_error"
15
+ }
16
+ export declare class ComprehensiveEnumEntity {
17
+ primaryColor: Color;
18
+ size?: Size;
19
+ status?: HttpStatus;
20
+ availableColors?: Color[];
21
+ supportedSizes: Size[];
22
+ }
23
+ export { Color, Size, HttpStatus };
@@ -0,0 +1,29 @@
1
+ declare enum UserRole {
2
+ ADMIN = "admin",
3
+ USER = "user",
4
+ MODERATOR = "moderator"
5
+ }
6
+ declare enum Priority {
7
+ LOW = 1,
8
+ MEDIUM = 2,
9
+ HIGH = 3
10
+ }
11
+ declare const Status: {
12
+ readonly ACTIVE: "active";
13
+ readonly INACTIVE: "inactive";
14
+ readonly PENDING: "pending";
15
+ };
16
+ declare enum BooleanEnum {
17
+ TRUE = "true",
18
+ FALSE = "false"
19
+ }
20
+ export declare class EnumTestEntity {
21
+ role: UserRole;
22
+ priority?: Priority;
23
+ status?: keyof typeof Status;
24
+ flag?: BooleanEnum;
25
+ roles?: UserRole[];
26
+ priorities?: Priority[];
27
+ optionalRole?: UserRole;
28
+ }
29
+ export { UserRole, Priority, Status, BooleanEnum };
@@ -0,0 +1,13 @@
1
+ export type User<T> = {
2
+ _id: number;
3
+ fullName: string;
4
+ role: T;
5
+ };
6
+ export declare class Role {
7
+ _id: number;
8
+ name: string;
9
+ }
10
+ export declare class QuoteDto {
11
+ _id: number;
12
+ user: User<Role>;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -2,3 +2,5 @@ import './main.test';
2
2
  import './integration.test';
3
3
  import './plain.test';
4
4
  import './optional-properties.test';
5
+ import './generic-types.test';
6
+ import './enum.test';