Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

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; and
  • OwnfoldRotation.

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.

PropType
ownfoldVaultPrismaVaultDelegate
TypePrismaVaultDelegate
ownfoldDevicePrismaDeviceDelegate
TypePrismaDeviceDelegate
ownfoldPairingPrismaPairingDelegate
TypePrismaPairingDelegate
ownfoldRotationPrismaRotationDelegate
TypePrismaRotationDelegate
$transaction<T>(operation: (client: PrismaTransactionClient) => Promise<T>) => Promise<T>
Type<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 deploy before 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.

Last updated on August 4, 2026

Was this page helpful?