@bernierllc/content-soft-delete
v1.2.0
Published
Pure utility for soft delete functionality - mark content as deleted without permanent removal, track deletion metadata, and support restoration
Readme
@bernierllc/content-soft-delete
Pure utility for soft delete functionality - mark content as deleted without permanent removal, track deletion metadata, and support restoration.
Installation
npm install @bernierllc/content-soft-deleteUsage
Basic Usage
import { ContentSoftDelete, createSoftDelete } from '@bernierllc/content-soft-delete';
// Create instance with default configuration
const softDelete = new ContentSoftDelete();
// Or use factory function
const softDelete = createSoftDelete({ retentionDays: 60 });
// Mark content as soft-deleted
const content = { id: 'article-123', title: 'My Article', author: 'John Doe' };
const result = softDelete.mark(content, 'user-456', 'No longer relevant');
if (result.success) {
console.log('Content marked as deleted:', result.data);
// { id: 'article-123', title: 'My Article', author: 'John Doe', softDelete: {...} }
}
// Restore soft-deleted content
const restored = softDelete.restore(result.data);
if (restored.success) {
console.log('Content restored:', restored.data);
// { id: 'article-123', title: 'My Article', author: 'John Doe' }
}Configuration Options
import { ContentSoftDelete, SoftDeleteConfig } from '@bernierllc/content-soft-delete';
const config: Partial<SoftDeleteConfig> = {
retentionDays: 30, // Days before permanent deletion (default: 30)
allowRestore: true, // Whether restoration is permitted (default: true)
requireReason: false // Whether delete reason is required (default: false)
};
const softDelete = new ContentSoftDelete(config);Filtering Content
const items = [
{ id: '1', title: 'Active 1' },
{ id: '2', title: 'Deleted', softDelete: {...} },
{ id: '3', title: 'Active 2' }
];
// Get only active content
const active = softDelete.filterActive(items);
// Returns: [{ id: '1', ... }, { id: '3', ... }]
// Get only deleted content
const deleted = softDelete.filterDeleted(items);
// Returns: [{ id: '2', ... }]Checking Deletion Status
// Check if content is soft-deleted
const isDeleted = softDelete.isSoftDeleted(content);
// Check if content is ready for permanent deletion
const canDelete = softDelete.isPermanentlyDeletable(content);
// Get days remaining until permanent deletion
const days = softDelete.getDaysUntilPermanentDelete(content);
console.log(`${days} days until permanent deletion`);Permanent Deletion
// Prepare content for permanent deletion
const result = softDelete.preparePermanentDelete(content);
if (result.success) {
// Only the ID remains for audit purposes
console.log(result.data); // { id: 'article-123' }
// Now you can permanently delete from your database
await database.delete(result.data.id);
}API Reference
Classes
ContentSoftDelete
Main class for soft delete operations.
Constructor:
new ContentSoftDelete(config?: Partial<SoftDeleteConfig>)Methods:
mark<T>(content: T, userId: string, reason?: string): SoftDeleteResult<T>- Marks content as soft-deleted with metadata
- Returns success status and updated content with softDelete metadata
restore<T>(content: T): SoftDeleteResult<T>- Restores soft-deleted content by removing softDelete metadata
- Returns success status and restored content
isSoftDeleted(content: SoftDeletableContent): boolean- Checks if content is currently soft-deleted
- Returns true if softDelete metadata exists
isPermanentlyDeletable(content: SoftDeletableContent): boolean- Checks if content is ready for permanent deletion
- Returns true if retention period has expired
filterActive<T>(items: T[]): T[]- Filters array to exclude soft-deleted items
- Returns only active content
filterDeleted<T>(items: T[]): T[]- Filters array to include only soft-deleted items
- Returns only deleted content
getDaysUntilPermanentDelete(content: SoftDeletableContent): number | null- Calculates days remaining until permanent deletion
- Returns number of days or null if not applicable
preparePermanentDelete<T>(content: T): SoftDeleteResult<{ id: string }>- Prepares content for permanent deletion (returns only ID)
- Returns success status and minimal data for audit
Interfaces
SoftDeleteConfig
Configuration for soft delete behavior.
interface SoftDeleteConfig {
retentionDays: number; // Days before permanent deletion (default: 30)
allowRestore: boolean; // Whether restoration is permitted (default: true)
requireReason: boolean; // Whether delete reason is required (default: false)
}SoftDeleteMetadata
Metadata attached to soft-deleted content.
interface SoftDeleteMetadata {
deletedAt: string; // ISO 8601 timestamp
deletedBy: string; // User ID who deleted
deleteReason?: string; // Optional reason
canRestore: boolean; // Whether restoration is allowed
permanentDeleteAt?: string; // When permanent deletion will occur
}SoftDeletableContent
Content item with soft delete support.
interface SoftDeletableContent {
id: string;
softDelete?: SoftDeleteMetadata;
[key: string]: any; // Other content properties
}SoftDeleteResult<T>
Result of soft delete operations.
interface SoftDeleteResult<T = SoftDeletableContent> {
success: boolean;
data?: T;
error?: string;
}Factory Function
createSoftDelete(config?: Partial<SoftDeleteConfig>): ContentSoftDelete
Convenience factory function for creating ContentSoftDelete instances.
const softDelete = createSoftDelete({ retentionDays: 60 });Examples
Example 1: Editorial Workflow
import { createSoftDelete } from '@bernierllc/content-soft-delete';
const softDelete = createSoftDelete({
retentionDays: 30,
requireReason: true
});
// Editor marks article for deletion
const article = { id: 'article-123', title: 'Draft Article', status: 'draft' };
const result = softDelete.mark(article, 'editor-456', 'Superseded by new version');
if (result.success) {
// Save to database with softDelete metadata
await database.save(result.data);
// Check when it will be permanently deleted
const days = softDelete.getDaysUntilPermanentDelete(result.data);
console.log(`Article will be permanently deleted in ${days} days`);
}
// Later, restore if needed
const restored = softDelete.restore(result.data);
if (restored.success) {
await database.save(restored.data);
console.log('Article restored to active status');
}Example 2: Filtering Content Lists
import { ContentSoftDelete } from '@bernierllc/content-soft-delete';
const softDelete = new ContentSoftDelete();
// Fetch all content from database
const allContent = await database.getAllContent();
// Show only active content to users
const activeContent = softDelete.filterActive(allContent);
displayToUser(activeContent);
// Admin view: show trash
const deletedContent = softDelete.filterDeleted(allContent);
displayTrash(deletedContent);Example 3: Automated Cleanup
import { ContentSoftDelete } from '@bernierllc/content-soft-delete';
const softDelete = new ContentSoftDelete({ retentionDays: 30 });
async function cleanupExpiredContent() {
const allContent = await database.getAllContent();
const deleted = softDelete.filterDeleted(allContent);
for (const item of deleted) {
if (softDelete.isPermanentlyDeletable(item)) {
const prepared = softDelete.preparePermanentDelete(item);
if (prepared.success) {
// Permanently delete from database
await database.permanentDelete(prepared.data.id);
console.log(`Permanently deleted: ${prepared.data.id}`);
}
}
}
}
// Run daily cleanup
setInterval(cleanupExpiredContent, 24 * 60 * 60 * 1000);Example 4: Custom Retention Policies
import { createSoftDelete } from '@bernierllc/content-soft-delete';
// Different retention periods for different content types
const articleSoftDelete = createSoftDelete({ retentionDays: 90 });
const commentSoftDelete = createSoftDelete({ retentionDays: 30 });
const draftSoftDelete = createSoftDelete({ retentionDays: 7 });
// Articles get 90 days
const deletedArticle = articleSoftDelete.mark(article, userId, 'Outdated');
// Comments get 30 days
const deletedComment = commentSoftDelete.mark(comment, userId, 'Spam');
// Drafts get only 7 days
const deletedDraft = draftSoftDelete.mark(draft, userId, 'Abandoned');Integration Status
- Logger: not-applicable (Pure utility with no side effects - logging is caller's responsibility)
- Docs-Suite: ready (Complete TypeDoc documentation)
- NeverHub: not-applicable (Pure utility with no runtime behavior - no events to publish)
Design Philosophy
This package follows MECE (Mutually Exclusive, Collectively Exhaustive) principles:
What it does:
- ✅ Mark content as soft-deleted with metadata
- ✅ Track deletion timestamp, user ID, and reason
- ✅ Restore soft-deleted content
- ✅ Filter arrays to exclude/include soft-deleted items
- ✅ Check if content is permanently deletable
- ✅ Calculate retention periods
- ✅ Validate deletion state transitions
What it does NOT do:
- ❌ Persist deletion state to database (caller's responsibility)
- ❌ Handle authorization/permissions
- ❌ Send notifications about deletions
- ❌ Archive or backup deleted content
- ❌ Manage content versions or history
Dependencies
None - This is a pure utility package with zero dependencies.
License
Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
See Also
- @bernierllc/content-editor-service - Uses this package for trash functionality
- @bernierllc/content-list-ui - Filters soft-deleted content from lists
- @bernierllc/content-management-suite - Complete editorial workflow
