ts-class-to-openapi 1.0.3 → 1.0.5

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,16 @@
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
8
11
 
9
12
  ## 🚀 Key Features
10
13
 
11
14
  - ✅ **Pure TypeScript Support** - Transform any TypeScript class without decorators
12
15
  - ✅ **class-validator Compatible** - Enhanced schemas with validation decorators
16
+ - ✅ **Enum Support** - Full support for TypeScript enums and object enums with @IsEnum
13
17
  - ✅ **CommonJS & ESM Compatible** - Works in any Node.js project
14
18
  - ✅ **Zero Runtime Dependencies** - No `reflect-metadata` or `emitDecoratorMetadata` required
15
19
  - ✅ **OpenAPI 3.1.0** - Industry-standard schema generation
@@ -81,7 +85,12 @@ This library is **100% compatible with both CommonJS and ESM**, allowing you to
81
85
  ```typescript
82
86
  // ESM import
83
87
  import { transform } from 'ts-class-to-openapi'
84
- import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
88
+ import { IsString, IsEmail, IsNotEmpty, IsEnum } from 'class-validator'
89
+
90
+ enum UserType {
91
+ ADMIN = 'admin',
92
+ USER = 'user',
93
+ }
85
94
 
86
95
  class User {
87
96
  @IsString()
@@ -90,6 +99,9 @@ class User {
90
99
 
91
100
  @IsEmail()
92
101
  email: string
102
+
103
+ @IsEnum(UserType)
104
+ type: UserType
93
105
  }
94
106
 
95
107
  const schema = transform(User)
@@ -101,7 +113,12 @@ console.log(JSON.stringify(schema, null, 2))
101
113
  ```typescript
102
114
  // TypeScript with CommonJS configuration
103
115
  import { transform } from 'ts-class-to-openapi'
104
- import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
116
+ import { IsString, IsEmail, IsNotEmpty, IsEnum } from 'class-validator'
117
+
118
+ enum UserType {
119
+ ADMIN = 'admin',
120
+ USER = 'user',
121
+ }
105
122
 
106
123
  class User {
107
124
  @IsString()
@@ -110,6 +127,9 @@ class User {
110
127
 
111
128
  @IsEmail()
112
129
  email: string
130
+
131
+ @IsEnum(UserType)
132
+ type: UserType
113
133
  }
114
134
 
115
135
  const schema = transform(User)
@@ -173,8 +193,22 @@ import {
173
193
  IsPositive,
174
194
  IsArray,
175
195
  IsNotEmpty,
196
+ IsEnum,
176
197
  } from 'class-validator'
177
198
 
199
+ // Define enums for better API contracts
200
+ enum ProductStatus {
201
+ DRAFT = 'draft',
202
+ PUBLISHED = 'published',
203
+ ARCHIVED = 'archived',
204
+ }
205
+
206
+ enum Priority {
207
+ LOW = 1,
208
+ MEDIUM = 2,
209
+ HIGH = 3,
210
+ }
211
+
178
212
  // Enhanced with validation decorators
179
213
  class Product {
180
214
  @IsNumber()
@@ -189,6 +223,13 @@ class Product {
189
223
  @IsPositive()
190
224
  price: number
191
225
 
226
+ @IsEnum(ProductStatus)
227
+ @IsNotEmpty()
228
+ status: ProductStatus
229
+
230
+ @IsEnum(Priority)
231
+ priority?: Priority
232
+
192
233
  @IsArray()
193
234
  categories: string[]
194
235
  }
@@ -204,6 +245,7 @@ const schema = transform(Product)
204
245
  - ✅ String length constraints
205
246
  - ✅ Number range validation
206
247
  - ✅ Array size validation
248
+ - ✅ Enum value constraints with automatic type detection
207
249
 
208
250
  ## 🚀 Quick Start
209
251
 
@@ -264,7 +306,7 @@ console.log(JSON.stringify(result, null, 2))
264
306
  "format": "date-time"
265
307
  }
266
308
  },
267
- "required": []
309
+ "required": ["id", "name", "email", "age", "isActive", "tags", "createdAt"]
268
310
  }
269
311
  }
270
312
  ```
@@ -319,7 +361,7 @@ console.log(JSON.stringify(result, null, 2))
319
361
  "maximum": 100
320
362
  }
321
363
  },
322
- "required": ["name"]
364
+ "required": ["name", "email", "age"]
323
365
  }
324
366
  }
325
367
  ```
@@ -750,7 +792,16 @@ const schema = transform(User)
750
792
  "format": "binary"
751
793
  }
752
794
  },
