AgentStack
SKILL verified MIT Self-run

Api Baas Planetscale

skill-agents-inc-skills-api-baas-planetscale · by agents-inc

Serverless MySQL platform with branching, deploy requests, and edge-compatible driver

No reviews yet
0 installs
15 views
0.0% view→install

Install

$ agentstack add skill-agents-inc-skills-api-baas-planetscale

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Api Baas Planetscale? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

PlanetScale Serverless MySQL Patterns

> Quick Guide: Use @planetscale/database for edge/serverless MySQL access via HTTP (Fetch API). Use Client to create per-request connections, conn.execute() for parameterized queries, and conn.transaction() for atomic operations. Never run DDL directly on production -- use deploy requests with safe migrations enabled. PlanetScale runs on Vitess: foreign keys are supported but opt-in, stored procedures are not supported, and all schema changes go through online DDL. The built-in cast handles regular integers and floats automatically, but provide a custom cast for BigInt, Date, and boolean columns. Branch your database like git branches for dev/preview environments.


CRITICAL: Before Using This Skill

> All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use conn.execute(sql, params) with parameterized queries -- never interpolate user input into SQL strings)

(You MUST use deploy requests for ALL schema changes on production branches with safe migrations enabled -- direct DDL is rejected)

(You MUST create a fresh Client.connection() per request in serverless environments -- do not reuse connections across invocations)

(You MUST handle the Vitess/MySQL compatibility differences: no stored procedures, no RENAME COLUMN via direct DDL, no := operator, no LOAD DATA INFILE)

(You MUST provide a custom cast function for BigInt (INT64/UINT64), Date (DATETIME/TIMESTAMP), and boolean (TINYINT(1)) columns -- the default cast handles regular integers and floats but leaves these as strings)


Auto-detection: PlanetScale, @planetscale/database, planetscale serverless driver, pscale, deploy request, safe migrations, Vitess, database branching, planetscale branch, planetscale boost, mysql serverless, pscale CLI, planetscale connection

When to use:

  • Querying MySQL from edge/serverless functions via the PlanetScale serverless driver
  • Managing schema changes through deploy requests and safe migrations
  • Creating database branches for dev, preview, or CI environments
  • Setting up connections with @planetscale/database (host/username/password or URL)
  • Running transactions in serverless contexts
  • Handling Vitess-specific SQL compatibility constraints
  • Programmatic branch management via pscale CLI

Key patterns covered:

  • connect() / Client connection setup with host, username, password
  • conn.execute() with positional (?) and named (:param) parameters
  • conn.transaction() for atomic multi-statement operations
  • Custom cast functions for type-safe value conversion (BigInt, Date, boolean)
  • Deploy request workflow (branch, change schema, create DR, review, deploy)
  • Safe migrations and the no-direct-DDL enforcement model
  • Database branching for dev/preview/CI environments
  • Vitess SQL compatibility constraints and workarounds
  • pscale CLI for branch and deploy request management

