prisma-driver-adapter-implementation by prisma/skills
npx skills add https://github.com/prisma/skills --skill prisma-driver-adapter-implementation此技能提供了为任何数据库实现 Prisma ORM v7 驱动适配器所需的一切。
┌─────────────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────────────┘
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
从 @prisma/driver-adapter-utils 导入:
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";
type SqlQuery = {
sql: string; // 带有占位符的参数化 SQL
args: Array<unknown>; // 绑定的参数值
argTypes: Array<ArgType>; // 每个参数的类型提示
};
type ArgType = {
scalarType: ArgScalarType; // 'string' | 'int' | 'bigint' | 'float' | 'decimal' | 'boolean' | 'enum' | 'uuid' | 'json' | 'datetime' | 'bytes' | 'unknown'
dbType?: string;
arity: "scalar" | "list";
};
interface SqlResultSet {
columnNames: Array<string>; // 按顺序排列的列名
columnTypes: Array<ColumnType>; // 与 columnNames 匹配的列类型
rows: Array<Array<unknown>>; // 作为数组的行数据
lastInsertId?: string; // 用于没有 RETURNING 的 INSERT 语句
}
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;
interface SqlDriverAdapter extends SqlQueryable {
executeScript(script: string): Promise<void>;
startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>;
getConnectionInfo?(): ConnectionInfo;
dispose(): Promise<void>;
}
interface Transaction extends SqlQueryable {
readonly options: TransactionOptions;
commit(): Promise<void>;
rollback(): Promise<void>;
}
type TransactionOptions = { usePhantomQuery: boolean };
interface SqlMigrationAwareDriverAdapterFactory {
readonly provider: "mysql" | "postgres" | "sqlite" | "sqlserver";
readonly adapterName: string;
connect(): Promise<SqlDriverAdapter>;
connectToShadowDb(): Promise<SqlDriverAdapter>;
}
class MyQueryable<TClient> implements SqlQueryable {
readonly provider = "postgres" as const; // 或 '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" })
);
// 使用你的驱动执行查询
const result = await this.client.query(query.sql, args);
// 提取列元数据
const columnNames = /* get from result */;
const columnTypes = /* map to ColumnTypeEnum */;
// 将行映射到 ResultValue 数组
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));
}
}
关键:commit() 和 rollback() 仅是生命周期钩子。它们不得发出 SQL 语句。Prisma 通过事务对象上的 executeRaw 发送 COMMIT/ROLLBACK。
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> {
// 不要在这里发出 COMMIT SQL 语句 — Prisma 通过 executeRaw 执行
this.#release(); // 释放连接/资源
return Promise.resolve();
}
rollback(): Promise<void> {
// 不要在这里发出 ROLLBACK SQL 语句 — Prisma 通过 executeRaw 执行
this.#release();
return Promise.resolve();
}
}
class MyAdapter extends MyQueryable<TClient> implements SqlDriverAdapter {
#transactionDepth = 0;
constructor(client: TClient) {
super(client);
}
async executeScript(script: string): Promise<void> {
// 对于 SQLite:按 ';' 分割并分别运行每个语句
// 对于 Postgres:使用多语句执行
try {
// 实现取决于驱动的能力
} catch (e) {
this.onError(e);
}
}
async startTransaction(
isolationLevel?: IsolationLevel,
): Promise<Transaction> {
// 为你的数据库验证隔离级别
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) {
// 发出 BEGIN(如果指定了隔离级别则附带)
const beginSql = isolationLevel
? `BEGIN ISOLATION LEVEL ${isolationLevel}`
: "BEGIN";
await this.client.query(beginSql);
} else {
// 嵌套:使用保存点
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();
}
}
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)));
}
}
将 Prisma 参数值转换为驱动原生类型:
function mapArg(arg: unknown, argType: ArgType): unknown {
if (arg === null || arg === undefined) return null;
// 字符串 → 数字,用于 int 列
if (typeof arg === "string" && argType.scalarType === "int")
return Number.parseInt(arg, 10);
// 字符串 → 数字,用于 float 列
if (typeof arg === "string" && argType.scalarType === "float")
return Number.parseFloat(arg);
// 字符串 → BigInt,用于 bigint 列
if (typeof arg === "string" && argType.scalarType === "bigint")
return BigInt(arg);
// Base64 字符串 → Buffer,用于 bytes 列
if (typeof arg === "string" && argType.scalarType === "bytes")
return Buffer.from(arg, "base64");
// 布尔值 → 0/1,用于 SQLite
if (typeof arg === "boolean" && /* SQLite */)
return arg ? 1 : 0;
return arg;
}
将驱动结果值转换为 Prisma 期望的类型:
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,用于 Int64 (JSON 安全)
if (typeof value === "bigint") {
result.push(value.toString());
continue;
}
// Date → ISO 8601 字符串,用于 DateTime
if (value instanceof Date) {
result.push(value.toISOString());
continue;
}
// JSON 对象 → 字符串化
if (colType === ColumnTypeEnum.Json && typeof value === "object") {
result.push(JSON.stringify(value));
continue;
}
result.push(value as ResultValue);
}
return result;
}
当驱动不提供类型元数据时,从 JS 值推断:
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; // 后备方案
if (typeof value === "object") return ColumnTypeEnum.Json;
if (typeof value === "number") return ColumnTypeEnum.UnknownNumber;
return ColumnTypeEnum.Text;
}
将驱动错误映射到 MappedError,以便 Prisma 正确处理:
function convertDriverError(error: unknown): MappedError {
if (error instanceof Error) {
// 数据库特定的错误映射
const dbError = error as Error & { code?: string; errno?: number };
// PostgreSQL 示例
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 示例
if (error.name === "SQLiteError") {
return {
kind: "sqlite",
extendedCode: dbError.errno ?? 1,
message: error.message,
};
}
// PostgreSQL 原始错误
if (dbError.code) {
return {
kind: "postgres",
code: dbError.code,
severity: "ERROR",
message: error.message,
detail: undefined,
column: undefined,
hint: undefined,
};
}
}
return { kind: "GenericJs", id: 0 };
}
safeIntegers: true 以获取大整数的 bigint 类型SERIALIZABLE 隔离级别有效executeScript:按 ; 分割并分别运行每个语句prepare: falsereserve() 模式)executeScript:使用多语句执行(某些驱动中的 .simple())int8 列可能返回为字符串(已由驱动字符串化)numeric 列返回为字符串以保持精度READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE? 作为参数占位符BIGINT 作为字符串处理以支持大值直接使用原始数据库驱动测试适配器:
describe("queryRaw", () => {
test("returns column names and types", async () => {
const adapter = new MyAdapter(createTestConnection());
const result = await adapter.queryRaw({
sql: "SELECT id, name FROM users",
args: [],
argTypes: [],
});
expect(result.columnNames).toEqual(["id", "name"]);
expect(result.columnTypes[0]).toBe(ColumnTypeEnum.Int32);
});
});
describe("startTransaction", () => {
test("commit persists changes", async () => {
const adapter = new MyAdapter(createTestConnection());
const tx = await adapter.startTransaction();
await tx.executeRaw({
sql: "INSERT INTO users (name) VALUES (?)",
args: ["Alice"],
argTypes: [],
});
// Prisma 通过 executeRaw 发送 COMMIT
await tx.executeRaw({ sql: "COMMIT", args: [], argTypes: [] });
await tx.commit(); // 仅是生命周期钩子
// 验证数据已持久化
});
});
测试完整集成:
describe("E2E", () => {
let prisma: PrismaClient;
beforeEach(async () => {
const factory = new MyAdapterFactory({ url: TEST_DB_URL });
prisma = new PrismaClient({ adapter: factory });
});
test("CRUD operations", async () => {
const user = await prisma.user.create({ data: { name: "Alice" } });
expect(user.id).toBeGreaterThan(0);
const found = await prisma.user.findUnique({ where: { id: user.id } });
expect(found?.name).toBe("Alice");
});
test("transactions roll back on error", async () => {
await expect(
prisma.$transaction(async (tx) => {
await tx.user.create({ data: { name: "Bob" } });
throw new Error("Rollback!");
}),
).rejects.toThrow();
expect(await prisma.user.count()).toBe(0);
});
});
import { PrismaClient } from "./generated/prisma/client";
import { MyAdapterFactory } from "@my-org/adapter-mydb";
const factory = new MyAdapterFactory({
url: process.env.DATABASE_URL!,
});
const prisma = new PrismaClient({ adapter: factory });
// 正常使用 prisma
const users = await prisma.user.findMany();
在认为适配器完成之前:
SqlMigrationAwareDriverAdapterFactory,包含 connect() 和 connectToShadowDb()SqlDriverAdapter 实现了 queryRaw、executeRaw、executeScript、startTransaction、disposeTransaction 实现了 queryRaw、executeRaw、commit、rollback,且 options: { usePhantomQuery: false }commit() 和 rollback() 仅是生命周期钩子(不发出 SQL)startTransaction 发出 BEGIN(深度 1)或 SAVEPOINT sp_N(嵌套)ColumnTypeEnumDriverAdapterError 中,并带有正确的 MappedError 类型每周安装数
949
仓库
GitHub 星标数
26
首次出现
2026年2月23日
安全审计
安装于
codex935
opencode932
github-copilot931
cursor930
gemini-cli928
amp928
This skill provides everything needed to implement a Prisma ORM v7 driver adapter for any database.
┌─────────────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────────────┘
Import from @prisma/driver-adapter-utils:
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";
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";
};
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
}
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;
interface SqlDriverAdapter extends SqlQueryable {
executeScript(script: string): Promise<void>;
startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>;
getConnectionInfo?(): ConnectionInfo;
dispose(): Promise<void>;
}
interface Transaction extends SqlQueryable {
readonly options: TransactionOptions;
commit(): Promise<void>;
rollback(): Promise<void>;
}
type TransactionOptions = { usePhantomQuery: boolean };
interface SqlMigrationAwareDriverAdapterFactory {
readonly provider: "mysql" | "postgres" | "sqlite" | "sqlserver";
readonly adapterName: string;
connect(): Promise<SqlDriverAdapter>;
connectToShadowDb(): Promise<SqlDriverAdapter>;
}
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));
}
}
Critical : commit() and rollback() are lifecycle hooks only. They must NOT issue SQL. Prisma sends COMMIT/ROLLBACK via executeRaw on the transaction object.
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();
}
}
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();
}
}
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)));
}
}
Convert Prisma argument values to driver-native types:
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;
}
Convert driver result values to Prisma-expected types:
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;
}
When the driver doesn't provide type metadata, infer from JS values:
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;
}
Map driver errors to MappedError for Prisma to handle correctly:
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,
column: undefined,
hint: undefined,
};
}
}
return { kind: "GenericJs", id: 0 };
}
safeIntegers: true when opening the database to get bigint for large integersSERIALIZABLE isolation level is validexecuteScript: split on ; and run each statement individuallyprepare: falsereserve() pattern)executeScript: use multi-statement execution (.simple() in some drivers)int8 columns may return as string (already stringified by driver)numeric columns return as string to preserve precisionREAD UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE? placeholders for parametersBIGINT as string for large valuesTest the adapter directly with the raw database driver:
describe("queryRaw", () => {
test("returns column names and types", async () => {
const adapter = new MyAdapter(createTestConnection());
const result = await adapter.queryRaw({
sql: "SELECT id, name FROM users",
args: [],
argTypes: [],
});
expect(result.columnNames).toEqual(["id", "name"]);
expect(result.columnTypes[0]).toBe(ColumnTypeEnum.Int32);
});
});
describe("startTransaction", () => {
test("commit persists changes", async () => {
const adapter = new MyAdapter(createTestConnection());
const tx = await adapter.startTransaction();
await tx.executeRaw({
sql: "INSERT INTO users (name) VALUES (?)",
args: ["Alice"],
argTypes: [],
});
// Prisma sends COMMIT via executeRaw
await tx.executeRaw({ sql: "COMMIT", args: [], argTypes: [] });
await tx.commit(); // lifecycle hook only
// Verify data persisted
});
});
Test the full integration:
describe("E2E", () => {
let prisma: PrismaClient;
beforeEach(async () => {
const factory = new MyAdapterFactory({ url: TEST_DB_URL });
prisma = new PrismaClient({ adapter: factory });
});
test("CRUD operations", async () => {
const user = await prisma.user.create({ data: { name: "Alice" } });
expect(user.id).toBeGreaterThan(0);
const found = await prisma.user.findUnique({ where: { id: user.id } });
expect(found?.name).toBe("Alice");
});
test("transactions roll back on error", async () => {
await expect(
prisma.$transaction(async (tx) => {
await tx.user.create({ data: { name: "Bob" } });
throw new Error("Rollback!");
}),
).rejects.toThrow();
expect(await prisma.user.count()).toBe(0);
});
});
import { PrismaClient } from "./generated/prisma/client";
import { MyAdapterFactory } from "@my-org/adapter-mydb";
const factory = new MyAdapterFactory({
url: process.env.DATABASE_URL!,
});
const prisma = new PrismaClient({ adapter: factory });
// Use prisma normally
const users = await prisma.user.findMany();
Before considering the adapter complete:
SqlMigrationAwareDriverAdapterFactory implemented with connect() and connectToShadowDb()SqlDriverAdapter implements queryRaw, executeRaw, executeScript, startTransaction, disposeTransaction implements queryRaw, , , with Weekly Installs
949
Repository
GitHub Stars
26
First Seen
Feb 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex935
opencode932
github-copilot931
cursor930
gemini-cli928
amp928
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
Gemini Interactions API 指南:统一接口、智能体交互与服务器端状态管理
833 周安装
Apollo MCP 服务器:让AI代理通过GraphQL API交互的完整指南
834 周安装
智能体记忆系统构建指南:分块策略、向量存储与检索优化
835 周安装
Scrapling官方网络爬虫框架 - 自适应解析、绕过Cloudflare、Python爬虫库
836 周安装
抽奖赢家选取器 - 随机选择工具,支持CSV、Excel、Google Sheets,公平透明
838 周安装
Medusa 前端开发指南:使用 SDK、React Query 构建电商商店
839 周安装
executeRawcommitrollbackoptions: { usePhantomQuery: false }commit() and rollback() are lifecycle hooks only (no SQL issued)startTransaction issues BEGIN (depth 1) or SAVEPOINT sp_N (nested)ColumnTypeEnumDriverAdapterError with proper MappedError kind