Two entry points have the surface of popular serverless database clients. Code written for Neon or PlanetScale moves to SurrealDB when you change the import.
Neon
@surrealdb/pg/neon has the surface of @neondatabase/serverless.
- import { neon, Pool, Client, neonConfig } from "@neondatabase/serverless";
+ import { neon, Pool, Client, neonConfig } from "@surrealdb/pg/neon";import { neon } from "@surrealdb/pg/neon";
const sql = neon("postgres://root:secret@abc123.surrealdb.earth/app?ns=prod");
const rows = await sql`SELECT * FROM person WHERE name = ${name}`;
const one = await sql.query("SELECT * FROM person WHERE id = $1", [id], {
fullResults: true,
});
const [created, total] = await sql.transaction([
sql`INSERT INTO person (name) VALUES (${name}) RETURNING id`,
sql`SELECT count(*) AS n FROM person`,
]);Options.
neon(connectionString, options)takesarrayMode,fullResults,authToken(a string or a function),fetchOptions,fetchFunction,isolationLevel,readOnlyanddeferrable. Callingsql(text, params)as a plain function throws, as in Neon. Usesql.query(text, params, options).Queries are lazy. A query is sent once, when you await it or pass it to
sql.transaction.Transactions.
sql.transactionis one atomic HTTP call. Each query in it must be one statement.isolationLevelandreadOnlyare sent with the call.ReadUncommitted,ReadCommittedandRepeatableReadrun assnapshot, anddeferrableis ignored.Connection string. Neon has no namespaces, so the SurrealDB namespace comes from the
ns(ornamespace) parameter, and the path is the database.sslmodeand similar parameters are ignored. TLS follows the host, and is off for loopback only.neonConfig. ThefetchFunction,fetchEndpointandwebSocketConstructorsettings are honoured. ThefetchEndpointtarget must speak the RPC contract, not the/sqlprotocol of Neon. Settings that describe the wire protocol and proxies of Neon are accepted and ignored:wsProxy,useSecureWebSocket,pipelineConnect,pipelineTLS,coalesceWrites,forceDisablePgSSL,disableSNI,fetchConnectionCache,poolQueryViaFetch,subtlsandrootCerts.Errors. A failed statement throws
NeonDbError, which carries the node-postgres fields such ascode,severityandposition.PoolandClientare the node-postgres layer, and read their WebSocket class fromneonConfig.webSocketConstructor.
Drizzle with neon-http and neon-serverless
Drizzle's neon-http and neon-serverless drivers import @neondatabase/serverless by name. Point that name at this entry point in your bundler instead of installing Neon's package.
export default {
resolve: { alias: { "@neondatabase/serverless": "@surrealdb/pg/neon" } },
};The same alias works in esbuild, webpack, tsconfig paths and the [alias] table of wrangler.toml.
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@surrealdb/pg/neon";
const db = drizzle(neon(connectionString));db.batch() becomes one atomic call. As on Neon, neon-http refuses interactive transactions, and neon-serverless runs them over the WebSocket Pool.
PlanetScale
@surrealdb/mysql/planetscale has the surface of @planetscale/database.
- import { Client, connect, cast, format } from "@planetscale/database";
+ import { Client, connect, cast, format } from "@surrealdb/mysql/planetscale";import { Client } from "@surrealdb/mysql/planetscale";
const client = new Client({ url: "mysql://root:secret@abc123.surrealdb.earth/app?ns=prod" });
const r = await client.execute(
"SELECT * FROM person WHERE name = ? AND age > ?",
["Tobie", 18],
);
const named = await client.execute(
"SELECT * FROM person WHERE age > :age",
{ age: 18 },
);
const rows = await client.execute("SELECT id, name FROM person", [], {
as: "array",
});
await client.transaction(async (tx) => {
await tx.execute("UPDATE account SET balance = balance - ? WHERE id = ?", [10, 1]);
await tx.execute("UPDATE account SET balance = balance + ? WHERE id = ?", [10, 2]);
});Config.
urlorhost,usernameandpassword, plusfetch,formatandcastas in the PlanetScale driver. It also takesnamespace,database,transport,WebSocket,headersandtimeout. PlanetScale has no namespaces, so the SurrealDB namespace comes from thensparameter of the URL or thenamespaceoption, and the path is the database.Placeholders.
execute(query, args, options)takes?placeholders with an array and:nameplaceholders with an object. Arguments are bound as parameters on the instance, and are not interpolated. An array argument expands to a list, an empty one toNULL, and an object to JSON text.Client-side formatting. The
formatoption ofConfigswitches to client-side rendering. Theformat(query, values)function is exported for that use.Row shape.
as: "object"(default) or"array".Casting. Every value reaches
castas text. The defaultcastreturns numbers for integer and float types up to 32 bits and parses JSON. It leaves 64-bit integers, decimals and dates as text.Transactions.
transaction(fn)runs on a WebSocket session opened for the call. It commits whenfnresolves, and rolls back whenfnthrows. A client configured withtransport: "http"throwsUnsupportedOnTransportError.Errors. A failed statement throws
DatabaseErrorwithbody.message,body.code,status400,errnoandsqlState. A reply that is not an RPC reply throwsUnknownError.refresh()resolves without a request, because HTTP calls keep no session.
The contract carries the type and nullability of a column, but not its declared length, collation or flag bits. For that reason, the columnLength, charset and flags fields of a result field are derived from the column type.
Drizzle with planetscale-serverless
Drizzle's planetscale-serverless driver checks instanceof Client against @planetscale/database. Alias that name to this entry point.
export default {
resolve: { alias: { "@planetscale/database": "@surrealdb/mysql/planetscale" } },
};import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Client } from "@surrealdb/mysql/planetscale";
const db = drizzle(new Client({ url: process.env.DATABASE_URL }));Drizzle transactions and savepoints run on the WebSocket session.
Compatibility notes
The repository lists, for every API of @neondatabase/serverless and @planetscale/database, whether the entry point supports it, supports it differently, or cannot. See COMPATIBILITY.md.