ts-class-to-openapi 1.2.0 → 1.2.2

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/dist/index.js CHANGED
@@ -112,7 +112,8 @@ class SchemaTransformer {
112
112
  const decorators = this.extractDecorators(member);
113
113
  const isOptional = !!member.questionToken;
114
114
  const isGeneric = this.isPropertyTypeGeneric(member);
115
- const isPrimitive = this.isPrimitiveType(type);
115
+ const isEnum = this.isEnum(member);
116
+ const isPrimitive = this.isPrimitiveType(type) || isEnum;
116
117
  const isClassType = this.isClassType(member);
117
118
  const isArray = this.isArrayProperty(member);
118
119
  const isTypeLiteral = this.isTypeLiteral(member);
@@ -126,6 +127,7 @@ class SchemaTransformer {
126
127
  isPrimitive,
127
128
  isClassType,
128
129
  isArray,
130
+ isEnum,
129
131
  isRef: false,
130
132
  isTypeLiteral: isTypeLiteral,
131
133
  };
@@ -277,9 +279,15 @@ class SchemaTransformer {
277
279
  return true;
278
280
  if (arg.kind === ts.SyntaxKind.FalseKeyword)
279
281
  return false;
280
- return arg.getText();
282
+ return arg;
281
283
  });
282
284
  }
285
+ getSafeDecoratorArgument(arg) {
286
+ if (arg && typeof arg === 'object' && 'kind' in arg) {
287
+ return arg.getText();
288
+ }
289
+ return arg;
290
+ }
283
291
  isPropertyTypeGeneric(property) {
284
292
  if (property.type && this.isGenericTypeFromNode(property.type)) {
285
293
  return true;
@@ -477,49 +485,135 @@ class SchemaTransformer {
477
485
  }
478
486
  }
479
487
  }