753
- "required": ["id", "tags", "role", "avatar"]
795
+ "required": [
796
+ "id",
797
+ "name",
798
+ "email",
799
+ "tags",
800
+ "createdAt",
801
+ "role",
802
+ "files",
803
+ "avatar"
804
+ ]
754
805
  }
755
806
  }
756
807
  ```
@@ -759,19 +810,19 @@ const schema = transform(User)
759
810
 
760
811
  ## 📊 Pure TypeScript vs Enhanced Mode Comparison
761
812
 
762
- | Feature | Pure TypeScript | Enhanced (class-validator) |
763
- | ---------------------- | ------------------------------------- | ------------------------------------ |
764
- | **Dependencies** | Zero | Requires `class-validator` |
765
- | **Configuration** | None | `experimentalDecorators: true` |
766
- | **Type Detection** | Automatic | Automatic + Decorators |
767
- | **Validation Rules** | Basic types only | Rich validation constraints |
768
- | **Required Fields** | None (all optional) | Specified by decorators |
769
- | **String Constraints** | None | Min/max length, patterns |
770
- | **Number Constraints** | None | Min/max values, positive |
771
- | **Array Constraints** | None | Min/max items, non-empty |
772
- | **Email Validation** | None | Email format validation |
773
- | **Date Handling** | `date-time` format | `date-time` format |
774
- | **Use Case** | Existing codebases, rapid prototyping | APIs with validation, robust schemas |
813
+ | Feature | Pure TypeScript | Enhanced (class-validator) |
814
+ | ---------------------- | ------------------------------------------- | ----------------------------------------- |
815
+ | **Dependencies** | Zero | Requires `class-validator` |
816
+ | **Configuration** | None | `experimentalDecorators: true` |
817
+ | **Type Detection** | Automatic | Automatic + Decorators |
818
+ | **Validation Rules** | Basic types only | Rich validation constraints |
819
+ | **Required Fields** | Based on TypeScript optional operator (`?`) | TypeScript optional operator + decorators |
820
+ | **String Constraints** | None | Min/max length, patterns |
821
+ | **Number Constraints** | None | Min/max values, positive |
822
+ | **Array Constraints** | None | Min/max items, non-empty |
823
+ | **Email Validation** | None | Email format validation |
824
+ | **Date Handling** | `date-time` format | `date-time` format |
825
+ | **Use Case** | Existing codebases, rapid prototyping | APIs with validation, robust schemas |
775
826
 
776
827
  ### Example Comparison
777
828
 
@@ -779,11 +830,11 @@ const schema = transform(User)
779
830
 
780
831
  ```typescript
781
832
  class User {
782
- name: string
783
- email: string
784
- age: number
833
+ name: string // Required (no ? operator)
834
+ email: string // Required (no ? operator)
835
+ age: number // Required (no ? operator)
785
836
  }
786
- // Generates: All properties optional, basic types
837
+ // Generates: All properties required (no ? operator), basic types
787
838
  ```
788
839
 
789
840
  **Enhanced Class:**
@@ -801,21 +852,126 @@ class User {
801
852
  @Min(18)
802
853
  age: number
803
854
  }
804
- // Generates: name required, email format validation, age minimum 18
855
+ // Generates: All properties required, email format validation, age minimum 18
805
856
  ```
806
857
 
807
858
  ## 🎨 Supported Decorators Reference
808
859
 
809
860
  ### Type Validation Decorators
