Skip to content

@mantleframework/database

Multi-provider Drizzle ORM client for AWS Lambda with connection pooling, observability, and automatic OCC retry.

Supported Providers

ProviderUse Case
aurora-dsqlAWS Aurora DSQL (serverless, zero-management)
aurora-serverless-v2AWS Aurora Serverless v2 (Data API)
neonNeon Postgres (serverless, branching)

Getting a Client

getDrizzleClient(config)

Get or create a Drizzle client. Caches the client for Lambda container reuse.

typescript
import { getDrizzleClient } from '@mantleframework/database'
import { getRequiredEnv } from '@mantleframework/env'

// Aurora DSQL
const db = await getDrizzleClient({
  provider: 'aurora-dsql',
  endpoint: getRequiredEnv('DSQL_ENDPOINT'),
  region: getRequiredEnv('AWS_REGION'),
})

// Aurora Serverless v2
const db = await getDrizzleClient({
  provider: 'aurora-serverless-v2',
  connectionString: getRequiredEnv('DATABASE_URL'),
})

// Neon
const db = await getDrizzleClient({
  provider: 'neon',
  connectionString: getRequiredEnv('DATABASE_URL'),
})

closeDrizzleClient(config?)

Close a cached database client. Call during graceful shutdown. Pass the same config used to create the client, or omit to close the default client.

Transactions

withTransaction(db, fn)

Execute a function within a database transaction. Rolls back automatically on error.

typescript
import { withTransaction } from '@mantleframework/database'

await withTransaction(db, async (tx) => {
  await tx.insert(users).values({ name: 'Alice' })
  await tx.insert(audit).values({ action: 'user_created' })
})

Query Instrumentation

withQueryMetrics(queryName, fn, options?)

Wrap a database query with automatic CloudWatch metrics, X-Ray tracing, structured error logging, and OCC retry.

typescript
import { withQueryMetrics } from '@mantleframework/database'

const users = await withQueryMetrics('Users.getActive', async () => {
  return db.select().from(usersTable).where(eq(usersTable.active, true))
})

Signature:

typescript
function withQueryMetrics<T>(
  queryName: string,
  fn: () => Promise<T>,
  options?: { logInput?: unknown; retry?: DsqlRetryOptions | false }
): Promise<T>
  • queryName -- used as the CloudWatch dimension and X-Ray span name.
  • logInput -- when provided, logs query parameters at DEBUG level (sanitized).
  • retry -- OCC retry is enabled by default. Pass false to disable, or a DsqlRetryOptions object to customise.

Throws DatabaseError when the underlying query fails.

onConnectionInvalidated(callback)

Register a callback for connection invalidation events (useful for Aurora DSQL token refresh).

OCC Retry

withDsqlRetry(fn, options?)

Execute an async function with retry logic for Aurora DSQL OCC conflict errors (40001, OC000, OC001). Non-OCC errors are re-thrown immediately.

withQueryMetrics uses this internally by default -- you only need withDsqlRetry directly for standalone database calls outside entity queries.

typescript
import { withDsqlRetry } from '@mantleframework/database'

const result = await withDsqlRetry(
  () => db.select().from(users).where(eq(users.id, id)),
  { maxRetries: 5, baseDelayMs: 50 },
)

DsqlRetryOptions

typescript
interface DsqlRetryOptions {
  maxRetries?: number    // default: 3
  baseDelayMs?: number   // default: 100
  maxDelayMs?: number    // default: 5000
  onRetry?: (error: Error, attempt: number) => void
}

Entity Decorators

@RequiresTable

Stage 3 method decorator that declares table-level permissions on static entity query methods. Consumed by mantle generate permissions to auto-generate per-Lambda PostgreSQL roles.

typescript
import { RequiresTable, DatabaseOperation, withQueryMetrics } from '@mantleframework/database'
import { eq } from '@mantleframework/database/orm'

class UserQueries {
  @RequiresTable([{ table: 'users', operations: [DatabaseOperation.Select] }])
  static async findById(db: DrizzleClient, id: string) {
    return withQueryMetrics('Users.findById', async () => {
      return db.select().from(users).where(eq(users.id, id))
    })
  }
}

export const findById = UserQueries.findById.bind(UserQueries)

getTablePermissions(target)

Retrieve the TablePermission[] array attached by @RequiresTable decorators on a class. Used by the CLI permission extractor.

