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}