Prisma
Merge Ownfold's models into a Prisma schema, deploy migrations, generate the client, and understand the structural adapter contract.
@ownfold/prisma connects a generated Prisma Client to Ownfold’s framework-independent storage
contract. The adapter does not bundle Prisma Client, a query engine, or a datasource configuration.
Install
pnpm add @ownfold/prisma @ownfold/server @prisma/client
pnpm add -D prisma
Use the Prisma versions already selected by your application. The generated client must expose the
four Ownfold model delegates and interactive $transaction.
Add the models
Generate the complete model fragment first:
pnpm add -D @ownfold/cli
pnpm ownfold init --database prisma --identity custom
Merge ownfold/schema.prisma into the application’s schema.
Copy all four models from the package’s prisma/schema.prisma fragment into the application’s
schema:
OwnfoldVault;OwnfoldDevice;OwnfoldPairing; andOwnfoldRotation.
Keep the @map, @@map, relation, unique, and index declarations. The adapter expects the generated
delegate names ownfoldVault, ownfoldDevice, ownfoldPairing, and ownfoldRotation.
Create and deploy the migration
In development, create a reviewed migration:
pnpm prisma validate
pnpm prisma migrate dev --name add-ownfold-vault-metadata
pnpm prisma generate
Commit the schema and generated migration directory. In production, apply pending migrations with:
pnpm prisma migrate deploy
Do not use prisma db push as the production migration workflow. It does not create the immutable
migration history needed for reliable upgrades and rollback planning.
For an existing Ownfold installation, compare your schema with the current package fragment. Ensure
the lastActiveAt field and its database column exist before deploying the current adapter.
Create the adapter
import { prismaVaultAdapter } from "@ownfold/prisma"
import { createVaultServer } from "@ownfold/server"
import { prisma } from "./database"
export const vaultServer = createVaultServer({
adapter: prismaVaultAdapter(prisma),
getUserId: async ({ request }) => {
const session = await readApplicationSession(request)
return session?.user.id ?? null
},
})
The input is structural: your generated client is accepted when it supplies the required model delegates and transaction callback. Ownfold does not import a generated client from your project.
ownfoldVaultPrismaVaultDelegate
PrismaVaultDelegateownfoldDevicePrismaDeviceDelegate
PrismaDeviceDelegateownfoldPairingPrismaPairingDelegate
PrismaPairingDelegateownfoldRotationPrismaRotationDelegate
PrismaRotationDelegate$transaction<T>(operation: (client: PrismaTransactionClient) => Promise<T>) => Promise<T>
<T>(operation: (client: PrismaTransactionClient) => Promise<T>) => Promise<T>Client lifecycle
Create one long-lived Prisma Client per server process in production. In development environments with module hot reload, use your framework’s recommended singleton pattern so each reload does not open another pool. Disconnect during controlled process shutdown after the HTTP server stops accepting requests.
Transaction guarantees
Rotation completion uses an interactive transaction. Within it, the adapter validates the vault, rotation, and every active device revision before committing replacements. Pairing approval also registers the new device atomically.
If any record is stale, the adapter aborts the transaction and returns ConflictError. Previously
committed vault state remains valid. Do not catch transaction errors and return success from a
wrapper around the adapter.
Application ciphertext model
Ownfold does not add application-specific record tables. A host model can store the versioned
envelope as Json:
model PrivateNote {
id String @id
ownerId String
encryptedPayload Json
createdAt DateTime @default(now())
@@index([ownerId, createdAt])
@@map("private_notes")
}
The server must authorize ownerId before returning the envelope. The browser decrypts it using
the matching namespace and record ID. Never add plaintext title or content columns for values
that the server must not read.
Prisma’s Json type describes serialization but does not replace Ownfold runtime parsing. Validate
untrusted persisted envelopes through @ownfold/core when they cross into application code.
Schema ownership and upgrades
Your application owns its merged Prisma schema and migration history. When upgrading
@ownfold/prisma:
Read the Ownfold release notes
Identify model or index changes introduced since the installed version.
Update the model fragment
Merge changes without removing application-specific schema declarations.
Generate and review a migration
Confirm it changes only the intended Ownfold tables and preserves existing rows.
Deploy migration before code
Run prisma migrate deploy, then release code that depends on the new columns.
Regenerate and test
Generate Prisma Client and run the shared adapter compliance suite.
Test the integration
import { checkVaultRotationAdapterCompliance } from "@ownfold/testing"
import { prismaVaultAdapter } from "@ownfold/prisma"
const result = await checkVaultRotationAdapterCompliance(prismaVaultAdapter(prisma))
if (result.status === "error") throw result.error
Run this against a disposable database. The suite intentionally creates records and stale revisions. Ownfold’s release gate generates a real Prisma Client and runs the same suite on PostgreSQL.
Troubleshooting
The client is not assignable to PrismaVaultClient
Regenerate Prisma Client after adding all four models. Confirm the model names and relations match
the packaged fragment and that the client supports interactive $transaction.
prisma.ownfoldVault is undefined
The running process is using an older generated client. Stop the dev server, run prisma generate,
and restart it. In a monorepo, verify the server imports the client generated from the intended
schema.
Production reports a missing column
The application code was deployed before its migration. Apply pending migrations with
prisma migrate deploy; do not patch the row into a success shape.
Rotation fails after a transaction timeout
Inspect server-side Prisma and database logs. Rotation cutover should remain a short metadata transaction; record rewrapping happens before cutover. Do not extend transaction timeouts without first identifying slow queries or lock contention.
Production checklist
- Copy and retain all four model definitions.
- Commit reviewed migrations.
- Run
prisma migrate deploybefore new application code. - Regenerate Prisma Client during the build.
- Use one correctly managed client per process.
- Run the compliance suite against real PostgreSQL.
- Keep encrypted application data in host-owned models.
- Never log Prisma JSON payloads containing user ciphertext unnecessarily.