DatabaseOperation

typescript
enum DatabaseOperation {
  Select = 'SELECT'
  Insert = 'INSERT'
  Update = 'UPDATE'
  Delete = 'DELETE'
  All = 'ALL'
}

TablePermission

typescript
interface TablePermission {
  table: string
  operations: DatabaseOperation[]
}

Migrations

readMigrationFiles(folder)

Read and parse all migration SQL files from a Drizzle migrations folder. Returns MigrationFile[].

runMigrations(config)

Run all pending migrations against the configured database provider. Handles locking, tracking, and DSQL-specific statement sanitization.

typescript
import { runMigrations } from '@mantleframework/database'

const result = await runMigrations({
  migrationsFolder: './drizzle',
  database: { provider: 'aurora-dsql', endpoint: '...', region: '...' },
  logger: console.log,
})
// result.applied, result.migrations

classifyStatement(stmt)

Classify a SQL statement for DSQL compatibility. Returns { type, description, table? } where type is 'compatible', 'create_index', 'needs_recreation', or 'unsupported_strip'.

MigrateConfig

typescript
interface MigrateConfig {
  migrationsFolder: string
  database: DatabaseConfig
  migrationsTable?: string   // default: '__drizzle_migrations'
  migrationsSchema?: string  // default: 'drizzle'
  useLock?: boolean          // default: true
  logger?: (msg: string) => void
}

MigrateResult

typescript
interface MigrateResult {
  applied: number
  migrations: Array<{ tag: string; hash: string; durationMs: number }>
}

MigrationFile

typescript
interface MigrationFile {
  tag: string         // e.g. '0001_my_migration'
  hash: string        // SHA-256 of raw SQL content
  sql: string         // full raw SQL
  statements: string[] // split on Drizzle breakpoint marker
}

Permissions

applyPermissions(config)

Apply SQL permission files to the database using an admin connection. Handles env var substitution and idempotent error skipping.

typescript
import { applyPermissions } from '@mantleframework/database'

const result = await applyPermissions({
  permissionsFolder: './infra/permissions',
  database: { provider: 'aurora-dsql', endpoint: '...', region: '...' },
  logger: console.log,
})
// result.applied, result.skipped, result.errors

PermissionsConfig

typescript
interface PermissionsConfig {
  permissionsFolder: string
  database: DatabaseConfig
  logger?: (msg: string) => void
}

PermissionsResult

typescript
interface PermissionsResult {
  applied: number
  skipped: number
  errors: string[]
}

Types

DatabaseConfig

typescript
interface DatabaseConfig {
  provider: DatabaseProvider
  connectionString?: string  // for Neon or Aurora Serverless v2
  endpoint?: string          // Aurora DSQL cluster endpoint
  region?: string            // AWS region for IAM signing
  username?: string          // database username/role name
  isAdmin?: boolean          // use admin token for DSQL
  schema?: Record<string, unknown>  // Drizzle schema object
  maxConnections?: number    // default: 1 (Lambda-optimized)
  idleTimeout?: number       // seconds
  connectTimeout?: number    // seconds
  instrument?: boolean       // emit metrics during connect (default: true)
}

DatabaseProvider

typescript
type DatabaseProvider = 'aurora-dsql' | 'aurora-serverless-v2' | 'neon'

TransactionClient

typescript
type TransactionClient<TSchema = Record<string, unknown>> = PgTransaction<...>

Drizzle transaction client type for PostgreSQL providers. Generic over the schema type.

Subpath: @mantleframework/database/orm

Re-exported Drizzle ORM operators and types for entity query building. Use this instead of importing drizzle-orm directly to avoid dual-instance type conflicts with linked packages.

Query Operators

typescript
import { eq, and, or, gt, gte, lt, lte, ne, sql, count, asc, desc, inArray, notInArray, isNull, isNotNull } from '@mantleframework/database/orm'

Type Helpers

typescript
import type { SelectModel, InsertModel, UpdateModel, InferSelectModel, InferInsertModel } from '@mantleframework/database/orm'

// Infer the select (read) model from a table
type UserRow = SelectModel<typeof usersTable>

// Infer the insert model from a table
type NewUser = InsertModel<typeof usersTable>

// Partial update type, excluding primary key and timestamps
type UserUpdate = UpdateModel<typeof usersTable, 'id' | 'createdAt'>