@arcteninc/core 0.0.48 → 0.0.50
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/package.json +1 -1
- package/scripts/cli-extract-types-auto.ts +321 -128
package/package.json
CHANGED
|
@@ -21,9 +21,11 @@ async function ensureGeneratorLoaded(): Promise<boolean> {
|
|
|
21
21
|
// @ts-ignore - ts-json-schema-generator is optional
|
|
22
22
|
tsJsonSchemaGeneratorModule = await import('ts-json-schema-generator');
|
|
23
23
|
generatorAvailable = true;
|
|
24
|
+
console.log('✓ ts-json-schema-generator loaded successfully');
|
|
24
25
|
return true;
|
|
25
26
|
} catch (error) {
|
|
26
27
|
tsJsonSchemaGeneratorModule = false; // Mark as failed
|
|
28
|
+
console.warn('⚠️ ts-json-schema-generator not available:', error);
|
|
27
29
|
return false;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
@@ -572,108 +574,41 @@ function resolveImportPath(
|
|
|
572
574
|
return null;
|
|
573
575
|
}
|
|
574
576
|
|
|
575
|
-
/**
|
|
576
|
-
* Try to resolve an anonymous type to a named type by checking structural compatibility
|
|
577
|
-
* This helps when function parameters use anonymous types that match existing interfaces
|
|
578
|
-
*/
|
|
579
|
-
function tryResolveToNamedType(
|
|
580
|
-
type: ts.Type,
|
|
581
|
-
checker: ts.TypeChecker,
|
|
582
|
-
sourceFile: ts.SourceFile
|
|
583
|
-
): { typeName: string; sourceFile: string } | null {
|
|
584
|
-
// Only try for object types
|
|
585
|
-
if (!(type.flags & ts.TypeFlags.Object)) return null;
|
|
586
|
-
|
|
587
|
-
const props = checker.getPropertiesOfType(type);
|
|
588
|
-
if (props.length === 0) return null;
|
|
589
|
-
|
|
590
|
-
// Get all exported interfaces/types from the same file
|
|
591
|
-
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
592
|
-
if (!moduleSymbol) return null;
|
|
593
|
-
|
|
594
|
-
const fileSymbols = checker.getExportsOfModule(moduleSymbol);
|
|
595
|
-
if (!fileSymbols || fileSymbols.length === 0) return null;
|
|
596
|
-
|
|
597
|
-
// Check each exported type/interface to see if it matches structurally
|
|
598
|
-
for (const symbol of fileSymbols) {
|
|
599
|
-
if (!symbol) continue;
|
|
600
|
-
|
|
601
|
-
const name = symbol.getName();
|
|
602
|
-
if (!name || name.length === 0) continue;
|
|
603
|
-
|
|
604
|
-
const declarations = symbol.getDeclarations();
|
|
605
|
-
if (!declarations || declarations.length === 0) continue;
|
|
606
|
-
|
|
607
|
-
for (const decl of declarations) {
|
|
608
|
-
if (ts.isInterfaceDeclaration(decl) || ts.isTypeAliasDeclaration(decl)) {
|
|
609
|
-
// Get the type from the declaration
|
|
610
|
-
const declaredType = checker.getTypeAtLocation(decl);
|
|
611
|
-
|
|
612
|
-
// Check if properties match (simple structural check)
|
|
613
|
-
const declaredProps = checker.getPropertiesOfType(declaredType);
|
|
614
|
-
if (declaredProps.length === props.length) {
|
|
615
|
-
// Check if all property names match
|
|
616
|
-
const propNames = new Set(props.map(p => p.getName()));
|
|
617
|
-
const declaredPropNames = new Set(declaredProps.map(p => p.getName()));
|
|
618
|
-
|
|
619
|
-
if (propNames.size === declaredPropNames.size &&
|
|
620
|
-
[...propNames].every(n => declaredPropNames.has(n))) {
|
|
621
|
-
// Properties match - this might be the same type
|
|
622
|
-
return {
|
|
623
|
-
typeName: name,
|
|
624
|
-
sourceFile: sourceFile.fileName,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
577
|
/**
|
|
636
578
|
* Try to get type name and source file for use with ts-json-schema-generator
|
|
579
|
+
* Handles interfaces, type aliases, classes, and enums
|
|
637
580
|
*/
|
|
638
581
|
function getTypeInfoForGenerator(
|
|
639
582
|
type: ts.Type,
|
|
640
|
-
checker: ts.TypeChecker
|
|
641
|
-
paramSourceFile?: ts.SourceFile
|
|
583
|
+
checker: ts.TypeChecker
|
|
642
584
|
): { typeName: string; sourceFile: string } | null {
|
|
643
585
|
const symbol = type.getSymbol();
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
586
|
+
if (!symbol) return null;
|
|
587
|
+
|
|
588
|
+
const name = symbol.getName();
|
|
589
|
+
if (!name || name.length === 0) return null;
|
|
590
|
+
|
|
591
|
+
const declarations = symbol.getDeclarations();
|
|
592
|
+
if (!declarations || declarations.length === 0) return null;
|
|
593
|
+
|
|
594
|
+
// Find the first declaration that's a type alias, interface, class, or enum
|
|
595
|
+
for (const decl of declarations) {
|
|
596
|
+
if (
|
|
597
|
+
ts.isTypeAliasDeclaration(decl) ||
|
|
598
|
+
ts.isInterfaceDeclaration(decl) ||
|
|
599
|
+
ts.isClassDeclaration(decl) ||
|
|
600
|
+
ts.isEnumDeclaration(decl)
|
|
601
|
+
) {
|
|
602
|
+
const sourceFile = decl.getSourceFile();
|
|
603
|
+
if (sourceFile) {
|
|
604
|
+
return {
|
|
605
|
+
typeName: name,
|
|
606
|
+
sourceFile: sourceFile.fileName,
|
|
607
|
+
};
|
|
667
608
|
}
|
|
668
609
|
}
|
|
669
610
|
}
|
|
670
|
-
|
|
671
|
-
// If it's an anonymous type, try to resolve it to a named type
|
|
672
|
-
if (paramSourceFile) {
|
|
673
|
-
const resolved = tryResolveToNamedType(type, checker, paramSourceFile);
|
|
674
|
-
if (resolved) return resolved;
|
|
675
|
-
}
|
|
676
|
-
|
|
611
|
+
|
|
677
612
|
return null;
|
|
678
613
|
}
|
|
679
614
|
|
|
@@ -695,17 +630,7 @@ async function serializeTypeWithGenerator(
|
|
|
695
630
|
return null;
|
|
696
631
|
}
|
|
697
632
|
|
|
698
|
-
|
|
699
|
-
let sourceFileForLookup: ts.SourceFile | undefined;
|
|
700
|
-
const symbol = type.getSymbol();
|
|
701
|
-
if (symbol) {
|
|
702
|
-
const declarations = symbol.getDeclarations();
|
|
703
|
-
if (declarations && declarations.length > 0) {
|
|
704
|
-
sourceFileForLookup = declarations[0].getSourceFile();
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const typeInfo = getTypeInfoForGenerator(type, checker, sourceFileForLookup);
|
|
633
|
+
const typeInfo = getTypeInfoForGenerator(type, checker);
|
|
709
634
|
if (!typeInfo) return null;
|
|
710
635
|
|
|
711
636
|
try {
|
|
@@ -741,7 +666,8 @@ async function serializeTypeWithGenerator(
|
|
|
741
666
|
schema: schemaWithoutDefs as JsonSchemaProperty,
|
|
742
667
|
};
|
|
743
668
|
} catch (error) {
|
|
744
|
-
// If generator fails,
|
|
669
|
+
// If generator fails, log the error for debugging
|
|
670
|
+
console.warn(`⚠️ ts-json-schema-generator failed for type "${typeInfo.typeName}" from ${typeInfo.sourceFile}:`, error);
|
|
745
671
|
return null;
|
|
746
672
|
}
|
|
747
673
|
}
|
|
@@ -749,39 +675,306 @@ async function serializeTypeWithGenerator(
|
|
|
749
675
|
// Cache for generator results to avoid repeated calls
|
|
750
676
|
const generatorCache = new Map<string, { isOptional: boolean; schema: JsonSchemaProperty }>();
|
|
751
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Custom serializer for anonymous types, enums, and object types
|
|
680
|
+
* This is a fallback when ts-json-schema-generator can't handle the type
|
|
681
|
+
*/
|
|
682
|
+
function serializeTypeCustom(
|
|
683
|
+
type: ts.Type,
|
|
684
|
+
checker: ts.TypeChecker,
|
|
685
|
+
defs?: Record<string, JsonSchemaProperty>,
|
|
686
|
+
visited = new Set<number>(),
|
|
687
|
+
depth = 0
|
|
688
|
+
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
689
|
+
// Prevent infinite recursion
|
|
690
|
+
if (depth > 10) {
|
|
691
|
+
return { isOptional: false, schema: {} };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const typeId = (type as any).id;
|
|
695
|
+
if (typeId !== undefined && visited.has(typeId)) {
|
|
696
|
+
return { isOptional: false, schema: {} };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (typeId !== undefined) {
|
|
700
|
+
visited.add(typeId);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Handle primitives first (safety net in case serializeType didn't catch them)
|
|
704
|
+
if (type.flags & ts.TypeFlags.String) {
|
|
705
|
+
return { isOptional: false, schema: { type: 'string' } };
|
|
706
|
+
}
|
|
707
|
+
if (type.flags & ts.TypeFlags.Number) {
|
|
708
|
+
return { isOptional: false, schema: { type: 'number' } };
|
|
709
|
+
}
|
|
710
|
+
if (type.flags & ts.TypeFlags.Boolean) {
|
|
711
|
+
return { isOptional: false, schema: { type: 'boolean' } };
|
|
712
|
+
}
|
|
713
|
+
if (type.flags & ts.TypeFlags.Null) {
|
|
714
|
+
return { isOptional: false, schema: { type: 'null' } };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Handle string/number literals (enum values are often represented as literals)
|
|
718
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
719
|
+
const literalType = type as ts.StringLiteralType;
|
|
720
|
+
return { isOptional: false, schema: { type: 'string', const: literalType.value } };
|
|
721
|
+
}
|
|
722
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
723
|
+
const literalType = type as ts.NumberLiteralType;
|
|
724
|
+
return { isOptional: false, schema: { type: 'number', const: literalType.value } };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Handle enums (TypeScript enum types)
|
|
728
|
+
const symbol = type.getSymbol();
|
|
729
|
+
if (symbol) {
|
|
730
|
+
const declarations = symbol.getDeclarations();
|
|
731
|
+
if (declarations && declarations.length > 0) {
|
|
732
|
+
const firstDecl = declarations[0];
|
|
733
|
+
if (ts.isEnumDeclaration(firstDecl)) {
|
|
734
|
+
const enumMembers = firstDecl.members.map(m => {
|
|
735
|
+
if (m.initializer && ts.isStringLiteral(m.initializer)) {
|
|
736
|
+
return m.initializer.text;
|
|
737
|
+
} else if (m.initializer && ts.isNumericLiteral(m.initializer)) {
|
|
738
|
+
return Number(m.initializer.text);
|
|
739
|
+
} else if (m.name && ts.isIdentifier(m.name)) {
|
|
740
|
+
// For enum members without initializers, use the name
|
|
741
|
+
return m.name.text;
|
|
742
|
+
}
|
|
743
|
+
return null;
|
|
744
|
+
}).filter((v): v is string | number => v !== null);
|
|
745
|
+
|
|
746
|
+
if (enumMembers.length > 0) {
|
|
747
|
+
return { isOptional: false, schema: { enum: enumMembers } };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Handle interfaces and type aliases (for recursive types)
|
|
752
|
+
if (ts.isInterfaceDeclaration(firstDecl) || ts.isTypeAliasDeclaration(firstDecl)) {
|
|
753
|
+
const typeName = symbol.getName();
|
|
754
|
+
if (typeName && defs) {
|
|
755
|
+
const defsKey = typeName; // Use the type name as key
|
|
756
|
+
|
|
757
|
+
// Check if we've visited this type before (circular/recursive reference)
|
|
758
|
+
if (visited.has(typeId)) {
|
|
759
|
+
// Create a reference to avoid infinite recursion
|
|
760
|
+
if (!defs[defsKey]) {
|
|
761
|
+
// Create placeholder - will be filled in below
|
|
762
|
+
defs[defsKey] = { type: 'object', properties: {} };
|
|
763
|
+
}
|
|
764
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${defsKey}` } };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Check if already defined (from a previous call)
|
|
768
|
+
if (defs[defsKey]) {
|
|
769
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${defsKey}` } };
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Mark as visited and build the definition
|
|
773
|
+
visited.add(typeId);
|
|
774
|
+
|
|
775
|
+
// Build the schema for this type
|
|
776
|
+
const props = checker.getPropertiesOfType(type);
|
|
777
|
+
if (props.length > 0) {
|
|
778
|
+
const properties: Record<string, JsonSchemaProperty> = {};
|
|
779
|
+
const required: string[] = [];
|
|
780
|
+
|
|
781
|
+
// Use the same visited set for properties to detect recursion
|
|
782
|
+
for (const prop of props) {
|
|
783
|
+
const propName = prop.getName();
|
|
784
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration || prop.declarations?.[0] || checker.getDeclaredTypeOfSymbol(prop).symbol?.valueDeclaration || null as any);
|
|
785
|
+
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
786
|
+
|
|
787
|
+
// Recursively serialize the property type (will detect recursion via visited set)
|
|
788
|
+
const propResult = serializeTypeCustom(propType, checker, defs, visited, depth + 1);
|
|
789
|
+
properties[propName] = propResult.schema;
|
|
790
|
+
|
|
791
|
+
if (!isOptional && !propResult.isOptional) {
|
|
792
|
+
required.push(propName);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const schema: JsonSchemaProperty = {
|
|
797
|
+
type: 'object',
|
|
798
|
+
properties,
|
|
799
|
+
...(required.length > 0 && { required })
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Store in defs
|
|
803
|
+
defs[defsKey] = schema;
|
|
804
|
+
|
|
805
|
+
// Return reference
|
|
806
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${defsKey}` } };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Handle object types with properties (anonymous types)
|
|
814
|
+
if (type.flags & ts.TypeFlags.Object) {
|
|
815
|
+
const props = checker.getPropertiesOfType(type);
|
|
816
|
+
if (props.length > 0) {
|
|
817
|
+
const properties: Record<string, JsonSchemaProperty> = {};
|
|
818
|
+
const required: string[] = [];
|
|
819
|
+
|
|
820
|
+
for (const prop of props) {
|
|
821
|
+
const propName = prop.getName();
|
|
822
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration || prop.declarations?.[0] || checker.getDeclaredTypeOfSymbol(prop).symbol?.valueDeclaration || null as any);
|
|
823
|
+
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
824
|
+
|
|
825
|
+
// Recursively serialize the property type
|
|
826
|
+
const propResult = serializeTypeCustom(propType, checker, defs, new Set(visited), depth + 1);
|
|
827
|
+
properties[propName] = propResult.schema;
|
|
828
|
+
|
|
829
|
+
if (!isOptional && !propResult.isOptional) {
|
|
830
|
+
required.push(propName);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return {
|
|
835
|
+
isOptional: false,
|
|
836
|
+
schema: {
|
|
837
|
+
type: 'object',
|
|
838
|
+
properties,
|
|
839
|
+
...(required.length > 0 && { required })
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Fallback to empty schema
|
|
846
|
+
return { isOptional: false, schema: {} };
|
|
847
|
+
}
|
|
848
|
+
|
|
752
849
|
/**
|
|
753
850
|
* Serialize a TypeScript type to JSON Schema format using ts-json-schema-generator
|
|
754
|
-
*
|
|
755
|
-
* Returns empty schema for anonymous types
|
|
851
|
+
* Handles primitives, arrays, and named types (interfaces, type aliases, classes, enums)
|
|
756
852
|
*/
|
|
757
853
|
async function serializeType(
|
|
758
854
|
type: ts.Type,
|
|
759
855
|
checker: ts.TypeChecker,
|
|
760
856
|
program: ts.Program | undefined,
|
|
761
857
|
configPath: string | undefined,
|
|
762
|
-
defs?: Record<string, JsonSchemaProperty
|
|
763
|
-
sourceFileForLookup?: ts.SourceFile
|
|
858
|
+
defs?: Record<string, JsonSchemaProperty>
|
|
764
859
|
): Promise<{ isOptional: boolean; schema: JsonSchemaProperty }> {
|
|
765
|
-
|
|
766
|
-
|
|
860
|
+
// Handle primitives first (these don't need the generator)
|
|
861
|
+
if (type.flags & ts.TypeFlags.String) {
|
|
862
|
+
return { isOptional: false, schema: { type: 'string' } };
|
|
863
|
+
}
|
|
864
|
+
if (type.flags & ts.TypeFlags.Number) {
|
|
865
|
+
return { isOptional: false, schema: { type: 'number' } };
|
|
866
|
+
}
|
|
867
|
+
if (type.flags & ts.TypeFlags.Boolean) {
|
|
868
|
+
return { isOptional: false, schema: { type: 'boolean' } };
|
|
869
|
+
}
|
|
870
|
+
if (type.flags & ts.TypeFlags.Null) {
|
|
871
|
+
return { isOptional: false, schema: { type: 'null' } };
|
|
872
|
+
}
|
|
873
|
+
if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
|
|
874
|
+
return { isOptional: true, schema: { type: 'null' } };
|
|
767
875
|
}
|
|
768
876
|
|
|
769
|
-
//
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
877
|
+
// Handle string/number literals (enum values are often represented as literals)
|
|
878
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
879
|
+
const literalType = type as ts.StringLiteralType;
|
|
880
|
+
return { isOptional: false, schema: { type: 'string', const: literalType.value } };
|
|
881
|
+
}
|
|
882
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
883
|
+
const literalType = type as ts.NumberLiteralType;
|
|
884
|
+
return { isOptional: false, schema: { type: 'number', const: literalType.value } };
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Handle arrays
|
|
888
|
+
if (checker.isArrayType(type)) {
|
|
889
|
+
const typeArgs = (type as ts.TypeReference).typeArguments;
|
|
890
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
891
|
+
const itemResult = await serializeType(typeArgs[0], checker, program, configPath, defs);
|
|
892
|
+
return {
|
|
893
|
+
isOptional: false,
|
|
894
|
+
schema: { type: 'array', items: itemResult.schema }
|
|
895
|
+
};
|
|
778
896
|
}
|
|
897
|
+
return { isOptional: false, schema: { type: 'array' } };
|
|
779
898
|
}
|
|
780
|
-
|
|
781
|
-
|
|
899
|
+
|
|
900
|
+
// Handle union types (like string | null)
|
|
901
|
+
if (type.isUnion()) {
|
|
902
|
+
const types = type.types;
|
|
903
|
+
const hasNull = types.some(t => t.flags & ts.TypeFlags.Null);
|
|
904
|
+
const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
|
|
905
|
+
const nonNullUndefinedTypes = types.filter(
|
|
906
|
+
t => !(t.flags & ts.TypeFlags.Null) && !(t.flags & ts.TypeFlags.Undefined)
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
// If it's just T | undefined, make it optional
|
|
910
|
+
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
911
|
+
const result = await serializeType(nonNullUndefinedTypes[0], checker, program, configPath, defs);
|
|
912
|
+
return { isOptional: true, schema: result.schema };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Check if union is all string/number literals (enum-like union)
|
|
916
|
+
// This handles cases where TypeScript represents enums as unions of literals
|
|
917
|
+
const allStringLiterals = nonNullUndefinedTypes.every(t => t.flags & ts.TypeFlags.StringLiteral);
|
|
918
|
+
const allNumberLiterals = nonNullUndefinedTypes.every(t => t.flags & ts.TypeFlags.NumberLiteral);
|
|
919
|
+
|
|
920
|
+
if (allStringLiterals && nonNullUndefinedTypes.length > 0) {
|
|
921
|
+
const enumValues = nonNullUndefinedTypes.map(t => {
|
|
922
|
+
const literalType = t as ts.StringLiteralType;
|
|
923
|
+
return literalType.value;
|
|
924
|
+
});
|
|
925
|
+
const schema: JsonSchemaProperty = { enum: enumValues };
|
|
926
|
+
return { isOptional: hasNull || hasUndefined, schema };
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (allNumberLiterals && nonNullUndefinedTypes.length > 0) {
|
|
930
|
+
const enumValues = nonNullUndefinedTypes.map(t => {
|
|
931
|
+
const literalType = t as ts.NumberLiteralType;
|
|
932
|
+
return literalType.value;
|
|
933
|
+
});
|
|
934
|
+
const schema: JsonSchemaProperty = { enum: enumValues };
|
|
935
|
+
return { isOptional: hasNull || hasUndefined, schema };
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Build anyOf for unions
|
|
939
|
+
if (nonNullUndefinedTypes.length > 0 || hasNull) {
|
|
940
|
+
const anyOf: JsonSchemaProperty[] = [];
|
|
941
|
+
if (hasNull || hasUndefined) {
|
|
942
|
+
anyOf.push({ type: 'null' });
|
|
943
|
+
}
|
|
944
|
+
for (const unionType of nonNullUndefinedTypes) {
|
|
945
|
+
const result = await serializeType(unionType, checker, program, configPath, defs);
|
|
946
|
+
anyOf.push(result.schema);
|
|
947
|
+
}
|
|
948
|
+
if (anyOf.length === 1) {
|
|
949
|
+
return { isOptional: hasNull || hasUndefined, schema: anyOf[0] };
|
|
950
|
+
}
|
|
951
|
+
return { isOptional: false, schema: { anyOf } };
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Check if it's an enum - handle with custom serializer first
|
|
956
|
+
const symbol = type.getSymbol();
|
|
957
|
+
if (symbol) {
|
|
958
|
+
const declarations = symbol.getDeclarations();
|
|
959
|
+
if (declarations && declarations.length > 0) {
|
|
960
|
+
const firstDecl = declarations[0];
|
|
961
|
+
if (ts.isEnumDeclaration(firstDecl)) {
|
|
962
|
+
// Use custom serializer for enums (more reliable)
|
|
963
|
+
return serializeTypeCustom(type, checker, defs);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// For named types (interfaces, type aliases, classes), try generator first
|
|
969
|
+
if (!program || !configPath) {
|
|
970
|
+
// No program/config - use custom serializer
|
|
971
|
+
return serializeTypeCustom(type, checker, defs);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const typeInfo = getTypeInfoForGenerator(type, checker);
|
|
782
975
|
if (!typeInfo) {
|
|
783
|
-
// Anonymous/inline type -
|
|
784
|
-
return
|
|
976
|
+
// Anonymous/inline type - use custom serializer
|
|
977
|
+
return serializeTypeCustom(type, checker, defs);
|
|
785
978
|
}
|
|
786
979
|
|
|
787
980
|
// Check cache first
|
|
@@ -790,15 +983,16 @@ async function serializeType(
|
|
|
790
983
|
return generatorCache.get(cacheKey)!;
|
|
791
984
|
}
|
|
792
985
|
|
|
793
|
-
// Use generator
|
|
986
|
+
// Use generator for named types
|
|
794
987
|
const result = await serializeTypeWithGenerator(type, checker, program, configPath, defs);
|
|
795
988
|
if (result) {
|
|
796
989
|
generatorCache.set(cacheKey, result);
|
|
797
990
|
return result;
|
|
798
991
|
}
|
|
799
992
|
|
|
800
|
-
// Generator failed -
|
|
801
|
-
|
|
993
|
+
// Generator failed - fall back to custom serializer
|
|
994
|
+
console.warn(`⚠️ Failed to generate schema for type "${typeInfo.typeName}" from ${typeInfo.sourceFile}. Falling back to custom serializer.`);
|
|
995
|
+
return serializeTypeCustom(type, checker, defs);
|
|
802
996
|
}
|
|
803
997
|
|
|
804
998
|
/**
|
|
@@ -999,8 +1193,7 @@ async function extractFunctionMetadata(
|
|
|
999
1193
|
|
|
1000
1194
|
// Use ts-json-schema-generator for named types only
|
|
1001
1195
|
// Anonymous types are not supported - agents can't use them anyway
|
|
1002
|
-
|
|
1003
|
-
const result = await serializeType(type, checker, program, configPath, defs, sourceFile);
|
|
1196
|
+
const result = await serializeType(type, checker, program, configPath, defs);
|
|
1004
1197
|
propSchema = result.schema;
|
|
1005
1198
|
isOptional = result.isOptional;
|
|
1006
1199
|
} else {
|