810
861
 
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 |
862
+ | Decorator | Generated Schema Property | Description |
863
+ | --------------------- | ----------------------------------------------- | --------------------------- |
864
+ | `@IsString()` | `type: "string"` | String type validation |
865
+ | `@IsInt()` | `type: "integer", format: "int32"` | Integer type validation |
866
+ | `@IsNumber()` | `type: "number", format: "double"` | Number type validation |
867
+ | `@IsBoolean()` | `type: "boolean"` | Boolean type validation |
868
+ | `@IsEmail()` | `type: "string", format: "email"` | Email format validation |
869
+ | `@IsDate()` | `type: "string", format: "date-time"` | Date-time format validation |
870
+ | `@IsEnum(enumObject)` | `type: "string/number/boolean", enum: [values]` | Enum constraint validation |
871
+
872
+ ### Enum Validation with @IsEnum
873
+
874
+ The `@IsEnum()` decorator provides powerful enum validation with automatic type detection and value extraction:
875
+
876
+ ```typescript
877
+ import { transform } from 'ts-class-to-openapi'
878
+ import { IsEnum, IsNotEmpty, IsArray } from 'class-validator'
879
+
880
+ // String enum
881
+ enum UserRole {
882
+ ADMIN = 'admin',
883
+ USER = 'user',
884
+ MODERATOR = 'moderator',
885
+ }
886
+
887
+ // Numeric enum
888
+ enum Priority {
889
+ LOW = 1,
890
+ MEDIUM = 2,
891
+ HIGH = 3,
892
+ }
893
+
894
+ // Auto-incremented enum
895
+ enum Size {
896
+ SMALL, // 0
897
+ MEDIUM, // 1
898
+ LARGE, // 2
899
+ }
900
+
901
+ // Object enum with 'as const'
902
+ const Status = {
903
+ ACTIVE: 'active',
904
+ INACTIVE: 'inactive',
905
+ PENDING: 'pending',
906
+ } as const
907
+
908
+ class TaskEntity {
909
+ @IsEnum(UserRole)
910
+ @IsNotEmpty()
911
+ assignedRole: UserRole // Required enum
912
+
913
+ @IsEnum(Priority)
914
+ priority?: Priority // Optional enum
915
+
916
+ @IsEnum(Size)
917
+ size?: Size // Auto-incremented enum
918
+
919
+ @IsEnum(Status)
920
+ status?: keyof typeof Status // Object enum
921
+
922
+ @IsEnum(UserRole)
923
+ @IsArray()
924
+ allowedRoles?: UserRole[] // Array of enums
925
+ }
926
+
927
+ const result = transform(TaskEntity)
928
+ ```
929
+
930
+ **Generated Output:**
931
+
932
+ ```json
933
+ {
934
+ "name": "TaskEntity",
935
+ "schema": {
936
+ "type": "object",
937
+ "properties": {
938
+ "assignedRole": {
939
+ "type": "string",
940
+ "enum": ["admin", "user", "moderator"]
941
+ },
942
+ "priority": {
943
+ "type": "number",
944
+ "enum": [1, 2, 3]
945
+ },
946
+ "size": {
947
+ "type": "number",
948
+ "enum": [0, 1, 2]
949
+ },
950
+ "status": {
951
+ "type": "string",
952
+ "enum": ["active", "inactive", "pending"]
953
+ },
954
+ "allowedRoles": {
955
+ "type": "array",
956
+ "items": {
957
+ "type": "string",
958
+ "enum": ["admin", "user", "moderator"]
959
+ }
960
+ }
961
+ },
962
+ "required": ["assignedRole"]
963
+ }
964
+ }
965
+ ```
966
+
967
+ **Supported Enum Types:**
968
+
969
+ - ✅ **String enums**: `enum Color { RED = 'red' }`
970
+ - ✅ **Numeric enums**: `enum Status { ACTIVE = 1 }`
971
+ - ✅ **Auto-incremented enums**: `enum Size { SMALL, MEDIUM }`
972
+ - ✅ **Object enums**: `const Colors = { RED: 'red' } as const`
973
+ - ✅ **Mixed enums**: `enum Mixed { NUM = 1, STR = 'text' }`
974
+ - ✅ **Array of enums**: `roles: UserRole[]`
819
975
 
820
976
  ### String Validation Decorators
821
977
 
@@ -933,7 +1089,7 @@ console.log(JSON.stringify(schema, null, 2))
933
1089
  "format": "binary"
934
1090
  }
935
1091
  },
936
- "required": ["document"]
1092
+ "required": ["document", "attachments"]
937
1093
  }
938
1094
  }
939
1095
  ```
@@ -981,6 +1137,75 @@ console.log(result.name) // "User"
981
1137
  console.log(result.schema) // OpenAPI schema object
982
1138
  ```
983
1139
 
