Each package has a compatibility layer that imitates a driver. Existing ORMs and query builders then run on top of it, over HTTP and WebSocket.
| Tool | Package | Entry point |
|---|---|---|
node-postgres Client and Pool | @surrealdb/pg | Client, Pool |
Drizzle node-postgres | @surrealdb/pg | drizzle({ client: pool as never }) |
Kysely PostgresDialect | @surrealdb/pg | new PostgresDialect({ pool }) |
Prisma @prisma/adapter-pg | @surrealdb/pg | new pg.Pool({ connectionString, Client }) |
| mysql2 promise API | @surrealdb/mysql | createPool, createConnection |
Drizzle mysql2 | @surrealdb/mysql | drizzle(pool as never, { mode: "default" }) |
Kysely MysqlDialect | @surrealdb/mysql/callback | new MysqlDialect({ pool }) |
@neondatabase/serverless | @surrealdb/pg/neon | See Neon and PlanetScale |
@planetscale/database | @surrealdb/mysql/planetscale | See Neon and PlanetScale |
The adapters' types name the classes of pg and mysql2, so pass the pool with as never, or cast it to the class the adapter asks for.
node-postgres
Client and Pool follow the node-postgres surface that the adapters use.
import { Pool } from "@surrealdb/pg";
const pool = new Pool({
connectionString: "postgres://root:secret@abc123.surrealdb.earth/app?namespace=prod",
});
const { rows, rowCount, command } = await pool.query("SELECT id, name FROM person WHERE name = $1", ["Tobie"]);
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query("UPDATE account SET balance = balance - 10 WHERE id = $1", ["x"]);
await client.query("COMMIT");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
await pool.end();Result.
{ command, rowCount, oid, rows, fields }.commandandrowCountcome from the command tag, soINSERT 0 5givesINSERTand 5.Values. Values use the text form of the dialect and go through
types.getTypeParser(oid), as in node-postgres.int8andnumericstay strings, and atimestampwithout a zone is read as local time.pool.query()runs over HTTP when the transport isauto, so a cold edge isolate needs no handshake. Clients frompool.connect()are WebSocket sessions.release()rolls back an open transaction before the client is reused.Pool options.
max(default 10),idleTimeoutMillis(default 10000),ClientandpoolQueryViaHttp(defaulttrue).Errors. A failed statement raises
DatabaseErrorwithcode,severity,messageandposition.
mysql2
createPool and createConnection follow the mysql2/promise API. Every promise method resolves [rows, fields], and a statement without rows resolves a ResultSetHeader.
import { createPool } from "@surrealdb/mysql";
const pool = createPool({ uri: "mysql://root:secret@abc123.surrealdb.earth/app?namespace=prod" });
const [rows] = await pool.query("SELECT id, name FROM person WHERE name = ?", ["Tobie"]);
const [header] = await pool.execute("UPDATE person SET age = ? WHERE id = ?", [31, "tobie"]);
console.log(header.affectedRows, header.insertId);queryandexecuteboth bind on the server, so there is no client-side interpolation. Forqueryonly, the conveniences of mysql2 remain: an array value expands to a list, an array of arrays expands to a list of lists, and??quotes an identifier.typeCast(field, next)works as in mysql2.Pool.
connectionLimitdefaults to 10. Poolqueryandexecuterun over HTTP when the transport isauto, andgetConnection()returns a WebSocket session.Ignored options.
supportBigNumbers,waitForConnectionsandmultipleStatementsare accepted and have no effect.BIGINTis exact beyond the safe range by default, and a text can always hold several statements.Errors. A failed statement raises
MysqlError.
Drizzle
import { Pool } from "@surrealdb/pg";
import { drizzle } from "drizzle-orm/node-postgres";
const pool = new Pool({ connectionString });
const db = drizzle({ client: pool as never });Drizzle detects a pool by its constructor name, and the built package keeps it. Transactions pin one pooled connection and use savepoints for nesting. The migrator works.
Kysely
import { Pool } from "@surrealdb/pg";
import { Kysely, PostgresDialect } from "kysely";
const db = new Kysely({
dialect: new PostgresDialect({ pool: new Pool({ connectionString }) as never }),
});Prisma
Prisma's adapter-pg needs a real pg.Pool. Install pg, and give it the Client of @surrealdb/pg.
import pg from "pg";
import { Client } from "@surrealdb/pg";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/client";
const pool = new pg.Pool({ connectionString, Client });
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });Prisma rejects an unknown type id below 16384, so the instance must report built-in PostgreSQL type ids. The Prisma adapter has been verified against a contract simulator, and not yet against a server. Prisma's MySQL driver adapters have not been assessed.
Not provided
PostgreSQL layer: cursors,
COPY, binary format,LISTENandNOTIFY, andpg-native. Kysely streaming is therefore unavailable.MySQL layer: streaming (
.stream()),LOAD DATA LOCAL,COM_CHANGE_USER, SSL options (TLS follows the URL scheme), named placeholders in mysql2, andnestTables.Vendor adapters:
@prisma/adapter-neon,@prisma/adapter-planetscaleand Kysely's PlanetScale dialect construct the vendor's own classes, so they are not supported.