Skip to content

Commit 727f056

Browse files
committed
feat: enhance error handling and support for WebSocket upgrades in request processing
- Added functions to normalize route errors and check for WebSocket upgrade responses. - Updated runRequestEffect to handle execution context and dispose of resources properly. - Improved error response generation by normalizing errors before returning them. - Enhanced runRouteEffect to utilize the new execution context handling.
1 parent bf23a7e commit 727f056

1 file changed

Lines changed: 62 additions & 10 deletions

File tree

worker/effect/runtime.ts

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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';
33
import type { Env } from '../types';
44
import {
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+
2151
export 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

3475
export 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+
57107
export 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

Comments
 (0)