PlanetScale
Serverless MySQL driver for PlanetScale
Installation
pnpm add @planetscale/database
Configuration
import { createClient } from "viborm/planetscale";
const client = createClient({
databaseUrl: process.env.DATABASE_URL,
schema,
});
Options
| Option | Type | Description |
|---|---|---|
client |
Client |
Existing PlanetScale client |
databaseUrl |
string |
PlanetScale connection URL |
namespace |
string |
Vitess keyspace qualifier submitted before VTGate routing |
options |
Config |
PlanetScale configuration options |
Using Options
import { createClient } from "viborm/planetscale";
const client = createClient({
options: {
host: process.env.DATABASE_HOST,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
},
schema,
});
Namespace
namespace records the Vitess keyspace qualifier VibORM submits in the
MySQL database position of a table reference. Omitted, runtime SQL stays
unqualified so PlanetScale’s Global Edge Network and VTGate keep full routing
authority.
import { createClient } from "viborm/planetscale";
const db = createClient({
databaseUrl: process.env.DATABASE_URL,
namespace: "billing",
schema,
});
SELECT `user`.`id` FROM `billing`.`user` AS `user`
Three things stay separate here:
| Concept | What it is | Is it namespace? |
|---|---|---|
| PlanetScale database | The enclosing product/cluster resource, which can contain several keyspaces | No |
| Keyspace qualifier | The identifier submitted in the table reference’s database position | Yes — and only from an explicit namespace |
@primary / @replica |
A connection routing selector | No |
The SDK config, endpoint, and resource name do not prove which keyspace
qualifier a query should submit, so VibORM never derives one. @primary and
@replica remain provider connection configuration: they are never accepted as
namespace, never copied to the adapter fact, and never emitted as a table
qualifier. A supplied Client is opaque and is bound only by an explicit
namespace.
Migrations are refused
PlanetScale exposes no non-redirecting attestation option, and neither an
explicit qualifier nor a reserved session can prove containment across routing
rules. Every effectful migration, push, reset, and destructive live verb is
refused — with or without a namespace. Admitted read-only and offline
migration paths still work, as does runtime qualification.
Cache scope follows the qualifier
Official cache entries are scoped by dialect plus the requested qualifier. Equal
qualifiers share a scope and distinct qualifiers stay separated, but a
routing-rule change is an external cache-topology event: invalidate every
affected qualifier scope or bump cache({ version }) before switching traffic.
VibORM does not query the PlanetScale control plane or correlate routing
aliases. See Namespaces.
Transactions & Batching
PlanetScale transactions use one @planetscale/database Connection and force
Vitess transaction_mode = 'single' before BEGIN. This preserves atomic
commit semantics: work that would span shards is rejected instead of falling
back to best-effort multi-shard commit behavior. See the
Vitess distributed transaction modes.
Dynamic Transactions
Use the callback API when operations need to depend on each other:
await client.$transaction(async (tx) => {
const user = await tx.user.create({
data: { name: "Alice", email: "alice@example.com" },
});
await tx.post.create({
data: { title: "Hello", authorId: user.id },
});
});
Batch Mode
Use the array API for independent operations:
const [users, posts] = await client.$transaction([
client.user.findMany(),
client.post.findMany(),
]);
Migrations
Migration atomicity is limited: Vitess online-DDL behavior and MySQL-family implicit commits mean a failed migration can leave earlier statements applied.
Capabilities
Dynamic transactions and batch mode are single-shard atomic; cross-shard work rejects. See the feature matrix for the full comparison.
Limitations
- No
RETURNINGclause - usesLAST_INSERT_ID() - Cross-shard transactions are rejected to preserve atomicity
- JSON columns return as strings and are parsed automatically
- Boolean values stored as
TINYINT(1)
Serverless Usage
PlanetScale driver is optimized for serverless environments:
// Cloudflare Workers, Vercel Edge, etc.
export default {
async fetch(request: Request) {
const client = createClient({
databaseUrl: process.env.DATABASE_URL,
schema,
});
const users = await client.user.findMany();
return Response.json(users);
},
};