Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

Database adapter API reference

Complete SQLite, PostgreSQL, Drizzle, and Prisma adapter reference with constructors, schemas, driver contracts, transactions, migrations, errors, and compliance requirements.

Database adapters persist vault coordination metadata: vault identity, recovery verification status, devices, pairing requests, and rotation checkpoints. They do not store application encrypted records unless the host creates separate application tables and routes.

All official adapters implement VaultAdapter & VaultRotationAdapter and return typed Result values.

Choose an adapter

Package Database/API Factory or class Schema source
@ownfold/sqlite Node DatabaseSync SqliteVaultAdapter ownfoldSqliteSchema
@ownfold/postgres Driver-neutral SQL executor PostgresVaultAdapter, postgresVaultAdapter CLI migration
@ownfold/postgres/node-postgres pg.Pool nodePostgresVaultAdapter CLI migration
@ownfold/drizzle Drizzle PostgreSQL database DrizzleVaultAdapter, drizzleVaultAdapter @ownfold/drizzle/schema
@ownfold/prisma Structurally typed Prisma client PrismaVaultAdapter, prismaVaultAdapter CLI Prisma schema fragment

SQLite

pnpm add @ownfold/sqlite@beta
import { DatabaseSync } from "node:sqlite"
import {
  applyOwnfoldSqliteSchema,
  SqliteVaultAdapter,
} from "@ownfold/sqlite"

const database = new DatabaseSync("ownfold.db")
applyOwnfoldSqliteSchema(database)

export const adapter = new SqliteVaultAdapter(database)

Exports

Export Type Purpose
ownfoldSqliteSchema string Idempotent strict-table SQL for all coordination tables and indexes.
applyOwnfoldSqliteSchema(database) (DatabaseSync) => void Executes the bundled schema on an existing connection.
SqliteVaultAdapter class Synchronous-driver implementation wrapped in async result methods.

The adapter enables foreign keys in the schema and stores each validated domain record as JSON in strict tables keyed by its primary identity. Mutations that span records run inside SQLite transactions. Use one controlled migration step before serving requests; do not call the schema helper in every request.

DatabaseSync is a Node runtime API. Browser SQLite or unrelated WASM database objects are not accepted by this package.

PostgreSQL

pnpm add @ownfold/postgres@beta

Driver-neutral executor

PropType
query<Row extends Record<string, unknown>>(text: string, values?: readonly unknown[] | undefined) => PromiseLike<PostgresQueryResult<Row>>
Type<Row extends Record<string, unknown>>(text: string, values?: readonly unknown[] | undefined) => PromiseLike<PostgresQueryResult<Row>>
transaction<T>(operation: (executor: PostgresExecutor) => Promise<T>) => Promise<T>
Type<T>(operation: (executor: PostgresExecutor) => Promise<T>) => Promise<T>
import { postgresVaultAdapter } from "@ownfold/postgres"

export const adapter = postgresVaultAdapter({
  query: (text, values) => database.query(text, values),
  transaction: (operation) => database.transaction((tx) => operation(tx)),
})

query must preserve parameter binding; never interpolate identifiers or values from Ownfold inputs. transaction must commit only when the callback completes and roll back on thrown or rejected failure. Rotation completion and pairing approval depend on this atomic boundary.

PropType
rowsreadonly Row[]
Typereadonly Row[]
rowCount?number | null
Typenumber | null

pg convenience entry point

import { Pool } from "pg"
import { nodePostgresVaultAdapter } from "@ownfold/postgres/node-postgres"

const pool = new Pool({ connectionString: process.env.DATABASE_URL })
export const adapter = nodePostgresVaultAdapter(pool)

The wrapper obtains a dedicated client for transactions and always releases it. Configure TLS, pool size, timeouts, and shutdown in the host application.

Class and factory

new PostgresVaultAdapter(executor) exposes the concrete implementation. postgresVaultAdapter(executor) returns the narrower contract intersection. Prefer the factory unless tests or dependency injection need the concrete class identity.

Could not generate a type table for PostgresVaultAdapter: No interface or type named "PostgresVaultAdapter" found.

Drizzle