When NOT to use:

  • Long-running server processes with persistent TCP MySQL connections (use mysql2 driver)
  • Complex ORM-specific patterns (use your ORM's own skill)
  • General MySQL query syntax (use a SQL/MySQL skill)
  • PostgreSQL workloads (use Neon or another Postgres provider)

Detailed Resources:

  • For decision frameworks, CLI reference, and quick lookup tables, see [reference.md](reference.md)

Driver & Queries:

  • [examples/core.md](examples/core.md) -- Connection setup, parameterized queries, transactions, type casting

Branching & Schema Changes:

  • [examples/branching.md](examples/branching.md) -- Dev branches, deploy requests, safe migrations, pscale CLI, CI/CD workflows

Philosophy

PlanetScale is a serverless MySQL platform built on Vitess, the same technology that powers YouTube's database infrastructure. The @planetscale/database driver uses HTTP (Fetch API) instead of TCP, making MySQL accessible from edge runtimes that lack TCP support.

Core principles:

  1. HTTP-based, stateless connections -- Every query is an HTTP request. There are no persistent connections to manage, no connection pools to configure. Create a connection, execute queries, done. PlanetScale handles connection pooling at the infrastructure level (Vitess VTTablet + Global Routing).
  2. Schema changes via deploy requests, never direct DDL -- Production branches with safe migrations reject direct CREATE, ALTER, DROP statements. All schema changes go through deploy requests: branch, modify schema on the branch, create a deploy request, review the diff, deploy with zero downtime via online DDL.
  3. Branches are cheap -- Database branches are isolated copies of your schema (and optionally data). Create them for feature development, PR previews, CI runs. Delete when done.
  4. Vitess under the hood -- PlanetScale runs Vitess, which adds horizontal scaling but introduces SQL compatibility differences. No stored procedures, no RENAME COLUMN in DDL, no := operator. Foreign keys are supported but opt-in and come with performance trade-offs.
  5. Default cast handles common types, customize for the rest -- The driver's built-in cast function automatically converts INT8-32 and FLOAT32/64 to JavaScript numbers, and parses JSON. However, INT64/UINT64 (BigInt), DATETIME/TIMESTAMP (Date), DECIMAL, and TINYINT(1) (boolean) remain as strings -- provide a custom cast function for these.

When to use PlanetScale serverless driver:

  • Edge/serverless functions that cannot open TCP connections
  • Applications using PlanetScale's branching and deploy request workflow
  • High-concurrency serverless apps benefiting from PlanetScale's infrastructure-level pooling
  • Teams wanting git-like database workflows (branch, review, merge)

When NOT to use:

  • Long-running server processes (use mysql2 with TCP for persistent connections)
  • Workloads requiring stored procedures, triggers, or events (Vitess does not support them)
  • Applications requiring LOAD DATA INFILE (not supported)

Core Patterns

Pattern 1: Connection Setup

The driver provides two connection methods: connect() for a single connection and Client for a connection factory. Use Client in serverless (fresh connection per request), connect() for single long-lived connection objects.

import { connect } from "@planetscale/database";

const conn = connect({
  host: process.env.DATABASE_HOST!,
  username: process.env.DATABASE_USERNAME!,
  password: process.env.DATABASE_PASSWORD!,
});
const { rows } = await conn.execute(
  "SELECT id, name FROM users WHERE active = ?",
  [true],
);

See [examples/core.md](examples/core.md) for full connection patterns including Client factory, URL-based config, and custom fetch for HTTP/2.


Pattern 2: Parameterized Queries

The driver supports positional (?) and named (:param) parameter styles. Both are auto-escaped preventing SQL injection. Never mix styles in a single execute() call.

// Positional: array of values
await conn.execute("SELECT id, name FROM users WHERE id = ? AND active = ?", [
  userId,
  true,
]);

// Named: object of values
await conn.execute("SELECT id, name FROM users WHERE role = :role", {
  role: "admin",
});

See [examples/core.md](examples/core.md) for complex named parameter queries and bad examples to avoid.


Pattern 3: Transactions

conn.transaction() executes multiple queries atomically with automatic rollback on error. Each tx.execute() is an HTTP round trip, but conditional logic runs client-side within the callback.

const result = await conn.transaction(async (tx) => {
  const debit = await tx.execute(
    "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
    [amount, fromId, amount],
  );
  if (debit.rowsAffected === 0) throw new Error("Insufficient funds"); // triggers rollback
  await tx.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", [
    amount,
    toId,
  ]);
  return debit;
});

See [examples/core.md](examples/core.md) for full transaction examples with inventory checks and FOR UPDATE locking.


Pattern 4: Custom Type Casting

The built-in cast handles INT8-32 and FLOAT32/64 automatically. Provide a custom cast for INT64/UINT64 (BigInt), DATETIME/TIMESTAMP (Date), and TINYINT(1) (boolean) -- these remain as strings by default.

import { connect, cast } from "@planetscale/database";
import type { Field } from "@planetscale/database";

function customCast(field: Field, value: any): any {
  if (value == null) return null;
  if (field.type === "INT64" || field.type === "UINT64") return BigInt(value);
  if (field.type === "DATETIME" || field.type === "TIMESTAMP")
    return new Date(value + "Z");
  if (field.type === "INT8" && field.columnLength === 1) return value === "1";
  return cast(field, value);
}

const conn = connect({ url: process.env.DATABASE_URL, cast: customCast });

See [examples/core.md](examples/core.md) for per-query cast overrides and type-specific cast variants.


Pattern 5: Deploy Request Workflow

Schema changes on production branches with safe migrations must go through deploy requests. Direct DDL is rejected. The workflow is: branch, modify schema, create deploy request, review diff, deploy.

pscale branch create my-database add-user-roles          # 1. Create dev branch
pscale shell my-database add-user-roles                  # 2. Make schema changes (DDL)
pscale deploy-request create my-database add-user-roles --into main  # 3. Create DR
pscale deploy-request diff my-database 1                 # 4. Review schema diff
pscale deploy-request deploy my-database 1               # 5. Deploy (online DDL)
pscale deploy-request revert my-database 1               # 6. Revert within 30 min if needed

See [examples/branching.md](examples/branching.md) for gated deployments, instant deployments, and CI/CD workflows.


Pattern 6: Database Branching

Branches are isolated copies of your database schema. Development branches allow direct DDL. Production branches require deploy requests when safe migrations is enabled.

pscale branch create my-database dev-alice               # Create dev branch
pscale shell my-database dev-alice                       # Interactive MySQL shell
pscale password create my-database dev-alice my-password # Generate app credentials
pscale branch delete my-database dev-alice               # Clean up when done

See [examples/branching.md](examples/branching.md) for PR preview branches, safe column renames, FK setup, and branch cleanup scripts.


Pattern 7: Vitess SQL Compatibility