1140
+ ## 🔍 Required Properties Rules
1141
+
1142
+ Understanding how properties are marked as required is crucial for generating accurate schemas:
1143
+
1144
+ ### TypeScript Optional Operator (`?`)
1145
+
1146
+ The presence or absence of the TypeScript optional operator (`?`) determines if a property is required:
1147
+
1148
+ ```typescript
1149
+ class User {
1150
+ name: string // ✅ REQUIRED (no ? operator)
1151
+ email: string // ✅ REQUIRED (no ? operator)
1152
+ age?: number // ❌ OPTIONAL (has ? operator)
1153
+ bio?: string // ❌ OPTIONAL (has ? operator)
1154
+ }
1155
+
1156
+ // Generated schema:
1157
+ // "required": ["name", "email"]
1158
+ ```
1159
+
1160
+ ### class-validator Decorators Override
1161
+
1162
+ When using class-validator decorators, they can override the TypeScript optional behavior:
1163
+
1164
+ ```typescript
1165
+ import { IsNotEmpty, IsOptional } from 'class-validator'
1166
+
1167
+ class User {
1168
+ @IsNotEmpty()
1169
+ requiredField?: string // ✅ REQUIRED (@IsNotEmpty overrides ?)
1170
+
1171
+ @IsOptional()
1172
+ optionalField: string // ❌ OPTIONAL (@IsOptional overrides no ?)
1173
+
1174
+ normalField: string // ✅ REQUIRED (no ? operator)
1175
+ normalOptional?: string // ❌ OPTIONAL (has ? operator)
1176
+ }
1177
+
1178
+ // Generated schema:
1179
+ // "required": ["requiredField", "normalField"]
1180
+ ```
1181
+
1182
+ ### Array Properties
1183
+
1184
+ Array properties follow the same rules, with additional decorators:
1185
+
1186
+ ```typescript
1187
+ import { ArrayNotEmpty } from 'class-validator'
1188
+
1189
+ class User {
1190
+ tags: string[] // ✅ REQUIRED (no ? operator)
1191
+
1192
+ @ArrayNotEmpty()
1193
+ categories?: string[] // ✅ REQUIRED (@ArrayNotEmpty overrides ?)
1194
+
1195
+ optionalTags?: string[] // ❌ OPTIONAL (has ? operator)
1196
+ }
1197
+
1198
+ // Generated schema:
1199
+ // "required": ["tags", "categories"]
1200
+ ```
1201
+
1202
+ ### Summary of Rules
1203
+
1204
+ 1. **No `?` operator** → Property is **REQUIRED**
1205
+ 2. **Has `?` operator** → Property is **OPTIONAL**
1206
+ 3. **`@IsNotEmpty()` or `@ArrayNotEmpty()`** → Forces **REQUIRED** (overrides `?`)
1207
+ 4. **`@IsOptional()`** → Forces **OPTIONAL** (overrides no `?`)
1208
+
984
1209
  ## 🌟 Advanced Features
985
1210
 
986
1211
  - ✅ **Pure TypeScript Support** - Works with any TypeScript class, no decorators required
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Example entity demonstrating circular reference issue
3
+ * User -> Role -> User (circular)
4
+ */
5
+ export declare class CircularUser {
6
+ id: string;
7
+ name: string;
8
+ role: CircularRole;
9
+ organization: CircularOrganization;
10
+ organizations: CircularOrganization[];
11
+ }
12
+ export declare class CircularRole {
13
+ id: string;
14
+ name: string;
15
+ assignedBy: CircularUser;
16
+ organization: CircularOrganization;
17
+ usersWithRole: CircularUser[];
18
+ }
19
+ /**
20
+ * Example entity demonstrating complex circular references
21
+ * Organization -> User, Role, Organization (multiple circular references)
22
+ */
23
+ export declare class CircularOrganization {
24
+ id: string;
25
+ name: string;
26
+ owner: CircularUser;
27
+ defaultRole: CircularRole;
28
+ members: CircularUser[];
29
+ availableRoles: CircularRole[];
30
+ parentOrganization?: CircularOrganization;
31
+ childOrganizations: CircularOrganization[];
32
+ }
33
+ /**
34
+ * Example demonstrating deep circular reference chain
35
+ * A -> B -> C -> D -> A (complex chain)
36
+ */
37
+ export declare class DeepCircularA {
38
+ id: string;
39
+ reference: DeepCircularB;
40
+ }
41
+ export declare class DeepCircularB {
42
+ id: string;
43
+ reference: DeepCircularC;
44
+ }
45
+ export declare class DeepCircularC {
46
+ id: string;
47
+ reference: DeepCircularD;
48
+ }
49
+ export declare class DeepCircularD {
50
+ id: string;
51
+ reference: DeepCircularA;
52
+ allReferences: {
53
+ a: DeepCircularA;
54
+ b: DeepCircularB;
55
+ c: DeepCircularC;
56
+ selfRef: DeepCircularD;
57
+ };
58
+ arrayReferences: (DeepCircularA | DeepCircularB | DeepCircularC | DeepCircularD)[];
59
+ }
@@ -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,8 @@ 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';
7
+ import './circular-reference.test';
8
+ import './ref-pattern.test';
9
+ import './singleton-behavior.test';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { transform } from './transformer';
2
- import { SchemaType } from './types';
3
- export { transform, type SchemaType };
1
+ import { transform, SchemaTransformer } from './transformer';
2
+ import { SchemaType, TransformerOptions } from './types';
3
+ export { transform, SchemaTransformer, type SchemaType, type TransformerOptions, };