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 Handler<
13 Req extends IncomingMessage = IncomingMessage,
14 Res extends ServerResponse<Req> = ServerResponse<Req>,
15> = (req: Req, res: Res, next: NextFunction) => void
16
17export type AsyncHandler<
18 Req extends IncomingMessage = IncomingMessage,
19 Res extends ServerResponse<Req> = ServerResponse<Req>,
20> = (req: Req, res: Res, next: NextFunction) => Promise<void>
21
22/**
23 * Wraps a request handler middleware to ensure that `next` is called if it
24 * throws or returns a promise that rejects.
25 */
26export function handler<
27 Req extends IncomingMessage = IncomingMessage,
28 Res extends ServerResponse<Req> = ServerResponse<Req>,
29>(fn: Handler<Req, Res> | AsyncHandler<Req, Res>): Handler<Req, Res> {
30 return (req, res, next) => {
31 // Optimization: NodeJS prefers objects over functions for garbage collection
32 const nextSafe = nextOnce.bind({ next })
33 try {
34 const result = fn(req, res, nextSafe)
35 if (result instanceof Promise) result.catch(nextSafe)
36 } catch (err) {
37 nextSafe(err)
38 }
39 }
40
41 function nextOnce(this: { next: NextFunction | null }, err?: unknown) {
42 const { next } = this
43 this.next = null
44 next?.(err)
45 }
46}
47
48/**
49 * Create an HTTP server with the provided request listener, ensuring that it
50 * can bind the listening port, and returns a termination function that allows
51 * graceful termination of HTTP connections.
52 */
53export async function startServer(
54 requestListener: RequestListener,
55 {
56 port,
57 gracefulTerminationTimeout,
58 }: { port?: number; gracefulTerminationTimeout?: number } = {},
59) {
60 const server = createServer(requestListener)
61 const { terminate } = createHttpTerminator({
62 gracefulTerminationTimeout,
63 server,
64 })
65 server.listen(port)
66 await once(server, 'listening')
67 return { server, terminate }
68}