Use when deleting records from a Bknd entity via the SDK or REST API. Covers deleteOne, deleteMany, soft delete patterns, cascade considerations, response handling, and common patterns.
npx claudepluginhub cameronapak/bknd-expert --plugin bknd-research-skillsThis skill uses the workspace's default tool permissions.
Delete records from your Bknd database using the SDK or REST API.
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`.
Delete records from your Bknd database using the SDK or REST API.
UI steps: Admin Panel > Data > Select Entity > Click record > Delete button > Confirm
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
});
// If auth required:
api.updateToken("your-jwt-token");
Use deleteOne(entity, id):
const { ok, data, error } = await api.data.deleteOne("posts", 1);
if (ok) {
console.log("Deleted post:", data.id);
} else {
console.error("Failed:", error.message);
}
The response object:
type DeleteResponse = {
ok: boolean; // Success/failure
data?: { // Deleted record (if ok)
id: number;
// ...all fields of deleted record
};
error?: { // Error info (if !ok)
message: string;
code: string;
};
};
Use deleteMany(entity, where):
// Delete all archived posts
const { ok, data } = await api.data.deleteMany("posts", {
status: { $eq: "archived" },
});
// data contains deleted records
console.log("Deleted", data.length, "posts");
Important: where clause is required to prevent accidental delete-all.
// Delete old sessions
await api.data.deleteMany("sessions", {
last_active: { $lt: "2024-01-01" },
});
// Delete by multiple conditions
await api.data.deleteMany("logs", {
level: { $eq: "debug" },
created_at: { $lt: "2024-06-01" },
});
Always verify record exists or check count before deleting:
// Check record exists
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
throw new Error("Post not found");
}
await api.data.deleteOne("posts", id);
// Check count before bulk delete
const { data: countResult } = await api.data.count("logs", {
level: { $eq: "debug" },
});
console.log(`About to delete ${countResult.count} records`);
curl -X DELETE http://localhost:7654/api/data/posts/1
curl -X DELETE http://localhost:7654/api/data/posts/1 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Delete all archived posts
curl -X DELETE "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22archived%22%7D"
# URL-decoded where: {"status":"archived"}
import { useApp } from "bknd/react";
import { useState } from "react";
function DeleteButton({ postId, onDeleted }: { postId: number; onDeleted?: () => void }) {
const { api } = useApp();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleDelete() {
if (!confirm("Are you sure you want to delete this post?")) {
return;
}
setLoading(true);
setError(null);
const { ok, error: apiError } = await api.data.deleteOne("posts", postId);
setLoading(false);
if (ok) {
onDeleted?.();
} else {
setError(apiError.message);
}
}
return (
<>
<button onClick={handleDelete} disabled={loading}>
{loading ? "Deleting..." : "Delete"}
</button>
{error && <p className="error">{error}</p>}
</>
);
}
import { mutate } from "swr";
function useDeletePost() {
const { api } = useApp();
async function deletePost(id: number) {
const { ok, error } = await api.data.deleteOne("posts", id);
if (ok) {
// Revalidate list
mutate("posts");
}
return { ok, error };
}
return { deletePost };
}
function useOptimisticDelete() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function deletePost(id: number) {
// Optimistic: remove immediately
const originalPosts = [...posts];
setPosts((prev) => prev.filter((p) => p.id !== id));
// Actual delete
const { ok } = await api.data.deleteOne("posts", id);
if (!ok) {
// Rollback on failure
setPosts(originalPosts);
}
return { ok };
}
return { posts, deletePost };
}
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
// Authenticate
await api.auth.login({ email: "admin@example.com", password: "password" });
// Simple delete
const { ok, data } = await api.data.deleteOne("posts", 1);
if (ok) {
console.log("Deleted:", data.title);
}
// Delete with verification
const postId = 5;
const { data: post } = await api.data.readOne("posts", postId);
if (post) {
await api.data.deleteOne("posts", postId);
}
// Bulk delete: remove old archived posts
const { data: deleted } = await api.data.deleteMany("posts", {
status: { $eq: "archived" },
created_at: { $lt: "2023-01-01" },
});
console.log("Deleted", deleted.length, "old archived posts");
// Cleanup expired sessions
await api.data.deleteMany("sessions", {
expires_at: { $lt: new Date().toISOString() },
});
Instead of permanent deletion, mark as deleted:
// Soft delete: set timestamp
async function softDelete(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: new Date().toISOString(),
});
}
// Restore soft-deleted record
async function restore(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: null,
});
}
// Query non-deleted records
async function findActive(api: Api, entity: string, query = {}) {
return api.data.readMany(entity, {
...query,
where: {
...query.where,
deleted_at: { $isnull: true },
},
});
}
// Permanently delete soft-deleted records older than 30 days
async function purgeDeleted(api: Api, entity: string) {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
return api.data.deleteMany(entity, {
deleted_at: { $lt: thirtyDaysAgo.toISOString() },
});
}
async function deleteWithConfirmation(
api: Api,
entity: string,
id: number,
confirm: () => Promise<boolean>
) {
const { data } = await api.data.readOne(entity, id);
if (!data) {
return { ok: false, error: { message: "Record not found" } };
}
const confirmed = await confirm();
if (!confirmed) {
return { ok: false, error: { message: "Cancelled by user" } };
}
return api.data.deleteOne(entity, id);
}
When Bknd doesn't auto-cascade, delete children first:
async function cascadeDelete(api: Api, userId: number) {
// Delete children first
await api.data.deleteMany("posts", { author_id: { $eq: userId } });
await api.data.deleteMany("comments", { user_id: { $eq: userId } });
await api.data.deleteMany("likes", { user_id: { $eq: userId } });
// Then delete parent
return api.data.deleteOne("users", userId);
}
async function batchDelete(
api: Api,
entity: string,
ids: number[],
onProgress?: (done: number, total: number) => void
) {
const results = [];
for (let i = 0; i < ids.length; i++) {
const result = await api.data.deleteOne(entity, ids[i]);
results.push(result);
onProgress?.(i + 1, ids.length);
}
return results;
}
// Usage
const idsToDelete = [1, 5, 12, 23];
await batchDelete(api, "posts", idsToDelete, (done, total) => {
console.log(`Deleted ${done}/${total}`);
});
async function archiveAndDelete(
api: Api,
sourceEntity: string,
archiveEntity: string,
id: number
) {
// Read current record
const { data: record } = await api.data.readOne(sourceEntity, id);
if (!record) {
return { ok: false, error: { message: "Record not found" } };
}
// Create archive copy
await api.data.createOne(archiveEntity, {
...record,
original_id: record.id,
archived_at: new Date().toISOString(),
});
// Delete original
return api.data.deleteOne(sourceEntity, id);
}
async function deleteIf(
api: Api,
entity: string,
id: number,
condition: (record: any) => boolean
) {
const { data } = await api.data.readOne(entity, id);
if (!data) {
return { ok: false, error: { message: "Record not found" } };
}
if (!condition(data)) {
return { ok: false, error: { message: "Condition not met" } };
}
return api.data.deleteOne(entity, id);
}
// Only delete if draft
await deleteIf(api, "posts", 1, (post) => post.status === "draft");
Problem: Delete returns no data or error.
Fix: Check if record exists first:
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
console.error("Post not found");
return;
}
await api.data.deleteOne("posts", id);
Problem: FOREIGN KEY constraint failed when deleting parent.
Fix: Delete or unlink children first:
// Option 1: Delete children
await api.data.deleteMany("comments", { post_id: { $eq: postId } });
await api.data.deleteOne("posts", postId);
// Option 2: Unlink children (if nullable FK)
await api.data.updateMany(
"comments",
{ post_id: { $eq: postId } },
{ post_id: null }
);
await api.data.deleteOne("posts", postId);
Problem: Assuming success without verification.
Fix: Always check ok:
// Wrong
await api.data.deleteOne("posts", id);
console.log("Deleted!"); // Might have failed!
// Correct
const { ok, error } = await api.data.deleteOne("posts", id);
if (!ok) {
console.error("Delete failed:", error.message);
return;
}
console.log("Deleted!");
Problem: Deleting more records than intended.
Fix: Always use specific where clause and verify count:
// Dangerous - might delete more than expected
await api.data.deleteMany("posts", { status: { $eq: "draft" } });
// Safer - check count first
const { data: count } = await api.data.count("posts", {
status: { $eq: "draft" }
});
console.log(`About to delete ${count.count} posts`);
if (count.count > 100) {
throw new Error("Too many records - aborting");
}
Problem: Unauthorized error.
Fix: Authenticate before deleting:
await api.auth.login({ email, password });
// or
api.updateToken(savedToken);
await api.data.deleteOne("posts", id);
Problem: Accidentally deleted important data.
Fix: Use soft delete for recoverable data:
// Instead of hard delete
await api.data.deleteOne("posts", id);
// Use soft delete
await api.data.updateOne("posts", id, {
deleted_at: new Date().toISOString(),
});
Problem: Users accidentally delete data.
Fix: Always confirm destructive actions:
// In frontend
function handleDelete(id: number) {
if (!confirm("Delete this post? This cannot be undone.")) {
return;
}
api.data.deleteOne("posts", id);
}
After deleting, verify the record is gone:
const { ok } = await api.data.deleteOne("posts", 1);
if (ok) {
const { data } = await api.data.readOne("posts", 1);
console.log("Record exists:", data !== null); // Should be false
}
Or via admin panel: Admin Panel > Data > Select Entity > Search for deleted record.
DO:
ok before assuming successDON'T: