Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Cascading Deletes

Relationship-based cleanup

Bijection source
git clone https://github.com/bijectionhq/cascading-deletes.git
cd cascading-deletes
git checkout acb1222095c6f18a7f6e0e949894cbf4717970c9

Bijection repository access is required. Request access.

Description

Relationship-based cleanup

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available as a reference

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/cascading-deletes. Based on sholajegede/convex-cascading-deletes. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

A convex cascading deletes component for Convex.

Explore the demo ↗

Upstream overview

From the upstream package documentation.

This component automates cascading deletes across related documents in your Convex database. You configure table relationships once using your existing indexes, then delete a record and all its dependents with a single function call. The component handles batching via the Convex scheduler, prevents infinite loops in circular relationships, and provides queryable deletion logs.

Use cases

From the upstream package documentation.

  • Clean up user data on account deletion - Delete a user and automatically cascade to their posts, comments, likes, and profile data without manually tracking dependencies
  • Remove complex hierarchical data - Delete a project and cascade through tasks, subtasks, files, and permissions while maintaining referential integrity
  • Enforce deletion policies across teams - Use the safe db helper to prevent direct deletes and ensure all deletions go through the cascading system
  • Audit and monitor data removal - Query deletion logs to track what was removed during cascades for compliance or debugging
  • Migrate existing apps to cascading deletes - Validate your relationship configuration against your current database schema and indexes

How it works

From the upstream package documentation.

You instantiate a CascadingDeletes client with relationship configurations that map your existing table indexes to parent-child relationships. Each relationship specifies the source table (child), target table (parent), index name, and foreign key field name.

When you call deleteWithCascade() in an action, the component traverses the dependency tree and deletes records in the correct order. Large deletion trees are processed in batches via the Convex scheduler to handle transaction limits, while maintaining atomicity within each batch.

The component provides a getSafeDb() method that patches ctx.db to throw errors on direct .delete() calls, enforcing cascade-only deletions throughout your codebase. All cascading operations are logged with per-table deletion counts and can be queried reactively using standard Convex queries.

Upstream package FAQ

How does the Cascading Deletes component handle circular relationships?

The Cascading Deletes component includes built-in circular relationship protection using visited tracking to prevent infinite loops. This ensures that complex relationship graphs with circular dependencies can be safely deleted without causing stack overflows or endless deletion cycles.

Do I need to modify my existing Convex schema to use cascading deletes?

No schema changes are required. The Cascading Deletes component uses your existing indexes to define relationships between tables. You simply configure which indexes represent parent-child relationships and the component handles the rest automatically.

What happens if a cascading delete operation fails partway through?

Deletions within each scheduler batch are atomic, so partial batches won't leave your database in an inconsistent state. If a batch fails, only that specific batch is rolled back while previous successful batches remain committed. The component logs all operations for audit and debugging purposes.

Can I query which records were deleted in a cascading operation?

Yes, the Cascading Deletes component maintains a reactive deletion log that tracks every cascade operation with per-table counts and timestamps. You can query this log using useQuery to see exactly which records were deleted and when the cascade ran.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Cascading Deletes with Bijection.

Read: https://bijection.com/components/cascading-deletes/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from sholajegede/convex-cascading-deletes, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

@sholajegede/convex-cascading-deletes

A Convex component for managing cascading deletes across related documents. Configure relationships via your existing indexes — when you delete a record, all dependent records are cleaned up automatically.

Found a bug? Feature request? File it here.

Features

  • Cascading deletes — delete a record and all its dependents in one call
  • Relationship config — declare relationships once using your existing indexes
  • Scheduler-based batching — large deletion trees are processed in batches via the Convex scheduler; deletes within a batch are atomic
  • Circular relationship protection — visited tracking prevents infinite loops
  • Safe db helper — patch ctx.db to throw on direct .delete() calls, enforcing cascade-only deletions
  • Deletion logs — every cascade is logged with a per-table count, queryable reactively
  • Index validation — validate your relationship config against the deployment at startup

Installation

npm install @sholajegede/convex-cascading-deletes

Add the component to your convex/convex.config.ts:

import { defineApp } from "convex/server";
import convexCascadingDeletes from "@sholajegede/convex-cascading-deletes/convex.config.js";

const app = defineApp();
app.use(convexCascadingDeletes);

export default app;

Usage

Instantiate the client once, declaring your table relationships:

// convex/cascadeDeletes.ts
import { components } from "./_generated/api.js";
import { CascadingDeletes } from "@sholajegede/convex-cascading-deletes";

export const cascadingDeletes = new CascadingDeletes(components.convexCascadingDeletes, {
  relationships: [
    {
      sourceTable: "posts",      // child table
      targetTable: "users",      // parent table
      indexName: "by_user",      // index on posts that references users
      fieldName: "userId",       // field on posts that holds the user ID
    },
    {
      sourceTable: "comments",
      targetTable: "posts",
      indexName: "by_post",
      fieldName: "postId",
    },
  ],
});

Delete with cascade

// convex/users.ts
import { action } from "./_generated/server.js";
import { cascadingDeletes } from "./cascadeDeletes.js";
import { v } from "convex/values";

export const deleteUser = action({
  args: { userId: v.string() },
  handler: async (ctx, args) => {
    const counts = await cascadingDeletes.deleteWithCascade(ctx, {
      table: "users",
      id: args.userId,
    });
    // counts: { users: 1, posts: 4, comments: 12 }
    return counts;
  },
});

Deleting a user cascades to their posts, then to each post's comments — all automatically, in the correct order.

Enforce cascade-only deletions

export const updatePost = mutation({
  args: { postId: v.id("posts"), title: v.string() },
  handler: async (ctx, args) => {
    const db = cascadingDeletes.getSafeDb(ctx);
    // db.delete() now throws — use deleteWithCascade instead
    await db.patch(args.postId, { title: args.title }); // fine
  },
});

Query deletion logs

// convex/logs.ts
import { query } from "./_generated/server.js";
import { components } from "./_generated/api.js";
import { v } from "convex/values";

export const getDeletionLog = query({
  args: { table: v.string(), id: v.string() },
  handler: async (ctx, args) => {
    return await ctx.runQuery(components.convexCascadingDeletes.lib.getDeletionLog, {
      rootTable: args.table,
      rootId: args.id,
    });
  },
});
// React — subscribes reactively
const log = useQuery(api.logs.getDeletionLog, { table: "users", id: userId });
// log.deletedCounts — JSON string: { "users": 1, "posts": 4, "comments": 12 }
// log.deletedAt     — timestamp of when the cascade ran

Validate indexes at startup

export const onStartup = mutation({
  handler: async (ctx) => {
    await cascadingDeletes.validate(ctx);
  },
});

API

CascadingDeletes class

MethodDescription
deleteWithCascade(ctx, { table, id })Delete a record and all configured dependents. Returns { [table]: count }.
getDeletionLog(ctx, { table, id })Query the deletion log for a previously deleted record.
getSafeDb(ctx)Returns a patched ctx.db that throws on .delete().
validate(ctx)Validates all configured relationships against the deployment.

Relationship config

FieldDescription
sourceTableThe child table (holds the foreign key)
targetTableThe parent table (the one being deleted)
indexNameIndex on sourceTable that references targetTable
fieldNameField on sourceTable that holds the parent ID

Development

npm i
npm run dev

License

Apache-2.0

Keywords