import { Config, Context, Effect, Layer, Match } from "effect" import type { Server } from "node:http" import type { AddressInfo } from "node:net" import { ImportError } from "../../ImportError" import { ExpressApp } from "./ExpressApp" export class ExpressNodeHTTPServer extends Context.Tag("ExpressNodeHTTPServer")() {} const importNodeHTTP = Effect.tryPromise({ try: () => import("node:http"), catch: cause => new ImportError({ path: "node:http", cause }), }) const serverListeningMessage = Match.type().pipe( Match.when(Match.null, () => "HTTP server listening"), Match.when(Match.string, v => `HTTP server listening on ${ v }`), Match.orElse(v => `HTTP server listening on ${ v.address === "::" ? "*" : v.address }:${ v.port }`), ) export const ExpressNodeHTTPServerLive = ( config: { readonly backlog?: Config.Config readonly exclusive?: Config.Config readonly host?: Config.Config readonly ipv6Only?: Config.Config readonly path?: Config.Config readonly port?: Config.Config readonly readableAll?: Config.Config readonly signal?: AbortSignal readonly writableAll?: Config.Config } = {} ) => Layer.effect(ExpressNodeHTTPServer, Effect.acquireRelease( Effect.gen(function*() { const app = yield* ExpressApp const http = yield* importNodeHTTP const options = { backlog: yield* config.backlog ?? Config.succeed(undefined), exclusive: yield* config.exclusive ?? Config.succeed(undefined), host: yield* config.host ?? Config.succeed(undefined), ipv6Only: yield* config.ipv6Only ?? Config.succeed(undefined), path: yield* config.path ?? Config.succeed(undefined), port: yield* config.port ?? Config.succeed(undefined), readableAll: yield* config.readableAll ?? Config.succeed(undefined), signal: config.signal, writableAll: yield* config.writableAll ?? Config.succeed(undefined), } as const return yield* Effect.async(resume => { const server = http.createServer(app).listen(options, () => resume( Effect.succeed(server).pipe( Effect.tap(Effect.logInfo( serverListeningMessage(server.address()) )) ) ) ) }) }), server => Effect.gen(function*() { yield* Effect.logInfo("HTTP server is stopping. Waiting for existing connections to end...") yield* Effect.async(resume => { server.close(() => resume(Effect.logInfo("HTTP server closed"))) }) }), ))