TanStack Start integration
Run Ownfold through a real React, TanStack Start, Better Auth session resolver, Drizzle, and PostgreSQL-compatible full-stack boundary.
The TanStack Start adapter does not select a database or authentication provider. Choose both in the framework recipe builder; browser and React packages remain optional.
The production tracer under examples/tanstack-start exercises Ownfold’s browser, React, fetch,
TanStack Start, Better Auth, server, and Drizzle packages together. Vault metadata and application
records cross the same boundaries they use in a production deployment.
Boundary map
application-owned React controls
│
▼
@ownfold/browser ── encrypts and decrypts locally
│
▼
@ownfold/fetch ── sends only vault metadata and encrypted envelopes
│
▼
TanStack Start server route
│
├── Better Auth resolves the authenticated user
└── Drizzle stores vault metadata in PostgreSQL
Application journal route ── stores validated encrypted records separately
The Ownfold coordination transport never receives journal plaintext or application-record storage methods. The journal route is deliberately application-owned.
Vault server
Create the server with the application database and Better Auth instance:
import { betterAuthUserResolver } from "@ownfold/better-auth"
import { drizzleVaultAdapter } from "@ownfold/drizzle"
import { createVaultServer } from "@ownfold/server"
export const vaultServer = createVaultServer({
adapter: drizzleVaultAdapter(db),
getUserId: betterAuthUserResolver(auth),
})
drizzleVaultAdapter accepts normal schema-registered PostgreSQL Drizzle instances. The adapter
uses only ownfold_vaults; host-application tables remain independent.
Server route
TanStack Start server routes receive a Web Request and return a Web Response, so the adapter can
delegate directly to Ownfold’s canonical fetch handler:
import { createTanStackStartVaultHandlers } from "@ownfold/tanstack-start"
import { createFileRoute } from "@tanstack/react-router"
const handlers = createTanStackStartVaultHandlers({ server: vaultServer })
export const Route = createFileRoute("/api/ownfold/$")({
server: {
handlers,
},
})
Mount Better Auth separately at /api/auth/$. Configure Better Auth’s
tanstackStartCookies() plugin last so its session cookies are applied through TanStack Start.
Ownfold never receives login passwords or treats an encryption recovery secret as an account secret.
Browser client
The browser uses the framework-independent fetch transport:
import { createVaultClient } from "@ownfold/browser"
import { createFetchVaultTransport } from "@ownfold/fetch"
export const vault = createVaultClient({
transport: createFetchVaultTransport(),
})
Wrap the application with <VaultProvider client={vault}> and compose lifecycle controls from the
headless hooks. The application supplies all markup and styles.
Encrypted application records
The example’s journal route validates EncryptedEnvelopeV1 before persistence. It also checks the
authenticated namespace, record ID, and owner ID against the request route and session. Invalid,
unauthenticated, or context-mismatched writes are rejected before reaching Drizzle.
The Playwright test inspects the actual journal HTTP request body and fails if either plaintext field appears. It then loads ciphertext from Drizzle and decrypts it locally.
Run the tracer
pnpm --filter @ownfold/example-tanstack-start dev
The example uses in-memory PostgreSQL-compatible PGlite so it runs without external infrastructure.
Its fixed identity header is explicitly demo-only. Replace demoAuth with a real Better Auth
instance and use secure session cookies before adapting the example for production.