Skip to content

Prisma.ORM reference

Source: src/Prisma/ORM/Contract.ts

A Prisma ORM v8 contract managed as an Alchemy resource.

Runs prisma contract emit and — when the contract’s storage shape has drifted from the migration graph — prisma migration plan as part of alchemy deploy, so the emitted contract artifacts (contract.json / contract.d.ts) and the migration packages under migrations/ are always regenerated from the source contract before anything downstream deploys. Pair it with Migrate to apply the planned packages to a database in the same deploy:

const contract = yield* Prisma.Contract("contract");
const migrate = yield* Prisma.Migrate("migrate", {
url: branch.origin.connectionString,
contract,
});

Plans that require a human decision (data backfills rendered as placeholder(...) closures in migration.ts) are never auto-answered: the deploy fails with instructions to fill the placeholder, self-emit the package (node migrations/app/<dir>/migration.ts), and commit — the next deploy then sees no drift and applies the committed package.

The resource is delete-safe: removing it from the stack does not wipe the migrations directory or the emitted contract, since both are checked in and shared with other environments.

Contract at the project root

// expects ./prisma.config.ts and writes ./migrations
const contract = yield* Prisma.Contract("contract");

Custom config location

const contract = yield* Prisma.Contract("contract", {
config: "./db/prisma.config.ts",
// resolved relative to ./db (the config's directory)
migrationsDir: "./migrations",
});
const contract = yield* Prisma.Contract("contract");
const project = yield* Neon.Project("db");
const branch = yield* Neon.Branch("main", { project });
yield* Prisma.Migrate("migrate", {
url: branch.origin.connectionString,
contract,
});

Source: src/Prisma/ORM/Migrate.ts

Applies pending Prisma migration packages to a Postgres database during alchemy deploy — the deploy-graph equivalent of running prisma db migrate --db $DATABASE_URL by hand.

The apply is idempotent: Prisma records the applied contract in the database’s prisma_contract.marker table and only walks the pending part of the migration graph, so re-deploys are no-ops and a database provisioned in the same deploy is bootstrapped from empty. Fresh databases that are still coming up are retried briefly (bounded) before failing.

Destroying the resource never touches the database — dropping tables is not the IaC engine’s call to make.

Neon branch

const contract = yield* Prisma.Contract("contract");
const project = yield* Neon.Project("db");
const branch = yield* Neon.Branch("main", { project });
yield* Prisma.Migrate("migrate", {
url: branch.origin.connectionString,
contract,
});

Prisma Postgres

const contract = yield* Prisma.Contract("contract");
const database = yield* Prisma.Database("db", { project });
yield* Prisma.Migrate("migrate", {
url: database.directConnectionString,
contract,
});

Pinning an environment to a contract ref

yield* Prisma.Migrate("migrate", {
url: branch.origin.connectionString,
contract,
to: "production",
});

Source: src/Prisma/ORM/Postgres.ts

Open a Prisma ORM v8 Postgres client from a connection URL, with Effect-native query surfaces over Prisma’s own builders and engine.

The client is built at most once per execution — a Worker fetch/queue/scheduled event, a Durable Object call, a Workflow run, or a Lambda invocation — and memoized on the execution’s Scope (via makeExecutionMemo), with close() registered as a scope finalizer so the underlying pg pool never outlives its event. That per-execution lifecycle is what makes the client safe on workerd, where sockets are pinned to the creating request’s IoContext. Construction does no I/O (Prisma connects lazily on the first query), so deploy/plan-time evaluations never touch the database.

Model types infer directly from the native TypeScript contract, together with the connection source’s error and requirement channels. No generated application imports are required. For Prisma rc.11, author contracts with defineContract from alchemy/Prisma/ORM to retain metadata lost by the upstream declarations. It uses Prisma’s native runtime builders.

import * as PrismaPostgres from "alchemy/Prisma/ORM/Postgres";
import { contract } from "./prisma/contract.ts";
const connection = yield* Cloudflare.Hyperdrive.Connect(hyperdrive);
const db = yield* PrismaPostgres.Postgres(
connection.connectionString,
{ contract },
);
fetch: Effect.gen(function* () {
// orm lane — queries ARE Effects, with typed errors
const user = yield* db.orm.public.User.where({ email }).include("posts").first();
const made = yield* db.orm.public.Post.create({ title, authorId: user.id });
// sql builder lane — pure plans, Effect executor
const rows = yield* db.execute(db.sql.public.user.select("id", "email").build());
// transactions — commit on success, rollback on failure/interrupt
yield* db.transaction((tx) =>
Effect.gen(function* () {
const u = yield* tx.orm.public.User.create({ email });
if (!u) return yield* tx.rollback();
return u;
}),
);
});
import { makeDatabase } from "./prisma/generated/client.ts";
const db = yield* makeDatabase(connection.connectionString);
const users = yield* db.orm.public.User.select("id", "email").all();

Run alchemy prisma generate with withEffect registered in the ORM configuration. The factory delegates to this runtime with Prisma’s emitted Contract type and contractJson; query behavior and cleanup are identical. Generated schemas.ts can be imported independently of this client.

const findUser = yield* db.prepare({ email: "pg/text@1" }, (sql, params) =>
sql.public.user
.select("id", "email")
.where((fields, fns) => fns.eq(fields.email, params.email))
.build(),
);
const users = yield* findUser.query({ email: "alice@example.com" });
const rows = findUser.query({ email: "alice@example.com" }).stream;

Prepared queries resolve the current execution’s client for each run. Inside db.transaction, tx.prepare binds executions to that transaction instead. An affected-count plan returns a prepared mutation with execute(params); a row-returning plan returns a prepared query with query(params).

The Stream surfaces use Prisma’s async iterators. The long-lived Postgres driver buffers raw results, so these streams do not guarantee bounded memory.

Queries are lazy and re-runnable: each evaluation replays the chain against the execution’s client, so Effect.retry re-issues the query. Failures surface as granular tagged errors: the SQL-standard integrity violations each get their own tag (Prisma.UniqueViolationError, Prisma.ForeignKeyViolationError, Prisma.NotNullViolationError, Prisma.CheckViolationError), other statement failures are Prisma.QueryError (with the normalized sqlState), connection failures are Prisma.ConnectionError (with the driver’s transient verdict), and Prisma’s structured codes split by category into Prisma.OrmError / Prisma.RuntimeError with an autocompleting code field.