Adapters

How brakit captures database queries from any library, and how to add your own.

Overview

Different projects use different database libraries: pg, mysql2, Prisma, and more. Brakit uses a plugin system called adapters to support each one. Each adapter is a single file that knows how to detect and instrument one library.

On startup, brakit checks which libraries are installed in your project and activates only the relevant adapters. If an adapter fails (maybe the library changed its API between versions), the others still work.

How It Works

Every adapter answers three questions:

  1. Is this library installed? Check if the package exists in your node_modules.
  2. How do I capture its queries? Patch the library's internal methods to record what's happening.
  3. How do I clean up? Optionally restore the original methods.

In code, that's the BrakitAdapter interface:

src/instrument/adapter.ts
interface BrakitAdapter {
  name: string;          // "pg", "mysql2", "prisma", etc.
  detect(): boolean;     // Is the library installed?
  patch(emit): void;     // Start capturing queries
  unpatch?(): void;      // Stop capturing (optional)
}

Current Adapters

AdapterLibraryWhat it patches
pgpg (PostgreSQL)Client.prototype.query: handles callback, promise, and EventEmitter patterns
mysql2mysql2Connection.prototype.query and .execute
prisma@prisma/clientInjects a client extension via $extends with $allModels.$allOperations

Normalization

Every adapter translates its library's output into a common format. Regardless of whether a query came from pg, mysql2, or Prisma, the analysis engine sees the same fields:

  • operation: SELECT, INSERT, UPDATE, DELETE, or OTHER
  • table: The table or model being queried
  • duration: How long it took, in milliseconds

This means the N+1 detector, security scanner, and all other analysis code never needs to know which database library you're using. It just sees normalized queries.

The request ID is captured before calling the database driver, not after. Database drivers use connection pools and native bindings that can break async context tracking. Capturing early ensures every query is correctly linked to its request.

Adding a New Adapter

Adding support for a new database library requires exactly one file implementing the BrakitAdapter interface. Here's the process:

  1. Create src/instrument/adapters/<library>.ts
  2. Register it in src/instrument/adapters/index.ts
  3. Mark the library as external in tsup.config.ts
  4. Run npm run build && npm test

For a detailed walkthrough with code examples, see the Contributing guide.

Key Rules for Adapters

  • Use tryRequire(): Resolve libraries from the user's project, not brakit's bundled dependencies.
  • Capture the request ID eagerly: Call captureRequestId() before any async operation to ensure correct correlation.
  • Always emit normalized fields: operation, table, and source must be present for analysis to work.
  • Wrap in try/catch: If patching fails, the adapter should fail silently. Other adapters must still load.
  • Handle all return patterns: Library methods may return callbacks, promises, or EventEmitters.