@perform/db’s source is a single re-export:
import { PrismaClient } from '@perform/db' in core-api is the client generated from packages/database. The client that packages/db generates for itself is only used by the Prisma CLI when it runs migrations.
Changing the schema
1
Edit the migration schema
Change the model in
packages/db/prisma/schema.prisma.2
Write the migration
Add
packages/db/prisma/migrations/<timestamp>_<name>/migration.sql. Migrations in this repo are hand written and applied with migrate deploy. Match Prisma’s naming so the schema and the SQL agree: quoted camelCase column names, indexes as <table>_<column>_idx, foreign keys as <table>_<column>_fkey.3
Mirror the model
Make the same change in
packages/database/prisma/schema.prisma. Keep the @@schema("public") line on the model. Declare every index in both files.4
Regenerate and rebuild
prisma generate, bundles with tsup and emits declarations. prisma generate does not need a database connection.5
Type check the API
6
Apply the migration
db:migrate is prisma migrate deploy. It applies to whatever database the env points at. Confirm the target first. Replay the full migration folder on a throwaway local Postgres before applying to a shared database.createPrismaClient
packages/database/prisma/client.ts.
pg Pool wrapped in PrismaPg.
Pool
A pool
error event (an idle client that died) is logged and does not crash the process.
Retry on transient errors
The client is extended with a$allOperations hook that retries a failed operation when the error is a transient connection problem:
- Prisma codes
P1001,P1002,P1008,P1017. - Messages matching “can’t reach database server”, “connection closed”,
ECONNRESET, “terminating connection”, “server closed the connection”, “connection terminated”.
The retry wraps single operations. An interactive transaction that loses its connection partway is not replayed as a whole. Keep transactions short.
Log filtering
Prismawarn and error events go to the supplied logger, with two exceptions that are logged at debug: transient connection errors (the retry handles them) and Unique constraint failed (expected where a constraint is used for dedup).
Two client instances in one process
Both are the same generated client class on the same database, each with its own pool of 10. Outside production the
db singleton is cached on globalThis so hot reloads do not open new pools.
Consequences:
- Importing
@repo/databasewithoutDATABASE_URLthrowsDATABASE_URL is not set. - A transaction on one instance cannot include a write on the other.
- The process can hold up to 20 connections.
Schemas in Postgres
auth are User, Session, Account, Verification, Passkey, TwoFactor, Organization, Member, Invitation, Purchase, Notification and UserNotificationPreference. Domain tables are in public. The domain model itself is documented in the data model section of these docs.
Exports of @repo/database
Client
createPrismaClient, db, Prisma, PrismaClient, PrismaClientInitializationError, PrismaClientKnownRequestError, and the types CreatePrismaClientOptions, PrismaLogger. Also the enums NotificationTarget and NotificationType.
Catch Prisma errors by class when you need the code:
Zod schemas
prisma/zod/index.ts is generated by prisma-zod-generator and exported from the package root. It provides a Zod schema per model, for example UserSchema and PurchaseSchema, which the auth tier packages use for input types.
Query helpers
prisma/queries/ holds functions used by the auth tier. They all use the db singleton.
countActiveCoachesByOrganizationId and countPlanTraineesByOrganizationId read domain tables (Coach, Client) through the organization link. They are how the billing procedures learn the team and trainee counts without calling core-api.
Admin deletion
admin-deletion.ts implements the platform admin’s delete studio and delete user flows as a staged process recorded in a deletion row with status QUIESCED, PURGING, DONE or FAILED:
- A preview lists what would be deleted and any blockers (for example a live billed subscription).
quiesceStudiostops the studio first: sign-in closes, pushes, API keys, hooks and automations stop.purgeStudiodeletes the data, and can be resumed if it fails partway.
AdminDeletionRefusedError. The oRPC procedures in @repo/api translate it for the admin panel.
Scripts in the packages
packages/db/prisma/seed.ts: the seed.packages/db/scripts/seed-demo/: a reversible demo seeder with a teardown and a verify step.packages/database/scripts/: one-off import tools for the food and exercise catalogs and the AutoFit migration, run through thedb:import:*root scripts.