@mantleframework/database
Multi-provider Drizzle ORM client for AWS Lambda with connection pooling, observability, and automatic OCC retry.
Supported Providers
| Provider | Use Case |
|---|---|
aurora-dsql | AWS Aurora DSQL (serverless, zero-management) |
aurora-serverless-v2 | AWS Aurora Serverless v2 (Data API) |
neon | Neon Postgres (serverless, branching) |
Getting a Client
getDrizzleClient(config)
Get or create a Drizzle client. Caches the client for Lambda container reuse.
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.
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.
import { withQueryMetrics } from '@mantleframework/database'
const users = await withQueryMetrics('Users.getActive', async () => {
return db.select().from(usersTable).where(eq(usersTable.active, true))
})Signature:
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. Passfalseto disable, or aDsqlRetryOptionsobject 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.
import { withDsqlRetry } from '@mantleframework/database'
const result = await withDsqlRetry(
() => db.select().from(users).where(eq(users.id, id)),
{ maxRetries: 5, baseDelayMs: 50 },
)DsqlRetryOptions
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.
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
enum DatabaseOperation {
Select = 'SELECT'
Insert = 'INSERT'
Update = 'UPDATE'
Delete = 'DELETE'
All = 'ALL'
}TablePermission
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.
import { runMigrations } from '@mantleframework/database'
const result = await runMigrations({
migrationsFolder: './drizzle',
database: { provider: 'aurora-dsql', endpoint: '...', region: '...' },
logger: console.log,
})
// result.applied, result.migrationsclassifyStatement(stmt)
Classify a SQL statement for DSQL compatibility. Returns { type, description, table? } where type is 'compatible', 'create_index', 'needs_recreation', or 'unsupported_strip'.
MigrateConfig
interface MigrateConfig {
migrationsFolder: string
database: DatabaseConfig
migrationsTable?: string // default: '__drizzle_migrations'
migrationsSchema?: string // default: 'drizzle'
useLock?: boolean // default: true
logger?: (msg: string) => void
}MigrateResult
interface MigrateResult {
applied: number
migrations: Array<{ tag: string; hash: string; durationMs: number }>
}MigrationFile
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.
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.errorsPermissionsConfig
interface PermissionsConfig {
permissionsFolder: string
database: DatabaseConfig
logger?: (msg: string) => void
}PermissionsResult
interface PermissionsResult {
applied: number
skipped: number
errors: string[]
}Types
DatabaseConfig
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
type DatabaseProvider = 'aurora-dsql' | 'aurora-serverless-v2' | 'neon'TransactionClient
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
import { eq, and, or, gt, gte, lt, lte, ne, sql, count, asc, desc, inArray, notInArray, isNull, isNotNull } from '@mantleframework/database/orm'Type Helpers
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'>