Soft Delete
Convert hard deletes to soft deletes with flags.
The soft delete plugin converts delete mutations into merge operations that set a deletion flag and timestamp. It also auto-filters soft-deleted records from queries on configured resources.
Setup
import { createDatafnClient, createSoftDeletePlugin } from "@datafn/client";
const client = createDatafnClient({
schema,
plugins: [
createSoftDeletePlugin({
resources: ["todos", "comments"],
}),
],
});Configuration
interface SoftDeleteConfig {
resources: string[]; // Resources that use soft-delete
deletedAtField?: string; // Field for deletion timestamp (default: "deletedAt")
isDeletedField?: string; // Field for deletion flag (default: "isDeleted")
getTimestamp?: () => Date; // Custom timestamp factory (default: () => new Date())
}Custom Field Names
createSoftDeletePlugin({
resources: ["todos"],
deletedAtField: "removed_at",
isDeletedField: "is_removed",
});Deterministic Timestamps for Testing
let now = new Date("2025-01-01T00:00:00Z");
createSoftDeletePlugin({
resources: ["todos"],
getTimestamp: () => now,
});Mutation Transformation
When a delete mutation targets a configured resource, the beforeMutation hook converts it to a merge operation:
Input:
{
resource: "todos",
operation: "delete",
id: "t1",
}Transformed to:
{
resource: "todos",
operation: "merge",
id: "t1",
record: {
isDeleted: true,
deletedAt: new Date(), // Current timestamp
},
}Delete operations on resources not listed in config.resources pass through unchanged.
Query Filtering
The beforeQuery hook automatically adds a filter to exclude soft-deleted records:
Input:
{
resource: "todos",
filters: { status: "active" },
}Transformed to:
{
resource: "todos",
filters: {
$and: [
{ status: "active" },
{ isDeleted: { $ne: true } },
],
},
}If the query has no existing filters, the soft-delete filter is applied directly:
{
resource: "todos",
filters: { isDeleted: { $ne: true } },
}Querying Deleted Records
To include soft-deleted records in a query, set metadata.includeDeleted: true:
const allTodos = await client.table("todos").query({
metadata: { includeDeleted: true },
});Batch Operations
The plugin handles both single mutations and batch mutations. When an array of mutations is passed to beforeMutation, each mutation is individually transformed:
// Both deletes are converted to soft deletes
await client.table("todos").mutate([
{ operation: "delete", id: "t1" },
{ operation: "delete", id: "t2" },
]);Restoring Soft-Deleted Records
Use the restore utility to clear the deletion flags:
import { restore } from "@datafn/client";
await restore(client.table("todos"), "t1");This performs a merge mutation that sets isDeleted: false and deletedAt: null.
With custom field names:
await restore(client.table("todos"), "t1", {
isDeletedField: "is_removed",
deletedAtField: "removed_at",
});Schema Requirements
Your resource schema should include the soft-delete fields:
{
name: "todos",
version: 1,
fields: [
{ name: "title", type: "string", required: true },
{ name: "isDeleted", type: "boolean", required: false, default: false },
{ name: "deletedAt", type: "date", required: false },
],
}