488
+ isEnum(propertyDeclaration) {
489
+ if (!propertyDeclaration.type) {
490
+ return false;
491
+ }
492
+ let typeNode = propertyDeclaration.type;
493
+ if (ts.isArrayTypeNode(typeNode)) {
494
+ typeNode = typeNode.elementType;
495
+ }
496
+ if (ts.isTypeReferenceNode(typeNode)) {
497
+ const type = this.checker.getTypeAtLocation(typeNode);
498
+ // console.log('isEnum check:', typeNode.getText(), type.flags)
499
+ return (!!(type.flags & ts.TypeFlags.Enum) ||
500
+ !!(type.flags & ts.TypeFlags.EnumLiteral));
501
+ }
502
+ return false;
503
+ }
480
504
  isClassType(propertyDeclaration) {
481
505
  // If there's no explicit type annotation, we can't determine reliably
482
506
  if (!propertyDeclaration.type) {
483
507
  return false;
484
508
  }
485
509
  // Check if the original property type is an array type
486
- if (this.isArrayProperty(propertyDeclaration) &&
487
- ts.isTypeReferenceNode(propertyDeclaration.type
488
- .elementType)) {
489
- const type = this.checker.getTypeAtLocation(propertyDeclaration.type.elementType);
510
+ if (this.isArrayProperty(propertyDeclaration)) {
511
+ const arrayType = propertyDeclaration.type;
512
+ const elementType = arrayType.elementType;
513
+ // Special handling for utility types with type arguments (e.g., PayloadEntity<Person>)
514
+ if (ts.isTypeReferenceNode(elementType) &&
515
+ elementType.typeArguments &&
516
+ elementType.typeArguments.length > 0) {
517
+ // Check the first type argument - it might be the actual class
518
+ const firstTypeArg = elementType.typeArguments[0];
519
+ if (firstTypeArg) {
520
+ const argType = this.checker.getTypeAtLocation(firstTypeArg);
521
+ const argSymbol = argType.getSymbol();
522
+ if (argSymbol && argSymbol.declarations) {
523
+ const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
524
+ if (hasClass)
525
+ return true;
526
+ }
527
+ }
528
+ }
529
+ // Get the type from the element, regardless of its syntaxkind
530
+ const type = this.checker.getTypeAtLocation(elementType);
490
531
  const symbol = type.getSymbol();
491
532
  if (symbol && symbol.declarations) {
492
533
  return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
493
534
  }
535
+ return false;
494
536
  }
495
- else if (ts.isTypeReferenceNode(propertyDeclaration.type)) {
537
+ // Check non-array types
538
+ else {
539
+ // Special handling for utility types with type arguments (e.g., PayloadEntity<Branch>)
540
+ if (ts.isTypeReferenceNode(propertyDeclaration.type) &&
541
+ propertyDeclaration.type.typeArguments &&
542
+ propertyDeclaration.type.typeArguments.length > 0) {
543
+ // Check the first type argument - it might be the actual class
544
+ const firstTypeArg = propertyDeclaration.type.typeArguments[0];
545
+ if (firstTypeArg) {
546
+ const argType = this.checker.getTypeAtLocation(firstTypeArg);
547
+ const argSymbol = argType.getSymbol();
548
+ if (argSymbol && argSymbol.declarations) {
549
+ const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
550
+ if (hasClass)
551
+ return true;
552
+ }
553
+ }
554
+ }
496
555
  const type = this.checker.getTypeAtLocation(propertyDeclaration.type);
497
556
  const symbol = type.getSymbol();
498
557
  if (symbol && symbol.declarations) {
499
558
  return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
500
559
  }
560
+ return false;
501
561
  }
502
- return false;
503
562
  }
504
563
  getDeclarationProperty(property) {
505
564
  if (!property.originalProperty.type) {
506
565
  return undefined;
507
566
  }
508
- if (ts.isArrayTypeNode(property.originalProperty.type) &&
509
- ts.isTypeReferenceNode(property.originalProperty.type.elementType)) {
510
- const type = this.checker.getTypeAtLocation(property.originalProperty.type.elementType);
567
+ // Handle array types - get the element type
568
+ if (ts.isArrayTypeNode(property.originalProperty.type)) {
569
+ const elementType = property.originalProperty.type.elementType;
570
+ // Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>[])
571
+ if (ts.isTypeReferenceNode(elementType) &&
572
+ elementType.typeArguments &&
573
+ elementType.typeArguments.length > 0) {
574
+ const firstTypeArg = elementType.typeArguments[0];
575
+ if (firstTypeArg) {
576
+ const argType = this.checker.getTypeAtLocation(firstTypeArg);
577
+ const argSymbol = argType.getSymbol();
578
+ if (argSymbol && argSymbol.declarations) {
579
+ const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
580
+ if (classDecl)
581
+ return classDecl;
582
+ }
583
+ }
584
+ }
585
+ const type = this.checker.getTypeAtLocation(elementType);
511
586
  const symbol = type.getSymbol();
512
587
  if (symbol && symbol.declarations) {
513
- return symbol.declarations[0];
588
+ // Return the first class declaration found
589
+ const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
590
+ return classDecl || symbol.declarations[0];
514
591
  }
592
+ return undefined;
515
593
  }
516
- else if (ts.isTypeReferenceNode(property.originalProperty.type)) {
517
- const type = this.checker.getTypeAtLocation(property.originalProperty.type);
518
- const symbol = type.getSymbol();
519
- if (symbol && symbol.declarations) {
520
- return symbol.declarations[0];
594
+ // Handle non-array types
595
+ // Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>)
596
+ if (ts.isTypeReferenceNode(property.originalProperty.type) &&
597
+ property.originalProperty.type.typeArguments &&
598
+ property.originalProperty.type.typeArguments.length > 0) {
599
+ const firstTypeArg = property.originalProperty.type.typeArguments[0];
600
+ if (firstTypeArg) {
601
+ const argType = this.checker.getTypeAtLocation(firstTypeArg);
602
+ const argSymbol = argType.getSymbol();
603
+ if (argSymbol && argSymbol.declarations) {
604
+ const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
605
+ if (classDecl)
606
+ return classDecl;
607
+ }
521
608
  }
522
609
  }
610
+ const type = this.checker.getTypeAtLocation(property.originalProperty.type);
611
+ const symbol = type.getSymbol();
612
+ if (symbol && symbol.declarations) {
613
+ // Return the first class declaration found
614
+ const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
615
+ return classDecl || symbol.declarations[0];
616
+ }
523
617
  return undefined;
524
618
  }
525
619
  isArrayProperty(propertyDeclaration) {
@@ -656,7 +750,58 @@ class SchemaTransformer {
656
750
  transformedSchema.set(declaration.name.text, schema);
657
751
  return schema;
658
752
  }
753
+ getSchemaFromEnum(property) {
754
+ let typeNode = property.originalProperty.type;
755
+ if (ts.isArrayTypeNode(typeNode)) {
756
+ typeNode = typeNode.elementType;
757
+ }
758
+ const type = this.checker.getTypeAtLocation(typeNode);
759
+ if (type.symbol && type.symbol.exports) {
760
+ const values = [];
761
+ type.symbol.exports.forEach(member => {
762
+ const declaration = member.valueDeclaration;
763
+ if (declaration && ts.isEnumMember(declaration)) {
764
+ const value = this.checker.getConstantValue(declaration);
765
+ if (value !== undefined) {
766
+ values.push(value);
767
+ }
768
+ }
769
+ });
770
+ if (values.length > 0) {
771
+ const propertySchema = { type: 'object' };
772
+ propertySchema.enum = values;
773
+ const isString = values.every(v => typeof v === 'string');
774
+ const isNumber = values.every(v => typeof v === 'number');
775
+ if (isString) {
776
+ propertySchema.type = 'string';
777
+ }
778
+ else if (isNumber) {
779
+ propertySchema.type = 'number';
780
+ }
781
+ else {
782
+ propertySchema.type = 'string';
783
+ }
784
+ if (property.isArray) {
785
+ const itemsSchema = { ...propertySchema };
786
+ propertySchema.type = 'array';
787
+ propertySchema.items = itemsSchema;
788
+ delete propertySchema.enum;
789
+ return propertySchema;
790
+ }
791
+ else {
792
+ return propertySchema;
793
+ }
794
+ }
795
+ }
796
+ return undefined;
797
+ }
659
798
  getSchemaFromPrimitive(property) {
799
+ if (property.isEnum) {
800
+ const enumSchema = this.getSchemaFromEnum(property);
801
+ if (enumSchema) {
802
+ return enumSchema;
803
+ }
804
+ }
660
805
  const propertySchema = { type: 'object' };
661
806
  const propertyType = property.type.toLowerCase().replace('[]', '').trim();
662
807
  let isFile = false;
@@ -744,8 +889,40 @@ class SchemaTransformer {
744
889
  ts.isTypeReferenceNode(typeNode) // Omit, Pick, Partial, etc.
745
890
  );
746
891
  }
747
- //Todo: implement properly
748
- applyEnumDecorator(decorator, schema) { }
892
+ applyEnumDecorator(decorator, schema) {
893
+ if (decorator.arguments.length === 0)
894
+ return;
895
+ const arg = decorator.arguments[0];
896
+ if (arg && typeof arg === 'object' && 'kind' in arg) {
897
+ const type = this.checker.getTypeAtLocation(arg);
898
+ if (type.symbol && type.symbol.exports) {
899
+ const values = [];
900
+ type.symbol.exports.forEach(member => {
901
+ const declaration = member.valueDeclaration;
902
+ if (declaration && ts.isEnumMember(declaration)) {
903
+ const value = this.checker.getConstantValue(declaration);
904
+ if (value !== undefined) {
905
+ values.push(value);
906
+ }
907
+ }
908
+ });
909
+ if (values.length > 0) {
910
+ schema.enum = values;
911
+ const isString = values.every(v => typeof v === 'string');
912
+ const isNumber = values.every(v => typeof v === 'number');
913
+ if (isString) {
914
+ schema.type = 'string';
915
+ }
916
+ else if (isNumber) {
917
+ schema.type = 'number';
918
+ }
919
+ else {
920
+ schema.type = 'string';
921
+ }
922
+ }
923
+ }
924
+ }
925
+ }
749
926
  applyDecorators(property, schema) {
750
927
  for (const decorator of property.decorators) {
751
928
  const decoratorName = decorator.name;
@@ -809,22 +986,22 @@ class SchemaTransformer {
809
986
  property.isOptional = true;
810
987
  break;
811
988
  case constants.validatorDecorators.MinLength.name:
812
- schema.minLength = decorator.arguments[0];
989
+ schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
813
990
  break;
814
991
  case constants.validatorDecorators.MaxLength.name:
815
- schema.maxLength = decorator.arguments[0];
992
+ schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
816
993
  break;
817
994
  case constants.validatorDecorators.Length.name:
818
- schema.minLength = decorator.arguments[0];
995
+ schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
819
996
  if (decorator.arguments[1]) {
820
- schema.maxLength = decorator.arguments[1];
997
+ schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[1]);
821
998
  }
822
999
  break;
823
1000
  case constants.validatorDecorators.Min.name:
824
- schema.minimum = decorator.arguments[0];
1001
+ schema.minimum = this.getSafeDecoratorArgument(decorator.arguments[0]);
825
1002
  break;
826
1003
  case constants.validatorDecorators.Max.name:
827
- schema.maximum = decorator.arguments[0];
1004
+ schema.maximum = this.getSafeDecoratorArgument(decorator.arguments[0]);
828
1005
  break;
829
1006
  case constants.validatorDecorators.IsPositive.name:
830
1007
  schema.minimum = 0;
@@ -837,13 +1014,18 @@ class SchemaTransformer {
837
1014
  property.isOptional = false;
838
1015
  break;
839
1016
  case constants.validatorDecorators.ArrayMinSize.name:
840
- schema.minItems = decorator.arguments[0];
1017
+ schema.minItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
841
1018
  break;
842
1019
  case constants.validatorDecorators.ArrayMaxSize.name:
843
- schema.maxItems = decorator.arguments[0];
1020
+ schema.maxItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
844
1021
  break;
845
1022
  case constants.validatorDecorators.IsEnum.name:
846
- this.applyEnumDecorator(decorator, schema);
1023
+ if (!property.isArray) {
1024
+ this.applyEnumDecorator(decorator, schema);
1025
+ }
1026
+ else if (schema.items) {
1027
+ this.applyEnumDecorator(decorator, schema.items);
1028
+ }
847
1029
  break;
848
1030
  }
849
1031
  }
package/dist/run.d.ts CHANGED
@@ -1 +1 @@
1
- import './__test__/';
1
+ export {};