Scratch space for learning atproto app development
1import { createHttpTerminator } from 'http-terminator'
2import { once } from 'node:events'
3import type {
4 IncomingMessage,
5 RequestListener,
6 ServerResponse,
7} from 'node:http'
8import { createServer } from 'node:http'
9
10export type NextFunction = (err?: unknown) => void
11
12export type Middleware<
13 Req extends IncomingMessage = IncomingMessage,
14 Res extends ServerResponse<Req> = ServerResponse<Req>,
15> = (req: Req, res: Res, next: NextFunction) => void
16
17export type Handler<
18 Req extends IncomingMessage = IncomingMessage,
19 Res extends ServerResponse<Req> = ServerResponse<Req>,
20> = (req: Req, res: Res) => unknown | Promise<unknown>
21/**
22 * Wraps a request handler middleware to ensure that `next` is called if it
23 * throws or returns a promise that rejects.
24 */
25export function handler<
26 Req extends IncomingMessage = IncomingMessage,
27 Res extends ServerResponse<Req> = ServerResponse<Req>,
28>(fn: Handler<Req, Res>): Middleware<Req, Res> {
29 return async (req, res, next) => {
30 try {
31 await fn(req, res)
32 } catch (err) {
33 next(err)
34 }
35 }
36}
37
38/**
39 * Create an HTTP server with the provided request listener, ensuring that it
40 * can bind the listening port, and returns a termination function that allows
41 * graceful termination of HTTP connections.
42 */
43export async function startServer(
44 requestListener: RequestListener,
45 {
46 port,
47 gracefulTerminationTimeout,
48 }: { port?: number; gracefulTerminationTimeout?: number } = {},
49) {
50 const server = createServer(requestListener)
51 const { terminate } = createHttpTerminator({
52 gracefulTerminationTimeout,
53 server,
54 })
55 server.listen(port)
56 await once(server, 'listening')
57 return { server, terminate }
58}