Loading...
Loading...
Use when modifying existing Bknd schema. Covers renaming entities, renaming fields, changing field types, altering field constraints, handling destructive changes, data migration strategies, and the sync workflow.
npx skill4agent add cameronapak/bknd-skills bknd-modify-schemabknd-create-entitybknd.config.ts.required()// Step 1: Add new entity alongside old
const schema = em({
// OLD - will be removed later
posts: entity("posts", {
title: text().required(),
content: text(),
}),
// NEW - desired name
articles: entity("articles", {
title: text().required(),
content: text(),
}),
});// Step 2: Migrate data (run once via script or 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,
});
}// Step 3: Remove old entity from schema
const schema = em({
articles: entity("articles", {
title: text().required(),
content: text(),
}),
});# Step 4: Sync with force to drop old table
npx bknd sync --forcehttp://localhost:1337// Step 1: Add new field alongside old
const schema = em({
users: entity("users", {
name: text(), // OLD - will be removed
full_name: text(), // NEW - desired name
}),
});// Step 2: Migrate data
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,
});
}
}// Step 3: Remove old field
const schema = em({
users: entity("users", {
full_name: text(),
}),
});# Step 4: Sync with force to drop old column
npx bknd sync --force| From | To | Notes |
|---|---|---|
| | Usually safe |
| | Safe (numbers become strings) |
| | Safe (0/1 values) |
| | Safe ("true"/"false") |
| From | To | Risk |
|---|---|---|
| | Fails if non-numeric data |
| | Fails if not "true"/"false"/0/1 |
| | Fails if not valid date format |
| | May truncate; loses structure |
// Step 1: Add new field with new type
const schema = em({
products: entity("products", {
price: text(), // OLD - string prices
price_cents: number(), // NEW - integer cents
}),
});// Step 2: Transform and migrate data
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,
});
}
}// Step 3: Remove old field, rename new if desired
const schema = em({
products: entity("products", {
price_cents: number(),
}),
});// Before
entity("users", {
email: text(), // Optional
});
// After
entity("users", {
email: text().required(), // Now required
});.required()// Step 1: Fill nulls with default
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",
});
}
// Step 2: Now safely add .required()// Before
entity("users", {
username: text(),
});
// After
entity("users", {
username: text().unique(),
});.unique()// Check for duplicates via raw SQL or manual inspection
// Resolve duplicates by updating or deleting
// Then add .unique() constraint// Before
entity("users", {
email: text().required().unique(),
});
// After - loosening constraints is safe
entity("users", {
email: text(), // Now optional, non-unique
});# See what sync would do without applying
npx bknd sync# Applies only additive changes
npx bknd sync# WARNING: This will drop tables/columns
npx bknd sync --force# Specifically enables drop operations
npx bknd sync --dropCannot convert column type from X to YColumn contains null values, cannot add NOT NULLDuplicate values exist for column--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("Starting migration...");
// Read all records from old structure
const records = await api.data.readMany("old_entity", { limit: 100000 });
console.log(`Found ${records.data.length} records`);
// Transform and insert into new structure
let migrated = 0;
for (const record of records.data) {
await api.data.createOne("new_entity", {
// Transform fields as needed
new_field: record.old_field,
});
migrated++;
if (migrated % 100 === 0) {
console.log(`Migrated ${migrated}/${records.data.length}`);
}
}
console.log("Migration complete!");
process.exit(0);
}
migrate().catch(console.error);npx bun scripts/migrate-schema.ts
# or
npx ts-node scripts/migrate-schema.ts# 1. Check sync status
npx bknd sync
# 2. Verify schema in debug output
npx bknd schema --prettyconst api = app.getApi();
// Verify field exists by querying
const result = await api.data.readMany("entity_name", { limit: 1 });
console.log(result.data[0]); // Check field names/valuesnpx bknd sync--force.required().unique()