| 1 | import type * as cf from "@cloudflare/workers-types"; |
| 2 | import * as Deferred from "effect/Deferred"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; |
| 5 | import * as Option from "effect/Option"; |
| 6 | import * as EffectHttp from "effect/http/HttpEffect"; |
| 7 | import * as HttpServerRequest from "effect/http/HttpServerRequest"; |
| 8 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 9 | import * as Http from "../../Http.ts"; |
| 10 | import { Request } from "./Request.ts"; |
| 11 | import type { WorkerServices } from "./Worker.ts"; |
| 12 | import { isWorkerEvent } from "./WorkerRuntime.ts"; |
| 13 | |
| 14 | export type HttpEffect = Http.HttpEffect<WorkerServices>; |
| 15 | |
| 16 | export 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 | |
| 25 | export 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 | |
| 55 | const 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 | |
| 77 | export { isScopeEjected } from "../../Http.ts"; |