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
آخر نشاط في المصدر
٢٦ يوليو ٢٠٢٦ في ٠٨:٠٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٬٥٣٠
التفرعات
١٬٤٧٨

خيارات التثبيت

يُحدَّد 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