Budgets, not vibes
Flag database work that exceeds a latency threshold so regressions surface before users feel them.
0.x
queryd wraps the SQL your app actually runs — tagged templates (e.g. postgres.js), string SQL
(wrapQueryFn), or Prisma via @olegkoval/queryd/prisma — with per-query latency
thresholds, optional per-requestId query budgets, sampling, optional
EXPLAIN ANALYZE, and pluggable sinks.
Flag database work that exceeds a latency threshold so regressions surface before users feel them.
wrapTaggedTemplate and wrapQueryFn instrument real clients; Prisma lives on
@olegkoval/queryd/prisma so the base package stays lean.
Sampling-first design so observability stays cheap enough to leave on in production.
Optional EXPLAIN pipeline to inspect the slowest offenders when you need depth, not noise.
import { PrismaClient } from "@prisma/client";
import {
createSlowQueryDetector,
createConsoleLogger,
runWithDbContext,
} from "@olegkoval/queryd";
import { wrapPrismaClient } from "@olegkoval/queryd/prisma";
const base = new PrismaClient();
const detector = createSlowQueryDetector(
{
warnThresholdMs: 200,
dbName: "primary",
requestBudget: { maxQueries: 80, maxTotalDurationMs: 2000 },
},
{ logger: createConsoleLogger() },
);
export const prisma = wrapPrismaClient(base, detector);
await runWithDbContext({ requestId: "req-1", userId: "u-1" }, async () => {
await prisma.$queryRaw`SELECT 1`;
});
import postgres from "postgres";
import {
createSlowQueryDetector,
wrapTaggedTemplate,
createConsoleLogger,
runWithDbContext,
} from "@olegkoval/queryd";
const sql = postgres(process.env.DATABASE_URL!);
const detector = createSlowQueryDetector(
{
warnThresholdMs: 200,
dbName: "primary",
requestBudget: { maxQueries: 80, maxTotalDurationMs: 2000 },
},
{ logger: createConsoleLogger() },
);
const instrumentedSql = wrapTaggedTemplate(sql, detector);
await runWithDbContext({ requestId: "req-abc" }, async () => {
await instrumentedSql`select ${1}::int`;
});
import {
createSlowQueryDetector,
wrapQueryFn,
createConsoleLogger,
runWithDbContext,
} from "@olegkoval/queryd";
const rawQuery = async (sql: string, params: unknown[]) => {
/* your client */
return [];
};
const detector = createSlowQueryDetector(
{ warnThresholdMs: 200, requestBudget: { maxQueries: 50 } },
{ logger: createConsoleLogger() },
);
const q = wrapQueryFn(rawQuery, detector);
await runWithDbContext({ requestId: "job-7" }, async () => {
await q("select 1", []);
});
Request budgets: LRU eviction resets per-requestId counters (same id string can violate again after re-insertion).
createSlowQueryDetector may push onto your sinks array — see README.
MIT licensed · oleg-koval/slow-query-detector · package name on npm is @olegkoval/queryd