@arcteninc/core 0.0.45 → 0.0.46
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 +121 -16
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ interface FunctionMetadata {
|
|
|
33
33
|
type: 'object';
|
|
34
34
|
properties: Record<string, JsonSchemaProperty>;
|
|
35
35
|
required?: string[]; // Array of required parameter names
|
|
36
|
+
$defs?: Record<string, JsonSchemaProperty>; // Type definitions for recursive types
|
|
36
37
|
};
|
|
37
38
|
returnType?: string;
|
|
38
39
|
isAsync?: boolean;
|
|
@@ -627,6 +628,48 @@ function getTypeId(type: ts.Type): number | undefined {
|
|
|
627
628
|
return (type as any).id;
|
|
628
629
|
}
|
|
629
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Get a stable name for a type to use in $defs
|
|
633
|
+
* Returns the type name if it's a named type (interface, type alias, class)
|
|
634
|
+
* Otherwise returns a generated name based on type ID
|
|
635
|
+
*/
|
|
636
|
+
function getTypeNameForDefs(
|
|
637
|
+
type: ts.Type,
|
|
638
|
+
checker: ts.TypeChecker
|
|
639
|
+
): string | null {
|
|
640
|
+
const symbol = type.getSymbol();
|
|
641
|
+
if (symbol) {
|
|
642
|
+
const name = symbol.getName();
|
|
643
|
+
if (name && name.length > 0) {
|
|
644
|
+
// Check if it's a named type (not a primitive)
|
|
645
|
+
const declarations = symbol.getDeclarations();
|
|
646
|
+
if (declarations && declarations.length > 0) {
|
|
647
|
+
// Check if it's a type alias, interface, or class
|
|
648
|
+
for (const decl of declarations) {
|
|
649
|
+
if (
|
|
650
|
+
ts.isTypeAliasDeclaration(decl) ||
|
|
651
|
+
ts.isInterfaceDeclaration(decl) ||
|
|
652
|
+
ts.isClassDeclaration(decl)
|
|
653
|
+
) {
|
|
654
|
+
return name;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Fallback: try to get a name from typeToString
|
|
662
|
+
const typeString = checker.typeToString(type);
|
|
663
|
+
// If it's a simple type name (not a complex expression), use it
|
|
664
|
+
if (typeString && !typeString.includes('|') && !typeString.includes('&') &&
|
|
665
|
+
!typeString.includes('<') && !typeString.includes('(') &&
|
|
666
|
+
typeString.length < 50 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(typeString)) {
|
|
667
|
+
return typeString;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
630
673
|
/**
|
|
631
674
|
* Serialize a literal type (boolean, number, or string literal) to JSON Schema
|
|
632
675
|
* Returns null if the type is not a literal
|
|
@@ -755,7 +798,9 @@ function serializeType(
|
|
|
755
798
|
type: ts.Type,
|
|
756
799
|
checker: ts.TypeChecker,
|
|
757
800
|
visited = new Set<number>(),
|
|
758
|
-
depth = 0
|
|
801
|
+
depth = 0,
|
|
802
|
+
defs?: Record<string, JsonSchemaProperty>,
|
|
803
|
+
defsVisited = new Set<number>()
|
|
759
804
|
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
760
805
|
const typeString = checker.typeToString(type);
|
|
761
806
|
|
|
@@ -764,7 +809,24 @@ function serializeType(
|
|
|
764
809
|
}
|
|
765
810
|
|
|
766
811
|
const typeId = getTypeId(type);
|
|
812
|
+
|
|
813
|
+
// Handle recursive types with $defs support
|
|
767
814
|
if (typeId !== undefined && visited.has(typeId)) {
|
|
815
|
+
// Check if this is a named type that we can reference
|
|
816
|
+
if (defs) {
|
|
817
|
+
const typeName = getTypeNameForDefs(type, checker);
|
|
818
|
+
if (typeName) {
|
|
819
|
+
// If the definition already exists, use $ref
|
|
820
|
+
if (defs[typeName]) {
|
|
821
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
822
|
+
}
|
|
823
|
+
// If we're currently building this definition, use $ref (circular reference)
|
|
824
|
+
if (defsVisited.has(typeId)) {
|
|
825
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Fallback: empty schema for anonymous recursive types
|
|
768
830
|
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
769
831
|
}
|
|
770
832
|
|
|
@@ -825,7 +887,7 @@ function serializeType(
|
|
|
825
887
|
|
|
826
888
|
// Handle optional (T | undefined) - make it optional instead of union
|
|
827
889
|
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
828
|
-
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1);
|
|
890
|
+
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1, defs, defsVisited);
|
|
829
891
|
return { isOptional: true, schema: result.schema };
|
|
830
892
|
}
|
|
831
893
|
|
|
@@ -860,7 +922,7 @@ function serializeType(
|
|
|
860
922
|
// Handle array types
|
|
861
923
|
const typeArgs = (unionType as ts.TypeReference).typeArguments;
|
|
862
924
|
if (typeArgs && typeArgs.length > 0) {
|
|
863
|
-
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
|
|
925
|
+
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1, defs, defsVisited);
|
|
864
926
|
anyOf.push({ type: 'array', items: itemResult.schema });
|
|
865
927
|
} else {
|
|
866
928
|
anyOf.push({ type: 'array' });
|
|
@@ -869,7 +931,7 @@ function serializeType(
|
|
|
869
931
|
// Try to serialize the type (handles objects, type references, etc.)
|
|
870
932
|
// This will recursively resolve type aliases and interfaces
|
|
871
933
|
try {
|
|
872
|
-
const refResult = serializeType(unionType, checker, visited, depth + 1);
|
|
934
|
+
const refResult = serializeType(unionType, checker, visited, depth + 1, defs, defsVisited);
|
|
873
935
|
|
|
874
936
|
// Check if we got a valid schema (not just 'any' fallback)
|
|
875
937
|
if (!isFallbackAnySchema(refResult.schema, unionType, checker)) {
|
|
@@ -879,7 +941,7 @@ function serializeType(
|
|
|
879
941
|
const resolvedType = resolveTypeReference(unionType, checker);
|
|
880
942
|
if (resolvedType) {
|
|
881
943
|
try {
|
|
882
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1);
|
|
944
|
+
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
883
945
|
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
884
946
|
anyOf.push(resolvedResult.schema);
|
|
885
947
|
} else {
|
|
@@ -938,7 +1000,7 @@ function serializeType(
|
|
|
938
1000
|
const allRequired: string[] = [];
|
|
939
1001
|
|
|
940
1002
|
for (const intersectionType of types) {
|
|
941
|
-
const result = serializeType(intersectionType, checker, visited, depth + 1);
|
|
1003
|
+
const result = serializeType(intersectionType, checker, visited, depth + 1, defs, defsVisited);
|
|
942
1004
|
if (result.schema.type === 'object' && result.schema.properties) {
|
|
943
1005
|
Object.assign(allProperties, result.schema.properties);
|
|
944
1006
|
if (result.schema.required) {
|
|
@@ -963,7 +1025,7 @@ function serializeType(
|
|
|
963
1025
|
if (checker.isArrayType(type)) {
|
|
964
1026
|
const typeArgs = (type as ts.TypeReference).typeArguments;
|
|
965
1027
|
if (typeArgs && typeArgs.length > 0) {
|
|
966
|
-
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
|
|
1028
|
+
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1, defs, defsVisited);
|
|
967
1029
|
return {
|
|
968
1030
|
isOptional: false,
|
|
969
1031
|
schema: { type: 'array', items: itemResult.schema }
|
|
@@ -981,6 +1043,28 @@ function serializeType(
|
|
|
981
1043
|
const properties: Record<string, JsonSchemaProperty> = {};
|
|
982
1044
|
const required: string[] = [];
|
|
983
1045
|
const props = checker.getPropertiesOfType(type);
|
|
1046
|
+
|
|
1047
|
+
// Check if this is a named type that should go in $defs
|
|
1048
|
+
const typeName = defs ? getTypeNameForDefs(type, checker) : null;
|
|
1049
|
+
const shouldUseDefs = defs && typeName && props.length > 0;
|
|
1050
|
+
|
|
1051
|
+
if (shouldUseDefs && typeId !== undefined) {
|
|
1052
|
+
// Check if we're already building this definition
|
|
1053
|
+
if (defsVisited.has(typeId)) {
|
|
1054
|
+
// Circular reference - return $ref
|
|
1055
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Check if definition already exists
|
|
1059
|
+
if (defs[typeName]) {
|
|
1060
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Start building the definition
|
|
1064
|
+
defsVisited.add(typeId);
|
|
1065
|
+
// Create placeholder in defs (will be filled below)
|
|
1066
|
+
defs[typeName] = { type: 'object', properties: {}, required: [] };
|
|
1067
|
+
}
|
|
984
1068
|
|
|
985
1069
|
if (props.length > 0) {
|
|
986
1070
|
for (const prop of props) {
|
|
@@ -995,7 +1079,7 @@ function serializeType(
|
|
|
995
1079
|
const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
|
|
996
1080
|
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
997
1081
|
|
|
998
|
-
const propResult = serializeType(propType, checker, visited, depth + 1);
|
|
1082
|
+
const propResult = serializeType(propType, checker, visited, depth + 1, defs, defsVisited);
|
|
999
1083
|
properties[prop.name] = propResult.schema;
|
|
1000
1084
|
|
|
1001
1085
|
// Track required properties
|
|
@@ -1015,7 +1099,7 @@ function serializeType(
|
|
|
1015
1099
|
const indexInfo = indexSignatures[0];
|
|
1016
1100
|
if (indexInfo.declaration) {
|
|
1017
1101
|
const indexType = checker.getTypeAtLocation(indexInfo.declaration);
|
|
1018
|
-
const indexResult = serializeType(indexType, checker, visited, depth + 1);
|
|
1102
|
+
const indexResult = serializeType(indexType, checker, visited, depth + 1, defs, defsVisited);
|
|
1019
1103
|
// JSON Schema uses additionalProperties for index signatures
|
|
1020
1104
|
if (indexResult.schema.type !== 'any' || indexResult.schema.properties) {
|
|
1021
1105
|
// Only set if it's a meaningful type (not just 'any' fallback)
|
|
@@ -1039,6 +1123,13 @@ function serializeType(
|
|
|
1039
1123
|
schema.required = required;
|
|
1040
1124
|
}
|
|
1041
1125
|
|
|
1126
|
+
// If we're building a definition, update it and return $ref
|
|
1127
|
+
if (shouldUseDefs && typeName && defs) {
|
|
1128
|
+
defs[typeName] = schema;
|
|
1129
|
+
defsVisited.delete(typeId!);
|
|
1130
|
+
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1042
1133
|
return { isOptional: false, schema };
|
|
1043
1134
|
}
|
|
1044
1135
|
}
|
|
@@ -1121,7 +1212,7 @@ function serializeType(
|
|
|
1121
1212
|
const resolvedType = resolveTypeReference(type, checker);
|
|
1122
1213
|
if (resolvedType) {
|
|
1123
1214
|
try {
|
|
1124
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1);
|
|
1215
|
+
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
1125
1216
|
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
1126
1217
|
return resolvedResult;
|
|
1127
1218
|
}
|
|
@@ -1286,6 +1377,7 @@ function extractFunctionMetadata(
|
|
|
1286
1377
|
|
|
1287
1378
|
const properties: Record<string, JsonSchemaProperty> = {};
|
|
1288
1379
|
const required: string[] = [];
|
|
1380
|
+
const defs: Record<string, JsonSchemaProperty> = {}; // Type definitions for recursive types (shared across all parameters)
|
|
1289
1381
|
|
|
1290
1382
|
// Check if async - works for both function declarations and arrow functions
|
|
1291
1383
|
const isAsync =
|
|
@@ -1317,6 +1409,9 @@ function extractFunctionMetadata(
|
|
|
1317
1409
|
}
|
|
1318
1410
|
}
|
|
1319
1411
|
|
|
1412
|
+
// Shared defsVisited across all parameters (tracks which definitions we're currently building)
|
|
1413
|
+
const defsVisited = new Set<number>();
|
|
1414
|
+
|
|
1320
1415
|
for (const param of parameters) {
|
|
1321
1416
|
const paramName = param.name.getText(sourceFile);
|
|
1322
1417
|
const hasDefault = param.initializer !== undefined;
|
|
@@ -1340,7 +1435,10 @@ function extractFunctionMetadata(
|
|
|
1340
1435
|
}
|
|
1341
1436
|
}
|
|
1342
1437
|
|
|
1343
|
-
|
|
1438
|
+
// Use a fresh visited set for each parameter to avoid false cycle detection
|
|
1439
|
+
// but share defs and defsVisited so recursive types are defined once
|
|
1440
|
+
const visited = new Set<number>();
|
|
1441
|
+
const result = serializeType(type, checker, visited, 0, defs, defsVisited);
|
|
1344
1442
|
propSchema = result.schema;
|
|
1345
1443
|
isOptional = result.isOptional;
|
|
1346
1444
|
} else {
|
|
@@ -1398,14 +1496,21 @@ function extractFunctionMetadata(
|
|
|
1398
1496
|
}
|
|
1399
1497
|
}
|
|
1400
1498
|
|
|
1499
|
+
const parametersSchema: FunctionMetadata['parameters'] = {
|
|
1500
|
+
type: 'object',
|
|
1501
|
+
properties,
|
|
1502
|
+
required: required.length > 0 ? required : undefined
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
// Add $defs if we have any type definitions
|
|
1506
|
+
if (Object.keys(defs).length > 0) {
|
|
1507
|
+
parametersSchema.$defs = defs;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1401
1510
|
return {
|
|
1402
1511
|
name: functionName,
|
|
1403
1512
|
description,
|
|
1404
|
-
parameters:
|
|
1405
|
-
type: 'object',
|
|
1406
|
-
properties,
|
|
1407
|
-
required: required.length > 0 ? required : undefined
|
|
1408
|
-
},
|
|
1513
|
+
parameters: parametersSchema,
|
|
1409
1514
|
returnType,
|
|
1410
1515
|
isAsync,
|
|
1411
1516
|
};
|