@@ -1393,6 +1393,14 @@ async function generateRpcTypes(schemaPath: string): Promise<void> {
13931393 generatedClasses . set ( resultRefName , true ) ;
13941394 allFiles . push ( await generateRpcDataClass ( resultRefName , resultSchema , packageName , packageDir , method . rpcMethod , "result" ) ) ;
13951395 }
1396+ } else if ( resultRefName && resultSchema . type === "array" ) {
1397+ // Named array aliases (e.g. AccountGetAllUsersResult) are returned
1398+ // as List<T> by wrappers, but resolving them here discovers any
1399+ // referenced item records that need standalone generation.
1400+ schemaTypeToJava ( resultSchema , false , resultRefName , "item" , new Map ( ) ) ;
1401+ } else if ( resultSchema . type === "array" ) {
1402+ // Inline arrays also need their referenced item records generated.
1403+ schemaTypeToJava ( resultSchema , false , `${ className } Result` , "item" , new Map ( ) ) ;
13961404 }
13971405 }
13981406 }
@@ -1521,8 +1529,8 @@ function apiClassName(prefix: string, path: string[]): string {
15211529}
15221530
15231531/**
1524- * Derive the result class name for an RPC method.
1525- * Handles $ref to named definitions (enums, anyOf unions, objects with properties).
1532+ * Derive the Java result type for an RPC method.
1533+ * Handles $ref to named definitions (enums, anyOf unions, objects with properties, arrays ).
15261534 * Falls back to Void for null results or schemas with no meaningful type.
15271535 */
15281536function wrapperResultClassName ( method : RpcMethodNode ) : string {
@@ -1553,6 +1561,11 @@ function wrapperResultClassName(method: RpcMethodNode): string {
15531561 if ( resolved . type === "object" && ! resolved . properties ) {
15541562 return refName ;
15551563 }
1564+ // Named array aliases → use the underlying List<T> Java type.
1565+ if ( resolved . type === "array" ) {
1566+ const result = schemaTypeToJava ( resolved , false , refName , "item" , new Map ( ) ) ;
1567+ return result . javaType ;
1568+ }
15561569 }
15571570 }
15581571
@@ -1568,6 +1581,11 @@ function wrapperResultClassName(method: RpcMethodNode): string {
15681581 return rpcMethodToClassName ( method . rpcMethod ) + "Result" ;
15691582 }
15701583
1584+ if ( result && typeof result === "object" && result . type === "array" ) {
1585+ const javaResult = schemaTypeToJava ( result , false , `${ rpcMethodToClassName ( method . rpcMethod ) } Result` , "item" , new Map ( ) ) ;
1586+ return javaResult . javaType ;
1587+ }
1588+
15711589 // Free-form object with additionalProperties (e.g., x-opaque-json) → JsonNode
15721590 if (
15731591 result &&
@@ -1582,6 +1600,41 @@ function wrapperResultClassName(method: RpcMethodNode): string {
15821600 return "Void" ;
15831601}
15841602
1603+ function wrapperResultTypeExpression ( resultType : string ) : string {
1604+ const listMatch = resultType . match ( / ^ L i s t < ( [ ^ < > ] + ) > $ / ) ;
1605+ if ( listMatch ) {
1606+ return `RpcMapper.INSTANCE.getTypeFactory().constructCollectionType(List.class, ${ javaClassLiteral ( listMatch [ 1 ] ) } )` ;
1607+ }
1608+
1609+ return javaClassLiteral ( resultType ) ;
1610+ }
1611+
1612+ function javaClassLiteral ( javaType : string ) : string {
1613+ return javaType === "Void" ? "Void.class" : `${ javaType } .class` ;
1614+ }
1615+
1616+ function addWrapperResultImports ( resultType : string , allImports : Set < string > , packageName : string ) : void {
1617+ if ( resultType === "Void" ) {
1618+ return ;
1619+ }
1620+
1621+ if ( resultType === "JsonNode" ) {
1622+ allImports . add ( "com.fasterxml.jackson.databind.JsonNode" ) ;
1623+ return ;
1624+ }
1625+
1626+ if ( resultType . startsWith ( "List<" ) ) {
1627+ allImports . add ( "java.util.List" ) ;
1628+ }
1629+
1630+ const builtInTypes = new Set ( [ "Boolean" , "Double" , "Long" , "List" , "Object" , "String" , "Void" ] ) ;
1631+ for ( const typeName of resultType . match ( / \b [ A - Z ] [ A - Z a - z 0 - 9 _ ] * \b / g) ?? [ ] ) {
1632+ if ( ! builtInTypes . has ( typeName ) ) {
1633+ allImports . add ( `${ packageName } .${ typeName } ` ) ;
1634+ }
1635+ }
1636+ }
1637+
15851638/**
15861639 * Return the params class name if the method has a params schema with properties
15871640 * other than sessionId (i.e. there are user-supplied parameters).
@@ -1659,18 +1712,18 @@ function generateApiMethod(
16591712 needsMapper = true ;
16601713 lines . push ( ` com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);` ) ;
16611714 lines . push ( ` _p.put("sessionId", ${ sessionIdExpr } );` ) ;
1662- lines . push ( ` return caller.invoke("${ method . rpcMethod } ", _p, ${ resultClass } .class );` ) ;
1715+ lines . push ( ` return caller.invoke("${ method . rpcMethod } ", _p, ${ wrapperResultTypeExpression ( resultClass ) } );` ) ;
16631716 } else if ( hasSessionId ) {
1664- lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of("sessionId", ${ sessionIdExpr } ), ${ resultClass } .class );` ) ;
1717+ lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of("sessionId", ${ sessionIdExpr } ), ${ wrapperResultTypeExpression ( resultClass ) } );` ) ;
16651718 } else {
1666- lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of(), ${ resultClass } .class );` ) ;
1719+ lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of(), ${ wrapperResultTypeExpression ( resultClass ) } );` ) ;
16671720 }
16681721 } else {
16691722 // Server-side: pass params directly (or empty map if no params)
16701723 if ( hasExtraParams ) {
1671- lines . push ( ` return caller.invoke("${ method . rpcMethod } ", params, ${ resultClass } .class );` ) ;
1724+ lines . push ( ` return caller.invoke("${ method . rpcMethod } ", params, ${ wrapperResultTypeExpression ( resultClass ) } );` ) ;
16721725 } else {
1673- lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of(), ${ resultClass } .class );` ) ;
1726+ lines . push ( ` return caller.invoke("${ method . rpcMethod } ", java.util.Map.of(), ${ wrapperResultTypeExpression ( resultClass ) } );` ) ;
16741727 }
16751728 }
16761729
@@ -1723,13 +1776,7 @@ async function generateNamespaceApiFile(
17231776 for ( const [ key , method ] of tree . methods ) {
17241777 const resultClass = wrapperResultClassName ( method ) ;
17251778 const paramsClass = wrapperParamsClassName ( method ) ;
1726- if ( resultClass !== "Void" ) {
1727- if ( resultClass === "JsonNode" ) {
1728- allImports . add ( "com.fasterxml.jackson.databind.JsonNode" ) ;
1729- } else {
1730- allImports . add ( `${ packageName } .${ resultClass } ` ) ;
1731- }
1732- }
1779+ addWrapperResultImports ( resultClass , allImports , packageName ) ;
17331780 if ( paramsClass ) allImports . add ( `${ packageName } .${ paramsClass } ` ) ;
17341781
17351782 const { lines, needsMapper : nm , needsExperimentalImport } = generateApiMethod ( key , method , isSession , sessionIdExpr ) ;
@@ -1849,13 +1896,7 @@ async function generateRpcRootFile(
18491896 for ( const [ key , method ] of tree . methods ) {
18501897 const resultClass = wrapperResultClassName ( method ) ;
18511898 const paramsClass = wrapperParamsClassName ( method ) ;
1852- if ( resultClass !== "Void" ) {
1853- if ( resultClass === "JsonNode" ) {
1854- allImports . add ( "com.fasterxml.jackson.databind.JsonNode" ) ;
1855- } else {
1856- allImports . add ( `${ packageName } .${ resultClass } ` ) ;
1857- }
1858- }
1899+ addWrapperResultImports ( resultClass , allImports , packageName ) ;
18591900 if ( paramsClass ) allImports . add ( `${ packageName } .${ paramsClass } ` ) ;
18601901
18611902 const { lines, needsMapper : nm , needsExperimentalImport } = generateApiMethod ( key , method , isSession , sessionIdExpr ) ;
@@ -1959,7 +2000,10 @@ async function generateRpcCallerInterface(packageName: string, packageDir: strin
19592000 lines . push ( `` ) ;
19602001 lines . push ( `package ${ packageName } ;` ) ;
19612002 lines . push ( `` ) ;
2003+ lines . push ( `import com.fasterxml.jackson.databind.JavaType;` ) ;
2004+ lines . push ( `import com.fasterxml.jackson.databind.JsonNode;` ) ;
19622005 lines . push ( `import java.util.concurrent.CompletableFuture;` ) ;
2006+ lines . push ( `import java.util.concurrent.CompletionException;` ) ;
19632007 lines . push ( `import javax.annotation.processing.Generated;` ) ;
19642008 lines . push ( `` ) ;
19652009 lines . push ( `/**` ) ;
@@ -1987,6 +2031,28 @@ async function generateRpcCallerInterface(packageName: string, packageDir: strin
19872031 lines . push ( ` * @return a {@link CompletableFuture} that completes with the deserialized result` ) ;
19882032 lines . push ( ` */` ) ;
19892033 lines . push ( ` <T> CompletableFuture<T> invoke(String method, Object params, Class<T> resultType);` ) ;
2034+ lines . push ( `` ) ;
2035+ lines . push ( ` /**` ) ;
2036+ lines . push ( ` * Invokes a JSON-RPC method and returns a future for the typed response.` ) ;
2037+ lines . push ( ` *` ) ;
2038+ lines . push ( ` * @param <T> the expected response type` ) ;
2039+ lines . push ( ` * @param method the JSON-RPC method name` ) ;
2040+ lines . push ( ` * @param params the request parameters (may be a {@code Map}, DTO record, or {@code JsonNode})` ) ;
2041+ lines . push ( ` * @param resultType the Jackson {@link JavaType} of the expected response type` ) ;
2042+ lines . push ( ` * @return a {@link CompletableFuture} that completes with the deserialized result` ) ;
2043+ lines . push ( ` */` ) ;
2044+ lines . push ( ` default <T> CompletableFuture<T> invoke(String method, Object params, JavaType resultType) {` ) ;
2045+ lines . push ( ` if (resultType.hasRawClass(Void.class) || resultType.hasRawClass(Void.TYPE)) {` ) ;
2046+ lines . push ( ` return invoke(method, params, Void.class).thenApply(ignored -> null);` ) ;
2047+ lines . push ( ` }` ) ;
2048+ lines . push ( ` return invoke(method, params, JsonNode.class).thenApply(result -> {` ) ;
2049+ lines . push ( ` try {` ) ;
2050+ lines . push ( ` return RpcMapper.INSTANCE.readerFor(resultType).readValue(result);` ) ;
2051+ lines . push ( ` } catch (java.io.IOException e) {` ) ;
2052+ lines . push ( ` throw new CompletionException(e);` ) ;
2053+ lines . push ( ` }` ) ;
2054+ lines . push ( ` });` ) ;
2055+ lines . push ( ` }` ) ;
19902056 lines . push ( `}` ) ;
19912057 lines . push ( `` ) ;
19922058
0 commit comments