Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

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 RETURNING clause - uses LAST_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);
  },
};

Was this page helpful?