Files
Shade/packages/shade-server/src/health.ts
Sterister 1bd5436506 feat(hardening): M-Hard 6 + 7 — PostgreSQL backend + production server infra
M-Hard 6: PostgreSQL Storage Backend
- @shade/storage-postgres with PostgresStorage + PostgresPrekeyStore
- Drizzle-style raw SQL ensureClientTables / ensurePrekeyServerTables
- All tables prefixed `shade_` to avoid collisions in shared databases
- DELETE ... FOR UPDATE SKIP LOCKED for concurrent OTPK consumption
- Tests skip gracefully without SHADE_TEST_PG_URL, run against real PG when set

M-Hard 7: Production Server Infrastructure
- Structured JSON logger (logger.ts) — SHADE_LOG_LEVEL configurable
- Health endpoints (/health, /healthz, /ready) — Kubernetes-friendly
- Prometheus metrics (/metrics) — counters, histograms, middleware
- Graceful shutdown with SIGTERM/SIGINT handlers + store close
- Production Dockerfile with non-root user, healthcheck, multi-stage build
- docker-compose.yml example for Dokploy with optional PostgreSQL

193 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:51:29 +02:00

56 lines
1.2 KiB
TypeScript

import { Hono } from 'hono';
import type { PrekeyStore } from './store.js';
/**
* Health check endpoint for production deployments.
*
* GET /health → 200 if storage backend reachable, 503 otherwise
*/
export function createHealthRoutes(store: PrekeyStore, version: string): Hono {
const startTime = Date.now();
const app = new Hono();
app.get('/health', async (c) => {
try {
// Probe the store with a no-op operation
await store.getOneTimePreKeyCount('__health__');
return c.json({
status: 'ok',
version,
uptimeSeconds: Math.floor((Date.now() - startTime) / 1000),
});
} catch (err) {
return c.json(
{
status: 'error',
version,
error: err instanceof Error ? err.message : String(err),
},
503,
);
}
});
// Kubernetes-style probes
app.get('/healthz', async (c) => {
try {
await store.getOneTimePreKeyCount('__health__');
return c.text('ok');
} catch {
return c.text('error', 503);
}
});
app.get('/ready', async (c) => {
try {
await store.getOneTimePreKeyCount('__health__');
return c.text('ready');
} catch {
return c.text('not ready', 503);
}
});
return app;
}