1- import type { Context } from 'hono' ;
2- import { Effect , ManagedRuntime } from 'effect' ;
1+ import type { Context , ExecutionContext } from 'hono' ;
2+ import { Cause , Exit , ManagedRuntime } from 'effect' ;
33import type { Env } from '../types' ;
44import {
55 getErrorMessage ,
@@ -18,25 +18,67 @@ type RequestServices =
1818 | import ( './services' ) . WorkspaceRepo
1919 | import ( './services' ) . ContainerRuntime ;
2020
21+ function isWebSocketUpgradeResponse ( value : unknown ) : boolean {
22+ if ( ! ( value instanceof Response ) ) {
23+ return false ;
24+ }
25+ if ( value . status === 101 ) {
26+ return true ;
27+ }
28+ return ( value as { webSocket ?: unknown } ) . webSocket != null ;
29+ }
30+
31+ /** Effect.runPromise rejects with wrappers; squash to the real tagged error for HTTP mapping. */
32+ function normalizeRouteError ( error : unknown ) : unknown {
33+ if ( isAppError ( error ) ) {
34+ return error ;
35+ }
36+ if ( Cause . isCause ( error ) ) {
37+ return Cause . squash ( error ) ;
38+ }
39+ if ( error instanceof Error && error . cause !== undefined ) {
40+ const inner = error . cause ;
41+ if ( Cause . isCause ( inner ) ) {
42+ return Cause . squash ( inner ) ;
43+ }
44+ if ( isAppError ( inner ) ) {
45+ return inner ;
46+ }
47+ }
48+ return error ;
49+ }
50+
2151export async function runRequestEffect < A > (
2252 env : Env ,
23- effect : Effect . Effect < A , AppError , RequestServices >
53+ effect : Effect . Effect < A , AppError , RequestServices > ,
54+ options ?: { executionCtx ?: ExecutionContext }
2455) : Promise < A > {
2556 const runtime = ManagedRuntime . make ( makeRequestLayer ( env ) ) ;
57+ const exit = await runtime . runPromiseExit ( effect ) ;
2658
27- try {
28- return await runtime . runPromise ( effect ) ;
29- } finally {
59+ if ( Exit . isFailure ( exit ) ) {
3060 await runtime . dispose ( ) ;
61+ throw Cause . squash ( exit . cause ) ;
3162 }
63+
64+ const value = exit . value ;
65+
66+ if ( isWebSocketUpgradeResponse ( value ) && options ?. executionCtx ) {
67+ options . executionCtx . waitUntil ( runtime . dispose ( ) ) ;
68+ return value ;
69+ }
70+
71+ await runtime . dispose ( ) ;
72+ return value ;
3273}
3374
3475export function toRouteErrorResponse ( c : WorkerContext , error : unknown ) : Response {
35- const appError = isAppError ( error )
36- ? error
76+ const normalized = normalizeRouteError ( error ) ;
77+ const appError = isAppError ( normalized )
78+ ? normalized
3779 : new UnexpectedFailure ( {
3880 message : 'Internal server error' ,
39- cause : error ,
81+ cause : normalized ,
4082 } ) ;
4183 const status = getErrorStatus ( appError ) ;
4284 const message = getErrorMessage ( appError ) ;
@@ -54,13 +96,23 @@ export function toRouteErrorResponse(c: WorkerContext, error: unknown): Response
5496 } ) ;
5597}
5698
99+ function tryGetExecutionCtx ( c : WorkerContext ) : ExecutionContext | undefined {
100+ try {
101+ return c . executionCtx ;
102+ } catch {
103+ return undefined ;
104+ }
105+ }
106+
57107export async function runRouteEffect < A > (
58108 c : WorkerContext ,
59109 effect : Effect . Effect < A , AppError , RequestServices > ,
60110 onSuccess : ( value : A ) => Response
61111) : Promise < Response > {
62112 try {
63- const value = await runRequestEffect ( c . env , effect ) ;
113+ const value = await runRequestEffect ( c . env , effect , {
114+ executionCtx : tryGetExecutionCtx ( c ) ,
115+ } ) ;
64116 return onSuccess ( value ) ;
65117 } catch ( error ) {
66118 return toRouteErrorResponse ( c , error ) ;
0 commit comments