@@ -10,11 +10,14 @@ import { execFile } from "child_process";
1010import fs from "fs/promises" ;
1111import path from "path" ;
1212import { promisify } from "util" ;
13- import type { JSONSchema7 } from "json-schema" ;
13+ import type { JSONSchema7 , JSONSchema7Definition } from "json-schema" ;
1414import {
1515 getSessionEventsSchemaPath ,
1616 getApiSchemaPath ,
1717 writeGeneratedFile ,
18+ collectDefinitions ,
19+ resolveRef ,
20+ refTypeName ,
1821 isRpcMethod ,
1922 isNodeFullyExperimental ,
2023 EXCLUDED_EVENT_TYPES ,
@@ -199,6 +202,9 @@ interface EventVariant {
199202
200203let generatedEnums = new Map < string , { enumName : string ; values : string [ ] } > ( ) ;
201204
205+ /** Schema definitions available during session event generation (for $ref resolution). */
206+ let sessionDefinitions : Record < string , JSONSchema7Definition > = { } ;
207+
202208function getOrCreateEnum ( parentClassName : string , propName : string , values : string [ ] , enumOutput : string [ ] , description ?: string ) : string {
203209 const valuesKey = [ ...values ] . sort ( ) . join ( "|" ) ;
204210 for ( const [ , existing ] of generatedEnums ) {
@@ -402,6 +408,21 @@ function resolveSessionPropertyType(
402408 nestedClasses : Map < string , string > ,
403409 enumOutput : string [ ]
404410) : string {
411+ // Handle $ref by resolving against schema definitions
412+ if ( propSchema . $ref ) {
413+ const typeName = refTypeName ( propSchema . $ref ) ;
414+ const className = typeToClassName ( typeName ) ;
415+ if ( ! nestedClasses . has ( className ) ) {
416+ const refSchema = resolveRef ( propSchema . $ref , sessionDefinitions ) ;
417+ if ( refSchema ) {
418+ if ( refSchema . enum && Array . isArray ( refSchema . enum ) ) {
419+ return getOrCreateEnum ( className , "" , refSchema . enum as string [ ] , enumOutput ) ;
420+ }
421+ nestedClasses . set ( className , generateNestedClass ( className , refSchema , knownTypes , nestedClasses , enumOutput ) ) ;
422+ }
423+ }
424+ return isRequired ? className : `${ className } ?` ;
425+ }
405426 if ( propSchema . anyOf ) {
406427 const hasNull = propSchema . anyOf . some ( ( s ) => typeof s === "object" && ( s as JSONSchema7 ) . type === "null" ) ;
407428 const nonNull = propSchema . anyOf . filter ( ( s ) => typeof s === "object" && ( s as JSONSchema7 ) . type !== "null" ) ;
@@ -433,6 +454,18 @@ function resolveSessionPropertyType(
433454 }
434455 if ( propSchema . type === "array" && propSchema . items ) {
435456 const items = propSchema . items as JSONSchema7 ;
457+ // Handle $ref in array items
458+ if ( items . $ref ) {
459+ const typeName = refTypeName ( items . $ref ) ;
460+ const className = typeToClassName ( typeName ) ;
461+ if ( ! nestedClasses . has ( className ) ) {
462+ const refSchema = resolveRef ( items . $ref , sessionDefinitions ) ;
463+ if ( refSchema ) {
464+ nestedClasses . set ( className , generateNestedClass ( className , refSchema , knownTypes , nestedClasses , enumOutput ) ) ;
465+ }
466+ }
467+ return isRequired ? `${ className } []` : `${ className } []?` ;
468+ }
436469 // Array of discriminated union (anyOf with shared discriminator)
437470 if ( items . anyOf && Array . isArray ( items . anyOf ) ) {
438471 const variants = items . anyOf . filter ( ( v ) : v is JSONSchema7 => typeof v === "object" ) ;
@@ -491,6 +524,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map<string, string
491524
492525function generateSessionEventsCode ( schema : JSONSchema7 ) : string {
493526 generatedEnums . clear ( ) ;
527+ sessionDefinitions = schema . definitions as Record < string , JSONSchema7Definition > || { } ;
494528 const variants = extractEventVariants ( schema ) ;
495529 const knownTypes = new Map < string , string > ( ) ;
496530 const nestedClasses = new Map < string , string > ( ) ;
@@ -600,6 +634,9 @@ let experimentalRpcTypes = new Set<string>();
600634let rpcKnownTypes = new Map < string , string > ( ) ;
601635let rpcEnumOutput : string [ ] = [ ] ;
602636
637+ /** Schema definitions available during RPC generation (for $ref resolution). */
638+ let rpcDefinitions : Record < string , JSONSchema7Definition > = { } ;
639+
603640function singularPascal ( s : string ) : string {
604641 const p = toPascalCase ( s ) ;
605642 if ( p . endsWith ( "ies" ) ) return `${ p . slice ( 0 , - 3 ) } y` ;
@@ -617,6 +654,16 @@ function paramsTypeName(rpcMethod: string): string {
617654}
618655
619656function resolveRpcType ( schema : JSONSchema7 , isRequired : boolean , parentClassName : string , propName : string , classes : string [ ] ) : string {
657+ // Handle $ref by resolving against schema definitions and generating the referenced class
658+ if ( schema . $ref ) {
659+ const typeName = refTypeName ( schema . $ref ) ;
660+ const refSchema = resolveRef ( schema . $ref , rpcDefinitions ) ;
661+ if ( refSchema && ! emittedRpcClasses . has ( typeName ) ) {
662+ const cls = emitRpcClass ( typeName , refSchema , "public" , classes ) ;
663+ if ( cls ) classes . push ( cls ) ;
664+ }
665+ return isRequired ? typeName : `${ typeName } ?` ;
666+ }
620667 // Handle anyOf: [T, null] → T? (nullable typed property)
621668 if ( schema . anyOf ) {
622669 const hasNull = schema . anyOf . some ( ( s ) => typeof s === "object" && ( s as JSONSchema7 ) . type === "null" ) ;
@@ -637,6 +684,16 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
637684 }
638685 if ( schema . type === "array" && schema . items ) {
639686 const items = schema . items as JSONSchema7 ;
687+ // Handle $ref in array items
688+ if ( items . $ref ) {
689+ const typeName = refTypeName ( items . $ref ) ;
690+ const refSchema = resolveRef ( items . $ref , rpcDefinitions ) ;
691+ if ( refSchema && ! emittedRpcClasses . has ( typeName ) ) {
692+ const cls = emitRpcClass ( typeName , refSchema , "public" , classes ) ;
693+ if ( cls ) classes . push ( cls ) ;
694+ }
695+ return isRequired ? `List<${ typeName } >` : `List<${ typeName } >?` ;
696+ }
640697 if ( items . type === "object" && items . properties ) {
641698 const itemClass = singularPascal ( propName ) ;
642699 if ( ! emittedRpcClasses . has ( itemClass ) ) classes . push ( emitRpcClass ( itemClass , items , "public" , classes ) ) ;
@@ -1065,6 +1122,7 @@ function generateRpcCode(schema: ApiSchema): string {
10651122 rpcKnownTypes . clear ( ) ;
10661123 rpcEnumOutput = [ ] ;
10671124 generatedEnums . clear ( ) ; // Clear shared enum deduplication map
1125+ rpcDefinitions = collectDefinitions ( schema as Record < string , unknown > ) ;
10681126 const classes : string [ ] = [ ] ;
10691127
10701128 let serverRpcParts : string [ ] = [ ] ;
0 commit comments