@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcteninc/core",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -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
- const result = serializeType(type, checker);
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
  };