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// Helper function for defining routes 23export function handler< 24 Req extends IncomingMessage = IncomingMessage, 25 Res extends ServerResponse<Req> = ServerResponse<Req>, 26>(fn: Handler<Req, Res> | AsyncHandler<Req, Res>): Handler<Req, Res> { 27 return (req, res, next) => { 28 // NodeJS prefers objects over functions for garbage collection, 29 const nextSafe = nextOnce.bind({ next }) 30 try { 31 const result = fn(req, res, nextSafe) 32 if (result instanceof Promise) result.catch(nextSafe) 33 } catch (err) { 34 nextSafe(err) 35 } 36 } 37 38 function nextOnce(this: { next: NextFunction | null }, err?: unknown) { 39 const { next } = this 40 this.next = null 41 next?.(err) 42 } 43} 44 45export async function startServer( 46 requestListener: RequestListener, 47 { 48 port, 49 gracefulTerminationTimeout, 50 }: { port?: number; gracefulTerminationTimeout?: number } = {}, 51) { 52 const server = createServer(requestListener) 53 const { terminate } = createHttpTerminator({ 54 gracefulTerminationTimeout, 55 server, 56 }) 57 server.listen(port) 58 await once(server, 'listening') 59 return { server, terminate } 60}