@@ -34,11 +34,11 @@ const INTERNAL_FLAGS = new Set([
3434 "log-level" ,
3535 "verbose" ,
3636 "fields" ,
37- // Streaming flags produce infinite output — not supported in library mode
38- "refresh" ,
39- "follow" ,
4037] ) ;
4138
39+ /** Flags that trigger streaming mode — included in params but change return type */
40+ const STREAMING_FLAGS = new Set ( [ "refresh" , "follow" ] ) ;
41+
4242/** Regex for stripping angle-bracket/ellipsis decorators from placeholder names */
4343const PLACEHOLDER_CLEAN_RE = / [ < > . ] / g;
4444
@@ -341,13 +341,12 @@ function generateParamsInterface(
341341 return { name : interfaceName , code } ;
342342}
343343
344- /** Generate the method body (invoke call) for a command . */
345- function generateMethodBody (
344+ /** Build the flag object expression and positional expression for an invoke call . */
345+ function buildInvokeArgs (
346346 path : string [ ] ,
347347 positional : PositionalInfo ,
348- flags : SdkFlagInfo [ ] ,
349- returnType : string
350- ) : string {
348+ flags : SdkFlagInfo [ ]
349+ ) : { flagObj : string ; positionalExpr : string ; pathStr : string } {
351350 const flagEntries = flags . map ( ( f ) => {
352351 const camel = camelCase ( f . name ) ;
353352 if ( f . name !== camel ) {
@@ -368,8 +367,65 @@ function generateMethodBody(
368367
369368 const flagObj =
370369 flagEntries . length > 0 ? `{ ${ flagEntries . join ( ", " ) } }` : "{}" ;
370+ const pathStr = JSON . stringify ( path ) ;
371+
372+ return { flagObj, positionalExpr, pathStr } ;
373+ }
374+
375+ /**
376+ * Generate the method body (invoke call) for a non-streaming command.
377+ * Uses `as Promise<T>` to narrow the invoke union return type, since
378+ * non-streaming calls never pass `meta.streaming` and always return a Promise.
379+ */
380+ function generateMethodBody (
381+ path : string [ ] ,
382+ positional : PositionalInfo ,
383+ flags : SdkFlagInfo [ ] ,
384+ returnType : string
385+ ) : string {
386+ const { flagObj, positionalExpr, pathStr } = buildInvokeArgs (
387+ path ,
388+ positional ,
389+ flags
390+ ) ;
391+ return `invoke<${ returnType } >(${ pathStr } , ${ flagObj } , ${ positionalExpr } ) as Promise<${ returnType } >` ;
392+ }
393+
394+ /** Options for generating a streaming method body. */
395+ type StreamingMethodOpts = {
396+ path : string [ ] ;
397+ positional : PositionalInfo ;
398+ flags : SdkFlagInfo [ ] ;
399+ returnType : string ;
400+ streamingFlagNames : string [ ] ;
401+ indent : string ;
402+ } ;
371403
372- return `invoke<${ returnType } >(${ JSON . stringify ( path ) } , ${ flagObj } , ${ positionalExpr } )` ;
404+ /**
405+ * Generate the method body for a streaming-capable command.
406+ *
407+ * Detects at runtime whether any streaming flag is present and passes
408+ * `{ streaming: true }` to the invoker when it is.
409+ */
410+ function generateStreamingMethodBody ( opts : StreamingMethodOpts ) : string {
411+ const { flagObj, positionalExpr, pathStr } = buildInvokeArgs (
412+ opts . path ,
413+ opts . positional ,
414+ opts . flags
415+ ) ;
416+
417+ // Build the streaming condition: params?.follow !== undefined || params?.refresh !== undefined
418+ const conditions = opts . streamingFlagNames
419+ . map ( ( name ) => `params?.${ camelCase ( name ) } !== undefined` )
420+ . join ( " || " ) ;
421+
422+ const lines = [
423+ "{" ,
424+ `${ opts . indent } const streaming = ${ conditions } ;` ,
425+ `${ opts . indent } return invoke<${ opts . returnType } >(${ pathStr } , ${ flagObj } , ${ positionalExpr } , { streaming });` ,
426+ `${ opts . indent } }` ,
427+ ] ;
428+ return lines . join ( "\n" ) ;
373429}
374430
375431// ---------------------------------------------------------------------------
@@ -472,6 +528,12 @@ for (const { path, command } of allCommands) {
472528 const flags = extractSdkFlags ( command ) ;
473529 const positional = derivePositional ( command ) ;
474530
531+ // Detect streaming-capable commands
532+ const streamingFlagNames = flags
533+ . filter ( ( f ) => STREAMING_FLAGS . has ( f . name ) )
534+ . map ( ( f ) => f . name ) ;
535+ const isStreaming = streamingFlagNames . length > 0 ;
536+
475537 // Generate return type from schema
476538 const schemaTypeName = `${ buildTypeName ( path ) } Result` ;
477539 const returnTypeInfo = generateReturnType ( command , schemaTypeName ) ;
@@ -493,40 +555,87 @@ for (const { path, command } of allCommands) {
493555 let paramsArg : string ;
494556 let body : string ;
495557
558+ const brief = command . brief || path . join ( " " ) ;
559+ const methodName = path . at ( - 1 ) ?? path [ 0 ] ;
560+ const indent = " " . repeat ( path . length - 1 ) ;
561+
496562 if ( hasVariadicPositional ) {
497563 // Variadic: (params: XParams, ...positional: string[]) or (params?: XParams, ...positional: string[])
498564 // Required flags make params required even with variadic positionals
499565 const paramsOpt = hasRequiredFlags ? "" : "?" ;
500566 paramsArg = params
501567 ? `params${ paramsOpt } : ${ params . name } , ...positional: string[]`
502568 : "...positional: string[]" ;
503- body = generateMethodBody ( path , positional , flags , returnType ) ;
569+ body = isStreaming
570+ ? generateStreamingMethodBody ( {
571+ path,
572+ positional,
573+ flags,
574+ returnType,
575+ streamingFlagNames,
576+ indent,
577+ } )
578+ : generateMethodBody ( path , positional , flags , returnType ) ;
504579 } else if ( params ) {
505580 const paramsRequired = hasRequiredFlags ;
506581 paramsArg = paramsRequired
507582 ? `params: ${ params . name } `
508583 : `params?: ${ params . name } ` ;
509- body = generateMethodBody ( path , positional , flags , returnType ) ;
584+ body = isStreaming
585+ ? generateStreamingMethodBody ( {
586+ path,
587+ positional,
588+ flags,
589+ returnType,
590+ streamingFlagNames,
591+ indent,
592+ } )
593+ : generateMethodBody ( path , positional , flags , returnType ) ;
510594 } else {
511595 body = generateMethodBody ( path , positional , flags , returnType ) ;
512596 paramsArg = "" ;
513597 }
514598
515- const brief = command . brief || path . join ( " " ) ;
516- const methodName = path . at ( - 1 ) ?? path [ 0 ] ;
517- const indent = " " . repeat ( path . length - 1 ) ;
518599 const sig = paramsArg ? `(${ paramsArg } )` : "()" ;
519- const methodCode = [
520- `${ indent } /** ${ brief } */` ,
521- `${ indent } ${ methodName } : ${ sig } : Promise<${ returnType } > =>` ,
522- `${ indent } ${ body } ,` ,
523- ] . join ( "\n" ) ;
524-
525- // Type declaration: method signature without implementation
526- const typeDecl = [
527- `${ indent } /** ${ brief } */` ,
528- `${ indent } ${ methodName } ${ sig } : Promise<${ returnType } >;` ,
529- ] . join ( "\n" ) ;
600+
601+ let methodCode : string ;
602+ let typeDecl : string ;
603+
604+ if ( isStreaming ) {
605+ // Streaming commands use a function body (not arrow expression)
606+ // because they need runtime streaming detection
607+ methodCode = [
608+ `${ indent } /** ${ brief } */` ,
609+ `${ indent } ${ methodName } : ${ sig } => ${ body } ,` ,
610+ ] . join ( "\n" ) ;
611+
612+ // Type declaration: callable interface with overloaded signatures
613+ const streamingFlagTypes = streamingFlagNames . map ( ( name ) => {
614+ const flag = flags . find ( ( f ) => f . name === name ) ;
615+ return `${ camelCase ( name ) } : ${ flag ?. tsType ?? "string" } ` ;
616+ } ) ;
617+ const streamingConstraint = streamingFlagTypes . join ( "; " ) ;
618+
619+ typeDecl = [
620+ `${ indent } /** ${ brief } */` ,
621+ `${ indent } ${ methodName } : {` ,
622+ `${ indent } (params: ${ params ?. name ?? "Record<string, never>" } & { ${ streamingConstraint } }): AsyncIterable<unknown>;` ,
623+ `${ indent } ${ sig } : Promise<${ returnType } >;` ,
624+ `${ indent } };` ,
625+ ] . join ( "\n" ) ;
626+ } else {
627+ methodCode = [
628+ `${ indent } /** ${ brief } */` ,
629+ `${ indent } ${ methodName } : ${ sig } : Promise<${ returnType } > =>` ,
630+ `${ indent } ${ body } ,` ,
631+ ] . join ( "\n" ) ;
632+
633+ // Type declaration: method signature without implementation
634+ typeDecl = [
635+ `${ indent } /** ${ brief } */` ,
636+ `${ indent } ${ methodName } ${ sig } : Promise<${ returnType } >;` ,
637+ ] . join ( "\n" ) ;
638+ }
530639
531640 insertMethod ( root , path , methodCode , typeDecl ) ;
532641}
0 commit comments