Database adapters
Integrate Ownfold vault metadata using Drizzle, Prisma, direct PostgreSQL, SQLite, or the shared storage-adapter contract.
Ownfold stores only vault coordination metadata. Encrypted application records remain in the host
application’s own tables. Every official adapter implements VaultAdapter and
VaultRotationAdapter and runs the shared checkVaultRotationAdapterCompliance suite for
idempotent creation, optimistic concurrency, and atomic root-key rotation cutover.
| Adapter | Best fit | Detailed guide |
|---|---|---|
| SQLite | One Node.js process, local or modest coordination traffic | SQLite |
| Direct PostgreSQL | Applications using pg or another query layer |
PostgreSQL |
| Drizzle | PostgreSQL applications with a Drizzle schema | Drizzle |
| Prisma | Applications with a generated Prisma Client | Prisma |
SQLite
@ownfold/sqlite targets Node.js 22.13 or newer and uses the built-in node:sqlite driver, so it
does not add a native third-party runtime dependency:
import { DatabaseSync } from "node:sqlite"
import { applyOwnfoldSqliteSchema, SqliteVaultAdapter } from "@ownfold/sqlite"
const database = new DatabaseSync("ownfold.sqlite")
applyOwnfoldSqliteSchema(database)
const adapter = new SqliteVaultAdapter(database)
Run the idempotent schema function during application migration or startup before serving vault
requests. The adapter validates every JSON record read from SQLite, uses BEGIN IMMEDIATE for
serialized metadata updates, and commits rotation cutover atomically. Give it a dedicated database
connection and do not call it from inside another transaction on that connection. Because
DatabaseSync is synchronous, use PostgreSQL for high-write, horizontally scaled deployments;
SQLite is intended for single-process and edge-local Node deployments with modest coordination
traffic.
Drizzle and PostgreSQL
Apply packages/drizzle/migrations/0000_ownfold_vaults.sql followed by
0001_ownfold_devices.sql, 0002_ownfold_pairings.sql, 0003_ownfold_rotations.sql, and
0004_ownfold_device_last_active.sql. Include the four Ownfold tables in the Drizzle schema and
construct the adapter:
import { drizzleVaultAdapter } from "@ownfold/drizzle"
import {
ownfoldDevices,
ownfoldPairings,
ownfoldRotations,
ownfoldVaults,
} from "@ownfold/drizzle/schema"
const db = drizzle(pool, {
schema: { ownfoldVaults, ownfoldDevices, ownfoldPairings, ownfoldRotations },
})
const adapter = drizzleVaultAdapter(db)
The adapter uses parameterized Drizzle queries, ON CONFLICT DO NOTHING for idempotent creation,
and a revision predicate for atomic recovery-status updates. Device activity updates use a
server-issued timestamp and intentionally leave the security revision unchanged. Its generic database input preserves
compatibility with schema-registered Drizzle clients without exposing host schema objects through
Ownfold’s shared adapter contract.
Prisma
Copy the Ownfold models from packages/prisma/prisma/schema.prisma into the application’s schema.
For an existing installation, apply the packaged device_last_active migration or create an
equivalent backfill migration before regenerating the client. Then pass the generated client:
import { prismaVaultAdapter } from "@ownfold/prisma"
const adapter = prismaVaultAdapter(prisma)
The adapter depends structurally on the generated Ownfold delegates and does not bundle a Prisma client or engine. Rotation completion uses an interactive transaction. Copy all four models before running the application’s migration workflow.
Direct PostgreSQL
Apply PostgreSQL migrations 0000 through 0004 in order, then provide a parameterized query
executor. A node-postgres pool can be exposed directly or through a small wrapper matching
PostgresExecutor:
import { nodePostgresVaultAdapter } from "@ownfold/postgres/node-postgres"
const adapter = nodePostgresVaultAdapter(pool)
All returned database rows are validated before becoming VaultRecord values. Malformed stored
metadata becomes a typed StorageAdapterError; raw driver errors and SQL details are not exposed to
the browser.
Pairing approval must register the device and approve the request atomically. Rotation completion must update every active device envelope, the Recovery Kit ID, the vault key version, and the rotation state in one transaction. The shared compliance suite deliberately supplies one stale device revision after another device could have been updated, then verifies that every prior value was preserved.
Real PostgreSQL release gate
pnpm test:postgres runs the complete lifecycle and rotation compliance suite through direct
node-postgres, Drizzle, and a generated Prisma Client against a real PostgreSQL server. It is
separate from the fast PGlite suite and is required by pnpm verify. The pull-request workflow
provisions PostgreSQL 16.
Set OWNFOLD_TEST_POSTGRES_URL to a local database named exactly ownfold_test. The test harness
refuses remote hosts and every other database name before issuing its schema reset, so an accidental
production URL is not touched. It drops and recreates the public schema on every run; the named
database must contain test data only.
OWNFOLD_TEST_POSTGRES_URL="postgresql://postgres:postgres@127.0.0.1:5432/ownfold_test" \
pnpm test:postgres
This lane executes the checked-in adapter migrations before each direct and Drizzle compliance run. For Prisma, it copies the packaged model fragment into an isolated schema, generates a Prisma 7.6 client, applies that schema to the disposable database, proves the generated client satisfies Ownfold’s structural adapter contract without a type assertion, and runs the same compliance suite. Prisma remains a peer dependency of the published adapter; the pinned client is test infrastructure only. PGlite remains valuable for fast tests, but passing PGlite alone is not presented as PostgreSQL production proof.