Trovella Wiki

Connection Management

PostgreSQL connection pool configuration, import paths, and the Database/Transaction type exports.

The @repo/db package manages a single connection pool shared across the entire Node.js process. This page covers how the pool is configured, the import paths that control access, and the exported types.

Connection Pool

// packages/db/src/client.ts
const pool = new Pool({
  connectionString: process.env["DATABASE_URL"],
  max: 5,
});

export const db = drizzle(pool, { schema });

The pool uses node-postgres (pg.Pool) with a maximum of 5 connections. The DATABASE_URL is read from the environment:

EnvironmentURLSource
Local devpostgresql://trovella:trovella_dev@localhost:5433/trovella.env file
CISame format, Docker service container on port 5433GitHub Actions env
Productionpostgresql://trovella-app:<pw>@<ip>:5432/trovellaSecret Manager

The Drizzle instance is created with the full schema import so that the relational query API (db.query.*) works.

Import Paths

The package uses two export paths to enforce the tenant isolation rule:

@repo/db (default)

import { withTenantContext, checkDatabase } from "@repo/db";
import type { Database, Transaction } from "@repo/db";

This is the standard import for application code. It exports:

ExportPurpose
withTenantContextTenant-scoped transaction wrapper
checkDatabaseHealth check (SELECT 1)
createPAT, listUserPATs, revokePATUser-scoped PAT operations (encapsulate bare db internally)
Database (type)Type of the Drizzle instance
Transaction (type)Type of a Drizzle transaction
All schema exportsTables, relations, enums

The bare db is not exported from this path.

@repo/db/client

import { db } from "@repo/db/client";

This exports the raw Drizzle instance that connects as the superuser. Importing from this path is a signal that the code intentionally bypasses RLS. Legitimate uses:

  • Migration runner (packages/db/src/migrate.ts)
  • Seed scripts (packages/db/src/seed.ts)
  • Auth bootstrap (Better Auth internals)
  • Health checks (already wrapped by checkDatabase)
  • PAT queries (user-scoped, not tenant-scoped -- encapsulated in @repo/db)

@repo/db/schema

import { researchPlan, planStep, member } from "@repo/db/schema";

Pure schema definitions -- table objects, relation definitions, enum types. No runtime database access. Useful for type imports and building queries.

Type Exports

// packages/db/src/client.ts
export type Database = typeof db;
export type Transaction = Parameters<Parameters<typeof db.transaction>[0]>[0];

Use Database as the parameter type when writing helper functions that accept a database connection or transaction:

import type { Database } from "@repo/db";

export async function handleCreateResearchPlan(
  tx: Database,
  params: CreateResearchPlanParams,
  organizationId: string,
  userId: string,
): Promise<{ planId: string; stepIds: string[] }> {
  // tx works for both db and transaction objects
}

The Transaction type is the raw Drizzle transaction type, which is a subset of Database. In practice, Database is used everywhere because withTenantContext casts the transaction to typeof db so the full API is available.

Health Check

import { checkDatabase } from "@repo/db";

const { ok, latencyMs } = await checkDatabase();

checkDatabase runs SELECT 1 against the pool and returns the result with timing. It uses the bare db internally (no tenant context needed for a connectivity check).

Environment Variable Loading

Scripts in packages/db/ (migrate, seed, generate) load the root .env via tsx --env-file=../../apps/web/.env. The Next.js application loads .env automatically. Production environments inject DATABASE_URL via Secret Manager at deploy time.

On this page