database-migrations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Database Migrations with @schemavaults/dbh

使用 @schemavaults/dbh 进行数据库迁移

@schemavaults/dbh
provides Kysely migrations for PostgreSQL, applied through the
dbh
CLI. Migrations are opinionated: every file is a numbered module that exports an
up()
and a
down()
function. TypeScript source migrations are built to JavaScript first, then applied with the CLI.
Invoke the CLI with your package runner. Use
bunx @schemavaults/dbh
for validating and building migrations —
build-db-migrations
uses Bun's bundler and requires Bun anyway. Use
npx @schemavaults/dbh
for running / applying migrations (
migrate
and
reverse
): most PostgreSQL drivers are built for Node.js rather than Bun, so apply migrations on the Node runtime.
@schemavaults/dbh
为 PostgreSQL 提供了基于 Kysely 的迁移功能,通过
dbh
CLI 执行。迁移是约定式的:每个文件都是一个带编号的模块,导出
up()
down()
函数。TypeScript 源码迁移会先被构建为 JavaScript,再通过 CLI 应用
使用你的包运行器调用 CLI。使用
bunx @schemavaults/dbh
验证和构建迁移 ——
build-db-migrations
命令使用 Bun 的打包器,因此本身就需要 Bun 环境。使用
npx @schemavaults/dbh
运行/应用迁移(
migrate
reverse
):大多数 PostgreSQL 驱动是为 Node.js 而非 Bun 构建的,因此请在 Node 运行时上执行迁移。

One-time setup (for consumers)

一次性设置(面向使用者)

Migrations import the
sql
template tag from
@/sql
rather than directly from the package. This indirection is required by the build step (see the note under "Building migrations"), so configure it once:
  1. Create a local
    sql
    module
    somewhere in your source tree, e.g.
    ./src/db/sql.ts
    , that re-exports the tag from the package:
    ts
    // src/db/sql.ts
    export { sql, sql as default } from "@schemavaults/dbh/sql";
    export type * from "@schemavaults/dbh/sql";
  2. Configure the
    @/sql
    path alias
    in your
    tsconfig.json
    so migration sources typecheck and resolve:
    jsonc
    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@/sql": ["./src/db/sql.ts"]
        }
      }
    }
  3. Create a migrations directory, e.g.
    ./src/db/migrations/
    , and add your numbered migration files there.
迁移文件从
@/sql
而非直接从包中导入
sql
模板标签。这种间接层是构建步骤所必需的(参见“构建迁移”下的说明),因此只需配置一次:
  1. 创建本地
    sql
    模块
    :在你的源码目录中创建一个文件,例如
    ./src/db/sql.ts
    ,从包中重新导出该标签:
    ts
    // src/db/sql.ts
    export { sql, sql as default } from "@schemavaults/dbh/sql";
    export type * from "@schemavaults/dbh/sql";
  2. tsconfig.json
    中配置
    @/sql
    路径别名
    ,以便迁移源码可以通过类型检查和路径解析:
    jsonc
    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@/sql": ["./src/db/sql.ts"]
        }
      }
    }
  3. 创建迁移目录,例如
    ./src/db/migrations/
    ,并在其中添加带编号的迁移文件。

Migration file format

迁移文件格式

Each migration is a single file in your migrations directory. The rules are:
  1. The directory is non-empty.
  2. Each file name is prefixed with a 5-digit migration number, followed by a short kebab-case description, e.g.
    00000-template-migration.ts
    ,
    00001-create-users-table.ts
    . The number defines apply order.
  3. Each module exports an
    up(db)
    and a
    down(db)
    function.
    up()
    applies the change;
    down()
    must reverse it exactly so migrations can be rolled back.
  4. Migration numbers are unique — never reuse a number. If two branches both add
    00040-*.ts
    , that collision must be resolved by renumbering one of them before merge.
Both
up
and
down
receive a
Kysely<any>
instance and return a
Promise
. Import the
Kysely
type from the package:
import type { Kysely } from "@schemavaults/dbh"
.
每个迁移都是迁移目录中的一个单独文件。规则如下:
  1. 目录非空。
  2. 每个文件名以5位迁移编号为前缀,后跟简短的 kebab-case 格式描述,例如
    00000-template-migration.ts
    00001-create-users-table.ts
    。编号决定了应用顺序。
  3. 每个模块导出
    up(db)
    down(db)
    函数。
    up()
    应用变更;
    down()
    必须精确回滚该变更,以便迁移可以撤销。
  4. 迁移编号唯一 —— 绝不要重复使用编号。如果两个分支都添加了
    00040-*.ts
    ,必须在合并前通过重新编号其中一个来解决冲突。
