@veap/database manages schema versions through reversible migrations, propagates database transactions across async call stacks using AsyncLocalStorage, and includes model factories and seeders for test fixtures and development data.
Schema migrations and CLI commands#
Migrations live in your project's migrations/ folder and use the Schema facade from @veap/database (or @veap/database/migrations). Each migration exports a unique name, an up() function, and an optional down() function:
// migrations/20261001_create_articles.ts
import { type Migration, Schema } from "@veap/database";
export const createArticlesMigration: Migration = {
name: "20261001_create_articles",
async up() {
await Schema.create("articles", (table) => {
table.increments("id").primary();
table.integer("author_id").notNullable().index();
table.string("title").notNullable();
table.string("slug").notNullable().unique();
table.text("content").notNullable();
table.boolean("is_published").notNullable().defaultTo(false);
table.json("metadata").nullable();
table.timestamps(true, true);
});
},
async down() {
await Schema.dropIfExists("articles");
},
};Managing migrations from the CLI#
MigrationServiceProvider registers four console commands in the veap CLI:
bun veap make:migration <name>— Scaffolds a timestamped migration file inmigrations/and adds it tomigrations/index.ts.bun veap migrate— Runs all pending migrations.bun veap migrate:status— Displays applied and pending migrations with their batch numbers.bun veap migrate:rollback— Rolls back the most recent migration batch (pass--forcein non-interactive CI environments).
Transactions with AsyncLocalStorage#
Wrap multi-step database writes in transaction(async () => { ... }). Because @veap/database stores the active Knex transaction in Node's AsyncLocalStorage, every Model.query(), model.save(), and dbClient call inside the callback automatically participates in the transaction without passing a transaction object through function arguments:
import { transaction } from "@veap/database";
import { Article } from "@/lib/models/article";
import { Author } from "@/lib/models/author";
await transaction(async () => {
const author = await Author.create({
name: "Ada Lovelace",
email: "ada@example.com",
});
await Article.create({
author_id: author.id,
title: "Notes on the Analytical Engine",
slug: "notes-on-the-analytical-engine",
content: "...",
is_published: true,
});
// Throws anywhere inside this block roll back both inserts automatically.
});Model factories and database seeders#
@veap/database includes defineFactory (and the Factory base class) along with Seeder and runSeeders for generating test fixtures and initial development data:
import { defineFactory, runSeeders, Seeder } from "@veap/database";
import { Article } from "./models/article";
export const ArticleFactory = defineFactory(Article, () => ({
title: "Sample Article",
slug: `sample-article-${Math.random().toString(36).slice(2, 8)}`,
content: "Generated by ArticleFactory",
is_published: true,
}));
export class DatabaseSeeder extends Seeder {
async run(): Promise<void> {
// Create and persist 5 articles with rotating sequence states
await ArticleFactory.count(5)
.sequence({ is_published: true }, { is_published: false })
.create();
}
}
// Run seeders wrapped in an AsyncLocalStorage database transaction
await runSeeders(DatabaseSeeder, { inTransaction: true });