Bknd模式修改Skill bknd-modify-schema

这个技能用于安全地修改Bknd应用中的数据库模式,包括重命名实体和字段、更改字段类型、修改字段约束等操作,提供数据迁移策略和同步工作流,适用于后端开发和低代码场景。关键词:Bknd, 模式修改, 数据库迁移, 后端开发, 低代码。

后端开发 0 次安装 0 次浏览 更新于 3/8/2026

name: bknd-modify-schema description: 用于修改现有Bknd模式。涵盖重命名实体、重命名字段、更改字段类型、修改字段约束、处理破坏性更改、数据迁移策略和同步工作流。

修改模式

修改Bknd中的现有模式:重命名实体/字段、更改字段类型或修改约束。

先决条件

  • 现有具有实体的Bknd应用(参见 bknd-create-entity
  • 对于代码模式:访问 bknd.config.ts
  • 在破坏性更改之前备份数据库

关键概念:破坏性 vs 非破坏性更改

Bknd的模式同步检测您的代码和数据库之间的差异。一些更改是安全的;其他更改会导致数据丢失。

非破坏性(安全)

  • 添加新实体
  • 添加新字段(可为空或具有默认值)
  • 添加新索引
  • 放宽约束(移除 .required()

破坏性(数据丢失风险)

  • 重命名实体(视为删除旧 + 创建新)
  • 重命名字段(视为删除旧 + 创建新)
  • 更改字段类型(可能失败或截断数据)
  • 移除字段(删除列和数据)
  • 移除实体(删除表和所有数据)
  • 对现有数据收紧约束

何时使用 UI vs 代码

使用 UI 模式当

  • 交互式探索模式更改
  • 快速原型设计(数据丢失可接受)
  • 无需版本控制

使用代码模式当

  • 生产模式更改
  • 需要版本控制
  • 团队协作
  • 可重复部署

重命名实体

警告: Bknd没有原生重命名。重命名 = 删除旧 + 创建新 = 数据丢失

安全方法:数据迁移

  1. 使用所需名称创建新实体
  2. 将数据从旧迁移到新
  3. 更新代码引用
  4. 删除旧实体

代码方法

// 步骤1:在旧实体旁边添加新实体
const schema = em({
  // 旧 - 稍后将移除
  posts: entity("posts", {
    title: text().required(),
    content: text(),
  }),
  // 新 - 所需名称
  articles: entity("articles", {
    title: text().required(),
    content: text(),
  }),
});
// 步骤2:迁移数据(通过脚本或CLI运行一次)
const api = app.getApi();
const oldData = await api.data.readMany("posts", { limit: 10000 });

for (const item of oldData.data) {
  await api.data.createOne("articles", {
    title: item.title,
    content: item.content,
  });
}
// 步骤3:从模式中移除旧实体
const schema = em({
  articles: entity("articles", {
    title: text().required(),
    content: text(),
  }),
});
# 步骤4:强制同步以删除旧表
npx bknd sync --force

UI 方法

  1. 打开管理面板 (http://localhost:1337)
  2. 转到 数据 部分
  3. 使用所需名称创建新实体
  4. 手动复制字段定义
  5. 从旧实体导出数据(如果需要)
  6. 将数据导入到新实体
  7. 删除旧实体

重命名字段

警告: Bknd将字段重命名视为删除 + 创建 = 该列的数据丢失

安全方法:数据迁移

// 步骤1:在旧字段旁边添加新字段
const schema = em({
  users: entity("users", {
    name: text(),           // 旧 - 将被移除
    full_name: text(),      // 新 - 所需名称
  }),
});
// 步骤2:迁移数据
const api = app.getApi();
const users = await api.data.readMany("users", { limit: 10000 });

for (const user of users.data) {
  if (user.name && !user.full_name) {
    await api.data.updateOne("users", user.id, {
      full_name: user.name,
    });
  }
}
// 步骤3:移除旧字段
const schema = em({
  users: entity("users", {
    full_name: text(),
  }),
});
# 步骤4:强制同步以删除旧列
npx bknd sync --force

UI 方法

  1. 添加具有所需名称的新字段
  2. 编写脚本或手动复制数据
  3. 删除旧字段

更改字段类型

类型更改有风险。一些转换有效;其他失败或截断。

兼容的类型更改

备注
text text (具有不同约束) 通常安全
number text 安全(数字变为字符串)
boolean number 安全(0/1值)
boolean text 安全(“true”/“false”)

不兼容的类型更改

风险
text number 如果非数字数据则失败
text boolean 如果不是 “true”/“false”/0/1 则失败
text date 如果不是有效日期格式则失败
json text 可能截断;丢失结构

类型更改的安全方法

// 步骤1:添加具有新类型的新字段
const schema = em({
  products: entity("products", {
    price: text(),            // 旧 - 字符串价格
    price_cents: number(),    // 新 - 整数分
  }),
});
// 步骤2:转换并迁移数据
const api = app.getApi();
const products = await api.data.readMany("products", { limit: 10000 });

for (const product of products.data) {
  if (product.price && !product.price_cents) {
    const cents = Math.round(parseFloat(product.price) * 100);
    await api.data.updateOne("products", product.id, {
      price_cents: cents,
    });
  }
}
// 步骤3:移除旧字段,如果需要重命名新字段
const schema = em({
  products: entity("products", {
    price_cents: number(),
  }),
});

更改字段约束

使字段必需

风险: 如果现有记录有空值则失败。

// 之前
entity("users", {
  email: text(),  // 可选
});

// 之后
entity("users", {
  email: text().required(),  // 现在必需
});

安全方法:

  1. 首先更新所有空值
  2. 然后添加 .required()
// 步骤1:用默认值填充空值
const api = app.getApi();
const usersWithNull = await api.data.readMany("users", {
  where: { email: { $isnull: true } },
});

for (const user of usersWithNull.data) {
  await api.data.updateOne("users", user.id, {
    email: "unknown@example.com",
  });
}

// 步骤2:现在安全地添加 .required()

使字段唯一

风险: 如果存在重复则失败。

// 之前
entity("users", {
  username: text(),
});

// 之后
entity("users", {
  username: text().unique(),
});

安全方法:

  1. 查找并解决重复项
  2. 然后添加 .unique()
// 通过原始SQL或手动检查查找重复项
// 通过更新或删除解决重复项
// 然后添加 .unique() 约束

移除必需/唯一

通常安全:

// 之前
entity("users", {
  email: text().required().unique(),
});

// 之后 - 放宽约束是安全的
entity("users", {
  email: text(),  // 现在可选,非唯一
});

同步工作流

预览更改(试运行)

# 查看同步将做什么而不应用
npx bknd sync

输出显示:

  • 要创建的新实体/字段
  • 要删除的实体/字段
  • 索引更改

应用非破坏性更改

# 仅应用添加性更改
npx bknd sync

应用所有更改(包括删除)

# 警告:这将删除表/列
npx bknd sync --force

仅应用删除

# 专门启用删除操作
npx bknd sync --drop

UI 方法:字段修改

更改字段类型

  1. 在数据部分打开实体
  2. 点击字段进行编辑
  3. 注意: 类型下拉菜单可能对现有字段锁定
  4. 如果锁定:创建具有正确类型的新字段,迁移数据,删除旧字段

更改约束

  1. 在数据部分打开实体
  2. 点击字段进行编辑
  3. 根据需要切换必需/唯一
  4. 点击 保存
  5. 点击 同步数据库

重命名字段

  1. 创建具有所需名称的新字段
  2. 手动复制数据或编写迁移脚本
  3. 删除旧字段
  4. 同步数据库

常见陷阱

同步在类型更改上失败

错误: 无法将列类型从 X 转换为 Y

修复: 使用迁移方法 - 创建新字段,复制数据,删除旧字段。

同步在必需约束上失败

错误: 列包含空值,无法添加 NOT NULL

修复: 首先将所有空值更新为非空,然后重新同步。

同步在唯一约束上失败

错误: 列存在重复值

修复: 在添加唯一约束之前移除重复项。

重命名后数据丢失

问题: 重命名实体/字段并丢失所有数据。

修复: 不幸的是,数据已丢失。从备份恢复。下次使用迁移方法。

强制标志被忽略

问题: --force 似乎不应用更改。

修复: 检查同步输出以查找实际错误。可能是验证问题,而不是权限。


迁移脚本模板

对于复杂迁移,创建独立脚本:

// scripts/migrate-schema.ts
import { App } from "bknd";

async function migrate() {
  const app = new App({
    connection: { url: process.env.DB_URL! },
  });
  await app.build();

  const api = app.getApi();

  console.log("开始迁移...");

  // 从旧结构读取所有记录
  const records = await api.data.readMany("old_entity", { limit: 100000 });
  console.log(`找到 ${records.data.length} 条记录`);

  // 转换并插入到新结构
  let migrated = 0;
  for (const record of records.data) {
    await api.data.createOne("new_entity", {
      // 根据需要转换字段
      new_field: record.old_field,
    });
    migrated++;
    if (migrated % 100 === 0) {
      console.log(`已迁移 ${migrated}/${records.data.length}`);
    }
  }

  console.log("迁移完成!");
  process.exit(0);
}

migrate().catch(console.error);

运行:

npx bun scripts/migrate-schema.ts
# 或
npx ts-node scripts/migrate-schema.ts

验证

模式修改后

# 1. 检查同步状态
npx bknd sync

# 2. 在调试输出中验证模式
npx bknd schema --pretty

通过代码

const api = app.getApi();

// 通过查询验证字段存在
const result = await api.data.readMany("entity_name", { limit: 1 });
console.log(result.data[0]);  // 检查字段名称/值

通过 UI

  1. 在数据部分打开实体
  2. 验证字段正确显示
  3. 使用新模式创建测试记录
  4. 查询现有记录以验证数据

该做和不该做

该做:

  • 在破坏性更改之前备份数据库
  • 对于重命名使用迁移方法
  • 在强制之前使用 npx bknd sync 预览
  • 首先在开发数据库上测试
  • 保持旧结构直到数据迁移

不该做:

  • 直接重命名实体/字段(数据丢失)
  • 不预览就使用 --force
  • 更改类型没有迁移计划
  • 向有空数据的字段添加 .required()
  • 向有重复项的字段添加 .unique()

相关技能

  • bknd-create-entity - 创建新实体
  • bknd-add-field - 向实体添加字段
  • bknd-delete-entity - 安全移除实体
  • bknd-seed-data - 填充迁移数据
  • bknd-crud-update - 在迁移期间更新记录