import type { Context, MiddlewareHandler } from 'hono' /** Hono variables used to collect Server-Timing metrics. */ export type Variables = { /** Operations that have started but not finished. */ activeTimings?: Active[] | undefined /** Metrics emitted in the `Server-Timing` response header. */ serverTiming?: Metric[] | undefined } /** Hono environment shape used by timing helpers. */ export type Environment = { Variables: Variables } /** One Server-Timing metric. */ export type Metric = { /** Metric duration in milliseconds. */ duration: number /** Metric name. */ name: string } /** One operation that has started but not finished. */ export type Active = { /** Metric name. */ name: string /** Monotonic start time. */ start: number } /** Adds a `Server-Timing` header with total request duration. */ export function middleware< environment extends Environment = Environment, >(): MiddlewareHandler { return async (c, next) => { const active: Active[] = [] const metrics: Metric[] = [] c.set('activeTimings', active) c.set('serverTiming', metrics) const start = performance.now() await next() metrics.push({ duration: performance.now() - start, name: 'request' }) const value = format(metrics) const existing = c.res.headers.get('Server-Timing') c.res.headers.set('Server-Timing', existing ? `${existing}, ${value}` : value) } } /** Measures one async operation and adds it to the current request timings. */ export async function time( c: Context, name: string, fn: () => Promise | value, ) { const start = performance.now() const active = { name, start } c.get('activeTimings')?.push(active) try { return await fn() } finally { const activeTimings = c.get('activeTimings') if (activeTimings) activeTimings.splice(activeTimings.indexOf(active), 1) c.get('serverTiming')?.push({ duration: performance.now() - start, name }) } } function format(metrics: readonly Metric[]) { return metrics .map((metric) => `${name(metric.name)};dur=${metric.duration.toFixed(1)}`) .join(', ') } function name(value: string) { return value.replace(/[^A-Za-z0-9_.*-]/g, '_') }