Monitoring & Hooks
Queue stats, per-task metrics, job progress, and worker heartbeats.
Queue stats, per-task metrics, job progress, and worker heartbeats.
Read live queue health straight from the Queue handle — no extra service. The
same data powers the dashboard and the
CLI read commands. These scan-heavy methods run
off the JS event loop on a background thread pool and return a Promise.
Counts by status, globally or per queue:
await queue.stats(); // { pending, running, completed, failed, dead, cancelled }
await queue.statsByQueue("default");
await queue.statsAllQueues(); // Record<queue, stats>getMetrics(sinceMs, task?) returns the raw per-execution metric rows
(taskName, jobId, wallTimeNs, memoryBytes, succeeded, recordedAt)
recorded at or after sinceMs (a Unix-ms timestamp, not a duration) — one
entry per finished job, not a pre-aggregated summary:
await queue.getMetrics(Date.now() - 3_600_000); // last hour, all tasks
await queue.getMetrics(Date.now() - 3_600_000, "add"); // one taskAggregate them yourself (counts, success/failure split, p50/p95/p99 latency
percentiles) — the dashboard does exactly
this over /api/metrics and /api/metrics/timeseries.
A running task reports progress through its job context; inspection and the dashboard surface it live:
import { currentJob } from "@byteveda/flexiq";
queue.task("import", async (rows: Row[]) => {
const job = currentJob();
for (let i = 0; i < rows.length; i++) {
await importRow(rows[i]);
job?.setProgress(Math.round(((i + 1) / rows.length) * 100));
}
});Each running worker registers and heartbeats every 5 seconds; list the live fleet:
await queue.listWorkers();
// [{ workerId, hostname, pid, queues, status, lastHeartbeat, threads, ... }]A worker appears on the dashboard Workers panel while it heartbeats; stop()
unregisters it. See Workers for the worker
lifecycle.
checkHealth() and checkReadiness(queue) are standalone — no dashboard, no
HTTP server. Wire them into whatever already serves your probes, or call them
from a script:
import { checkHealth, checkReadiness } from "@byteveda/flexiq";
checkHealth(); // { status: "ok" } — liveness, never touches storage
const report = await checkReadiness(queue);
// {
// status: "ready" | "degraded",
// checks: {
// storage: "ok",
// workers: { count: 2, status: "ok" },
// resources: { count: 1, unhealthy: [], status: "ok" },
// },
// }checkReadiness never throws: a dependency that fails is reported as
"error: <message>" in checks and degrades the overall status, so a probe
endpoint can always answer. The resources check is omitted when no worker
advertises a worker resource; when one does, it is
built from resourceStatus(queue), which aggregates per-resource health across
every live worker's heartbeat.
Serve them behind your own routes — return 503 on degraded so orchestrators
drain the instance:
app.get("/healthz", (_req, res) => res.json(checkHealth()));
app.get("/readyz", async (_req, res) => {
const report = await checkReadiness(queue);
res.status(report.status === "ready" ? 200 : 503).json(report);
});The dashboard and the
Express / Fastify
helpers serve these same helpers on /health and /readiness. The
KEDA scaler server carries liveness only —
/health, alongside its /api/scaler metric.
For metric scraping and distributed traces, the contrib integrations wrap the events layer:
/metrics endpoint.Lifecycle events and middleware hooks let you push the same signals to any backend.