.agents/skills/bknd-seed-data/SKILL.md
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 skillsauth add cameronapak/freedom-stack-v3 bknd-seed-dataInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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: "[email protected]", name: "Admin" },
{ email: "[email protected]", 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: "[email protected]" } },
});
if (existing) {
console.log("Database already seeded");
return;
}
// Seed data
await ctx.em.mutator("users").insertOne({
email: "[email protected]",
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: "[email protected]", name: "Admin", role: "admin" },
{ email: "[email protected]", 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: "[email protected]",
name: "Admin",
role: "admin",
});
// Dev-only test data
if (isDev) {
await ctx.em.mutator("users").insertMany([
{ email: "[email protected]", name: "Test User 1" },
{ email: "[email protected]", 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: "[email protected]" } },
{ email: "[email protected]", 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: "[email protected]", // 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:
development
Use btca (Better Context App) to efficiently query and learn from the bknd backend framework. Use when working with bknd for (1) Understanding data module and schema definitions, (2) Implementing authentication and authorization, (3) Setting up media file handling, (4) Configuring adapters (Node, Cloudflare, etc.), (5) Learning from bknd source code and examples, (6) Debugging bknd-specific issues
development
Use when configuring webhook integrations in Bknd. Covers receiving incoming webhooks via HTTP triggers, sending outgoing webhooks with FetchTask, event-triggered webhooks on data changes, signature verification, retry patterns, and async processing.
development
Use when encountering Bknd errors, getting error messages, something not working, or needing quick fixes. Covers error code reference, quick solutions, and common mistake patterns.
tools
Use when writing tests for Bknd applications, setting up test infrastructure, creating unit/integration tests, or testing API endpoints. Covers in-memory database setup, test helpers, mocking, and test patterns.