Application
Data that stays in sync
SQLite migrations, transactions and topic-based live updates.
SQLite through Kysely
ajo-kit/database exposes connect(), db(), close() and Kysely’s SQL tools. Keep reads explicit and bounded. Use a transaction for a logical write that changes several rows.
import { connect, db as database } from 'ajo-kit/database'
type Data = { notes: { id: number; text: string } }
connect(process.env.DATABASE_PATH ?? 'app.sqlite')
export const db = () => database<Data>()Evolve the schema deliberately
Every migration exports up() and down(). Each project or plugin owns a contiguous sequence starting at 0001. Qualified identities share one history, lock and transaction boundary.
pnpm exec kit migrate create notes
pnpm exec kit migrate up
pnpm exec kit migrate status
# Revert the most recently executed migration:
pnpm exec kit migrate downTrack what a loader reads
A loader tracks each topic it depends on. After a durable write commits, an action emits the changed topics. Active routes revalidate over SSE and receive a complete route payload. For API handlers and other server work, use emit() from ajo-kit/server.
// In a loader, before reading notes:
req.track?.('notes')
// In an action, after the write commits:
action.emit('notes')Give on-disk data a home
Declare a filesystem root in package.json, create that writable directory, and set AJO_DATA=/ajo/data. DATABASE_PATH is relative to it. An in-memory SQLite database does not need a filesystem grant.
Topics and SSE fanout live in one process. Multiple processes require shared coordination. Persistent SQLite also needs an operational backup and restore plan.
{
"kit": {
"engine": {
"fs": { "roots": ["/ajo/data"] }
}
}
}