Drizzle ORM
PerSQL exposes a drizzle-orm/sqlite-proxy driver and a small codegen
CLI, so a TypeScript app can use PerSQL with full Drizzle ergonomics
— typed schemas, fluent query builder, migrations.
npm i @persql/sdk drizzle-ormimport { drizzle } from "drizzle-orm/sqlite-proxy";import { PerSQL } from "@persql/sdk";import * as schema from "./db/schema";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });const db = drizzle(persql.database("acme/orders").driver(), { schema });
const recent = await db.select().from(schema.orders).limit(10);db.driver() returns the callback shape the proxy adapter expects.
Internally it forwards to PerSQL’s /v1/.../query and reshapes rows
to match method = all|run|get|values.
Generate the schema file
Section titled “Generate the schema file”The codegen CLI introspects a live database (over the same /v1 API)
and emits a Drizzle schema:
PERSQL_TOKEN=psql_live_… npx persql-codegen \ --db acme/orders \ --out src/db/schema.tsIt writes one sqliteTable(...) per user table, mapping SQLite types
to Drizzle column helpers (integer, real, blob, text).
Re-run after schema changes — the file is meant to be regenerated,
not edited.
Tradeoffs
Section titled “Tradeoffs”- No prepared statements — every Drizzle query becomes one HTTP
request. Use
db.batch([...])for multi-statement work to amortize the round-trip. - Async only —
prepare().all()is a Promise, not a sync iterator. Drizzle’s typed APIs assume this; nothing changes for you, but if you’re porting code that usedbetter-sqlite3’s sync API, expect to await. - No transactions API on the proxy adapter — Drizzle’s
proxyadapter doesn’t exposedb.transaction(...). Use PerSQL’sdb.transaction([{ sql, params }, ...])directly, or wrap withBEGIN; … COMMIT;in a singledb.run()call for ad-hoc work.
Migrations
Section titled “Migrations”Use the in-product Migrations tab (per-database) for schema
evolution. Drizzle’s own migration runner (drizzle-kit) emits SQL
files you can paste directly into a new migration row in PerSQL.