@idebug/db
Database query monitor. Every API request carries the SQL (or Mongo command) that ran during it — what was queried, how long it took, and which wrapped function issued it.
Queries are dbQuery TraceEvents with parentName + executionOrder from the same ALS call stack that logFunction / @Loggable already maintain. They show up inside the call tree:
GET /users
└─ UsersController.list
└─ UsersService.list
└─ prisma.User.findMany 8ms
SELECT * FROM "User" …not as a separate “DB” panel next to the request.
Install
pnpm add @idebug/db
# or it is already pulled in by @idebug/node / @idebug/nestPeer ORMs are optional — only install the ones your app uses:
Prisma, TypeORM, Drizzle, Knex, Mongoose, Sequelize.
Instrument a client (primary API)
Wrap the instance you actually call. Assign the return value for Prisma ($extends returns a new client).
import { PrismaClient } from '@prisma/client';
import { instrumentPrisma } from '@idebug/db';
// or from `@idebug/node` / `@idebug/nest`
const prisma = instrumentPrisma(new PrismaClient());Same pattern for the others:
| ORM | API |
|---|---|
| Prisma | instrumentPrisma(client) |
| TypeORM | instrumentTypeOrm(ds) |
| Drizzle | instrumentDrizzle(db) |
| Knex | instrumentKnex(knex) |
| Mongoose | instrumentMongoose(mongoose) |
| Sequelize | instrumentSequelize(seq) |
Or one shot:
import { instrumentDatabase } from '@idebug/db';
instrumentDatabase({
prisma,
typeorm: dataSource,
drizzle: db,
knex,
mongoose,
sequelize,
});Call-tree attribution
Wrap the functions that call the ORM (logFunction / logModule / @Loggable). Then:
export const users = logModule(
{
async list() {
return prisma.user.findMany(); // recorded under `list`
},
},
'users',
);Without wrappers, queries still attach to the request session (ALS); they just sit at the root of the tree.
SDK config
Enabled automatically when IDebug is enabled:
IDebug.configure({
enabled: true,
db: true, // default
});
// IDebugModule.forRoot({ enabled: true, db: { captureParams: true } })Pass db: false to turn the monitor off. Options:
db: {
enabled?: boolean
captureParams?: boolean // default true (still redacted)
maxQueryLength?: number // default 8192
orms?: Array<'prisma' | 'typeorm' | 'drizzle' | 'knex' | 'mongoose' | 'sequelize'>
}installDatabaseMonitor() (called from configure) is best-effort auto-patch of CJS constructors already loaded from process.cwd(). ESM apps should always wrap instances explicitly.
Custom query layers
import { captureQuery, traceQuery } from '@idebug/db';
const rows = await traceQuery(
{ orm: 'unknown', query: sql, params, target: 'orders' },
() => pool.query(sql, params),
);Runnable sample
samples/db-api (sample-db-api, :3003) is a full Express shop demo:
- Drizzle (users + posts) and Knex (orders) on in-memory SQLite
logModulecontrollers/services so queries nest under the caller- Raw
traceQuerycatalog ping - N+1, slow function vs query time, failed SQL, password redaction
pnpm build:packages
pnpm --filter sample-db-api start
# http://localhost:3003/__idebug
curl -s http://127.0.0.1:3003/users/1See the sample README and Database queries.
Event payload (dbQuery)
{
name: 'prisma.User.findMany',
orm: 'prisma',
query: 'SELECT …',
params: unknown[] | Record<string, unknown> | null,
target: 'User',
operation: 'findMany',
executionTimeMs: 8.1,
rowCount: 2,
error: null | { name?, message, stack? },
parentName: 'UsersService.list',
executionOrder: ['UsersController.list', 'UsersService.list', 'prisma.User.findMany'],
database: 'app' | null
}