/**
* Response Helpers — standardized HTTP response utilities.
* Same pattern as hypernote.
*/
export function json (res, data, status = 200) {
res.statusCode = status
res.setHeader('Content-Type', 'application/json')
res.setHeader('X-Content-Type-Options', 'nosniff')
res.end(JSON.stringify(data))
}
export function html (res, content, status = 200) {
res.statusCode = status
res.setHeader('Content-Type', 'text/html')
res.setHeader('X-Frame-Options', 'DENY')
res.setHeader('X-Content-Type-Options', 'nosniff')
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
res.end(content)
}
export function text (res, content, status = 200) {
res.statusCode = status
res.setHeader('Content-Type', 'text/plain')
res.end(content)
}
export function error (res, message, status = 500) {
res.statusCode = status
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: message }))
}
export function notFound (res, message = 'Not found') {
error(res, message, 404)
}
export function badRequest (res, message = 'Bad request') {
error(res, message, 400)
}
export function unauthorized (res, message = 'Unauthorized') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Bearer realm="hyper-booking"')
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: message }))
}
const MAX_BODY_SIZE = 1024 * 1024 // 1MB
export async function readBody (req) {
let body = ''
let size = 0
for await (const chunk of req) {
size += chunk.length
if (size > MAX_BODY_SIZE) {
throw new Error('Request body too large (max 1MB)')
}
body += chunk.toString()
}
return body
}
export async function readJson (req) {
const body = await readBody(req)
if (!body) return {}
return JSON.parse(body)
}
export function escapeHtml (text) {
if (!text) return ''
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
export function redirect (res, url, status = 302) {
res.statusCode = status
res.setHeader('Location', url)
res.end()
}