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}