Prisma Driver Adapter Implementation
PrismaOfficialCreated by prisma
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This guide provides the necessary technical contract and implementation patterns for developers creating or modifying SQL driver adapters for Prisma ORM 7. It covers the required interfaces, transaction lifecycle management, result mapping, error handling, and shadow database integration. The documentation emphasizes maintaining protocol boundaries to prevent connection leaks, value corruption, and transaction state errors, ensuring that custom adapters integrate seamlessly with the Prisma engine.
What it does
- Defines the required interfaces for SqlDriverAdapter and Transaction objects.
- Provides patterns for mapping driver-specific results and metadata to Prisma types.
- Outlines the mandatory lifecycle for transaction management and connection handling.
- Details the implementation of savepoint hooks for nested transaction support.
- Specifies error mapping requirements to preserve original database error codes.
- Establishes rules for shadow database isolation and resource disposal.
When to use it
- When implementing a new database driver adapter for Prisma ORM.
- When modifying existing adapter logic for transactions or query execution.
- When adding support for nested transactions or savepoints.
- When debugging transaction leaks or incorrect query result mapping.
- When implementing migration-aware adapters for shadow database support.
Advantages
- Ensures type-safe communication between the database driver and Prisma.
- Prevents common issues like connection leaks and silent value corruption.
- Standardizes error reporting to improve debugging via P2039 fallbacks.
- Provides a clear contract for stable transaction and savepoint behavior.
- Promotes consistent resource management across different database providers.
Usage examples
- “How do I implement the startTransaction method in a custom Prisma driver adapter?”
- The guide provides a code pattern showing how to acquire a dedicated connection from a pool, execute the BEGIN command, set the isolation level, and return a Transaction object.
- “What is the correct way to map database errors in a Prisma adapter?”
- The guide instructs to wrap driver-specific errors in a DriverAdapterError, ensuring that original error codes and messages are preserved for better diagnostic visibility.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/prisma/prisma-driver-adapter-implementation/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip prisma-driver-adapter-implementation.zip -d .agents/skills
Documentation by prisma
Prisma SQL Driver Adapter Implementation
Use this guide with the exact @prisma/driver-adapter-utils version installed by the target Prisma release. Driver adapters are a protocol boundary: type-compatible code can still corrupt values, leak connections, or break transactions.
When to Apply
Implementing
SqlDriverAdapterFactory,SqlMigrationAwareDriverAdapterFactory,SqlDriverAdapter, orTransactionAdding nested-transaction/savepoint support
Mapping driver values, column metadata, bind arguments, or database errors
Debugging
P2039, transaction leaks, shadow-database failures, or adapter-specific query behavior
Contract snapshot
interface SqlDriverAdapterFactory extends AdapterInfo {
connect(): Promise<SqlDriverAdapter>
}
interface SqlMigrationAwareDriverAdapterFactory extends SqlDriverAdapterFactory {
connectToShadowDb(): Promise<SqlDriverAdapter>
}
interface SqlDriverAdapter extends AdapterInfo {
queryRaw(query: SqlQuery): Promise<SqlResultSet>
executeRaw(query: SqlQuery): Promise<number>
executeScript(script: string): Promise<void>
startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>
getConnectionInfo?(): ConnectionInfo
dispose(): Promise<void>
}
interface Transaction extends AdapterInfo {
readonly options: { usePhantomQuery: boolean }
queryRaw(query: SqlQuery): Promise<SqlResultSet>
executeRaw(query: SqlQuery): Promise<number>
commit(): Promise<void>
rollback(): Promise<void>
createSavepoint?(name: string): Promise<void>
rollbackToSavepoint?(name: string): Promise<void>
releaseSavepoint?(name: string): Promise<void>
}IsolationLevel currently includes READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SNAPSHOT, and SERIALIZABLE; validate what the concrete database supports.
Priority rules
| Priority | Rule | Impact |
|---|---|---|
| CRITICAL | One dedicated connection per transaction | Prevents interleaving and leaks |
| CRITICAL | commit/rollback are lifecycle cleanup hooks | Prevents duplicate COMMIT/ROLLBACK |
| CRITICAL | Savepoints live on Transaction, not adapter-global depth | Makes nested scopes connection-local |
| CRITICAL | Preserve original database error code/message | Enables useful P2039 fallback |
| HIGH | Map arguments and result metadata exactly | Prevents silent value corruption |
| HIGH | Shadow databases are isolated and always cleaned up | Makes Migrate safe |
| HIGH | Dispose only resources the adapter owns | Prevents shutting down caller-owned pools |
Query implementation
SqlQuery contains sql, args, and parallel argTypes. Map each argument using both value and ArgType; do not discard type/arity information. Execute in the driver's array/tuple row mode so column order is stable.
class ExampleQueryable {
readonly provider = 'postgres' as const
readonly adapterName = '@acme/adapter-example'
constructor(protected readonly connection: DriverConnection) {}
async queryRaw(query: SqlQuery): Promise<SqlResultSet> {
try {
const result = await this.connection.query({
text: query.sql,
values: query.args.map((value, index) =>
mapArg(value, query.argTypes[index]),
),
rowMode: 'array',
})
return {
columnNames: result.fields.map((field) => field.name),
columnTypes: result.fields.map(mapColumnType),
rows: result.rows,
}
} catch (error) {
throwAdapterError(error)
}
}
async executeRaw(query: SqlQuery): Promise<number> {
try {
const result = await this.connection.execute(
query.sql,
query.args.map((value, index) => mapArg(value, query.argTypes[index])),
)
return result.rowsAffected ?? 0
} catch (error) {
throwAdapterError(error)
}
}
}Result mapping
Return columnNames, columnTypes, and rows with identical lengths/order. Map driver metadata to ColumnTypeEnum deliberately:
signed integer widths to
Int32/Int64; preserve 64-bit values without JS number truncationdecimal/numeric to
Numericusing the representation expected by Prismabinary to
Uint8Array/Bytesdate-only, time-only, and timestamp to
Date,Time, andDateTimeUUID, JSON, enum, arrays, and provider-specific unknown values to their explicit types
unsupported native types to
DriverAdapterError({ kind: 'UnsupportedNativeDataType', type })
Test null, empty arrays, array element types, big integers, decimals, byte arrays, JSON, dates, and user-defined/unknown native types.
Script execution
executeScript must execute a migration script as the provider expects. Prefer the driver's native multi-statement/script facility or a real SQL parser. Naively splitting on ; breaks functions, triggers, quoted strings, and dialect-specific blocks.
Transaction protocol
startTransaction must acquire one dedicated connection, start the database transaction, apply the requested isolation level, and return a Transaction bound to that same connection. If setup fails, release it immediately.
async startTransaction(level?: IsolationLevel): Promise<Transaction> {
const connection = await this.pool.acquire()
try {
const tx = new ExampleTransaction(connection, () => connection.release())
await tx.executeRaw({ sql: 'BEGIN', args: [], argTypes: [] })
if (level) {
await tx.executeRaw({
sql: `SET TRANSACTION ISOLATION LEVEL ${validateLevel(level)}`,
args: [],
argTypes: [],
})
}
return tx
} catch (error) {
connection.release(error)
throwAdapterError(error)
}
}Commit and rollback
Prisma coordinates the SQL COMMIT/ROLLBACK through executeRaw. The transaction object's commit() and rollback() methods are lifecycle hooks: detach listeners and release the dedicated connection exactly once. They must not issue a second SQL commit/rollback.
class ExampleTransaction extends ExampleQueryable implements Transaction {
readonly options = { usePhantomQuery: false }
#closed = false
constructor(connection: DriverConnection, private readonly release: () => void) {
super(connection)
}
async commit() { this.finish() }
async rollback() { this.finish() }
private finish() {
if (this.#closed) return
this.#closed = true
this.release()
}
async createSavepoint(name: string) {
await this.control(`SAVEPOINT ${safeSavepoint(name)}`)
}
async rollbackToSavepoint(name: string) {
await this.control(`ROLLBACK TO SAVEPOINT ${safeSavepoint(name)}`)
}
async releaseSavepoint(name: string) {
await this.control(`RELEASE SAVEPOINT ${safeSavepoint(name)}`)
}
private async control(sql: string) {
await this.executeRaw({ sql, args: [], argTypes: [] })
}
}Implement the optional savepoint methods only where the provider supports them. Validate/quote savepoint identifiers. For providers whose savepoints are intentionally no-ops, document and test that limitation.
Never keep transaction depth on the shared adapter. Parallel transactions make adapter-global depth incorrect; nested state belongs to the returned transaction connection and Prisma's savepoint calls.
Error mapping
Wrap recognized driver failures in DriverAdapterError. Map known conditions to MappedError kinds such as constraint violations, authentication/reachability, missing table/column/database, timeouts, closed transactions, invalid input, value range, and write conflicts.
For database errors, preserve originalCode and originalMessage even when falling back to the provider-specific raw variant:
import {
DriverAdapterError,
type Error as DriverAdapterErrorObject,
type MappedError,
} from '@prisma/driver-adapter-utils'
function convertDriverError(error: DatabaseError): DriverAdapterErrorObject {
return {
originalCode: String(error.code),
originalMessage: error.message,
...mapKnownOrRaw(error),
}
}
function mapKnownOrRaw(error: DatabaseError): MappedError {
if (error.code === '23505') {
return { kind: 'UniqueConstraintViolation', constraint: parsedConstraint(error) }
}
return {
kind: 'postgres',
code: String(error.code ?? 'N/A'),
severity: error.severity ?? 'N/A',
message: error.message,
detail: error.detail,
column: error.column,
hint: error.hint,
}
}
function throwAdapterError(error: unknown): never {
if (!isDatabaseError(error)) throw error
throw new DriverAdapterError(convertDriverError(error))
}Prisma uses preserved original details when an unmapped driver error becomes P2039. Do not replace every unknown exception with a fabricated GenericJs id; rethrow genuinely unexpected non-driver errors so programming bugs remain visible.
Factory, ownership, and shadow database
connect()returns a fresh usable adapter connection/pool wrapper.Track whether the factory created the pool.
dispose()closes owned pools and only detaches listeners from caller-owned pools unless an explicit option transfers ownership.Implement
SqlMigrationAwareDriverAdapterFactoryonly whenconnectToShadowDb()can create an isolated shadow database, connect to it, and drop it during disposal/failure cleanup.Never point the shadow adapter at the primary database. Quote generated identifiers and use cryptographically unique names.
getConnectionInfo()should accurately reportschemaName,maxBindValueswhen applicable, andsupportsRelationJoins.
Verification checklist
[ ] Typecheck against the exact target
@prisma/driver-adapter-utilsversion[ ]
queryRawpreserves column order, types, nulls, and precision[ ]
executeRawreports affected rows correctly[ ]
executeScripthandles provider-specific multi-statement syntax[ ] Concurrent interactive transactions use distinct dedicated connections
[ ] Success commits and releases once; failure rolls back and releases once
[ ] Nested transaction tests exercise create/rollback/release savepoint hooks
[ ] Unsupported isolation levels fail as
InvalidIsolationLevel[ ] Known constraints map to structured errors
[ ] Unmapped database errors retain original code/message and surface useful
P2039[ ] Dispose ownership is tested for internal and external pools
[ ] Shadow database creation, use, failure cleanup, and disposal are isolated
[ ] Run Prisma Client integration/E2E tests, not only adapter unit tests
Source references
Files
- ATTRIBUTION.mdAdded by AutoGineer924 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md11.1 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- prisma
- Publisher
- Prisma (Official)
- Source
- prisma/skills · prisma-driver-adapter-implementation · commit 8ea289b
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by prisma.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download prisma-driver-adapter-implementation.zip