MySQL2
MySQL driver using mysql2 with connection pooling
Installation
pnpm add mysql2
Configuration
import { createClient } from "viborm/mysql2";
const client = createClient({
databaseUrl: "mysql://user:password@localhost:3306/database",
schema,
});
Options
| Option | Type | Description |
|---|---|---|
pool |
Pool |
Existing mysql2 pool instance |
options |
PoolOptions |
mysql2 pool configuration |
databaseUrl |
string |
MySQL connection URL |
namespace |
string |
MySQL database for VibORM-owned objects |
migrationNamespaceAttestation |
"non-redirecting" |
Caller assertion required for effectful migrations |
Using Pool Options
import { createClient } from "viborm/mysql2";
const client = createClient({
options: {
host: "localhost",
port: 3306,
user: "root",
password: "password",
database: "mydb",
connectionLimit: 10,
},
schema,
});
Using Existing Pool
import { createPool } from "mysql2/promise";
import { createClient } from "viborm/mysql2";
const pool = createPool({
host: "localhost",
user: "root",
database: "mydb",
});
const client = createClient({
pool,
schema,
});
Namespace
namespace selects the MySQL database holding this driver’s tables, junctions,
indexes, and migration tracking table.
import { createClient } from "viborm/mysql2";
const db = createClient({
databaseUrl: "mysql://user:password@localhost:3306/app_dev",
namespace: "billing",
schema,
});
SELECT `user`.`id` FROM `billing`.`user` AS `user`
The target is resolved once, in this order:
- an explicit
namespace; - for a driver-created pool, a non-empty database path in
databaseUrl; - for a driver-created pool,
options.database; - otherwise unbound — SQL stays unqualified and is byte-for-byte what it was before this option existed.
A URL overrides copied pool options, and an empty URL path contributes neither a
namespace nor a pool-database override. Whichever source resolves the target,
the driver-created pool’s default database is set to that same value on a
copy of your options — the object you passed in is never mutated, and the
databaseUrl is parsed once at construction and never re-read, so the pool
cannot be pointed somewhere the resolved target never saw.
Effectful migrations need an attestation
A resolved database says which qualifier VibORM emits. It does not prove that a
qualified database.table reference reaches that database — mysql2 is also an
ordinary Vitess/PlanetScale client, and a proxy can rewrite qualifiers while
emulating host, handshake, vendor, and server-version evidence.
VibORM performs no backend detection. It asks you instead:
const db = createClient({
databaseUrl: process.env.DATABASE_URL,
namespace: "billing",
migrationNamespaceAttestation: "non-redirecting",
schema,
});
| Work | Needs namespace |
Needs the attestation |
|---|---|---|
| Runtime queries | No (unbound is valid) | No |
| Offline generation | No | No |
| Admitted read-only live migration commands | Yes | No |
| Effectful or concurrency-stable live migration work | Yes | Yes |
Omission fails closed, and the value is never inferred from the driver class, URL, host, server version, handshake, or the resolved namespace. It is immutable across transactions, nested transaction views, and the pinned migration session.
reset no longer does nothing
MySQL reset previously emitted only foreign-key-check toggles, so the CLI reset
dropped nothing. That silent no-op is fixed: reset is now real, the CLI
confirmation names the target namespace, and effectful reset additionally
requires the attestation. An unattested driver now refuses loudly where it
used to succeed while doing nothing.
Portable migration artifacts
Generated MySQL artifacts stay database-relative, so one estate deploys to
app_dev, app_test, and app_prod unchanged. Execution establishes the
selected database on one private pinned migration session; live push/reset SQL
and tracking SQL stay explicitly qualified.
Unqualified raw SQL remains caller-owned and follows the connection’s default database — see Namespaces.
Transactions
MySQL2 supports full transactions with savepoints for nested transactions — see Transactions.
Limitations
- No
RETURNINGclause - VibORM refetches created rows (using the driver-reported insert id for auto-increment keys) - JSON columns return as strings and are parsed automatically
- Boolean values stored as
TINYINT(1) updateMany/deleteManywith a self-relation filter (e.g. filtering users by their ownmanager/reportsrelation) fails with MySQL error 1093: MySQL cannot reference the table being updated/deleted in a filter subquery. Workaround: select the matching ids first, then mutate withwhere: { id: { in: ids } }.