ts-class-to-openapi 1.3.4 → 1.4.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 +24 -108
- package/dist/index.cjs +155 -49
- package/dist/index.mjs +155 -49
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,7 +27,26 @@ class User {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const schema = transform(User)
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
console.log(JSON.stringify(shema), null, 2)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Generated output:**
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"name": "User",
|
|
39
|
+
"schema": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"id": { "type": "number" },
|
|
43
|
+
"name": { "type": "string" },
|
|
44
|
+
"email": { "type": "string" },
|
|
45
|
+
"age": { "type": "number" }
|
|
46
|
+
},
|
|
47
|
+
"required": ["id", "name", "email"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
31
50
|
```
|
|
32
51
|
|
|
33
52
|
## 📦 Installation
|
|
@@ -55,57 +74,7 @@ npm install ts-class-to-openapi class-validator
|
|
|
55
74
|
|
|
56
75
|
## 🎨 Class Transformation Examples
|
|
57
76
|
|
|
58
|
-
### 1.
|
|
59
|
-
|
|
60
|
-
Fundamental method: transform any TypeScript class without decorators:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { transform } from 'ts-class-to-openapi'
|
|
64
|
-
|
|
65
|
-
// Basic TypeScript class - no decorators required
|
|
66
|
-
class User {
|
|
67
|
-
id: number
|
|
68
|
-
name: string
|
|
69
|
-
email: string
|
|
70
|
-
age: number
|
|
71
|
-
active: boolean
|
|
72
|
-
tags: string[]
|
|
73
|
-
createdAt: Date
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Transform class to OpenAPI schema
|
|
77
|
-
const result = transform(User)
|
|
78
|
-
console.log(JSON.stringify(result, null, 2))
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**Generated output:**
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"name": "User",
|
|
86
|
-
"schema": {
|
|
87
|
-
"type": "object",
|
|
88
|
-
"properties": {
|
|
89
|
-
"id": { "type": "number" },
|
|
90
|
-
"name": { "type": "string" },
|
|
91
|
-
"email": { "type": "string" },
|
|
92
|
-
"age": { "type": "number" },
|
|
93
|
-
"active": { "type": "boolean" },
|
|
94
|
-
"tags": {
|
|
95
|
-
"type": "array",
|
|
96
|
-
"items": { "type": "string" }
|
|
97
|
-
},
|
|
98
|
-
"createdAt": {
|
|
99
|
-
"type": "string",
|
|
100
|
-
"format": "date-time"
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
"required": ["id", "name", "email", "age", "active", "tags", "createdAt"]
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### 2. Class with Advanced Validations
|
|
77
|
+
### 1. Class with Advanced Validations
|
|
109
78
|
|
|
110
79
|
For more detailed schemas, class-validator decorators can be incorporated:
|
|
111
80
|
|
|
@@ -156,7 +125,7 @@ const result = transform(User)
|
|
|
156
125
|
}
|
|
157
126
|
```
|
|
158
127
|
|
|
159
|
-
###
|
|
128
|
+
### 2. Nested Objects and Arrays
|
|
160
129
|
|
|
161
130
|
Automatic processing of complex relationships:
|
|
162
131
|
|
|
@@ -229,9 +198,9 @@ const schema = transform(User)
|
|
|
229
198
|
}
|
|
230
199
|
```
|
|
231
200
|
|
|
232
|
-
###
|
|
201
|
+
### 3. Enumerations and Special Types
|
|
233
202
|
|
|
234
|
-
Full compatibility with TypeScript enumerations (both decorated and pure):
|
|
203
|
+
Full compatibility with TypeScript enumerations (both decorated and pure), and literal object as enums:
|
|
235
204
|
|
|
236
205
|
```typescript
|
|
237
206
|
import { transform } from 'ts-class-to-openapi'
|
|
@@ -299,59 +268,6 @@ const schema = transform(Task)
|
|
|
299
268
|
}
|
|
300
269
|
```
|
|
301
270
|
|
|
302
|
-
### 5. File Upload
|
|
303
|
-
|
|
304
|
-
Integrated support for binary file handling:
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
import { transform } from 'ts-class-to-openapi'
|
|
308
|
-
import { IsNotEmpty, IsOptional } from 'class-validator'
|
|
309
|
-
|
|
310
|
-
// Custom file type definition
|
|
311
|
-
class UploadFile {}
|
|
312
|
-
|
|
313
|
-
class UserProfile {
|
|
314
|
-
@IsNotEmpty()
|
|
315
|
-
profilePicture: UploadFile
|
|
316
|
-
|
|
317
|
-
@IsOptional()
|
|
318
|
-
resume: UploadFile
|
|
319
|
-
|
|
320
|
-
documents: UploadFile[] // Multiple files
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const schema = transform(UserProfile)
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
**Generated output:**
|
|
327
|
-
|
|
328
|
-
```json
|
|
329
|
-
{
|
|
330
|
-
"name": "UserProfile",
|
|
331
|
-
"schema": {
|
|
332
|
-
"type": "object",
|
|
333
|
-
"properties": {
|
|
334
|
-
"profilePicture": {
|
|
335
|
-
"type": "string",
|
|
336
|
-
"format": "binary"
|
|
337
|
-
},
|
|
338
|
-
"resume": {
|
|
339
|
-
"type": "string",
|
|
340
|
-
"format": "binary"
|
|
341
|
-
},
|
|
342
|
-
"documents": {
|
|
343
|
-
"type": "array",
|
|
344
|
-
"items": {
|
|
345
|
-
"type": "string",
|
|
346
|
-
"format": "binary"
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
"required": ["profilePicture", "documents"]
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
271
|
## 🌐 REST API Integration
|
|
356
272
|
|
|
357
273
|
### Implementation with Express.js and Swagger UI
|
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
189
189
|
maxCacheSize;
|
|
190
190
|
autoCleanup;
|
|
191
191
|
classFileIndex = /* @__PURE__ */ new Map();
|
|
192
|
+
transformCallIndex = /* @__PURE__ */ new Map();
|
|
192
193
|
constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
|
|
193
194
|
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
194
195
|
this.autoCleanup = options.autoCleanup ?? true;
|
|
@@ -200,19 +201,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
200
201
|
const { options: tsOptions, fileNames } = typescript.default.parseJsonConfigFileContent(config, typescript.default.sys, "./");
|
|
201
202
|
this.program = typescript.default.createProgram(fileNames, tsOptions);
|
|
202
203
|
this.checker = this.program.getTypeChecker();
|
|
203
|
-
this.
|
|
204
|
-
sf.statements.forEach((stmt) => {
|
|
205
|
-
if (typescript.default.isClassDeclaration(stmt) && stmt.name) {
|
|
206
|
-
const name = stmt.name.text;
|
|
207
|
-
const entry = this.classFileIndex.get(name) || [];
|
|
208
|
-
entry.push({
|
|
209
|
-
sourceFile: sf,
|
|
210
|
-
node: stmt
|
|
211
|
-
});
|
|
212
|
-
this.classFileIndex.set(name, entry);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
204
|
+
this.buildTransformCallIndex();
|
|
216
205
|
}
|
|
217
206
|
getPropertiesByClassDeclaration(classNode, visitedDeclarations = /* @__PURE__ */ new Set(), genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
218
207
|
if (visitedDeclarations.has(classNode)) return [];
|
|
@@ -260,6 +249,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
260
249
|
const isOptional = !!member.questionToken;
|
|
261
250
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
262
251
|
const isEnum = this.isEnum(member);
|
|
252
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
253
|
+
const isClassType = this.isClassType(member);
|
|
254
|
+
const isArray = this.isArrayProperty(member);
|
|
255
|
+
const isTypeLiteral = this.isTypeLiteral(member);
|
|
256
|
+
let genericClassReference = void 0;
|
|
257
|
+
if (isGeneric && !isPrimitive) {
|
|
258
|
+
const baseTypeName = type.replace(/\[\]$/, "").trim();
|
|
259
|
+
if (!this.isPrimitiveType(baseTypeName)) {
|
|
260
|
+
const matches = this.classFileIndex.get(baseTypeName);
|
|
261
|
+
if (matches && matches.length > 0 && matches[0]) genericClassReference = matches[0].node;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
263
264
|
const property = {
|
|
264
265
|
name: propertyName,
|
|
265
266
|
type,
|
|
@@ -267,12 +268,13 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
267
268
|
isOptional,
|
|
268
269
|
isGeneric,
|
|
269
270
|
originalProperty: member,
|
|
270
|
-
isPrimitive
|
|
271
|
-
isClassType
|
|
272
|
-
isArray
|
|
271
|
+
isPrimitive,
|
|
272
|
+
isClassType,
|
|
273
|
+
isArray,
|
|
273
274
|
isEnum,
|
|
274
275
|
isRef: false,
|
|
275
|
-
isTypeLiteral
|
|
276
|
+
isTypeLiteral,
|
|
277
|
+
genericClassReference
|
|
276
278
|
};
|
|
277
279
|
if (property.isClassType) {
|
|
278
280
|
const declaration = this.getDeclarationProperty(property);
|
|
@@ -310,6 +312,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
310
312
|
if (typeName.toLowerCase() === "uploadfile") return "UploadFile";
|
|
311
313
|
if (typeName.toLowerCase() === "uploadfiledto") return "UploadFileDto";
|
|
312
314
|
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
315
|
+
if (typeName === "Array") return `${this.getTypeNodeToString(typeNode.typeArguments[0], genericTypeMap)}[]`;
|
|
313
316
|
const firstTypeArg = typeNode.typeArguments[0];
|
|
314
317
|
if (firstTypeArg && typescript.default.isTypeReferenceNode(firstTypeArg) && typescript.default.isIdentifier(firstTypeArg.typeName)) {
|
|
315
318
|
if (firstTypeArg.typeName.text.toLowerCase() === "uploadfile") return "UploadFile";
|
|
@@ -332,6 +335,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
332
335
|
if (types.length > 0 && types[0]) return types[0];
|
|
333
336
|
return "object";
|
|
334
337
|
default:
|
|
338
|
+
if (typescript.default.isIndexedAccessTypeNode(typeNode)) {
|
|
339
|
+
const resolvedType = this.checker.getTypeAtLocation(typeNode);
|
|
340
|
+
const resolved = this.checker.typeToString(resolvedType);
|
|
341
|
+
if (this.isPrimitiveType(resolved)) return resolved;
|
|
342
|
+
}
|
|
335
343
|
const typeText = typeNode.getText();
|
|
336
344
|
if (genericTypeMap && genericTypeMap.has(typeText)) return genericTypeMap.get(typeText);
|
|
337
345
|
if (typeText.startsWith("Date")) return constants.jsPrimitives.Date.type;
|
|
@@ -406,7 +414,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
406
414
|
isGenericTypeFromSymbol(type) {
|
|
407
415
|
if (this.isSimpleArrayType(type)) return false;
|
|
408
416
|
if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
|
|
409
|
-
if (type.typeArguments && type.typeArguments.length > 0) {
|
|
417
|
+
if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
|
|
410
418
|
const symbol = type.getSymbol();
|
|
411
419
|
if (symbol && symbol.getName() === "Array") {
|
|
412
420
|
const elementType = type.typeArguments[0];
|
|
@@ -451,6 +459,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
451
459
|
if (!elementType) return false;
|
|
452
460
|
if (this.isUtilityTypeFromType(elementType)) return false;
|
|
453
461
|
if (elementType.typeArguments && elementType.typeArguments.length > 0) return false;
|
|
462
|
+
if (type.typeArguments && type.typeArguments[0].symbol && type.typeArguments[0].symbol.getName() !== "Array") return false;
|
|
454
463
|
return true;
|
|
455
464
|
}
|
|
456
465
|
return false;
|
|
@@ -508,22 +517,6 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
508
517
|
}
|
|
509
518
|
return matches[0];
|
|
510
519
|
}
|
|
511
|
-
checkTypeMatch(value, typeNode) {
|
|
512
|
-
const runtimeType = typeof value;
|
|
513
|
-
if (runtimeType === "string" && typeNode.kind === typescript.default.SyntaxKind.StringKeyword) return true;
|
|
514
|
-
if (runtimeType === "number" && typeNode.kind === typescript.default.SyntaxKind.NumberKeyword) return true;
|
|
515
|
-
if (runtimeType === "boolean" && typeNode.kind === typescript.default.SyntaxKind.BooleanKeyword) return true;
|
|
516
|
-
if (Array.isArray(value) && typescript.default.isArrayTypeNode(typeNode)) {
|
|
517
|
-
if (value.length === 0) return true;
|
|
518
|
-
const firstItem = value[0];
|
|
519
|
-
const elementType = typeNode.elementType;
|
|
520
|
-
return this.checkTypeMatch(firstItem, elementType);
|
|
521
|
-
}
|
|
522
|
-
if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
|
|
523
|
-
if (typescript.default.isTypeReferenceNode(typeNode) || typeNode.kind === typescript.default.SyntaxKind.ObjectKeyword) return true;
|
|
524
|
-
}
|
|
525
|
-
return false;
|
|
526
|
-
}
|
|
527
520
|
findBestMatch(cls, matches) {
|
|
528
521
|
let instance = {};
|
|
529
522
|
try {
|
|
@@ -565,7 +558,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
565
558
|
isClassType(propertyDeclaration) {
|
|
566
559
|
if (!propertyDeclaration.type) return false;
|
|
567
560
|
if (this.isArrayProperty(propertyDeclaration)) {
|
|
568
|
-
|
|
561
|
+
var _propertyDeclaration$;
|
|
562
|
+
let elementType;
|
|
563
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) elementType = propertyDeclaration.type.elementType;
|
|
564
|
+
else if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && ((_propertyDeclaration$ = propertyDeclaration.type.typeArguments) === null || _propertyDeclaration$ === void 0 ? void 0 : _propertyDeclaration$[0])) elementType = propertyDeclaration.type.typeArguments[0];
|
|
565
|
+
if (!elementType) return false;
|
|
569
566
|
if (typescript.default.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
|
|
570
567
|
const firstTypeArg = elementType.typeArguments[0];
|
|
571
568
|
if (firstTypeArg) {
|
|
@@ -626,7 +623,9 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
626
623
|
}
|
|
627
624
|
isArrayProperty(propertyDeclaration) {
|
|
628
625
|
if (!propertyDeclaration.type) return false;
|
|
629
|
-
|
|
626
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) return true;
|
|
627
|
+
if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && typescript.default.isIdentifier(propertyDeclaration.type.typeName) && propertyDeclaration.type.typeName.text === "Array") return true;
|
|
628
|
+
return false;
|
|
630
629
|
}
|
|
631
630
|
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
|
|
632
631
|
let schema = {};
|
|
@@ -661,6 +660,17 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
661
660
|
visitedClass,
|
|
662
661
|
transformedSchema
|
|
663
662
|
});
|
|
663
|
+
else if (property.isGeneric) if (property.genericClassReference) schema = this.getSchemaFromClass({
|
|
664
|
+
isArray: property.isArray,
|
|
665
|
+
visitedClass,
|
|
666
|
+
transformedSchema,
|
|
667
|
+
declaration: property.genericClassReference
|
|
668
|
+
});
|
|
669
|
+
else schema = {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {},
|
|
672
|
+
additionalProperties: true
|
|
673
|
+
};
|
|
664
674
|
else schema = {
|
|
665
675
|
type: "object",
|
|
666
676
|
properties: {},
|
|
@@ -838,6 +848,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
838
848
|
if (decorator.arguments.length === 0) return;
|
|
839
849
|
const arg = decorator.arguments[0];
|
|
840
850
|
if (arg && typeof arg === "object" && "kind" in arg) {
|
|
851
|
+
if (typescript.default.isArrayLiteralExpression(arg)) {
|
|
852
|
+
const values = this.extractValuesFromArrayLiteral(arg);
|
|
853
|
+
if (values.length > 0) this.applyEnumValues(values, schema);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
841
856
|
const type = this.checker.getTypeAtLocation(arg);
|
|
842
857
|
if (type.symbol && type.symbol.exports) {
|
|
843
858
|
const values = [];
|
|
@@ -849,15 +864,46 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
849
864
|
}
|
|
850
865
|
});
|
|
851
866
|
if (values.length > 0) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const isNumber = values.every((v) => typeof v === "number");
|
|
855
|
-
if (isString) schema.type = "string";
|
|
856
|
-
else if (isNumber) schema.type = "number";
|
|
857
|
-
else schema.type = "string";
|
|
867
|
+
this.applyEnumValues(values, schema);
|
|
868
|
+
return;
|
|
858
869
|
}
|
|
859
870
|
}
|
|
871
|
+
const values = this.extractValuesFromObjectLiteral(type);
|
|
872
|
+
if (values.length > 0) this.applyEnumValues(values, schema);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
extractValuesFromArrayLiteral(arrayLiteral) {
|
|
876
|
+
const values = [];
|
|
877
|
+
for (const element of arrayLiteral.elements) if (typescript.default.isStringLiteral(element)) values.push(element.text);
|
|
878
|
+
else if (typescript.default.isNumericLiteral(element)) values.push(Number(element.text));
|
|
879
|
+
else if (typescript.default.isPrefixUnaryExpression(element) && element.operator === typescript.default.SyntaxKind.MinusToken && typescript.default.isNumericLiteral(element.operand)) values.push(-Number(element.operand.text));
|
|
880
|
+
return values;
|
|
881
|
+
}
|
|
882
|
+
extractValuesFromObjectLiteral(type) {
|
|
883
|
+
const values = [];
|
|
884
|
+
const properties = type.getProperties();
|
|
885
|
+
if (!properties || properties.length === 0) return values;
|
|
886
|
+
for (const prop of properties) {
|
|
887
|
+
const propType = this.checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
888
|
+
if (propType.isStringLiteral()) values.push(propType.value);
|
|
889
|
+
else if (propType.isNumberLiteral()) values.push(propType.value);
|
|
890
|
+
else if (prop.valueDeclaration && typescript.default.isPropertyAssignment(prop.valueDeclaration)) {
|
|
891
|
+
const initializer = prop.valueDeclaration.initializer;
|
|
892
|
+
if (typescript.default.isStringLiteral(initializer)) values.push(initializer.text);
|
|
893
|
+
else if (typescript.default.isNumericLiteral(initializer)) values.push(Number(initializer.text));
|
|
894
|
+
}
|
|
860
895
|
}
|
|
896
|
+
return values;
|
|
897
|
+
}
|
|
898
|
+
applyEnumValues(values, schema) {
|
|
899
|
+
schema.enum = values;
|
|
900
|
+
const isString = values.every((v) => typeof v === "string");
|
|
901
|
+
const isNumber = values.every((v) => typeof v === "number");
|
|
902
|
+
if (isString) schema.type = "string";
|
|
903
|
+
else if (isNumber) schema.type = "number";
|
|
904
|
+
else schema.type = "string";
|
|
905
|
+
delete schema.properties;
|
|
906
|
+
delete schema.additionalProperties;
|
|
861
907
|
}
|
|
862
908
|
applyDecorators(property, schema) {
|
|
863
909
|
for (const decorator of property.decorators) switch (decorator.name) {
|
|
@@ -935,16 +981,65 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
935
981
|
break;
|
|
936
982
|
case constants.validatorDecorators.IsEnum.name:
|
|
937
983
|
if (!property.isArray) this.applyEnumDecorator(decorator, schema);
|
|
938
|
-
else
|
|
984
|
+
else {
|
|
985
|
+
if (!schema.items) {
|
|
986
|
+
schema.type = "array";
|
|
987
|
+
schema.items = {};
|
|
988
|
+
}
|
|
989
|
+
this.applyEnumDecorator(decorator, schema.items);
|
|
990
|
+
}
|
|
939
991
|
break;
|
|
940
992
|
}
|
|
941
993
|
}
|
|
994
|
+
/**
|
|
995
|
+
* Scans all non-declaration source files in the program once and records
|
|
996
|
+
* every call of the form `transform(Foo<Bar, Baz>)`, keyed by class name.
|
|
997
|
+
* This means generic resolution in transform() is a pure O(1) Map lookup
|
|
998
|
+
* with no runtime stack inspection.
|
|
999
|
+
*/
|
|
1000
|
+
buildTransformCallIndex() {
|
|
1001
|
+
this.program.getSourceFiles().forEach((sf) => {
|
|
1002
|
+
if (sf.isDeclarationFile) return;
|
|
1003
|
+
sf.statements.forEach((stmt) => {
|
|
1004
|
+
if (typescript.default.isClassDeclaration(stmt) && stmt.name) {
|
|
1005
|
+
const name = stmt.name.text;
|
|
1006
|
+
const entry = this.classFileIndex.get(name) || [];
|
|
1007
|
+
entry.push({
|
|
1008
|
+
sourceFile: sf,
|
|
1009
|
+
node: stmt
|
|
1010
|
+
});
|
|
1011
|
+
this.classFileIndex.set(name, entry);
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
const visit = (node) => {
|
|
1015
|
+
if (typescript.default.isCallExpression(node) && node.arguments.length > 0) {
|
|
1016
|
+
const callee = node.expression;
|
|
1017
|
+
if (typescript.default.isIdentifier(callee) && callee.text === "transform" || typescript.default.isPropertyAccessExpression(callee) && callee.name.text === "transform") {
|
|
1018
|
+
const firstArg = node.arguments[0];
|
|
1019
|
+
const typeArgs = firstArg.typeArguments;
|
|
1020
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
1021
|
+
const baseExpr = firstArg.expression ?? firstArg;
|
|
1022
|
+
if (typescript.default.isIdentifier(baseExpr)) {
|
|
1023
|
+
var _this$classFileIndex$;
|
|
1024
|
+
const classNode = (_this$classFileIndex$ = this.classFileIndex.get(baseExpr.text)) === null || _this$classFileIndex$ === void 0 || (_this$classFileIndex$ = _this$classFileIndex$[0]) === null || _this$classFileIndex$ === void 0 ? void 0 : _this$classFileIndex$.node;
|
|
1025
|
+
if (classNode === null || classNode === void 0 ? void 0 : classNode.typeParameters) {
|
|
1026
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1027
|
+
classNode.typeParameters.forEach((param, i) => {
|
|
1028
|
+
const typeArg = typeArgs[i];
|
|
1029
|
+
if (typeArg) typeMap.set(param.name.text, this.getTypeNodeToString(typeArg, /* @__PURE__ */ new Map()));
|
|
1030
|
+
});
|
|
1031
|
+
if (typeMap.size > 0) this.transformCallIndex.set(baseExpr.text, typeMap);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
typescript.default.forEachChild(node, visit);
|
|
1038
|
+
};
|
|
1039
|
+
visit(sf);
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
942
1042
|
transform(cls, sourceOptions) {
|
|
943
|
-
if (this.classCache.has(cls)) return this.classCache.get(cls);
|
|
944
|
-
let schema = {
|
|
945
|
-
type: "object",
|
|
946
|
-
properties: {}
|
|
947
|
-
};
|
|
948
1043
|
const result = this.getSourceFileByClass(cls, sourceOptions);
|
|
949
1044
|
if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
|
|
950
1045
|
console.warn(`Class ${cls.name} not found in any source file.`);
|
|
@@ -958,11 +1053,22 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
958
1053
|
}
|
|
959
1054
|
};
|
|
960
1055
|
}
|
|
961
|
-
const
|
|
1056
|
+
const genericTypeMap = this.transformCallIndex.get(cls.name) ?? /* @__PURE__ */ new Map();
|
|
1057
|
+
const hasGenericArgs = genericTypeMap.size > 0;
|
|
1058
|
+
if (!hasGenericArgs && this.classCache.has(cls)) return this.classCache.get(cls);
|
|
1059
|
+
let schema = {
|
|
1060
|
+
type: "object",
|
|
1061
|
+
properties: {}
|
|
1062
|
+
};
|
|
1063
|
+
const properties = this.getPropertiesByClassDeclaration(result.node, void 0, genericTypeMap);
|
|
962
1064
|
schema = this.getSchemaFromProperties({
|
|
963
1065
|
properties,
|
|
964
1066
|
classDeclaration: result.node
|
|
965
1067
|
});
|
|
1068
|
+
if (!hasGenericArgs) this.classCache.set(cls, {
|
|
1069
|
+
name: cls.name,
|
|
1070
|
+
schema
|
|
1071
|
+
});
|
|
966
1072
|
return {
|
|
967
1073
|
name: cls.name,
|
|
968
1074
|
schema
|
package/dist/index.mjs
CHANGED
|
@@ -164,6 +164,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
164
164
|
maxCacheSize;
|
|
165
165
|
autoCleanup;
|
|
166
166
|
classFileIndex = /* @__PURE__ */ new Map();
|
|
167
|
+
transformCallIndex = /* @__PURE__ */ new Map();
|
|
167
168
|
constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
|
|
168
169
|
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
169
170
|
this.autoCleanup = options.autoCleanup ?? true;
|
|
@@ -175,19 +176,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
175
176
|
const { options: tsOptions, fileNames } = ts.parseJsonConfigFileContent(config, ts.sys, "./");
|
|
176
177
|
this.program = ts.createProgram(fileNames, tsOptions);
|
|
177
178
|
this.checker = this.program.getTypeChecker();
|
|
178
|
-
this.
|
|
179
|
-
sf.statements.forEach((stmt) => {
|
|
180
|
-
if (ts.isClassDeclaration(stmt) && stmt.name) {
|
|
181
|
-
const name = stmt.name.text;
|
|
182
|
-
const entry = this.classFileIndex.get(name) || [];
|
|
183
|
-
entry.push({
|
|
184
|
-
sourceFile: sf,
|
|
185
|
-
node: stmt
|
|
186
|
-
});
|
|
187
|
-
this.classFileIndex.set(name, entry);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
179
|
+
this.buildTransformCallIndex();
|
|
191
180
|
}
|
|
192
181
|
getPropertiesByClassDeclaration(classNode, visitedDeclarations = /* @__PURE__ */ new Set(), genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
193
182
|
if (visitedDeclarations.has(classNode)) return [];
|
|
@@ -235,6 +224,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
235
224
|
const isOptional = !!member.questionToken;
|
|
236
225
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
237
226
|
const isEnum = this.isEnum(member);
|
|
227
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
228
|
+
const isClassType = this.isClassType(member);
|
|
229
|
+
const isArray = this.isArrayProperty(member);
|
|
230
|
+
const isTypeLiteral = this.isTypeLiteral(member);
|
|
231
|
+
let genericClassReference = void 0;
|
|
232
|
+
if (isGeneric && !isPrimitive) {
|
|
233
|
+
const baseTypeName = type.replace(/\[\]$/, "").trim();
|
|
234
|
+
if (!this.isPrimitiveType(baseTypeName)) {
|
|
235
|
+
const matches = this.classFileIndex.get(baseTypeName);
|
|
236
|
+
if (matches && matches.length > 0 && matches[0]) genericClassReference = matches[0].node;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
238
239
|
const property = {
|
|
239
240
|
name: propertyName,
|
|
240
241
|
type,
|
|
@@ -242,12 +243,13 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
242
243
|
isOptional,
|
|
243
244
|
isGeneric,
|
|
244
245
|
originalProperty: member,
|
|
245
|
-
isPrimitive
|
|
246
|
-
isClassType
|
|
247
|
-
isArray
|
|
246
|
+
isPrimitive,
|
|
247
|
+
isClassType,
|
|
248
|
+
isArray,
|
|
248
249
|
isEnum,
|
|
249
250
|
isRef: false,
|
|
250
|
-
isTypeLiteral
|
|
251
|
+
isTypeLiteral,
|
|
252
|
+
genericClassReference
|
|
251
253
|
};
|
|
252
254
|
if (property.isClassType) {
|
|
253
255
|
const declaration = this.getDeclarationProperty(property);
|
|
@@ -285,6 +287,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
285
287
|
if (typeName.toLowerCase() === "uploadfile") return "UploadFile";
|
|
286
288
|
if (typeName.toLowerCase() === "uploadfiledto") return "UploadFileDto";
|
|
287
289
|
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
290
|
+
if (typeName === "Array") return `${this.getTypeNodeToString(typeNode.typeArguments[0], genericTypeMap)}[]`;
|
|
288
291
|
const firstTypeArg = typeNode.typeArguments[0];
|
|
289
292
|
if (firstTypeArg && ts.isTypeReferenceNode(firstTypeArg) && ts.isIdentifier(firstTypeArg.typeName)) {
|
|
290
293
|
if (firstTypeArg.typeName.text.toLowerCase() === "uploadfile") return "UploadFile";
|
|
@@ -307,6 +310,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
307
310
|
if (types.length > 0 && types[0]) return types[0];
|
|
308
311
|
return "object";
|
|
309
312
|
default:
|
|
313
|
+
if (ts.isIndexedAccessTypeNode(typeNode)) {
|
|
314
|
+
const resolvedType = this.checker.getTypeAtLocation(typeNode);
|
|
315
|
+
const resolved = this.checker.typeToString(resolvedType);
|
|
316
|
+
if (this.isPrimitiveType(resolved)) return resolved;
|
|
317
|
+
}
|
|
310
318
|
const typeText = typeNode.getText();
|
|
311
319
|
if (genericTypeMap && genericTypeMap.has(typeText)) return genericTypeMap.get(typeText);
|
|
312
320
|
if (typeText.startsWith("Date")) return constants.jsPrimitives.Date.type;
|
|
@@ -381,7 +389,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
381
389
|
isGenericTypeFromSymbol(type) {
|
|
382
390
|
if (this.isSimpleArrayType(type)) return false;
|
|
383
391
|
if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
|
|
384
|
-
if (type.typeArguments && type.typeArguments.length > 0) {
|
|
392
|
+
if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
|
|
385
393
|
const symbol = type.getSymbol();
|
|
386
394
|
if (symbol && symbol.getName() === "Array") {
|
|
387
395
|
const elementType = type.typeArguments[0];
|
|
@@ -426,6 +434,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
426
434
|
if (!elementType) return false;
|
|
427
435
|
if (this.isUtilityTypeFromType(elementType)) return false;
|
|
428
436
|
if (elementType.typeArguments && elementType.typeArguments.length > 0) return false;
|
|
437
|
+
if (type.typeArguments && type.typeArguments[0].symbol && type.typeArguments[0].symbol.getName() !== "Array") return false;
|
|
429
438
|
return true;
|
|
430
439
|
}
|
|
431
440
|
return false;
|
|
@@ -483,22 +492,6 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
483
492
|
}
|
|
484
493
|
return matches[0];
|
|
485
494
|
}
|
|
486
|
-
checkTypeMatch(value, typeNode) {
|
|
487
|
-
const runtimeType = typeof value;
|
|
488
|
-
if (runtimeType === "string" && typeNode.kind === ts.SyntaxKind.StringKeyword) return true;
|
|
489
|
-
if (runtimeType === "number" && typeNode.kind === ts.SyntaxKind.NumberKeyword) return true;
|
|
490
|
-
if (runtimeType === "boolean" && typeNode.kind === ts.SyntaxKind.BooleanKeyword) return true;
|
|
491
|
-
if (Array.isArray(value) && ts.isArrayTypeNode(typeNode)) {
|
|
492
|
-
if (value.length === 0) return true;
|
|
493
|
-
const firstItem = value[0];
|
|
494
|
-
const elementType = typeNode.elementType;
|
|
495
|
-
return this.checkTypeMatch(firstItem, elementType);
|
|
496
|
-
}
|
|
497
|
-
if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
|
|
498
|
-
if (ts.isTypeReferenceNode(typeNode) || typeNode.kind === ts.SyntaxKind.ObjectKeyword) return true;
|
|
499
|
-
}
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
495
|
findBestMatch(cls, matches) {
|
|
503
496
|
let instance = {};
|
|
504
497
|
try {
|
|
@@ -540,7 +533,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
540
533
|
isClassType(propertyDeclaration) {
|
|
541
534
|
if (!propertyDeclaration.type) return false;
|
|
542
535
|
if (this.isArrayProperty(propertyDeclaration)) {
|
|
543
|
-
|
|
536
|
+
var _propertyDeclaration$;
|
|
537
|
+
let elementType;
|
|
538
|
+
if (ts.isArrayTypeNode(propertyDeclaration.type)) elementType = propertyDeclaration.type.elementType;
|
|
539
|
+
else if (ts.isTypeReferenceNode(propertyDeclaration.type) && ((_propertyDeclaration$ = propertyDeclaration.type.typeArguments) === null || _propertyDeclaration$ === void 0 ? void 0 : _propertyDeclaration$[0])) elementType = propertyDeclaration.type.typeArguments[0];
|
|
540
|
+
if (!elementType) return false;
|
|
544
541
|
if (ts.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
|
|
545
542
|
const firstTypeArg = elementType.typeArguments[0];
|
|
546
543
|
if (firstTypeArg) {
|
|
@@ -601,7 +598,9 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
601
598
|
}
|
|
602
599
|
isArrayProperty(propertyDeclaration) {
|
|
603
600
|
if (!propertyDeclaration.type) return false;
|
|
604
|
-
|
|
601
|
+
if (ts.isArrayTypeNode(propertyDeclaration.type)) return true;
|
|
602
|
+
if (ts.isTypeReferenceNode(propertyDeclaration.type) && ts.isIdentifier(propertyDeclaration.type.typeName) && propertyDeclaration.type.typeName.text === "Array") return true;
|
|
603
|
+
return false;
|
|
605
604
|
}
|
|
606
605
|
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
|
|
607
606
|
let schema = {};
|
|
@@ -636,6 +635,17 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
636
635
|
visitedClass,
|
|
637
636
|
transformedSchema
|
|
638
637
|
});
|
|
638
|
+
else if (property.isGeneric) if (property.genericClassReference) schema = this.getSchemaFromClass({
|
|
639
|
+
isArray: property.isArray,
|
|
640
|
+
visitedClass,
|
|
641
|
+
transformedSchema,
|
|
642
|
+
declaration: property.genericClassReference
|
|
643
|
+
});
|
|
644
|
+
else schema = {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {},
|
|
647
|
+
additionalProperties: true
|
|
648
|
+
};
|
|
639
649
|
else schema = {
|
|
640
650
|
type: "object",
|
|
641
651
|
properties: {},
|
|
@@ -813,6 +823,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
813
823
|
if (decorator.arguments.length === 0) return;
|
|
814
824
|
const arg = decorator.arguments[0];
|
|
815
825
|
if (arg && typeof arg === "object" && "kind" in arg) {
|
|
826
|
+
if (ts.isArrayLiteralExpression(arg)) {
|
|
827
|
+
const values = this.extractValuesFromArrayLiteral(arg);
|
|
828
|
+
if (values.length > 0) this.applyEnumValues(values, schema);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
816
831
|
const type = this.checker.getTypeAtLocation(arg);
|
|
817
832
|
if (type.symbol && type.symbol.exports) {
|
|
818
833
|
const values = [];
|
|
@@ -824,15 +839,46 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
824
839
|
}
|
|
825
840
|
});
|
|
826
841
|
if (values.length > 0) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const isNumber = values.every((v) => typeof v === "number");
|
|
830
|
-
if (isString) schema.type = "string";
|
|
831
|
-
else if (isNumber) schema.type = "number";
|
|
832
|
-
else schema.type = "string";
|
|
842
|
+
this.applyEnumValues(values, schema);
|
|
843
|
+
return;
|
|
833
844
|
}
|
|
834
845
|
}
|
|
846
|
+
const values = this.extractValuesFromObjectLiteral(type);
|
|
847
|
+
if (values.length > 0) this.applyEnumValues(values, schema);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
extractValuesFromArrayLiteral(arrayLiteral) {
|
|
851
|
+
const values = [];
|
|
852
|
+
for (const element of arrayLiteral.elements) if (ts.isStringLiteral(element)) values.push(element.text);
|
|
853
|
+
else if (ts.isNumericLiteral(element)) values.push(Number(element.text));
|
|
854
|
+
else if (ts.isPrefixUnaryExpression(element) && element.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(element.operand)) values.push(-Number(element.operand.text));
|
|
855
|
+
return values;
|
|
856
|
+
}
|
|
857
|
+
extractValuesFromObjectLiteral(type) {
|
|
858
|
+
const values = [];
|
|
859
|
+
const properties = type.getProperties();
|
|
860
|
+
if (!properties || properties.length === 0) return values;
|
|
861
|
+
for (const prop of properties) {
|
|
862
|
+
const propType = this.checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
863
|
+
if (propType.isStringLiteral()) values.push(propType.value);
|
|
864
|
+
else if (propType.isNumberLiteral()) values.push(propType.value);
|
|
865
|
+
else if (prop.valueDeclaration && ts.isPropertyAssignment(prop.valueDeclaration)) {
|
|
866
|
+
const initializer = prop.valueDeclaration.initializer;
|
|
867
|
+
if (ts.isStringLiteral(initializer)) values.push(initializer.text);
|
|
868
|
+
else if (ts.isNumericLiteral(initializer)) values.push(Number(initializer.text));
|
|
869
|
+
}
|
|
835
870
|
}
|
|
871
|
+
return values;
|
|
872
|
+
}
|
|
873
|
+
applyEnumValues(values, schema) {
|
|
874
|
+
schema.enum = values;
|
|
875
|
+
const isString = values.every((v) => typeof v === "string");
|
|
876
|
+
const isNumber = values.every((v) => typeof v === "number");
|
|
877
|
+
if (isString) schema.type = "string";
|
|
878
|
+
else if (isNumber) schema.type = "number";
|
|
879
|
+
else schema.type = "string";
|
|
880
|
+
delete schema.properties;
|
|
881
|
+
delete schema.additionalProperties;
|
|
836
882
|
}
|
|
837
883
|
applyDecorators(property, schema) {
|
|
838
884
|
for (const decorator of property.decorators) switch (decorator.name) {
|
|
@@ -910,16 +956,65 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
910
956
|
break;
|
|
911
957
|
case constants.validatorDecorators.IsEnum.name:
|
|
912
958
|
if (!property.isArray) this.applyEnumDecorator(decorator, schema);
|
|
913
|
-
else
|
|
959
|
+
else {
|
|
960
|
+
if (!schema.items) {
|
|
961
|
+
schema.type = "array";
|
|
962
|
+
schema.items = {};
|
|
963
|
+
}
|
|
964
|
+
this.applyEnumDecorator(decorator, schema.items);
|
|
965
|
+
}
|
|
914
966
|
break;
|
|
915
967
|
}
|
|
916
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Scans all non-declaration source files in the program once and records
|
|
971
|
+
* every call of the form `transform(Foo<Bar, Baz>)`, keyed by class name.
|
|
972
|
+
* This means generic resolution in transform() is a pure O(1) Map lookup
|
|
973
|
+
* with no runtime stack inspection.
|
|
974
|
+
*/
|
|
975
|
+
buildTransformCallIndex() {
|
|
976
|
+
this.program.getSourceFiles().forEach((sf) => {
|
|
977
|
+
if (sf.isDeclarationFile) return;
|
|
978
|
+
sf.statements.forEach((stmt) => {
|
|
979
|
+
if (ts.isClassDeclaration(stmt) && stmt.name) {
|
|
980
|
+
const name = stmt.name.text;
|
|
981
|
+
const entry = this.classFileIndex.get(name) || [];
|
|
982
|
+
entry.push({
|
|
983
|
+
sourceFile: sf,
|
|
984
|
+
node: stmt
|
|
985
|
+
});
|
|
986
|
+
this.classFileIndex.set(name, entry);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
const visit = (node) => {
|
|
990
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
991
|
+
const callee = node.expression;
|
|
992
|
+
if (ts.isIdentifier(callee) && callee.text === "transform" || ts.isPropertyAccessExpression(callee) && callee.name.text === "transform") {
|
|
993
|
+
const firstArg = node.arguments[0];
|
|
994
|
+
const typeArgs = firstArg.typeArguments;
|
|
995
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
996
|
+
const baseExpr = firstArg.expression ?? firstArg;
|
|
997
|
+
if (ts.isIdentifier(baseExpr)) {
|
|
998
|
+
var _this$classFileIndex$;
|
|
999
|
+
const classNode = (_this$classFileIndex$ = this.classFileIndex.get(baseExpr.text)) === null || _this$classFileIndex$ === void 0 || (_this$classFileIndex$ = _this$classFileIndex$[0]) === null || _this$classFileIndex$ === void 0 ? void 0 : _this$classFileIndex$.node;
|
|
1000
|
+
if (classNode === null || classNode === void 0 ? void 0 : classNode.typeParameters) {
|
|
1001
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1002
|
+
classNode.typeParameters.forEach((param, i) => {
|
|
1003
|
+
const typeArg = typeArgs[i];
|
|
1004
|
+
if (typeArg) typeMap.set(param.name.text, this.getTypeNodeToString(typeArg, /* @__PURE__ */ new Map()));
|
|
1005
|
+
});
|
|
1006
|
+
if (typeMap.size > 0) this.transformCallIndex.set(baseExpr.text, typeMap);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
ts.forEachChild(node, visit);
|
|
1013
|
+
};
|
|
1014
|
+
visit(sf);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
917
1017
|
transform(cls, sourceOptions) {
|
|
918
|
-
if (this.classCache.has(cls)) return this.classCache.get(cls);
|
|
919
|
-
let schema = {
|
|
920
|
-
type: "object",
|
|
921
|
-
properties: {}
|
|
922
|
-
};
|
|
923
1018
|
const result = this.getSourceFileByClass(cls, sourceOptions);
|
|
924
1019
|
if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
|
|
925
1020
|
console.warn(`Class ${cls.name} not found in any source file.`);
|
|
@@ -933,11 +1028,22 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
933
1028
|
}
|
|
934
1029
|
};
|
|
935
1030
|
}
|
|
936
|
-
const
|
|
1031
|
+
const genericTypeMap = this.transformCallIndex.get(cls.name) ?? /* @__PURE__ */ new Map();
|
|
1032
|
+
const hasGenericArgs = genericTypeMap.size > 0;
|
|
1033
|
+
if (!hasGenericArgs && this.classCache.has(cls)) return this.classCache.get(cls);
|
|
1034
|
+
let schema = {
|
|
1035
|
+
type: "object",
|
|
1036
|
+
properties: {}
|
|
1037
|
+
};
|
|
1038
|
+
const properties = this.getPropertiesByClassDeclaration(result.node, void 0, genericTypeMap);
|
|
937
1039
|
schema = this.getSchemaFromProperties({
|
|
938
1040
|
properties,
|
|
939
1041
|
classDeclaration: result.node
|
|
940
1042
|
});
|
|
1043
|
+
if (!hasGenericArgs) this.classCache.set(cls, {
|
|
1044
|
+
name: cls.name,
|
|
1045
|
+
schema
|
|
1046
|
+
});
|
|
941
1047
|
return {
|
|
942
1048
|
name: cls.name,
|
|
943
1049
|
schema
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-class-to-openapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Transform TypeScript classes into OpenAPI 3.1.0 schema objects, which support class-validator decorators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -81,9 +81,9 @@
|
|
|
81
81
|
"test": "node --import tsx --test test/testCases/**/*.test.ts",
|
|
82
82
|
"test:watch": "node --import tsx --inspect --test --watch test/testCases/**/*.test.ts",
|
|
83
83
|
"test:coverage": "node --import tsx --test --experimental-test-coverage test/testCases/**/*.test.ts",
|
|
84
|
-
"build": "tsc --noEmit &&
|
|
84
|
+
"build": "tsc --noEmit && tsdown",
|
|
85
85
|
"build:watch": "rm -rf dist && tsdown --watch",
|
|
86
|
-
"dev": "node --import tsx --
|
|
86
|
+
"dev": "node --import tsx --inspect --watch ./src/run.ts",
|
|
87
87
|
"format": "prettier --write .",
|
|
88
88
|
"format:check": "prettier --check .",
|
|
89
89
|
"prepublish": "pnpm build"
|