PlanetScale runs on Vitess, which introduces SQL differences from standard MySQL. Key constraints: no stored procedures/triggers/events, no RENAME COLUMN (use three-step add/migrate/drop pattern), no := operator, no LOAD DATA INFILE, no CREATE DATABASE.

See [reference.md](reference.md) for the full supported/unsupported SQL compatibility table.


Decision Framework

Connection Method

What is the runtime environment?
+-- Edge/serverless (Cloudflare Workers, Vercel Edge, etc.)
|   +-- Use @planetscale/database (HTTP-based, no TCP needed)
+-- Traditional Node.js server (always-on)
|   +-- Need PlanetScale branching/deploy workflow?
|   |   +-- YES --> @planetscale/database works fine (HTTP)
|   |   +-- NO --> mysql2 driver with TCP may be simpler
+-- ORM integration?
    +-- Check your ORM's docs for its PlanetScale/serverless adapter

connect() vs Client

How many connections per process?
+-- Single connection (scripts, simple handlers) --> connect()
+-- Multiple connections (serverless, per-request) --> Client + client.connection()

Schema Change Strategy

Is the target branch a production branch with safe migrations?
+-- YES --> Deploy requests ONLY (direct DDL is rejected)
|   +-- Simple change (add column, add index) --> Standard deploy request
|   +-- Needs controlled cutover timing --> Gated deployment (--disable-auto-apply)
|   +-- Instant-eligible change --> Deploy with --instant flag
+-- NO (development branch) --> Direct DDL is allowed
    +-- Experimenting --> pscale shell  
    +-- Scripted migration --> Connect to branch, run DDL

Foreign Keys

Do you need foreign key constraints?
+-- YES --> Enable in database settings (opt-in)
|   +-- Aware of limitations?
|   |   +-- Deploy requests don't validate existing referential integrity
|   |   +-- Reverts can create orphaned rows
|   |   +-- Performance impact in high-concurrency workloads
|   +-- Sharded database? --> FK only supported on unsharded databases
+-- NO --> Use application-level referential integrity
    +-- ORM-level relationship definitions
    +-- Application validation before INSERT/DELETE

RED FLAGS

High Priority Issues:

  • String interpolation in SQL -- conn.execute(\SELECT \* FROM users WHERE id = '${id}'\)bypasses parameterization. Always use?or:param placeholders with the params argument.
  • Direct DDL on production with safe migrations -- ALTER TABLE statements are silently rejected on production branches with safe migrations enabled. All schema changes must go through deploy requests.
  • No custom cast for BigInt/Date columns -- The default cast handles regular integers and floats, but INT64/UINT64 remain as strings and DATETIME/TIMESTAMP are not converted to Date objects. Provide a custom cast for these types.

Medium Priority Issues:

  • Reusing connections across serverless invocations -- Each serverless invocation gets a fresh execution context. Do not store connection state in global variables expecting it to persist.
  • Using RENAME COLUMN in deploy requests -- Column renames can be destructive through Vitess online DDL. Use the three-step pattern: add new column, migrate data, drop old column.
  • Missing revert window awareness -- Deploy requests can be reverted within 30 minutes. After that window closes, you must create a new deploy request to undo changes. Plan accordingly.
  • Foreign keys enabled without understanding implications -- FK constraints on PlanetScale don't validate existing referential integrity during ALTER TABLE ADD FOREIGN KEY. Orphaned rows will silently remain.

Common Mistakes:

  • Wrong package name -- The package is @planetscale/database, not planetscale, mysql-planetscale, or @planetscale/serverless.
  • Expecting connection pooling in the driver -- @planetscale/database does not do client-side connection pooling. PlanetScale handles pooling at the infrastructure level (Vitess VTTablet + Global Routing). Do not wrap it in a pool library.
  • Using positional and named params together -- A single execute() call uses either ? with an array OR :param with an object. Never mix them.
  • Expecting Node.js mysql2 compatibility -- @planetscale/database has a different API from mysql2. There is no pool.query(), no connection.query(). The API is conn.execute(sql, params).
  • Running CREATE DATABASE or DROP DATABASE -- Database creation/deletion is managed via the PlanetScale dashboard, API, or pscale CLI, not SQL.

Gotchas & Edge Cases:

  • INT64/UINT64 and dates remain as strings with the default cast -- SELECT count(*) as total returns { total: 42 } (INT64 is an exception -- it stays as "42" string). DATETIME returns "2024-01-15 10:30:00". Regular INT32 and FLOAT types are auto-converted.
  • rowsAffected is 0 for SELECT -- Only DML statements (INSERT, UPDATE, DELETE) populate rowsAffected. For SELECT, check rows.length or size.
  • insertId is a string -- Even though MySQL auto-increment IDs are integers, insertId in the result is always a string. Cast if needed: BigInt(result.insertId).
  • **Transactions over HTTP ar

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.