Skip to main content

prisma-driver-adapter-implementation

Required reference for Prisma v7 driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter/Transaction interfaces. Contains critical contract details not inferable from code examples — including the transaction lifecycle protocol, error mapping requirements, and verification checklist. Existing implementations do not replace this skill.

소스 정보

저장소
nitrocloudofficial/nitrostack
최근 소스 활동
2026년 7월 26일 08:02
감지된 SKILL.md 언어
영어
스타
2,530
포크
1,478

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
prisma-driver-adapter-implementation
description
Required reference for Prisma v7 driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter/Transaction interfaces. Contains critical contract details not inferable from code examples — including the transaction lifecycle protocol, error mapping requirements, and verification checklist. Existing implementations do not replace this skill.
license
MIT
metadata
{"author":"Tyler Benfield","version":"7.6.0"}
# Prisma 7 Driver Adapter Implementation Guide This skill provides everything needed to implement a Prisma ORM v7 driver adapter for any database. ## Architecture Overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ PrismaClient │ │ (requires adapter factory) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ SqlMigrationAwareDriverAdapterFactory │ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ connect() │ │ connectToShadowDb() │ │ │ │ → SqlDriverAdapter │ │ → SqlDriverAdapter │ │ │ └─────────────────────┘ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ SqlDriverAdapter │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ queryRaw() │ │ executeRaw() │ │ startTransaction() │ │ │ │ → ResultSet │ │ → number │ │ → Transaction │ │ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ │executeScript │ │ dispose() │ │ getConnectionInfo() │ │ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Transaction │ │ Extends SqlQueryable + commit() + rollback() + options │ │ (lifecycle hooks only — Prisma sends SQL via executeRaw) │ └─────────────────────────────────────────────────────────────────┘ ``` ## Required Interfaces Import from `@prisma/driver-adapter-utils`: ```typescript import type { ColumnType, IsolationLevel, SqlDriverAdapter, SqlMigrationAwareDriverAdapterFactory, SqlQuery, SqlQueryable, SqlResultSet, Transaction, TransactionOptions, ArgType, ConnectionInfo, MappedError, } from "@prisma/driver-adapter-utils"; import { ColumnTypeEnum, DriverAdapterError, } from "@prisma/driver-adapter-utils"; ``` ## Interface Definitions ### SqlQuery (input to queryRaw/executeRaw) ```typescript type SqlQuery = { sql: string; // Parameterized SQL with placeholders args: Array<unknown>; // Bound parameter values argTypes: Array<ArgType>; // Type hints for each argument }; type ArgType = { scalarType: ArgScalarType; // 'string' | 'int' | 'bigint' | 'float' | 'decimal' | 'boolean' | 'enum' | 'uuid' | 'json' | 'datetime' | 'bytes' | 'unknown' dbType?: string; arity: "scalar" | "list"; }; ``` ### SqlResultSet (output from queryRaw) ```typescript interface SqlResultSet { columnNames: Array<string>; // Column names in order columnTypes: Array<ColumnType>; // Column types matching columnNames rows: Array<Array<unknown>>; // Row data as arrays lastInsertId?: string; // For INSERT without RETURNING } ``` ### ColumnTypeEnum values ```typescript const ColumnTypeEnum = { Int32: 0, Int64: 1, Float: 2, Double: 3, Numeric: 4, Boolean: 5, Character: 6, Text: 7, Date: 8, Time: 9, DateTime: 10, Json: 11, Enum: 12, Bytes: 13, Set: 14, Uuid: 15, Int32Array: 64, Int64Array: 65, FloatArray: 66, DoubleArray: 67, NumericArray: 68, BooleanArray: 69, CharacterArray: 70, TextArray: 71, DateArray: 72, TimeArray: 73, DateTimeArray: 74, JsonArray: 75, EnumArray: 76, BytesArray: 77, UuidArray: 78, UnknownNumber: 128, } as const; ``` ### SqlDriverAdapter ```typescript interface SqlDriverAdapter extends SqlQueryable { executeScript(script: string): Promise<void>; startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>; getConnectionInfo?(): ConnectionInfo; dispose(): Promise<void>; } ``` ### Transaction ```typescript interface Transaction extends SqlQueryable { readonly options: TransactionOptions; commit(): Promise<void>; rollback(): Promise<void>; } type TransactionOptions = { usePhantomQuery: boolean }; ``` ### SqlMigrationAwareDriverAdapterFactory ```typescript interface SqlMigrationAwareDriverAdapterFactory { readonly provider: "mysql" | "postgres" | "sqlite" | "sqlserver"; readonly adapterName: string; connect(): Promise<SqlDriverAdapter>; connectToShadowDb(): Promise<SqlDriverAdapter>; } ``` ## Implementation Steps ### Step 1: Create the Queryable base class ```typescript class MyQueryable<TClient> implements SqlQueryable { readonly provider = "postgres" as const; // or 'sqlite' | 'mysql' | 'sqlserver' readonly adapterName = "@my-org/adapter-mydb" as const; constructor(protected readonly client: TClient) {} async queryRaw(query: SqlQuery): Promise<SqlResultSet> { try { const args = query.args.map((arg, i) => mapArg(arg, query.argTypes[i] ?? { scalarType: "unknown", arity: "scalar" }) ); // Execute query with your driver const result = await this.client.query(query.sql, args); // Extract column metadata const columnNames = /* get from result */; const columnTypes = /* map to ColumnTypeEnum */; // Map rows to ResultValue arrays const rows = result.map(row => mapRow(row, columnTypes)); return { columnNames, columnTypes, rows }; } catch (e) { this.onError(e); } } async executeRaw(query: SqlQuery): Promise<number> { try { const args = query.args.map((arg, i) => mapArg(arg, query.argTypes[i] ?? { scalarType: "unknown", arity: "scalar" }) ); const result = await this.client.query(query.sql, args); return result.affectedRows ?? 0; } catch (e) { this.onError(e); } } protected onError(error: unknown): never { throw new DriverAdapterError(convertDriverError(error)); } } ``` ### Step 2: Create the Transaction class **Critical**: `commit()` and `rollback()` are **lifecycle hooks only**. They must NOT issue SQL. Prisma sends `COMMIT`/`ROLLBACK` via `executeRaw` on the transaction object. ```typescript class MyTransaction extends MyQueryable<TClient> implements Transaction { readonly options: TransactionOptions; readonly #release: () => void; constructor( client: TClient, options: TransactionOptions, release: () => void, ) { super(client); this.options = options; this.#release = release; } commit(): Promise<void> { // DO NOT issue COMMIT SQL here — Prisma does it via executeRaw this.#release(); // Release connection/resources return Promise.resolve(); } rollback(): Promise<void> { // DO NOT issue ROLLBACK SQL here — Prisma does it via executeRaw this.#release(); return Promise.resolve(); } } ``` ### Step 3: Create the Adapter class ```typescript class MyAdapter extends MyQueryable<TClient> implements SqlDriverAdapter { #transactionDepth = 0; constructor(client: TClient) { super(client); } async executeScript(script: string): Promise<void> { // For SQLite: split on ';' and run each statement // For Postgres: use multi-statement execution try { // Implementation depends on driver capabilities } catch (e) { this.onError(e); } } async startTransaction( isolationLevel?: IsolationLevel, ): Promise<Transaction> { // Validate isolation level for your database const validLevels = new Set<IsolationLevel>([ "READ UNCOMMITTED", "READ COMMITTED", "REPEATABLE READ", "SERIALIZABLE", ]); if (isolationLevel !== undefined && !validLevels.has(isolationLevel)) { throw new DriverAdapterError({ kind: "InvalidIsolationLevel", level: isolationLevel, }); } const options: TransactionOptions = { usePhantomQuery: false }; this.#transactionDepth += 1; const depth = this.#transactionDepth; try { if (depth === 1) { // Issue BEGIN (with isolation level if specified) const beginSql = isolationLevel ? `BEGIN ISOLATION LEVEL ${isolationLevel}` : "BEGIN"; await this.client.query(beginSql); } else { // Nested: use savepoints await this.client.query(`SAVEPOINT sp_${depth}`); } } catch (e) { this.#transactionDepth -= 1; this.onError(e); } const release = () => { this.#transactionDepth -= 1; }; return new MyTransaction(this.client, options, release); } getConnectionInfo(): ConnectionInfo { return { supportsRelationJoins: true }; } async dispose(): Promise<void> { await this.client.close(); } } ``` ### Step 4: Create the Factory class ```typescript export type MyAdapterConfig = { url: string; }; export type MyAdapterOptions = { shadowDatabaseUrl?: string; }; export class MyAdapterFactory implements SqlMigrationAwareDriverAdapterFactory { readonly provider = "postgres" as const; readonly adapterName = "@my-org/adapter-mydb" as const; constructor( private readonly config: MyAdapterConfig, private readonly options?: MyAdapterOptions, ) {} connect(): Promise<SqlDriverAdapter> { return Promise.resolve(new MyAdapter(openConnection(this.config.url))); } connectToShadowDb(): Promise<SqlDriverAdapter> { const url = this.options?.shadowDatabaseUrl ?? this.config.url; return Promise.resolve(new MyAdapter(openConnection(url))); } } ``` ## Conversion Helpers ### Argument Mapping (input) Convert Prisma argument values to driver-native types: ```typescript function mapArg(arg: unknown, argType: ArgType): unknown { if (arg === null || arg === undefined) return null; // String → number for int columns if (typeof arg === "string" && argType.scalarType === "int") return Number.parseInt(arg, 10); // String → number for float columns if (typeof arg === "string" && argType.scalarType === "float") return Number.parseFloat(arg); // String → BigInt for bigint columns if (typeof arg === "string" && argType.scalarType === "bigint") return BigInt(arg); // Base64 string → Buffer for bytes columns if (typeof arg === "string" && argType.scalarType === "bytes") return Buffer.from(arg, "base64"); // Boolean → 0/1 for SQLite if (typeof arg === "boolean" && /* SQLite */) return arg ? 1 : 0; return arg; } ``` ### Row Mapping (output) Convert driver result values to Prisma-expected types: ```typescript function mapRow(row: unknown[], columnTypes: ColumnType[]): ResultValue[] { const result: ResultValue[] = []; for (let i = 0; i < row.length; i++) { const value = row[i] ?? null; const colType = columnTypes[i]; if (value === null) { result.push(null); continue; } // bigint → string for Int64 (JSON-safe) if (typeof value === "bigint") { result.push(value.toString()); continue; } // Date → ISO 8601 string for DateTime if (value instanceof Date) { result.push(value.toISOString()); continue; } // JSON objects → stringified if (colType === ColumnTypeEnum.Json && typeof value === "object") { result.push(JSON.stringify(value)); continue; } result.push(value as ResultValue); } return result; } ``` ### Column Type Inference When the driver doesn't provide type metadata, infer from JS values: ```typescript function inferColumnType(value: NonNullable<unknown>): ColumnType { if (typeof value === "boolean") return ColumnTypeEnum.Boolean; if (typeof value === "bigint") return ColumnTypeEnum.Int64; if (value instanceof Uint8Array) return ColumnTypeEnum.Bytes; if (value instanceof Date) return ColumnTypeEnum.DateTime; if (Array.isArray(value)) return ColumnTypeEnum.Text; // fallback if (typeof value === "object") return ColumnTypeEnum.Json; if (typeof value === "number") return ColumnTypeEnum.UnknownNumber; return ColumnTypeEnum.Text; } ``` ## Error Handling Map driver errors to `MappedError` for Prisma to handle correctly: ```typescript function convertDriverError(error: unknown): MappedError { if (error instanceof Error) { // Database-specific error mapping const dbError = error as Error & { code?: string; errno?: number }; // PostgreSQL example if (dbError.code === "23505") { return { kind: "UniqueConstraintViolation" }; } if (dbError.code === "23502") { return { kind: "NullConstraintViolation" }; } if (dbError.code === "23503") { return { kind: "ForeignKeyConstraintViolation" }; } if (dbError.code === "42P01") { return { kind: "TableDoesNotExist" }; } // SQLite example if (error.name === "SQLiteError") { return { kind: "sqlite", extendedCode: dbError.errno ?? 1, message: error.message, }; } // PostgreSQL raw error if (dbError.code) { return { kind: "postgres", code: dbError.code, severity: "ERROR", message: error.message, detail: undefined,
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기