1import type * as cf from "@cloudflare/workers-types";
2import * as Deferred from "effect/Deferred";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6import * as EffectHttp from "effect/http/HttpEffect";
7import * as HttpServerRequest from "effect/http/HttpServerRequest";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import * as Http from "../../Http.ts";
10import { Request } from "./Request.ts";
11import type { WorkerServices } from "./Worker.ts";
12import { isWorkerEvent } from "./WorkerRuntime.ts";
13
14export type HttpEffect = Http.HttpEffect<WorkerServices>;
15
16export const makeRequestHandler =
17 <Req = never>(
18 handler: Http.HttpEffect<Req> | Effect.Effect<Http.HttpEffect<Req>>,
19 ) =>
20 (event: any) =>
21 isWorkerEvent(event) && event.type === "fetch"
22 ? makeRequestEffect(event.input, handler)
23 : undefined;
24
25export const makeRequestEffect = <Req = never>(
26 webRequest: cf.Request,
27 handler: Http.HttpEffect<Req> | Effect.Effect<Http.HttpEffect<Req>>,
28) => {
29 const safeHandler = Http.safeHttpEffect(handler);
30 return Effect.gen(function* () {
31 const request = HttpServerRequest.fromWeb(
32 webRequest as any as globalThis.Request,
33 ).modify({
34 remoteAddress: Option.fromUndefinedOr(
35 webRequest.headers.get("cf-connecting-ip") ?? undefined,
36 ),
37 });
38
39 Object.defineProperty(request, "raw", {
40 get: () =>
41 Object.assign(request.stream, {
42 raw: webRequest.body,
43 }),
44 });
45
46 return yield* toHandledWebResponse(safeHandler).pipe(
47 Effect.provide([
48 Layer.succeed(HttpServerRequest.HttpServerRequest, request),
49 Layer.succeed(Request, webRequest as any),
50 ]),
51 );
52 }) as any;
53};
54
55const toHandledWebResponse = <Req>(
56 handler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, Req>,
57) =>
58 Effect.gen(function* () {
59 // `toHandled` exposes the final response through this callback, not its
60 // return value. Keep the assignment isolated here so callers get Response.
61 const context = yield* Effect.context();
62 const webResponse = yield* Deferred.make<Response>();
63
64 yield* EffectHttp.toHandled(handler, (request, response) =>
65 Deferred.succeed(
66 webResponse,
67 // Conversion to web response with options matches `EffectHttp.toWebHandler`'s callback.
68 HttpServerResponse.toWeb(EffectHttp.scopeTransferToStream(response), {
69 withoutBody: request.method === "HEAD",
70 context,
71 }),
72 ),
73 );
74 return yield* Deferred.await(webResponse);
75 });
76
77export { isScopeEjected } from "../../Http.ts";