Skip to content

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.

Terminal window
npm i @persql/sdk drizzle-orm
import { 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.

The codegen CLI introspects a live database (over the same /v1 API) and emits a Drizzle schema:

Terminal window
PERSQL_TOKEN=psql_live_… npx persql-codegen \
--db acme/orders \
--out src/db/schema.ts

It 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.

  • No prepared statements — every Drizzle query becomes one HTTP request. Use db.batch([...]) for multi-statement work to amortize the round-trip.
  • Async onlyprepare().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 used better-sqlite3’s sync API, expect to await.
  • No transactions API on the proxy adapter — Drizzle’s proxy adapter doesn’t expose db.transaction(...). Use PerSQL’s db.transaction([{ sql, params }, ...]) directly, or wrap with BEGIN; … COMMIT; in a single db.run() call for ad-hoc work.

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.