up
down
都接收一个
Kysely<any>
实例并返回一个
Promise
。从包中导入
Kysely
类型:
import type { Kysely } from "@schemavaults/dbh"

Example: using the
Kysely<any>
query builder

示例:使用
Kysely<any>
查询构建器

Prefer the typed query builder for schema operations:
ts
// 00001-create-users-table.ts
import type { Kysely } from "@schemavaults/dbh";

export async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable("users")
    .addColumn("user_id", "uuid", (col) => col.primaryKey())
    .addColumn("email", "text", (col) => col.notNull().unique())
    .addColumn("created_at", "bigint", (col) => col.notNull())
    .execute();
}

export async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable("users").execute();
}
优先使用类型化查询构建器进行 schema 操作:
ts
// 00001-create-users-table.ts
import type { Kysely } from "@schemavaults/dbh";

export async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable("users")
    .addColumn("user_id", "uuid", (col) => col.primaryKey())
    .addColumn("email", "text", (col) => col.notNull().unique())
    .addColumn("created_at", "bigint", (col) => col.notNull())
    .execute();
}

export async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable("users").execute();
}

Example: using the
sql
template tag

示例:使用
sql
模板标签

For statements the builder can't express (or raw DDL), import
sql
from
@/sql
(your local module from setup, which re-exports Kysely's
sql
tag) and call
.execute(db)
:
ts
// 00002-create-squirrels-table.ts
import type { Kysely } from "@schemavaults/dbh";
import { sql } from "@/sql";

