71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
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")<ExpressNodeHTTPServer, Server>() {}
|
|
|
|
|
|
const importNodeHTTP = Effect.tryPromise({
|
|
try: () => import("node:http"),
|
|
catch: cause => new ImportError({ path: "node:http", cause }),
|
|
})
|
|
|
|
const serverListeningMessage = Match.type<AddressInfo | string | null>().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<number | undefined>
|
|
readonly exclusive?: Config.Config<boolean | undefined>
|
|
readonly host?: Config.Config<string | undefined>
|
|
readonly ipv6Only?: Config.Config<boolean | undefined>
|
|
readonly path?: Config.Config<string | undefined>
|
|
readonly port?: Config.Config<number | undefined>
|
|
readonly readableAll?: Config.Config<boolean | undefined>
|
|
readonly signal?: AbortSignal
|
|
readonly writableAll?: Config.Config<boolean | undefined>
|
|
} = {}
|
|
) => 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<Server>(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")))
|
|
})
|
|
}),
|
|
))
|