IDebug is experimental. Prefer enabling only in development.
Packages@idebug/db

@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/nest

Peer 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:

ORMAPI
PrismainstrumentPrisma(client)
TypeORMinstrumentTypeOrm(ds)
DrizzleinstrumentDrizzle(db)
KnexinstrumentKnex(knex)
MongooseinstrumentMongoose(mongoose)
SequelizeinstrumentSequelize(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
  • logModule controllers/services so queries nest under the caller
  • Raw traceQuery catalog 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/1

See 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
}