export async function up(db: Kysely<any>): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS EXAMPLE_SQUIRRELS (
      squirrel_id UUID PRIMARY KEY,
      squirrel_name TEXT NOT NULL,
      created_at BIGINT NOT NULL
    );
  `.execute(db);

  // Always interpolate values via ${...}; the sql tag parameterizes them.
  await sql`CREATE INDEX squirrels_name_idx ON EXAMPLE_SQUIRRELS (squirrel_name);`.execute(
    db,
  );
}

export async function down(db: Kysely<any>): Promise<void> {
  await sql`DROP TABLE IF EXISTS EXAMPLE_SQUIRRELS;`.execute(db);
}
Important: migration files must always import
sql
from
@/sql
, never directly from
@schemavaults/dbh/sql
. The
build-db-migrations
step rewrites the literal
@/sql
import specifier to a relative path pointing at the built, standalone
sql.js
, so the import must be written exactly as
@/sql
for the build to work. (This is why the one-time setup configures the
@/sql
alias.)
对于构建器无法表达的语句(或原生 DDL),从
@/sql
导入
sql
(即你在设置中创建的本地模块,它重新导出 Kysely 的
sql
标签)并调用
.execute(db)
ts
// 00002-create-squirrels-table.ts
import type { Kysely } from "@schemavaults/dbh";
import { sql } from "@/sql";

export async function up(db: Kysely<any>): Promise<void> {
  await sql`
    CREATE TABLE IF NOT EXISTS EXAMPLE_SQUIRRELS (
      squirrel_id UUID PRIMARY KEY,
      squirrel_name TEXT NOT NULL,
      created_at BIGINT NOT NULL
    );
  `.execute(db);

  // Always interpolate values via ${...}; the sql tag parameterizes them.
  await sql`CREATE INDEX squirrels_name_idx ON EXAMPLE_SQUIRRELS (squirrel_name);`.execute(
    db,
  );
}

export async function down(db: Kysely<any>): Promise<void> {
  await sql`DROP TABLE IF EXISTS EXAMPLE_SQUIRRELS;`.execute(db);
}
重要提示:迁移文件必须始终
@/sql
导入
sql
,绝不能直接从
@schemavaults/dbh/sql
导入。
build-db-migrations
步骤会将字面量
@/sql
导入说明符重写为指向构建后的独立
sql.js
的相对路径,因此导入必须精确写为
@/sql
才能使构建正常工作。(这就是一次性设置要配置
@/sql
别名的原因。)

Empty template migration

空模板迁移

A no-op migration is valid (useful as a starting template):
ts
// 00000-template-migration.ts
import type { Kysely } from "@schemavaults/dbh";

export async function up(
  db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<void> {}

export async function down(
  db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<void> {}
无操作迁移是有效的(可用作起始模板):
ts
// 00000-template-migration.ts
import type { Kysely } from "@schemavaults/dbh";

export async function up(
  db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<void> {}

export async function down(
  db: Kysely<any>, // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<void> {}

Validating migrations

验证迁移

Before building or applying, assert your source migrations directory is well-formed. The
validate-migration-directory
command checks all four rules above and exits
0
when valid, non-zero otherwise (good for CI / pre-commit):
bash
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
It reports each problem with an
[ERROR]
/
[WARN]
prefix:
  • empty directory,
  • a file missing the 5-digit prefix,
  • a module missing
    up()
    or
    down()
    ,
  • duplicate migration numbers (branch collisions).
Treat duplicate numbers as warnings (non-fatal) with
--duplicates-as-warnings
.
Migrations importing through tsconfig path aliases (like
@/sql
from the one-time setup) validate correctly: the command discovers the nearest
tsconfig.json
declaring
compilerOptions.paths
by walking up from the migrations directory (following
extends
), and applies those aliases when importing each module. Use
--tsconfig <path>
to point at a specific config instead of relying on discovery.
在构建或应用之前,确认你的源码迁移目录格式正确。
validate-migration-directory
命令会检查上述所有四条规则,验证通过时退出码为
0
,否则为非零值(适用于 CI / 预提交检查):
bash
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
它会以
[ERROR]
/
[WARN]
前缀报告每个问题:
  • 空目录
  • 文件缺少5位前缀
  • 模块缺少
    up()
    down()
  • 迁移编号重复(分支冲突)
使用
--duplicates-as-warnings
可将重复编号视为警告(非致命错误)。
通过 tsconfig 路径别名导入的迁移(例如一次性设置中的
@/sql
)可以正确验证:该命令会从迁移目录向上遍历(遵循
extends
),找到最近的声明了
compilerOptions.paths
tsconfig.json
,并在导入每个模块时应用这些别名。使用
--tsconfig <path>
可以指定特定的配置文件,而不依赖自动发现。

Building migrations

构建迁移

TypeScript migrations must be compiled to JavaScript before they're applied (the
migrate
step runs on Node and imports
.js
). The
build-db-migrations
command uses Bun's bundler and also builds the standalone
sql
module the migrations depend on. Point
--sql-module
at the local
sql.ts
you created during setup:
bash
bunx @schemavaults/dbh build-db-migrations ./src/db/migrations \
  --outdir ./dist/migrations \
  --sql-module ./src/db/sql.ts \
  --sql-outdir ./dist
Key options:
  • <migrations-src>
    — directory of
    .ts
    migration sources (positional).
  • --outdir <dir>
    — where compiled
    .js
    migrations are written (required).
  • --sql-module <path>
    — path to your local
    sql.ts
    module to build alongside (required).
  • --sql-outdir <dir>
    — where the built
    sql.js
    goes (defaults to the parent of
    --outdir
    ).
  • --external <pkg...>
    — packages to keep external (default:
    @schemavaults/dbh
    ,
    kysely
    ).
build-db-migrations
requires
bun
to be installed and on the PATH.
TypeScript 迁移必须先编译为 JavaScript 才能应用(
migrate
步骤在 Node 上运行并导入
.js
文件)。
build-db-migrations
命令使用 Bun 的打包器,同时也会构建迁移依赖的独立
sql
模块。将
--sql-module
指向你在设置期间创建的本地
sql.ts
bash
bunx @schemavaults/dbh build-db-migrations ./src/db/migrations \
  --outdir ./dist/migrations \
  --sql-module ./src/db/sql.ts \
  --sql-outdir ./dist
关键选项:
  • <migrations-src>
    ——
    .ts
    迁移源码目录(位置参数)
  • --outdir <dir>
    —— 编译后的
    .js
    迁移文件输出目录(必填)
  • --sql-module <path>
    —— 要一同构建的本地
    sql.ts
    模块路径(必填)
  • --sql-outdir <dir>
    —— 构建后的
    sql.js
    输出位置(默认为
    --outdir
    的父目录)
  • --external <pkg...>
    —— 不打包进产物、保持为外部依赖的包(默认:
    @schemavaults/dbh
    kysely
build-db-migrations
需要安装
bun
并将其添加到 PATH 中。

Running migrations

运行迁移

Apply built migrations with
migrate
, and roll back with
reverse
. Both take the built migration folder and require an
--environment
; credentials come from
process.env
(or an
--env-file
). Run these with
npx
(Node.js): most PostgreSQL drivers target Node rather than Bun.
bash
undefined
使用
migrate
应用已构建的迁移,使用
reverse
回滚。两者都需要传入已构建的迁移文件夹,并且需要指定
--environment
;凭证来自
process.env
(或
--env-file
)。请使用
npx
(Node.js)运行这些命令:大多数 PostgreSQL 驱动面向 Node 而非 Bun。
bash
undefined

Apply all pending migrations (to latest):

应用所有待执行的迁移(到最新版本):

npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production
npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production

Apply up to a specific version (the migration name w/o extension):

应用到指定版本(不带扩展名的迁移名称):

npx @schemavaults/dbh migrate ./dist/migrations 00001-create-users-table --environment staging
npx @schemavaults/dbh migrate ./dist/migrations 00001-create-users-table --environment staging

Roll back down to a target version:

回滚到目标版本:

npx @schemavaults/dbh reverse ./dist/migrations 00000-template-migration --environment staging

Options for `migrate` / `reverse`:
- `<folder>` — path to the built migration folder (positional).
- `[version]` / `<version>` — target migration name; `migrate` defaults to latest, `reverse` requires it.
- `-e, --environment <env>` — `development | test | staging | production` (required).
- `--ws-proxy-url <url>` — custom Neon-compatible WebSocket proxy URL.
- `--env-file <path>` — load DB credentials from a `.env` file first.

Each result line prints as `[Up|Down] <migrationName>: <Success|Error|NotExecuted>`.
npx @schemavaults/dbh reverse ./dist/migrations 00000-template-migration --environment staging

`migrate` / `reverse` 的选项:
- `<folder>` —— 已构建的迁移文件夹路径(位置参数)
- `[version]` / `<version>` —— 目标迁移名称;`migrate` 默认为最新版本,`reverse` 必填
- `-e, --environment <env>` —— `development | test | staging | production`(必填)
- `--ws-proxy-url <url>` —— 自定义的兼容 Neon 的 WebSocket 代理 URL
- `--env-file <path>` —— 优先从 `.env` 文件加载数据库凭证

每行结果会以 `[Up|Down] <migrationName>: <Success|Error|NotExecuted>` 的格式打印。

Programmatic API

编程式 API

The same operations are available from
@schemavaults/dbh/migrate
for tests or custom scripts, using the adapter's Kysely instance:
ts
import { migrate, reverse } from "@schemavaults/dbh/migrate";

await migrate({ db: adapter.db, migrationFolder, version /* optional */ });
await reverse({ db: adapter.db, migrationFolder, version });
相同的操作也可以通过
@schemavaults/dbh/migrate
在测试或自定义脚本中使用,使用适配器的 Kysely 实例:
ts
import { migrate, reverse } from "@schemavaults/dbh/migrate";

await migrate({ db: adapter.db, migrationFolder, version /* optional */ });
await reverse({ db: adapter.db, migrationFolder, version });

Typical end-to-end flow

典型端到端流程

bash
undefined
bash
undefined

1. Validate the source migrations directory.

1. 验证源码迁移目录

bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations
bunx @schemavaults/dbh validate-migration-directory ./src/db/migrations

2. Build .ts migrations (+ sql module) to .js.

2. 将 .ts 迁移(+ sql 模块)构建为 .js

bunx @schemavaults/dbh build-db-migrations ./src/db/migrations
--outdir ./dist/migrations --sql-module ./src/db/sql.ts --sql-outdir ./dist
bunx @schemavaults/dbh build-db-migrations ./src/db/migrations
--outdir ./dist/migrations --sql-module ./src/db/sql.ts --sql-outdir ./dist

3. Apply the built migrations (npx / Node.js — pg drivers target Node).

3. 应用已构建的迁移(npx / Node.js —— pg 驱动面向 Node)

npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production
undefined
npx @schemavaults/dbh migrate ./dist/migrations --environment production --env-file ./.env.production
undefined

Required environment variables (for migrate/reverse)

必需的环境变量(用于 migrate/reverse)

POSTGRES_USER
,
POSTGRES_PASSWORD
,
POSTGRES_URL
,
POSTGRES_HOST
,
POSTGRES_PORT
,
POSTGRES_DATABASE
(and optional
POSTGRES_URL_NON_POOLING
). Set
SCHEMAVAULTS_DBH_DEBUG=true
for verbose debug logging.
POSTGRES_USER
POSTGRES_PASSWORD
POSTGRES_URL
POSTGRES_HOST
POSTGRES_PORT
POSTGRES_DATABASE
(以及可选的
POSTGRES_URL_NON_POOLING
)。设置
SCHEMAVAULTS_DBH_DEBUG=true
可启用详细调试日志。