@patolord/softdelete
v0.1.2
Published
A softdelete component for Convex.
Readme
Convex Soft Delete Component
A Convex component that provides soft-delete functionality for your documents. Instead of permanently deleting documents, move them to a "trash" where they can be restored or permanently deleted later.
Features
- Soft delete any document - Move documents to an isolated trash table instead of permanent deletion
- Restore documents - Recover accidentally deleted data with full document preservation
- Auto-purge - Schedule automatic permanent deletion after a specified time period (built-in cron handles cleanup)
- Track deletion metadata - Record who deleted a document and when
- Query by table - List and filter deleted documents by their original table
- Hard delete - Permanently remove documents from trash when needed
- Idempotent operations - All mutations are safe to retry without side effects
Installation
npm install @patolord/softdeleteCreate a convex.config.ts file in your app's convex/ folder and install the
component:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import softdelete from "@patolord/softdelete/convex.config";
const app = defineApp();
app.use(softdelete);
export default app;Usage
Basic Setup
import { SoftDelete } from "@patolord/softdelete";
import { components } from "./_generated/api";
const softDelete = new SoftDelete(components.softdelete);Soft Delete a Document
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const deleteUser = mutation({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
// Get the document
const user = await ctx.db.get(args.userId);
if (!user) throw new Error("User not found");
// Store in soft-delete trash with auto-purge after 30 days
await softDelete.delete(ctx, "users", args.userId, user, {
deletedBy: "admin",
purgeAfterMs: 30 * 24 * 60 * 60 * 1000, // Auto-purge after 30 days
});
// Remove from original table
await ctx.db.delete(args.userId);
},
});That's it! The component includes a built-in cron job that automatically purges expired documents every 15 minutes. No additional setup required.
Restore a Document
export const restoreUser = mutation({
args: { originalUserId: v.string() },
handler: async (ctx, args) => {
// Restore from trash
const userData = await softDelete.restoreByOriginalId(
ctx,
"users",
args.originalUserId,
);
if (!userData) throw new Error("Deleted user not found");
// Re-insert into original table
const { _id, _creationTime, ...data } = userData;
return await ctx.db.insert("users", data);
},
});List Deleted Documents
export const listDeletedUsers = query({
args: {},
handler: async (ctx) => {
return await softDelete.listByTable(ctx, "users");
},
});Permanently Delete
export const permanentlyDelete = mutation({
args: { deletedDocId: v.string() },
handler: async (ctx, args) => {
return await softDelete.hardDelete(ctx, args.deletedDocId);
},
});Auto-Purge Behavior
When you set purgeAfterMs on a document, the component automatically handles
cleanup:
await softDelete.delete(ctx, "todos", id, todo, {
purgeAfterMs: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Document will be permanently deleted ~7 days after soft-deletionHow It Works
purgeAfterMssets ascheduledPurgeAttimestamp on the document- A built-in cron job runs every 15 minutes
- The cron finds all documents past their
scheduledPurgeAtand deletes them
Custom Purge Frequency
The default 15-minute interval works for most use cases. If you need faster cleanup (e.g., documents that expire in seconds), add your own cron:
// convex/crons.ts
import { cronJobs } from "convex/server";
import { components } from "./_generated/api";
const crons = cronJobs();
// Fast purge for short-lived documents (every 10 seconds)
crons.interval(
"fast purge",
{ seconds: 10 },
components.softdelete.lib.purgeExpired,
{ limit: 100 },
);
export default crons;You can run multiple crons at different intervals - they all call the same
purgeExpired function which finds and deletes expired documents.
API Reference
SoftDelete Class
Constructor
const softDelete = new SoftDelete(components.softdelete);Methods
| Method | Description |
| ---------------------------------------------------- | ----------------------------- |
| delete(ctx, table, originalId, document, options?) | Soft-delete a document |
| restore(ctx, deletedDocId) | Restore by deleted doc ID |
| restoreByOriginalId(ctx, table, originalId) | Restore by original ID |
| hardDelete(ctx, deletedDocId) | Permanently delete |
| hardDeleteByTable(ctx, table, limit?) | Empty trash for a table |
| get(ctx, deletedDocId) | Get a deleted document |
| getByOriginalId(ctx, table, originalId) | Find by original ID |
| listByTable(ctx, table, limit?) | List deleted docs for a table |
| listAll(ctx, limit?) | List all deleted docs |
| purgeExpired(ctx, limit?) | Manually purge expired docs |
Options for delete()
| Option | Type | Description |
| -------------- | -------- | --------------------------------------- |
| deletedBy | string | User ID or identifier of who deleted |
| purgeAfterMs | number | Auto-purge after this many milliseconds |
Deleted Document Structure
interface DeletedDocument {
_id: string;
_creationTime: number;
table: string; // Original table name
originalId: string; // Original document ID
document: object; // The full document data
deletedAt: number; // Timestamp of deletion
deletedBy?: string; // Who deleted it
scheduledPurgeAt?: number; // When to auto-purge
}Counting Deleted Documents
This component intentionally does not include a count function. For efficient
counting at scale, use the
Aggregate component alongside
this one, or derive counts from listByTable:
const deleted = await softDelete.listByTable(ctx, "users");
const count = deleted.length;Development
npm install
npm run devLicense
Apache-2.0
