1- import { Duration , Effect , Option , Queue , Stream , type Scope } from 'effect' ;
1+ import { Deferred , Duration , Effect , Exit , Option , Queue , Stream , type Scope } from 'effect' ;
22import { createElement , isValidElement , type ReactElement , type ReactNode } from 'react' ;
33import { createFromReadableStream } from 'react-server-dom-rspack/client.node' ;
44
@@ -18,6 +18,7 @@ import { decodeAgentDocument } from './decode-document.js';
1818import {
1919 abortToInterrupt ,
2020 isAbortError ,
21+ mapCause ,
2122 runPromise ,
2223 scopedAbortSignal ,
2324 streamToReadableStream ,
@@ -353,6 +354,7 @@ const reconcileLoopStream = (
353354 ids : Map < string , string > ,
354355 initial : TreeSnapshot ,
355356 limits : Partial < AgentRenderLimits > | undefined ,
357+ flightDone : Deferred . Deferred < void , Error > ,
356358 progressInputs : Queue . Queue < AgentRenderEventInput > ,
357359 sequence : AgentRenderEventSequence ,
358360) : Stream . Stream < AgentRenderEventInput , Error > =>
@@ -363,7 +365,8 @@ const reconcileLoopStream = (
363365 Error
364366 > => {
365367 if ( snapshot . pending . length === 0 ) {
366- return Queue . clear ( progressInputs ) . pipe (
368+ return Deferred . await ( flightDone ) . pipe (
369+ Effect . andThen ( Queue . clear ( progressInputs ) ) ,
367370 Effect . flatMap ( ( queued ) =>
368371 Effect . try ( {
369372 catch : ( error ) => toRuntimeError ( error ) ,
@@ -412,14 +415,28 @@ const reconcileLoopStream = (
412415const gatedFlightStream = (
413416 flight : ReadableStream < Uint8Array > ,
414417 demand : FlightDemand ,
418+ flightDone : Deferred . Deferred < void , Error > ,
415419) : Stream . Stream < Uint8Array , Error > =>
416420 Stream . unwrap (
417421 Effect . gen ( function * ( ) {
418422 const reader = yield * Effect . acquireRelease (
419423 Effect . sync ( ( ) => flight . getReader ( ) ) ,
420- // cancel() rejects when the source already errored; a defect inside
421- // this closing scope must not replace the stream's own failure.
422- ( handle ) => Effect . promise ( ( ) => handle . cancel ( ) . then ( ( ) => undefined , ( ) => undefined ) ) ,
424+ ( handle , exit ) => Effect . gen ( function * ( ) {
425+ const cancelExit = yield * Effect . exit ( Effect . tryPromise ( {
426+ catch : ( error ) => toRuntimeError ( error ) ,
427+ try : ( ) => handle . cancel ( ) ,
428+ } ) ) ;
429+ if ( Exit . isFailure ( exit ) ) {
430+ yield * Deferred . fail ( flightDone , mapCause ( exit . cause ) ) ;
431+ return ;
432+ }
433+ if ( Exit . isFailure ( cancelExit ) ) {
434+ const error = mapCause ( cancelExit . cause ) ;
435+ yield * Deferred . fail ( flightDone , error ) ;
436+ return yield * Effect . die ( error ) ;
437+ }
438+ yield * Deferred . succeed ( flightDone , undefined ) ;
439+ } ) ,
423440 ) ;
424441 return Stream . unfold ( undefined , ( ) =>
425442 demand . wait . pipe (
@@ -439,6 +456,7 @@ const decodeFlightRoot = (
439456 flight : ReadableStream < Uint8Array > ,
440457 demand : FlightDemand ,
441458 signal : AbortSignal ,
459+ flightDone : Deferred . Deferred < void , Error > ,
442460) : Effect . Effect < ReactNode , Error , Scope . Scope > =>
443461 Effect . gen ( function * ( ) {
444462 ensureAgentFlightManifest ( ) ;
@@ -449,7 +467,7 @@ const decodeFlightRoot = (
449467 // (the maxEvents hang). The scoped signal interrupts the source stream
450468 // without touching the locked ReadableStream.
451469 const flightAbort = yield * scopedAbortSignal ;
452- const readable = streamToReadableStream ( gatedFlightStream ( flight , demand ) , {
470+ const readable = streamToReadableStream ( gatedFlightStream ( flight , demand , flightDone ) , {
453471 signal : flightAbort ,
454472 strategy : { highWaterMark : 1 } ,
455473 } ) ;
@@ -521,6 +539,7 @@ export const createAgentRenderEventSession = (
521539 const events = Stream . unwrap (
522540 Effect . gen ( function * ( ) {
523541 const progressInputs = yield * Queue . bounded < AgentRenderEventInput > ( 0 ) ;
542+ const flightDone = yield * Deferred . make < void , Error > ( ) ;
524543 const bindProgress = ( ) : void => {
525544 offerProgress = ( input ) =>
526545 Queue . offer ( progressInputs , input ) . pipe (
@@ -541,7 +560,7 @@ export const createAgentRenderEventSession = (
541560 try : ( ) => options . flight ,
542561 } ) ;
543562 if ( options . signal . aborted ) return yield * Effect . fail ( abortError ( ) ) ;
544- const root = yield * decodeFlightRoot ( flight , options . demand , options . signal ) ;
563+ const root = yield * decodeFlightRoot ( flight , options . demand , options . signal , flightDone ) ;
545564 if ( options . signal . aborted ) return yield * Effect . fail ( abortError ( ) ) ;
546565 const prepared = yield * Effect . try ( {
547566 catch : ( error ) => toRuntimeError ( error ) ,
@@ -566,21 +585,24 @@ export const createAgentRenderEventSession = (
566585 prepared . ids ,
567586 prepared . initial ,
568587 options . limits ,
588+ flightDone ,
569589 progressInputs ,
570590 sequence ,
571591 ) ,
572592 ) . pipe (
573593 Stream . mapEffect ( ( input ) => emitBoundRenderEvent ( sequence , input ) ) ,
574594 Stream . tap ( ( event ) => ( event . type === 'shell' ? options . demand . markShell : Effect . void ) ) ,
575- Stream . tapError ( ( error ) => Effect . sync ( ( ) => {
576- progressFailure = error ;
577- } ) ) ,
578595 Stream . takeUntil ( ( event ) => event . type === 'complete' ) ,
579- Stream . ensuring ( Effect . suspend ( ( ) => finalizeProgress ( progressFailure ?? handoffRequired ( ) ) ) ) ,
596+ Stream . onExit ( ( exit ) => {
597+ if ( Exit . isSuccess ( exit ) ) return finalizeProgress ( handoffRequired ( ) ) ;
598+ const error = mapCause ( exit . cause ) ;
599+ return finalizeProgress ( sequence . completed && isAbortError ( error ) ? handoffRequired ( ) : error ) ;
600+ } ) ,
580601 ) ;
581602 } ) ;
582603 return yield * setup . pipe (
583- Effect . catch ( ( error ) => finalizeProgress ( error ) . pipe ( Effect . andThen ( Effect . fail ( error ) ) ) ) ,
604+ Effect . onExit ( ( exit ) =>
605+ Exit . isFailure ( exit ) ? finalizeProgress ( mapCause ( exit . cause ) ) : Effect . void ) ,
584606 ) ;
585607 } ) ,
586608 ) ;
0 commit comments