pnpm add @ownfold/drizzle@beta drizzle-orm
import { drizzleVaultAdapter } from "@ownfold/drizzle"
import * as ownfoldSchema from "@ownfold/drizzle/schema"
import { drizzle } from "drizzle-orm/node-postgres"

const db = drizzle(pool, { schema: ownfoldSchema })
export const adapter = drizzleVaultAdapter(db)

Adapter exports

Export Purpose
DrizzleVaultAdapter Concrete PostgreSQL Drizzle adapter class.
drizzleVaultAdapter(database) Factory returning VaultAdapter & VaultRotationAdapter.
Could not generate a type table for DrizzleVaultAdapter: No interface or type named "DrizzleVaultAdapter" found.

Schema entry point

@ownfold/drizzle/schema exports:

  • ownfoldVaults;
  • ownfoldDevices;
  • ownfoldPairings; and
  • ownfoldRotations.

Register these tables in the same schema object used to construct the database. The adapter imports the canonical table objects, so redefining lookalike tables does not replace them. Generate and apply SQL migrations through the CLI or the application’s Drizzle migration workflow.

Prisma

pnpm add @ownfold/prisma@beta @prisma/client
import { prismaVaultAdapter } from "@ownfold/prisma"
import { prisma } from "@/src/lib/prisma"

export const adapter = prismaVaultAdapter(prisma)
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>

The package uses structural delegate interfaces instead of importing a generated application client type. The client must expose ownfoldVault, ownfoldDevice, ownfoldPairing, ownfoldRotation, and interactive $transaction with the methods in PrismaVaultClient.

Public Prisma types

Category Exports
Client boundaries PrismaVaultClient, PrismaTransactionClient
Vault PrismaVaultRow, PrismaVaultDelegate
Device PrismaDeviceRow, PrismaDeviceCreateRow, PrismaDeviceDelegate
Pairing PrismaPairingRow, PrismaPairingCreateRow, PrismaPairingDelegate
Rotation PrismaRotationRow, PrismaRotationDelegate
JSON PrismaJsonValue, PrismaJsonNestedValue
Adapter PrismaVaultAdapter, prismaVaultAdapter

These interfaces are extension seams, not a second schema. Generate the canonical model fragment with the CLI, merge it into the application schema, migrate, then regenerate Prisma Client.

Shared adapter methods

Every official adapter implements the same operations:

Family Methods
Vault getVault, createVault, updateRecoveryStatus, replaceRecoveryKit
Devices listDevices, registerDevice, touchDevice, revokeDevice
Pairing createPairing, getPairing, approvePairing, cancelPairing
Rotation getRotation, beginRotation, updateRotationProgress, completeRotation

Read methods return StorageAdapterError on driver or stored-data failure. Mutations may additionally return ConflictError for duplicate identity or stale revision. Adapters do not throw expected conflicts and never return raw driver errors to the server.

Transaction requirements

Operation Atomic requirement
Pairing approval Mark request approved and register the exact offered device together.
Rotation completion Validate revisions, update vault key/Recovery Kit metadata, replace every active device envelope, and complete rotation together.
Revision mutation Include ownership, identity, status, and expected revision in the write predicate.

A transaction callback succeeding after a partial write is a correctness bug. Timeouts after an unknown commit outcome must be reconciled by reading current state and relying on idempotent identities.

Migrations

pnpm ownfold generate
pnpm ownfold migrate --database-url "$DATABASE_URL"

SQLite may use --database-path. Drizzle and Prisma users may apply the generated artifacts through their native migration system. Keep migrations in application source control and apply them before new code begins serving.

Application record tables

The adapter schema does not create tables for notes, messages, documents, or other application records. Store each encryptedPayload in an application-owned JSON/JSONB/text column with its server-authorized owner and immutable record ID. Validate writes through VaultServer.validateEncryptedRecordWrite.

Compliance and custom adapters

Run both official compliance suites against a disposable real database:

import {
  checkVaultAdapterCompliance,
  checkVaultRotationAdapterCompliance,
} from "@ownfold/testing"

await checkVaultAdapterCompliance(adapter)
await checkVaultRotationAdapterCompliance(adapter)

See Adapter and transport contracts, Testing APIs, and the complete export index.

Last updated on August 4, 2026

Was this page helpful?