bknd-crud-delete

📁 cameronapak/bknd-skills 📅 Jan 21, 2026
8
总安装量
6
周安装量
#33818
全站排名
安装命令
npx skills add https://github.com/cameronapak/bknd-skills --skill bknd-crud-delete

Agent 安装分布

gemini-cli 4
codex 4
cursor 3
antigravity 3
junie 3
windsurf 3

Skill 文档

CRUD Delete

Delete records from your Bknd database using the SDK or REST API.

Prerequisites

  • Bknd project running (local or deployed)
  • Entity exists with records to delete
  • SDK configured or API endpoint known
  • Record ID or filter criteria known
  • Understanding of any relationships/dependencies

When to Use UI Mode

  • Quick one-off deletions
  • Manual data cleanup
  • Visual verification of what’s being deleted

UI steps: Admin Panel > Data > Select Entity > Click record > Delete button > Confirm

When to Use Code Mode

  • Application logic for user-initiated deletes
  • Automated data cleanup/maintenance
  • Bulk deletions
  • Soft delete implementations

Code Approach

Step 1: Set Up SDK Client

import { Api } from "bknd";

const api = new Api({
  host: "http://localhost:7654",
});

// If auth required:
api.updateToken("your-jwt-token");

Step 2: Delete Single Record

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);
}

Step 3: Handle Response

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;
  };
};

Step 4: Delete Multiple Records (Bulk)

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" },
});

Step 5: Verify Before Delete

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`);

REST API Approach

Delete One

curl -X DELETE http://localhost:7654/api/data/posts/1

Delete with Auth

curl -X DELETE http://localhost:7654/api/data/posts/1 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Delete Many

# 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"}

React Integration

Delete Button

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>}
    </>
  );
}

With SWR Revalidation

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 };
}

Optimistic Delete

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 };
}

Full Example

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() },
});

Common Patterns

Soft Delete (Recommended for User Data)

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() },
  });
}

Delete with Confirmation

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);
}

Cascade Delete (Manual)

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);
}

Batch Delete with Progress

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}`);
});

Archive Before Delete

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);
}

Conditional Delete

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");

Common Pitfalls

Record Not Found

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);

Foreign Key Constraint

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);

Not Checking Response

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!");

Accidental Mass Delete

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");
}

Missing Auth

Problem: Unauthorized error.

Fix: Authenticate before deleting:

await api.auth.login({ email, password });
// or
api.updateToken(savedToken);

await api.data.deleteOne("posts", id);

No Undo for Hard Delete

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(),
});

Deleting Without Confirmation

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);
}

Verification

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.

DOs and DON’Ts

DO:

  • Check ok before assuming success
  • Verify record exists before deleting
  • Use soft delete for user data
  • Handle foreign key constraints
  • Confirm with user before destructive actions
  • Check count before bulk deletes
  • Consider archiving before permanent delete

DON’T:

  • Assume deleteOne always succeeds
  • Delete parent before children with FK constraints
  • Hard delete user data without confirmation
  • Forget to revalidate caches after delete
  • Use deleteMany without specific where clause
  • Delete without authentication on protected entities

Related Skills

  • bknd-crud-read – Verify records before deleting
  • bknd-crud-update – Update instead of delete (soft delete)
  • bknd-crud-create – Recreate accidentally deleted records
  • bknd-define-relationship – Understand FK constraints
  • bknd-bulk-operations – Large-scale delete patterns