ts-class-to-openapi 1.4.0 → 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.
Files changed (3) hide show
  1. package/dist/index.cjs +101 -42
  2. package/dist/index.mjs +101 -42
  3. package/package.json +1 -1
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.program.getSourceFiles().forEach((sf) => {
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: this.isPrimitiveType(type) || isEnum,
271
- isClassType: this.isClassType(member),
272
- isArray: this.isArrayProperty(member),
271
+ isPrimitive,
272
+ isClassType,
273
+ isArray,
273
274
  isEnum,
274
275
  isRef: false,
275
- isTypeLiteral: this.isTypeLiteral(member)
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";
@@ -411,7 +414,7 @@ var SchemaTransformer = class SchemaTransformer {
411
414
  isGenericTypeFromSymbol(type) {
412
415
  if (this.isSimpleArrayType(type)) return false;
413
416
  if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
414
- if (type.typeArguments && type.typeArguments.length > 0) {
417
+ if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
415
418
  const symbol = type.getSymbol();
416
419
  if (symbol && symbol.getName() === "Array") {
417
420
  const elementType = type.typeArguments[0];
@@ -456,6 +459,7 @@ var SchemaTransformer = class SchemaTransformer {
456
459
  if (!elementType) return false;
457
460
  if (this.isUtilityTypeFromType(elementType)) return false;
458
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;
459
463
  return true;
460
464
  }
461
465
  return false;
@@ -513,22 +517,6 @@ var SchemaTransformer = class SchemaTransformer {
513
517
  }
514
518
  return matches[0];
515
519
  }
516
- checkTypeMatch(value, typeNode) {
517
- const runtimeType = typeof value;
518
- if (runtimeType === "string" && typeNode.kind === typescript.default.SyntaxKind.StringKeyword) return true;
519
- if (runtimeType === "number" && typeNode.kind === typescript.default.SyntaxKind.NumberKeyword) return true;
520
- if (runtimeType === "boolean" && typeNode.kind === typescript.default.SyntaxKind.BooleanKeyword) return true;
521
- if (Array.isArray(value) && typescript.default.isArrayTypeNode(typeNode)) {
522
- if (value.length === 0) return true;
523
- const firstItem = value[0];
524
- const elementType = typeNode.elementType;
525
- return this.checkTypeMatch(firstItem, elementType);
526
- }
527
- if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
528
- if (typescript.default.isTypeReferenceNode(typeNode) || typeNode.kind === typescript.default.SyntaxKind.ObjectKeyword) return true;
529
- }
530
- return false;
531
- }
532
520
  findBestMatch(cls, matches) {
533
521
  let instance = {};
534
522
  try {
@@ -570,7 +558,11 @@ var SchemaTransformer = class SchemaTransformer {
570
558
  isClassType(propertyDeclaration) {
571
559
  if (!propertyDeclaration.type) return false;
572
560
  if (this.isArrayProperty(propertyDeclaration)) {
573
- const elementType = propertyDeclaration.type.elementType;
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;
574
566
  if (typescript.default.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
575
567
  const firstTypeArg = elementType.typeArguments[0];
576
568
  if (firstTypeArg) {
@@ -631,7 +623,9 @@ var SchemaTransformer = class SchemaTransformer {
631
623
  }
632
624
  isArrayProperty(propertyDeclaration) {
633
625
  if (!propertyDeclaration.type) return false;
634
- return typescript.default.isArrayTypeNode(propertyDeclaration.type);
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;
635
629
  }
636
630
  getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
637
631
  let schema = {};
@@ -666,6 +660,17 @@ var SchemaTransformer = class SchemaTransformer {
666
660
  visitedClass,
667
661
  transformedSchema
668
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
+ };
669
674
  else schema = {
670
675
  type: "object",
671
676
  properties: {},
@@ -986,12 +991,55 @@ var SchemaTransformer = class SchemaTransformer {
986
991
  break;
987
992
  }
988
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
+ }
989
1042
  transform(cls, sourceOptions) {
990
- if (this.classCache.has(cls)) return this.classCache.get(cls);
991
- let schema = {
992
- type: "object",
993
- properties: {}
994
- };
995
1043
  const result = this.getSourceFileByClass(cls, sourceOptions);
996
1044
  if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
997
1045
  console.warn(`Class ${cls.name} not found in any source file.`);
@@ -1005,11 +1053,22 @@ var SchemaTransformer = class SchemaTransformer {
1005
1053
  }
1006
1054
  };
1007
1055
  }
1008
- const properties = this.getPropertiesByClassDeclaration(result.node);
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);
1009
1064
  schema = this.getSchemaFromProperties({
1010
1065
  properties,
1011
1066
  classDeclaration: result.node
1012
1067
  });
1068
+ if (!hasGenericArgs) this.classCache.set(cls, {
1069
+ name: cls.name,
1070
+ schema
1071
+ });
1013
1072
  return {
1014
1073
  name: cls.name,
1015
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.program.getSourceFiles().forEach((sf) => {
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: this.isPrimitiveType(type) || isEnum,
246
- isClassType: this.isClassType(member),
247
- isArray: this.isArrayProperty(member),
246
+ isPrimitive,
247
+ isClassType,
248
+ isArray,
248
249
  isEnum,
249
250
  isRef: false,
250
- isTypeLiteral: this.isTypeLiteral(member)
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";
@@ -386,7 +389,7 @@ var SchemaTransformer = class SchemaTransformer {
386
389
  isGenericTypeFromSymbol(type) {
387
390
  if (this.isSimpleArrayType(type)) return false;
388
391
  if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
389
- if (type.typeArguments && type.typeArguments.length > 0) {
392
+ if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
390
393
  const symbol = type.getSymbol();
391
394
  if (symbol && symbol.getName() === "Array") {
392
395
  const elementType = type.typeArguments[0];
@@ -431,6 +434,7 @@ var SchemaTransformer = class SchemaTransformer {
431
434
  if (!elementType) return false;
432
435
  if (this.isUtilityTypeFromType(elementType)) return false;
433
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;
434
438
  return true;
435
439
  }
436
440
  return false;
@@ -488,22 +492,6 @@ var SchemaTransformer = class SchemaTransformer {
488
492
  }
489
493
  return matches[0];
490
494
  }
491
- checkTypeMatch(value, typeNode) {
492
- const runtimeType = typeof value;
493
- if (runtimeType === "string" && typeNode.kind === ts.SyntaxKind.StringKeyword) return true;
494
- if (runtimeType === "number" && typeNode.kind === ts.SyntaxKind.NumberKeyword) return true;
495
- if (runtimeType === "boolean" && typeNode.kind === ts.SyntaxKind.BooleanKeyword) return true;
496
- if (Array.isArray(value) && ts.isArrayTypeNode(typeNode)) {
497
- if (value.length === 0) return true;
498
- const firstItem = value[0];
499
- const elementType = typeNode.elementType;
500
- return this.checkTypeMatch(firstItem, elementType);
501
- }
502
- if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
503
- if (ts.isTypeReferenceNode(typeNode) || typeNode.kind === ts.SyntaxKind.ObjectKeyword) return true;
504
- }
505
- return false;
506
- }
507
495
  findBestMatch(cls, matches) {
508
496
  let instance = {};
509
497
  try {
@@ -545,7 +533,11 @@ var SchemaTransformer = class SchemaTransformer {
545
533
  isClassType(propertyDeclaration) {
546
534
  if (!propertyDeclaration.type) return false;
547
535
  if (this.isArrayProperty(propertyDeclaration)) {
548
- const elementType = propertyDeclaration.type.elementType;
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;
549
541
  if (ts.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
550
542
  const firstTypeArg = elementType.typeArguments[0];
551
543
  if (firstTypeArg) {
@@ -606,7 +598,9 @@ var SchemaTransformer = class SchemaTransformer {
606
598
  }
607
599
  isArrayProperty(propertyDeclaration) {
608
600
  if (!propertyDeclaration.type) return false;
609
- return ts.isArrayTypeNode(propertyDeclaration.type);
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;
610
604
  }
611
605
  getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
612
606
  let schema = {};
@@ -641,6 +635,17 @@ var SchemaTransformer = class SchemaTransformer {
641
635
  visitedClass,
642
636
  transformedSchema
643
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
+ };
644
649
  else schema = {
645
650
  type: "object",
646
651
  properties: {},
@@ -961,12 +966,55 @@ var SchemaTransformer = class SchemaTransformer {
961
966
  break;
962
967
  }
963
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
+ }
964
1017
  transform(cls, sourceOptions) {
965
- if (this.classCache.has(cls)) return this.classCache.get(cls);
966
- let schema = {
967
- type: "object",
968
- properties: {}
969
- };
970
1018
  const result = this.getSourceFileByClass(cls, sourceOptions);
971
1019
  if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
972
1020
  console.warn(`Class ${cls.name} not found in any source file.`);
@@ -980,11 +1028,22 @@ var SchemaTransformer = class SchemaTransformer {
980
1028
  }
981
1029
  };
982
1030
  }
983
- const properties = this.getPropertiesByClassDeclaration(result.node);
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);
984
1039
  schema = this.getSchemaFromProperties({
985
1040
  properties,
986
1041
  classDeclaration: result.node
987
1042
  });
1043
+ if (!hasGenericArgs) this.classCache.set(cls, {
1044
+ name: cls.name,
1045
+ schema
1046
+ });
988
1047
  return {
989
1048
  name: cls.name,
990
1049
  schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-class-to-openapi",
3
- "version": "1.4.0",
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",