Use when populating a Bknd database with initial or test data. Covers the seed function in options, ctx.em.mutator() for insertOne/insertMany, conditional seeding, environment-based data, and common patterns for dev/test fixtures.
npx claudepluginhub cameronapak/bknd-expert --plugin bknd-research-skillsThis skill uses the workspace's default tool permissions.
Populate your Bknd database with initial, test, or development data using the built-in seed function.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Populate your Bknd database with initial, test, or development data using the built-in seed function.
Note: Seed function is code-only—no UI equivalent. For one-off data entry, use the admin panel Data section directly.
The seed function lives in the options section of your config:
import { type BunBkndConfig, serve } from "bknd/adapter/bun";
import { em, entity, text, boolean } from "bknd";
const schema = em({
todos: entity("todos", {
title: text().required(),
done: boolean({ default_value: false }),
}),
});
const config: BunBkndConfig = {
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
},
options: {
seed: async (ctx) => {
// Seed logic here
},
},
};
serve(config);
Use ctx.em.mutator(entity) for server-side inserts:
options: {
seed: async (ctx) => {
// Insert single record
await ctx.em.mutator("todos").insertOne({
title: "Welcome task",
done: false,
});
// Insert multiple records
await ctx.em.mutator("todos").insertMany([
{ title: "Learn Bknd basics", done: false },
{ title: "Create first entity", done: true },
{ title: "Set up authentication", done: false },
]);
},
}
Insert parent records first, then children:
options: {
seed: async (ctx) => {
// Create users first
const users = await ctx.em.mutator("users").insertMany([
{ email: "admin@example.com", name: "Admin" },
{ email: "user@example.com", name: "User" },
]);
// Create posts referencing users
await ctx.em.mutator("posts").insertMany([
{ title: "First Post", author_id: users[0].id },
{ title: "Second Post", author_id: users[1].id },
]);
},
}
Check if data exists before seeding to avoid duplicates:
options: {
seed: async (ctx) => {
// Check if already seeded
const existing = await ctx.em.repo("users").findOne({
where: { email: { $eq: "admin@example.com" } },
});
if (existing) {
console.log("Database already seeded");
return;
}
// Seed data
await ctx.em.mutator("users").insertOne({
email: "admin@example.com",
name: "Admin",
});
},
}
import { type BunBkndConfig, serve } from "bknd/adapter/bun";
import { em, entity, text, boolean, number, date } from "bknd";
const schema = em({
users: entity("users", {
email: text().required().unique(),
name: text(),
role: text({ default_value: "user" }),
}),
posts: entity("posts", {
title: text().required(),
content: text(),
published: boolean({ default_value: false }),
view_count: number({ default_value: 0 }),
created_at: date({ default_value: "now" }),
}),
tags: entity("tags", {
name: text().required().unique(),
}),
});
type Database = (typeof schema)["DB"];
declare module "bknd" {
interface DB extends Database {}
}
const config: BunBkndConfig = {
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
},
options: {
seed: async (ctx) => {
// Check if already seeded
const count = await ctx.em.repo("users").count();
if (count > 0) {
console.log("Skipping seed: data exists");
return;
}
console.log("Seeding database...");
// Seed users
const [admin, author] = await ctx.em.mutator("users").insertMany([
{ email: "admin@example.com", name: "Admin", role: "admin" },
{ email: "author@example.com", name: "Author", role: "author" },
]);
// Seed tags
const tags = await ctx.em.mutator("tags").insertMany([
{ name: "javascript" },
{ name: "typescript" },
{ name: "bknd" },
]);
// Seed posts
await ctx.em.mutator("posts").insertMany([
{
title: "Getting Started with Bknd",
content: "Learn the basics...",
published: true,
author_id: author.id,
},
{
title: "Advanced Patterns",
content: "Deep dive into...",
published: false,
author_id: admin.id,
},
]);
console.log("Seed complete!");
},
},
};
serve(config);
For browser-based apps using BkndBrowserApp:
import { BkndBrowserApp } from "bknd/adapter/browser";
function App() {
return (
<BkndBrowserApp
config={{
data: schema.toJSON(),
}}
options={{
seed: async (ctx) => {
await ctx.em.mutator("todos").insertMany([
{ title: "Sample task 1", done: false },
{ title: "Sample task 2", done: true },
]);
},
}}
>
<YourApp />
</BkndBrowserApp>
);
}
Different data for dev vs production:
options: {
seed: async (ctx) => {
const isDev = process.env.NODE_ENV !== "production";
// Always seed admin
await ctx.em.mutator("users").insertOne({
email: "admin@example.com",
name: "Admin",
role: "admin",
});
// Dev-only test data
if (isDev) {
await ctx.em.mutator("users").insertMany([
{ email: "test1@example.com", name: "Test User 1" },
{ email: "test2@example.com", name: "Test User 2" },
]);
// Generate bulk test data
const testPosts = Array.from({ length: 50 }, (_, i) => ({
title: `Test Post ${i + 1}`,
content: `Content for test post ${i + 1}`,
published: i % 2 === 0,
}));
await ctx.em.mutator("posts").insertMany(testPosts);
}
},
}
| Scenario | Seed Runs? |
|---|---|
| First startup (empty DB) | Yes |
| Subsequent startups | Yes (every time) |
| After schema sync | Yes |
| Production deployment | Yes (use guards!) |
Important: The seed function runs on every startup. Always add existence checks to prevent duplicate data.
| Method | Description | Example |
|---|---|---|
insertOne(data) | Insert single record | mutator("users").insertOne({ email: "..." }) |
insertMany(data[]) | Insert multiple records | mutator("users").insertMany([...]) |
async function seedIfNotExists(ctx, entity: string, where: object, data: object) {
const existing = await ctx.em.repo(entity).findOne({ where });
if (!existing) {
return ctx.em.mutator(entity).insertOne(data);
}
return existing;
}
// Usage
options: {
seed: async (ctx) => {
await seedIfNotExists(ctx, "users",
{ email: { $eq: "admin@example.com" } },
{ email: "admin@example.com", name: "Admin", role: "admin" }
);
},
}
function createTestUser(overrides = {}) {
return {
email: `user${Date.now()}@test.com`,
name: "Test User",
role: "user",
...overrides,
};
}
function createTestPost(authorId: number, overrides = {}) {
return {
title: "Test Post",
content: "Lorem ipsum...",
published: false,
author_id: authorId,
...overrides,
};
}
// Usage
options: {
seed: async (ctx) => {
const user = await ctx.em.mutator("users").insertOne(
createTestUser({ role: "admin" })
);
await ctx.em.mutator("posts").insertMany([
createTestPost(user.id, { title: "Post 1", published: true }),
createTestPost(user.id, { title: "Post 2" }),
]);
},
}
import { faker } from "@faker-js/faker";
options: {
seed: async (ctx) => {
const users = Array.from({ length: 10 }, () => ({
email: faker.internet.email(),
name: faker.person.fullName(),
role: faker.helpers.arrayElement(["user", "author", "admin"]),
}));
await ctx.em.mutator("users").insertMany(users);
},
}
Problem: Seed runs every startup, creating duplicates.
Fix: Check for existing data:
seed: async (ctx) => {
const count = await ctx.em.repo("users").count();
if (count > 0) return; // Already seeded
// Seed logic...
}
Problem: Foreign key constraint failed error.
Fix: Insert parent records before children:
// ❌ Wrong order
await ctx.em.mutator("posts").insertOne({ author_id: 1, ... }); // User doesn't exist!
await ctx.em.mutator("users").insertOne({ id: 1, ... });
// ✅ Correct order
const user = await ctx.em.mutator("users").insertOne({ ... });
await ctx.em.mutator("posts").insertOne({ author_id: user.id, ... });
Problem: NOT NULL constraint failed error.
Fix: Include all required fields:
// ❌ Missing required field
await ctx.em.mutator("users").insertOne({ name: "Admin" });
// Error: email is required
// ✅ Include all required fields
await ctx.em.mutator("users").insertOne({
email: "admin@example.com", // required
name: "Admin"
});
Problem: Test data appears in production.
Fix: Guard with environment check:
seed: async (ctx) => {
if (process.env.NODE_ENV === "production") {
// Only seed essential data in production
const adminExists = await ctx.em.repo("users").findOne({
where: { role: { $eq: "admin" } },
});
if (!adminExists) {
await ctx.em.mutator("users").insertOne({
email: process.env.ADMIN_EMAIL,
name: "Admin",
role: "admin",
});
}
return;
}
// Full dev seed...
}
After seeding, verify data was inserted:
seed: async (ctx) => {
// ... insert data ...
// Verify
const userCount = await ctx.em.repo("users").count();
const postCount = await ctx.em.repo("posts").count();
console.log(`Seeded: ${userCount} users, ${postCount} posts`);
}
Or via API after startup:
const api = app.getApi();
const { data } = await api.data.readMany("users");
console.log("Users:", data.length);
DO:
DON'T: