@arcteninc/core 0.0.44 → 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 +147 -35
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
|
|
@@ -698,9 +741,10 @@ function isFallbackAnySchema(
|
|
|
698
741
|
type: ts.Type,
|
|
699
742
|
checker: ts.TypeChecker
|
|
700
743
|
): boolean {
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
|
|
744
|
+
// Empty schema {} means "any value" in JSON Schema draft 2020-12
|
|
745
|
+
// If schema is empty (no keys), it's a fallback "any" schema
|
|
746
|
+
if (Object.keys(schema).length === 0) {
|
|
747
|
+
return true;
|
|
704
748
|
}
|
|
705
749
|
|
|
706
750
|
// If it has properties, anyOf, or items, it's a real schema
|
|
@@ -708,6 +752,11 @@ function isFallbackAnySchema(
|
|
|
708
752
|
return false;
|
|
709
753
|
}
|
|
710
754
|
|
|
755
|
+
// If it has a type property (and it's not 'any'), it's a real schema
|
|
756
|
+
if (schema.type && schema.type !== 'any') {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
|
|
711
760
|
// Check if it's actually a type reference we couldn't resolve
|
|
712
761
|
// (not a primitive, not an object we processed, etc.)
|
|
713
762
|
return isTypeReference(type, checker);
|
|
@@ -749,17 +798,36 @@ function serializeType(
|
|
|
749
798
|
type: ts.Type,
|
|
750
799
|
checker: ts.TypeChecker,
|
|
751
800
|
visited = new Set<number>(),
|
|
752
|
-
depth = 0
|
|
801
|
+
depth = 0,
|
|
802
|
+
defs?: Record<string, JsonSchemaProperty>,
|
|
803
|
+
defsVisited = new Set<number>()
|
|
753
804
|
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
754
805
|
const typeString = checker.typeToString(type);
|
|
755
806
|
|
|
756
807
|
if (depth > MAX_SERIALIZATION_DEPTH) {
|
|
757
|
-
return { isOptional: false, schema: {
|
|
808
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
758
809
|
}
|
|
759
810
|
|
|
760
811
|
const typeId = getTypeId(type);
|
|
812
|
+
|
|
813
|
+
// Handle recursive types with $defs support
|
|
761
814
|
if (typeId !== undefined && visited.has(typeId)) {
|
|
762
|
-
|
|
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
|
|
830
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
763
831
|
}
|
|
764
832
|
|
|
765
833
|
// Handle primitives
|
|
@@ -819,7 +887,7 @@ function serializeType(
|
|
|
819
887
|
|
|
820
888
|
// Handle optional (T | undefined) - make it optional instead of union
|
|
821
889
|
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
822
|
-
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1);
|
|
890
|
+
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1, defs, defsVisited);
|
|
823
891
|
return { isOptional: true, schema: result.schema };
|
|
824
892
|
}
|
|
825
893
|
|
|
@@ -854,7 +922,7 @@ function serializeType(
|
|
|
854
922
|
// Handle array types
|
|
855
923
|
const typeArgs = (unionType as ts.TypeReference).typeArguments;
|
|
856
924
|
if (typeArgs && typeArgs.length > 0) {
|
|
857
|
-
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
|
|
925
|
+
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1, defs, defsVisited);
|
|
858
926
|
anyOf.push({ type: 'array', items: itemResult.schema });
|
|
859
927
|
} else {
|
|
860
928
|
anyOf.push({ type: 'array' });
|
|
@@ -863,7 +931,7 @@ function serializeType(
|
|
|
863
931
|
// Try to serialize the type (handles objects, type references, etc.)
|
|
864
932
|
// This will recursively resolve type aliases and interfaces
|
|
865
933
|
try {
|
|
866
|
-
const refResult = serializeType(unionType, checker, visited, depth + 1);
|
|
934
|
+
const refResult = serializeType(unionType, checker, visited, depth + 1, defs, defsVisited);
|
|
867
935
|
|
|
868
936
|
// Check if we got a valid schema (not just 'any' fallback)
|
|
869
937
|
if (!isFallbackAnySchema(refResult.schema, unionType, checker)) {
|
|
@@ -873,25 +941,25 @@ function serializeType(
|
|
|
873
941
|
const resolvedType = resolveTypeReference(unionType, checker);
|
|
874
942
|
if (resolvedType) {
|
|
875
943
|
try {
|
|
876
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1);
|
|
944
|
+
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
877
945
|
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
878
946
|
anyOf.push(resolvedResult.schema);
|
|
879
947
|
} else {
|
|
880
|
-
anyOf.push({
|
|
948
|
+
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
881
949
|
}
|
|
882
950
|
} catch (error) {
|
|
883
|
-
anyOf.push({
|
|
951
|
+
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
884
952
|
}
|
|
885
953
|
} else {
|
|
886
|
-
// It's a type reference we couldn't fully resolve - use
|
|
887
|
-
anyOf.push({
|
|
954
|
+
// It's a type reference we couldn't fully resolve - use empty schema (any value)
|
|
955
|
+
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
888
956
|
}
|
|
889
957
|
}
|
|
890
958
|
} catch (error) {
|
|
891
959
|
// Fallback for unresolvable types
|
|
892
960
|
const unionTypeString = checker.typeToString(unionType);
|
|
893
961
|
console.warn(`Warning: Could not serialize union type: ${unionTypeString}`, error);
|
|
894
|
-
anyOf.push({
|
|
962
|
+
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
895
963
|
}
|
|
896
964
|
}
|
|
897
965
|
}
|
|
@@ -921,7 +989,7 @@ function serializeType(
|
|
|
921
989
|
// Fallback
|
|
922
990
|
return {
|
|
923
991
|
isOptional: false,
|
|
924
|
-
schema: {
|
|
992
|
+
schema: {} // Empty schema = any value (JSON Schema draft 2020-12)
|
|
925
993
|
};
|
|
926
994
|
}
|
|
927
995
|
|
|
@@ -932,7 +1000,7 @@ function serializeType(
|
|
|
932
1000
|
const allRequired: string[] = [];
|
|
933
1001
|
|
|
934
1002
|
for (const intersectionType of types) {
|
|
935
|
-
const result = serializeType(intersectionType, checker, visited, depth + 1);
|
|
1003
|
+
const result = serializeType(intersectionType, checker, visited, depth + 1, defs, defsVisited);
|
|
936
1004
|
if (result.schema.type === 'object' && result.schema.properties) {
|
|
937
1005
|
Object.assign(allProperties, result.schema.properties);
|
|
938
1006
|
if (result.schema.required) {
|
|
@@ -957,7 +1025,7 @@ function serializeType(
|
|
|
957
1025
|
if (checker.isArrayType(type)) {
|
|
958
1026
|
const typeArgs = (type as ts.TypeReference).typeArguments;
|
|
959
1027
|
if (typeArgs && typeArgs.length > 0) {
|
|
960
|
-
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
|
|
1028
|
+
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1, defs, defsVisited);
|
|
961
1029
|
return {
|
|
962
1030
|
isOptional: false,
|
|
963
1031
|
schema: { type: 'array', items: itemResult.schema }
|
|
@@ -975,6 +1043,28 @@ function serializeType(
|
|
|
975
1043
|
const properties: Record<string, JsonSchemaProperty> = {};
|
|
976
1044
|
const required: string[] = [];
|
|
977
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
|
+
}
|
|
978
1068
|
|
|
979
1069
|
if (props.length > 0) {
|
|
980
1070
|
for (const prop of props) {
|
|
@@ -989,7 +1079,7 @@ function serializeType(
|
|
|
989
1079
|
const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
|
|
990
1080
|
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
991
1081
|
|
|
992
|
-
const propResult = serializeType(propType, checker, visited, depth + 1);
|
|
1082
|
+
const propResult = serializeType(propType, checker, visited, depth + 1, defs, defsVisited);
|
|
993
1083
|
properties[prop.name] = propResult.schema;
|
|
994
1084
|
|
|
995
1085
|
// Track required properties
|
|
@@ -1009,7 +1099,7 @@ function serializeType(
|
|
|
1009
1099
|
const indexInfo = indexSignatures[0];
|
|
1010
1100
|
if (indexInfo.declaration) {
|
|
1011
1101
|
const indexType = checker.getTypeAtLocation(indexInfo.declaration);
|
|
1012
|
-
const indexResult = serializeType(indexType, checker, visited, depth + 1);
|
|
1102
|
+
const indexResult = serializeType(indexType, checker, visited, depth + 1, defs, defsVisited);
|
|
1013
1103
|
// JSON Schema uses additionalProperties for index signatures
|
|
1014
1104
|
if (indexResult.schema.type !== 'any' || indexResult.schema.properties) {
|
|
1015
1105
|
// Only set if it's a meaningful type (not just 'any' fallback)
|
|
@@ -1033,6 +1123,13 @@ function serializeType(
|
|
|
1033
1123
|
schema.required = required;
|
|
1034
1124
|
}
|
|
1035
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
|
+
|
|
1036
1133
|
return { isOptional: false, schema };
|
|
1037
1134
|
}
|
|
1038
1135
|
}
|
|
@@ -1080,8 +1177,8 @@ function serializeType(
|
|
|
1080
1177
|
} catch (error) {
|
|
1081
1178
|
console.warn(`Warning: Could not extract enum values for type: ${typeString}`, error);
|
|
1082
1179
|
}
|
|
1083
|
-
// If we can't extract enum values, use
|
|
1084
|
-
return { isOptional: false, schema: {
|
|
1180
|
+
// If we can't extract enum values, use empty schema (any value)
|
|
1181
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1085
1182
|
}
|
|
1086
1183
|
|
|
1087
1184
|
// Check if it's a type reference (like InternalFilterType.StringFilter)
|
|
@@ -1093,7 +1190,7 @@ function serializeType(
|
|
|
1093
1190
|
// Empty object or type reference we can't resolve
|
|
1094
1191
|
// Check if typeString suggests it's a named type reference
|
|
1095
1192
|
if (typeString && typeString.includes('.')) {
|
|
1096
|
-
return { isOptional: false, schema: {
|
|
1193
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1097
1194
|
}
|
|
1098
1195
|
}
|
|
1099
1196
|
}
|
|
@@ -1115,7 +1212,7 @@ function serializeType(
|
|
|
1115
1212
|
const resolvedType = resolveTypeReference(type, checker);
|
|
1116
1213
|
if (resolvedType) {
|
|
1117
1214
|
try {
|
|
1118
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1);
|
|
1215
|
+
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
1119
1216
|
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
1120
1217
|
return resolvedResult;
|
|
1121
1218
|
}
|
|
@@ -1129,17 +1226,18 @@ function serializeType(
|
|
|
1129
1226
|
return { isOptional: false, schema: { type: 'null' } };
|
|
1130
1227
|
}
|
|
1131
1228
|
if (type.flags & ts.TypeFlags.Unknown) {
|
|
1132
|
-
return { isOptional: false, schema: {
|
|
1229
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1133
1230
|
}
|
|
1134
1231
|
if (type.flags & ts.TypeFlags.Any) {
|
|
1135
|
-
return { isOptional: false, schema: {
|
|
1232
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1136
1233
|
}
|
|
1137
1234
|
|
|
1138
|
-
// For any other unrecognized type, use
|
|
1235
|
+
// For any other unrecognized type, use empty schema instead of invalid type strings
|
|
1139
1236
|
// This prevents errors like "type": "InternalFilterType.StringFilter"
|
|
1237
|
+
// Empty schema {} means "allow any value" in JSON Schema draft 2020-12
|
|
1140
1238
|
return {
|
|
1141
1239
|
isOptional: false,
|
|
1142
|
-
schema: {
|
|
1240
|
+
schema: {} // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1143
1241
|
};
|
|
1144
1242
|
}
|
|
1145
1243
|
|
|
@@ -1279,6 +1377,7 @@ function extractFunctionMetadata(
|
|
|
1279
1377
|
|
|
1280
1378
|
const properties: Record<string, JsonSchemaProperty> = {};
|
|
1281
1379
|
const required: string[] = [];
|
|
1380
|
+
const defs: Record<string, JsonSchemaProperty> = {}; // Type definitions for recursive types (shared across all parameters)
|
|
1282
1381
|
|
|
1283
1382
|
// Check if async - works for both function declarations and arrow functions
|
|
1284
1383
|
const isAsync =
|
|
@@ -1310,6 +1409,9 @@ function extractFunctionMetadata(
|
|
|
1310
1409
|
}
|
|
1311
1410
|
}
|
|
1312
1411
|
|
|
1412
|
+
// Shared defsVisited across all parameters (tracks which definitions we're currently building)
|
|
1413
|
+
const defsVisited = new Set<number>();
|
|
1414
|
+
|
|
1313
1415
|
for (const param of parameters) {
|
|
1314
1416
|
const paramName = param.name.getText(sourceFile);
|
|
1315
1417
|
const hasDefault = param.initializer !== undefined;
|
|
@@ -1333,11 +1435,14 @@ function extractFunctionMetadata(
|
|
|
1333
1435
|
}
|
|
1334
1436
|
}
|
|
1335
1437
|
|
|
1336
|
-
|
|
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);
|
|
1337
1442
|
propSchema = result.schema;
|
|
1338
1443
|
isOptional = result.isOptional;
|
|
1339
1444
|
} else {
|
|
1340
|
-
propSchema = {
|
|
1445
|
+
propSchema = {}; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1341
1446
|
}
|
|
1342
1447
|
|
|
1343
1448
|
// Check if parameter has default value or question mark
|
|
@@ -1391,14 +1496,21 @@ function extractFunctionMetadata(
|
|
|
1391
1496
|
}
|
|
1392
1497
|
}
|
|
1393
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
|
+
|
|
1394
1510
|
return {
|
|
1395
1511
|
name: functionName,
|
|
1396
1512
|
description,
|
|
1397
|
-
parameters:
|
|
1398
|
-
type: 'object',
|
|
1399
|
-
properties,
|
|
1400
|
-
required: required.length > 0 ? required : undefined
|
|
1401
|
-
},
|
|
1513
|
+
parameters: parametersSchema,
|
|
1402
1514
|
returnType,
|
|
1403
1515
|
isAsync,
|
|
1404
1516
|
};
|