@immera/vitals
v0.1.0
Published
Framework-agnostic /vitals health endpoint: one core, thin adapters for Node workers, Express, Next.js, Nuxt, plus a build-time vitals.json generator for static SPAs.
Maintainers
Readme
@immera/vitals
One /vitals endpoint for every kind of service: a framework-agnostic core that
answers "is this service online, and which build is it running?", plus thin
adapters for each transport. Works for HTTP-less workers (RabbitMQ consumers),
SSR sites (Next / Nuxt), classic Node/Express services and static SPAs — from a
single package, with zero runtime dependencies.
{
"status": "pass",
"service": "rabbitmq-worker",
"version": "1.4.2",
"commit": "9f2a1c7",
"environment": "production",
"timestamp": "2026-07-30T12:00:00.000Z",
"uptimeSeconds": 8123.4,
"node": "v22.17.0",
"memory": { "rss": 74448896, "heapTotal": 18219008, "heapUsed": 12030040, "unit": "bytes" },
"checks": { "rabbitmq": { "status": "pass", "durationMs": 12 } }
}The schema follows the IETF Health Check Response Format draft
(application/health+json, status: pass | warn | fail) with checks
simplified to a flat record. HTTP mapping: pass → 200, warn → 200
(degraded-but-serving; configurable), fail → 503, always with
Cache-Control: no-store.
Install
npm install @immera/vitalsRequires Node >= 22.11. Framework peers (express, next, vue, react,
h3) are optional — you only need the one whose subpath you import. A
worker that imports only @immera/vitals/node never pulls in any of them.
Core
import { createVitals } from "@immera/vitals";
const vitals = createVitals({ service: "rabbitmq-worker" });
vitals.registerCheck("rabbitmq", async ({ signal }) => {
await connection.ping({ signal });
});registerCheck(name, fn, opts?)—opts.critical(defaulttrue),opts.timeoutMs(default 5000),opts.kind("readiness"default, or"liveness"). A check that throws or times out becomesfail— it never rejects the snapshot. Return{ status: "warn", output: "..." }for partial degradation.snapshot()— runs checks in parallel, derives the overall status: any criticalfail→fail; otherwise anywarn(or non-criticalfail) →warn; otherwisepass.livenessSnapshot()— synchronous, runs no dependency checks; maps to Kubernetes/livez("the process is up").
Metadata resolution precedence: explicit config → environment
(IMMERA_VITALS_SERVICE/VERSION/COMMIT/ENV, npm_package_*, GIT_COMMIT,
VERCEL_GIT_COMMIT_SHA, GITHUB_SHA, NODE_ENV) → nearest package.json →
defaults. The core never statically imports Node builtins, so it also runs on
Next's edge runtime (runtime fields are simply omitted there).
Workers without HTTP (queue consumers, cron)
import { createVitals } from "@immera/vitals";
import { startVitalsServer } from "@immera/vitals/node";
const vitals = createVitals({ service: "rabbitmq-worker" });
vitals.registerCheck("rabbitmq", async () => { await conn.ping(); });
const { url, close } = await startVitalsServer({
vitals,
port: 9100, // default: IMMERA_VITALS_PORT, else ephemeral
livenessPath: "/livez", // optional k8s probes
readinessPath: "/readyz",
});
console.log(`vitals at ${url}`);Only node:http — zero dependencies. createVitalsHandler is also exported to
mount the same behaviour inside an existing server.
Express
import { vitalsMiddleware } from "@immera/vitals/express";
app.get("/vitals", vitalsMiddleware(vitals));Next.js
// App Router — app/vitals/route.ts
export const dynamic = "force-dynamic"; // keep the route out of static caching
export const { GET, HEAD } = createVitalsRoute(vitals);
// Pages Router — pages/api/vitals.ts
export default createVitalsApiHandler(vitals);Nuxt / Nitro
// server/routes/vitals.get.ts
export default createVitalsEventHandler(vitals); // from "@immera/vitals/nuxt"Static SPAs (Vue / React without a server)
There is no runtime server, so vitals are generated at build time and served as a static file:
npx @immera/vitals generate --out public/vitals.json --service web --env productionThe payload has the same schema (status is always pass, timestamp is the
build time, no uptime/memory/checks). runGenerate() from
@immera/vitals/generate exposes the same thing programmatically.
Dashboard badge
import { VitalsBadge } from "@immera/vitals/react"; // or "@immera/vitals/vue"
<VitalsBadge url="/vitals" pollMs={30000} /> // static SPAs: url="/vitals.json"Renders a colored status dot + label, polls the endpoint, and accepts a custom
render prop (React) / default slot (Vue).
Package layout
Single package, subpath exports, dual ESM + CJS with types per condition:
| Subpath | Purpose | Peer needed |
|---|---|---|
| @immera/vitals / ./core | createVitals, serializer, types | — |
| ./node | sidecar HTTP server / handler | — |
| ./express | middleware (type-only peer) | express at runtime by you |
| ./next | App + Pages handlers (type-only peer) | next at runtime by you |
| ./nuxt | h3 event handler | h3 |
| ./react / ./vue | <VitalsBadge /> | react / vue |
| ./generate | runGenerate() build-time vitals | — |
Development
npm run build # tsup: dual ESM/CJS + .d.ts/.d.cts per entry
npm run typecheck
npm test # vitest: core + every adapter + clients (jsdom)
npm run check:exports # publint + attw --pack + dist purity gate
npm run check:fixture # npm pack → real nodenext consumer: tsc + ESM/CJS/CLI smokeLicense
MIT © Immera
