import type { IncomingMessage, ServerResponse } from "node:http"; import type { Session, Store } from "./store"; export class HttpError extends Error { constructor( readonly status: number, readonly code: string, message: string, readonly details?: unknown, ) { super(message); } } export type Ctx = { req: IncomingMessage; res: ServerResponse; url: URL; params: string[]; // Request bodies are checked field by field in each handler. // eslint-disable-next-line @typescript-eslint/no-explicit-any body: any; session: Session | null; store: Store; }; export type Route = { method: string; pattern: RegExp; /** public: anyone; session: signed in with an email code; step: also passed the second factor; patients: also accepted the terms (as requireClinician({ realPatients })). */ auth: "public" | "session" | "step" | "patients"; handler: (ctx: Ctx) => unknown; /** The backend answers 201 for creates and 202 for an access request. */ status: number; }; export const route = (method: string, path: string, auth: Route["auth"], handler: Route["handler"], status = 200): Route => ({ method, pattern: new RegExp(`^${path.replace(/:[a-zA-Z]+/g, "([^/]+)")}$`), auth, handler, status, }); export const text = (value: unknown, max = 500) => (typeof value === "string" ? value.trim().slice(0, max) : ""); export const need = (value: T | undefined | null, what: string): T => { if (value === undefined || value === null) throw new HttpError(404, "NOT_FOUND", `${what} was not found.`); return value; }; export const invalid = (message: string, details?: unknown) => new HttpError(400, "VALIDATION_ERROR", message, details); export const clinicianName = (store: Store) => store.clinician.fullName;