Delete Operations
Delete operations in Routier allow you to remove entities from your collections. The framework provides both individual and batch deletion methods, with support for query-based removal and proper cleanup.
Quick Navigation
- Overview
- Basic Delete Operations
- Query-Based Deletion
- Batch Deletion Patterns
- Advanced Deletion Patterns
- Change Management for Deletions
- Performance Considerations
- Error Handling
- Best Practices
- Common Deletion Patterns
- Deletion Strategies
- Next Steps
Overview
Routier’s delete operations feature:
- Individual entity removal - Remove specific entities by reference
- Batch deletion - Remove multiple entities efficiently
- Query-based removal - Remove entities matching specific criteria
- Automatic cleanup - Proper disposal of removed entities
- Change tracking - Deletions are tracked until saved
⚠️ Important: Persistence Requires Save
Note: When you call removeAsync(), the entity is marked for removal in memory, but it is NOT automatically removed from the database. You must call saveChanges() or saveChangesAsync() to persist the deletion.
Basic Delete Operations
Removing Single Entities
Remove individual entities by reference:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Removing single entities
const user = await ctx.users.firstAsync();
console.log("User to remove:", user);
// Remove the entity by reference
const removedUser = await ctx.users.removeAsync(user);
console.log("Removed user:", removedUser);
// Save changes to persist the deletion
await ctx.saveChangesAsync();
console.log("Deletion persisted");Removing Multiple Entities
Remove multiple entities in a single operation:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Removing multiple entities
const users = await ctx.users.where(u => u.age < 18).toArrayAsync();
console.log("Users to remove:", users);
// Remove multiple entities by reference
const removedUsers = await ctx.users.removeAsync(...users);
console.log("Removed users:", removedUsers);
// Save changes to persist the deletions
await ctx.saveChangesAsync();
console.log("Deletions persisted");Removing with Callbacks
Use callback-based deletion for advanced error handling:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Removing with callbacks (discriminated union result pattern)
const user = await ctx.users.firstAsync();
ctx.users.remove([user], (result) => {
if (result.ok === "success") {
console.log("User removed successfully:", result.data);
// result.data is InferType<typeof userSchema>[]
} else {
console.error("Failed to remove user:", result.error);
// result.error contains the error details
}
});
// Save changes after callback completes
await ctx.saveChangesAsync();Query-Based Deletion
Remove by Query
Remove entities matching specific criteria:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove by query - remove entities matching specific criteria
await ctx.users.where(u => u.age < 18).removeAsync();
console.log("Removed all users under 18");
// Remove inactive users
await ctx.users.where(u => u.isActive === false).removeAsync();
console.log("Removed all inactive users");
// Remove users created before a certain date
const cutoffDate = new Date("2023-01-01");
await ctx.users.where(u => u.createdAt < cutoffDate).removeAsync();
console.log("Removed users created before 2023");
// Save all changes
await ctx.saveChangesAsync();Remove with Complex Criteria
Apply complex filtering conditions for deletion:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove with complex criteria
await ctx.users
.where(u => u.age >= 18)
.where(u => u.age <= 65)
.where(u => u.isActive === false)
.removeAsync();
console.log("Removed inactive users between 18-65");
// Remove users with specific email patterns
await ctx.users
.where(u => u.email.includes("temp"))
.where(u => u.createdAt < new Date("2023-01-01"))
.removeAsync();
console.log("Removed old temporary users");
// Remove users with complex conditions
await ctx.users
.where(u => u.name.includes("Test") || u.name.includes("Demo"))
.where(u => u.createdAt < new Date("2022-01-01"))
.removeAsync();
console.log("Removed old test/demo users");
// Save all changes
await ctx.saveChangesAsync();Remove with Parameters
Use parameterized queries for dynamic deletion:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove with parameters - more efficient for database queries
const minAge = 18;
const maxAge = 65;
const isActive = false;
// Using variables in filters (non-parameterized - less efficient)
await ctx.users.where(u => u.age >= minAge && u.age <= maxAge && u.isActive === isActive).removeAsync();
console.log("Removed users with variables");
// Parameterized filters (more efficient - translates to database queries)
await ctx.users.where((u, p) => u.age >= p.minAge && u.age <= p.maxAge && u.isActive === p.isActive, {
minAge,
maxAge,
isActive
}).removeAsync();
console.log("Removed users with parameters");
// Save all changes
await ctx.saveChangesAsync();Batch Deletion Patterns
Remove by Status
Delete entities based on status or state:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
status: s.string("active", "inactive", "pending", "suspended").default("pending"),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove by status - batch deletion based on status
await ctx.users.where(u => u.status === "suspended").removeAsync();
console.log("Removed all suspended users");
await ctx.users.where(u => u.status === "inactive").removeAsync();
console.log("Removed all inactive users");
// Remove users with specific statuses
await ctx.users.where(u => u.status === "pending" && u.createdAt < new Date("2023-01-01")).removeAsync();
console.log("Removed old pending users");
// Save all changes
await ctx.saveChangesAsync();Remove with Confirmation
Implement confirmation patterns for important deletions:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove with confirmation - check before deleting
async function removeUsersWithConfirmation(userIds: string[]) {
// First, get the users to confirm what will be deleted
const usersToDelete = await ctx.users.where(u => userIds.includes(u.id)).toArrayAsync();
if (usersToDelete.length === 0) {
console.log("No users found to delete");
return;
}
console.log(`Found ${usersToDelete.length} users to delete:`);
usersToDelete.forEach(user => {
console.log(`- ${user.name} (${user.email})`);
});
// In a real application, you would show a confirmation dialog
const confirmed = true; // Simulate user confirmation
if (confirmed) {
// Remove the users
await ctx.users.removeAsync(...usersToDelete);
await ctx.saveChangesAsync();
console.log("Users deleted successfully");
} else {
console.log("Deletion cancelled");
}
}
// Usage
await removeUsersWithConfirmation(["user-1", "user-2", "user-3"]);Remove with Backup
Create backups before performing deletions:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Remove with backup - create backup before deletion
async function removeUsersWithBackup(userIds: string[]) {
// Create backup of users before deletion
const usersToDelete = await ctx.users.where(u => userIds.includes(u.id)).toArrayAsync();
if (usersToDelete.length === 0) {
console.log("No users found to delete");
return;
}
// Create backup (in a real app, you might save to a file or backup database)
const backup = {
timestamp: new Date().toISOString(),
users: usersToDelete.map(user => ({
id: user.id,
name: user.name,
email: user.email,
age: user.age,
isActive: user.isActive,
createdAt: user.createdAt
}))
};
console.log("Backup created:", backup);
try {
// Remove the users
await ctx.users.removeAsync(...usersToDelete);
await ctx.saveChangesAsync();
console.log("Users deleted successfully");
// In a real app, you might save the backup to a file
// fs.writeFileSync(`backup-${Date.now()}.json`, JSON.stringify(backup, null, 2));
} catch (error) {
console.error("Failed to delete users:", error);
console.log("Backup available for recovery:", backup);
}
}
// Usage
await removeUsersWithBackup(["user-1", "user-2", "user-3"]);Advanced Deletion Patterns
Cascading Deletion
Handle related data when deleting entities:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define schemas for cascading deletion
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
createdAt: s.date().default(() => new Date()),
}).compile();
const postSchema = s.define("posts", {
id: s.string().key().identity(),
title: s.string(),
content: s.string(),
userId: s.string(), // Foreign key to users
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collections
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
posts = this.collection(postSchema).create();
}
const ctx = new AppContext();
// Cascading deletion - remove related data
async function deleteUserWithPosts(userId: string) {
// First, get the user
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return;
}
// Get all posts by this user
const userPosts = await ctx.posts.where(p => p.userId === userId).toArrayAsync();
console.log(`Found ${userPosts.length} posts by user ${user.name}`);
// Remove all posts first (cascading deletion)
if (userPosts.length > 0) {
await ctx.posts.removeAsync(...userPosts);
console.log("Removed all user posts");
}
// Then remove the user
await ctx.users.removeAsync(user);
console.log("Removed user");
// Save all changes
await ctx.saveChangesAsync();
console.log("Cascading deletion completed");
}
// Usage
await deleteUserWithPosts("user-123");Soft Deletion
Implement soft deletion patterns:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema with soft deletion
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
isDeleted: s.boolean().default(false), // Soft deletion flag
deletedAt: s.date().optional(), // When it was soft deleted
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Soft deletion - mark as deleted instead of removing
async function softDeleteUser(userId: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId && u.isDeleted === false);
if (!user) {
console.log("User not found or already deleted");
return;
}
// Mark as deleted instead of removing
user.isDeleted = true;
user.deletedAt = new Date();
user.isActive = false;
console.log("User soft deleted:", user);
// Save changes
await ctx.saveChangesAsync();
}
// Restore soft deleted user
async function restoreUser(userId: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId && u.isDeleted === true);
if (!user) {
console.log("User not found or not deleted");
return;
}
// Restore the user
user.isDeleted = false;
user.deletedAt = undefined;
user.isActive = true;
console.log("User restored:", user);
// Save changes
await ctx.saveChangesAsync();
}
// Get only active users (excluding soft deleted)
async function getActiveUsers() {
const activeUsers = await ctx.users.where(u => u.isDeleted === false).toArrayAsync();
console.log("Active users:", activeUsers);
return activeUsers;
}
// Usage
await softDeleteUser("user-123");
await getActiveUsers();
await restoreUser("user-123");Conditional Deletion
Apply conditional logic to deletion operations:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
isActive: s.boolean().default(true),
role: s.string("admin", "user", "guest").default("user"),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Conditional deletion - delete based on business rules
async function conditionalDeleteUser(userId: string, reason: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Business rules for deletion
if (user.role === "admin") {
console.log("Cannot delete admin users");
return false;
}
if (user.isActive && reason !== "inactive") {
console.log("Cannot delete active users unless they are inactive");
return false;
}
if (user.createdAt > new Date("2023-01-01") && reason === "old") {
console.log("Cannot delete users created after 2023 with 'old' reason");
return false;
}
// All conditions passed, proceed with deletion
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log(`User ${user.name} deleted successfully. Reason: ${reason}`);
return true;
}
// Usage
await conditionalDeleteUser("user-123", "inactive");
await conditionalDeleteUser("admin-456", "inactive"); // Will be blocked
await conditionalDeleteUser("new-user-789", "old"); // Will be blockedChange Management for Deletions
Checking Deletion Changes
Monitor deletion changes before saving:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Checking deletion changes
const user = await ctx.users.firstAsync();
// Check if there are any pending changes
const hasChangesBefore = ctx.hasChanges();
console.log("Has changes before deletion:", hasChangesBefore); // false
// Mark user for deletion
await ctx.users.removeAsync(user);
// Check if there are pending changes after deletion
const hasChangesAfter = ctx.hasChanges();
console.log("Has changes after deletion:", hasChangesAfter); // true
// Save changes to persist deletion
await ctx.saveChangesAsync();
// Check after save
const hasChangesAfterSave = ctx.hasChanges();
console.log("Has changes after save:", hasChangesAfterSave); // falseSaving Deletion Changes
Persist deletion changes to the database:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Saving deletion changes
const users = await ctx.users.where(u => u.age < 18).toArrayAsync();
console.log(`Found ${users.length} users to delete`);
// Mark users for deletion
await ctx.users.removeAsync(...users);
console.log("Users marked for deletion");
// Save all deletion changes
await ctx.saveChangesAsync();
console.log("Deletion changes saved successfully");
// Verify deletion
const remainingUsers = await ctx.users.where(u => u.age < 18).countAsync();
console.log(`Remaining users under 18: ${remainingUsers}`);Rolling Back Deletions
Implement rollback mechanisms for deletions:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Rolling back deletions - Routier doesn't have built-in rollback
// But you can implement it by not calling saveChangesAsync()
const user = await ctx.users.firstAsync();
console.log("User before deletion:", user);
// Mark for deletion
await ctx.users.removeAsync(user);
console.log("User marked for deletion");
// Check if there are pending changes
const hasChanges = ctx.hasChanges();
console.log("Has pending changes:", hasChanges); // true
// Rollback by not saving changes
// In Routier, if you don't call saveChangesAsync(), changes are not persisted
console.log("Rolling back deletion by not saving changes");
// The user is still in memory but marked for deletion
// To truly rollback, you would need to implement your own mechanism
// or use a transaction-like pattern
// If you want to "rollback", you could:
// 1. Not call saveChangesAsync() (changes are lost on next operation)
// 2. Implement your own undo mechanism
// 3. Use soft deletion instead of hard deletionPerformance Considerations
Batch Deletion
Optimize performance with batch deletion operations:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Batch deletion for better performance
const usersToDelete = await ctx.users.where(u => u.age < 18).toArrayAsync();
console.log(`Found ${usersToDelete.length} users to delete`);
// ✅ Good - batch deletion (more efficient)
await ctx.users.removeAsync(...usersToDelete);
await ctx.saveChangesAsync();
console.log("Batch deletion completed");
// ❌ Less efficient - individual deletions
// for (const user of usersToDelete) {
// await ctx.users.removeAsync(user);
// await ctx.saveChangesAsync();
// }
// Query-based batch deletion (most efficient)
await ctx.users.where(u => u.createdAt < new Date("2023-01-01")).removeAsync();
await ctx.saveChangesAsync();
console.log("Query-based batch deletion completed");Large Dataset Deletion
Handle large dataset deletions efficiently:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Large dataset deletion - delete in chunks to avoid memory issues
async function deleteLargeDataset() {
const batchSize = 1000;
let totalDeleted = 0;
while (true) {
// Get a batch of users to delete
const usersToDelete = await ctx.users
.where(u => u.createdAt < new Date("2020-01-01"))
.take(batchSize)
.toArrayAsync();
if (usersToDelete.length === 0) {
break; // No more users to delete
}
// Delete the batch
await ctx.users.removeAsync(...usersToDelete);
await ctx.saveChangesAsync();
totalDeleted += usersToDelete.length;
console.log(`Deleted ${usersToDelete.length} users. Total: ${totalDeleted}`);
// Optional: Add a small delay to prevent overwhelming the system
// await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(`Large dataset deletion completed. Total deleted: ${totalDeleted}`);
}
// Alternative: Use query-based deletion for large datasets (most efficient)
async function deleteLargeDatasetEfficient() {
const countBefore = await ctx.users.countAsync();
console.log(`Users before deletion: ${countBefore}`);
// Single query-based deletion (most efficient)
await ctx.users.where(u => u.createdAt < new Date("2020-01-01")).removeAsync();
await ctx.saveChangesAsync();
const countAfter = await ctx.users.countAsync();
console.log(`Users after deletion: ${countAfter}`);
console.log(`Deleted: ${countBefore - countAfter} users`);
}
// Usage
await deleteLargeDatasetEfficient();Error Handling
Safe Deletion
Implement safe deletion patterns with error handling:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Safe deletion with comprehensive error handling
async function safeDeleteUser(userId: string) {
try {
// First, check if user exists
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
throw new Error(`User with ID ${userId} not found`);
}
console.log(`Deleting user: ${user.name} (${user.email})`);
// Perform the deletion
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log("User deleted successfully");
return true;
} catch (error) {
console.error("Deletion failed:", error.message);
// Handle different types of errors
if (error.message.includes("not found")) {
console.error("User not found - check user ID");
} else if (error.message.includes("network")) {
console.error("Network error - retry later");
} else if (error.message.includes("database")) {
console.error("Database error - check connection");
} else {
console.error("Unexpected error:", error);
}
return false;
}
}
// Usage with error handling
const success = await safeDeleteUser("user-123");
if (success) {
console.log("Deletion completed successfully");
} else {
console.log("Deletion failed - check logs for details");
}Deletion with Recovery
Implement recovery mechanisms for failed deletions:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Deletion with recovery - implement recovery mechanism
async function deleteUserWithRecovery(userId: string) {
// Create backup before deletion
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Create recovery data
const recoveryData = {
id: user.id,
name: user.name,
email: user.email,
age: user.age,
createdAt: user.createdAt,
deletedAt: new Date().toISOString()
};
console.log("Recovery data created:", recoveryData);
try {
// Perform deletion
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log("User deleted successfully");
// In a real app, you might save recovery data to a separate table or file
// await ctx.deletedUsers.addAsync(recoveryData);
return true;
} catch (error) {
console.error("Deletion failed:", error.message);
console.log("Recovery data available:", recoveryData);
// Recovery mechanism could restore the user from backup
// await ctx.users.addAsync(recoveryData);
return false;
}
}
// Recovery function
async function recoverUser(recoveryData: any) {
try {
// Restore user from recovery data
const recoveredUser = await ctx.users.addAsync({
name: recoveryData.name,
email: recoveryData.email,
age: recoveryData.age,
createdAt: new Date(recoveryData.createdAt)
});
await ctx.saveChangesAsync();
console.log("User recovered successfully:", recoveredUser);
return true;
} catch (error) {
console.error("Recovery failed:", error.message);
return false;
}
}
// Usage
const recoveryData = {
id: "user-123",
name: "John Doe",
email: "[email protected]",
age: 30,
createdAt: "2023-01-01T00:00:00.000Z",
deletedAt: "2023-12-01T00:00:00.000Z"
};
await recoverUser(recoveryData);Best Practices
1. Confirm Deletions for Important Data
Always confirm important deletions:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
role: s.string("admin", "user", "guest").default("user"),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Confirm deletions for important data
async function confirmDeletion(userId: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Show confirmation details
console.log("⚠️ CONFIRMATION REQUIRED ⚠️");
console.log(`User: ${user.name}`);
console.log(`Email: ${user.email}`);
console.log(`Role: ${user.role}`);
console.log(`Created: ${user.createdAt}`);
// In a real application, you would show a confirmation dialog
const confirmed = true; // Simulate user confirmation
if (confirmed) {
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log("✅ User deleted successfully");
return true;
} else {
console.log("❌ Deletion cancelled");
return false;
}
}
// Usage
await confirmDeletion("user-123");2. Use Appropriate Deletion Methods
Choose the right deletion method for your use case:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Use appropriate deletion methods
async function demonstrateDeletionMethods() {
// ✅ Good - Remove specific entities by reference
const user = await ctx.users.firstAsync();
await ctx.users.removeAsync(user);
console.log("Removed user by reference");
// ✅ Good - Remove multiple entities by reference
const users = await ctx.users.where(u => u.age < 18).toArrayAsync();
await ctx.users.removeAsync(...users);
console.log("Removed multiple users by reference");
// ✅ Good - Query-based removal (most efficient for large datasets)
await ctx.users.where(u => u.createdAt < new Date("2023-01-01")).removeAsync();
console.log("Removed users by query");
// ✅ Good - Remove all entities
await ctx.users.removeAllAsync();
console.log("Removed all users");
// Save all changes
await ctx.saveChangesAsync();
}
// Choose the right method based on your needs:
// - removeAsync(entity) - for specific entities
// - removeAsync(...entities) - for multiple specific entities
// - where().removeAsync() - for query-based removal
// - removeAllAsync() - for removing all entities
await demonstrateDeletionMethods();3. Handle Related Data Appropriately
Consider related data when deleting entities:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define schemas for related data
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
createdAt: s.date().default(() => new Date()),
}).compile();
const postSchema = s.define("posts", {
id: s.string().key().identity(),
title: s.string(),
content: s.string(),
userId: s.string(), // Foreign key to users
createdAt: s.date().default(() => new Date()),
}).compile();
const commentSchema = s.define("comments", {
id: s.string().key().identity(),
content: s.string(),
postId: s.string(), // Foreign key to posts
userId: s.string(), // Foreign key to users
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collections
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
posts = this.collection(postSchema).create();
comments = this.collection(commentSchema).create();
}
const ctx = new AppContext();
// Handle related data appropriately
async function deleteUserWithRelatedData(userId: string) {
// Get the user
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Get related data
const userPosts = await ctx.posts.where(p => p.userId === userId).toArrayAsync();
const userComments = await ctx.comments.where(c => c.userId === userId).toArrayAsync();
console.log(`Found ${userPosts.length} posts and ${userComments.length} comments by user ${user.name}`);
// Handle related data based on business rules
// Option 1: Cascading deletion (delete related data)
if (userPosts.length > 0) {
// Delete comments on user's posts first
for (const post of userPosts) {
const postComments = await ctx.comments.where(c => c.postId === post.id).toArrayAsync();
if (postComments.length > 0) {
await ctx.comments.removeAsync(...postComments);
console.log(`Deleted ${postComments.length} comments on post: ${post.title}`);
}
}
// Delete user's posts
await ctx.posts.removeAsync(...userPosts);
console.log(`Deleted ${userPosts.length} posts by user`);
}
// Delete user's comments on other posts
if (userComments.length > 0) {
await ctx.comments.removeAsync(...userComments);
console.log(`Deleted ${userComments.length} comments by user`);
}
// Finally, delete the user
await ctx.users.removeAsync(user);
console.log("Deleted user");
// Save all changes
await ctx.saveChangesAsync();
console.log("User and related data deleted successfully");
return true;
}
// Option 2: Orphan related data (set foreign keys to null)
async function deleteUserOrphanRelatedData(userId: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Update posts to remove user reference (orphan the posts)
const userPosts = await ctx.posts.where(p => p.userId === userId).toArrayAsync();
userPosts.forEach(post => {
post.userId = ""; // Or set to a default "deleted" user ID
});
// Update comments to remove user reference
const userComments = await ctx.comments.where(c => c.userId === userId).toArrayAsync();
userComments.forEach(comment => {
comment.userId = ""; // Or set to a default "deleted" user ID
});
// Delete the user
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log("User deleted, related data orphaned");
return true;
}
// Usage
await deleteUserWithRelatedData("user-123");4. Log Deletion Operations
Implement logging for deletion operations:
import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } from "@routier/core/schema";
// Define a user schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
name: s.string(),
email: s.string().distinct(),
age: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Create DataStore with collection
class AppContext extends DataStore {
constructor() {
super(new MemoryPlugin("app-db"));
}
users = this.collection(userSchema).create();
}
const ctx = new AppContext();
// Log deletion operations
async function logDeletionOperation(userId: string, reason: string, performedBy: string) {
const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);
if (!user) {
console.log("User not found");
return false;
}
// Log the deletion operation
const deletionLog = {
timestamp: new Date().toISOString(),
operation: "DELETE_USER",
userId: user.id,
userName: user.name,
userEmail: user.email,
reason: reason,
performedBy: performedBy,
userAge: user.age,
userCreatedAt: user.createdAt
};
console.log("Deletion log:", deletionLog);
// In a real application, you would save this to a log table or file
// await ctx.deletionLogs.addAsync(deletionLog);
// Perform the deletion
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
console.log("User deleted and operation logged");
return true;
}
// Batch deletion with logging
async function logBatchDeletion(userIds: string[], reason: string, performedBy: string) {
const users = await ctx.users.where(u => userIds.includes(u.id)).toArrayAsync();
if (users.length === 0) {
console.log("No users found to delete");
return false;
}
// Log batch deletion
const batchDeletionLog = {
timestamp: new Date().toISOString(),
operation: "BATCH_DELETE_USERS",
userIds: users.map(u => u.id),
userCount: users.length,
reason: reason,
performedBy: performedBy,
users: users.map(u => ({
id: u.id,
name: u.name,
email: u.email,
age: u.age,
createdAt: u.createdAt
}))
};
console.log("Batch deletion log:", batchDeletionLog);
// Perform batch deletion
await ctx.users.removeAsync(...users);
await ctx.saveChangesAsync();
console.log(`Batch deletion completed: ${users.length} users deleted`);
return true;
}
// Usage
await logDeletionOperation("user-123", "Account closure requested", "admin");
await logBatchDeletion(["user-1", "user-2", "user-3"], "Cleanup of inactive accounts", "system");Common Deletion Patterns
User Account Deletion
const user = await ctx.users.where((u) => u.id === userId).firstAsync();
if (user) {
// Delete related data first
await ctx.userSessions.where((s) => s.userId === userId).removeAsync();
await ctx.userPreferences.where((p) => p.userId === userId).removeAsync();
// Delete the user
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
}
Cleanup Operations
// Remove old sessions
const cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
await ctx.sessions.where((s) => s.lastActivity < cutoffDate).removeAsync();
// Remove inactive users
await ctx.users
.where((u) => u.isActive === false && u.lastLogin < cutoffDate)
.removeAsync();
await ctx.saveChangesAsync();
Batch Cleanup with Confirmation
const itemsToDelete = await ctx.items
.where((i) => i.status === "archived")
.toArrayAsync();
if (itemsToDelete.length > 0) {
const confirmed = await confirmDeletion(itemsToDelete.length);
if (confirmed) {
await ctx.items.removeAsync(itemsToDelete);
await ctx.saveChangesAsync();
}
}
Deletion Strategies
Hard Delete vs Soft Delete
Hard Delete: Permanently removes entities from the database
await ctx.users.removeAsync(user);
await ctx.saveChangesAsync();
Soft Delete: Marks entities as deleted without removing them
user.isDeleted = true;
user.deletedAt = new Date();
await ctx.saveChangesAsync();
Cascade Delete Patterns
Manual Cascade: Explicitly delete related entities
// Delete user and all related data
await ctx.userSessions.where((s) => s.userId === userId).removeAsync();
await ctx.userPosts.where((p) => p.userId === userId).removeAsync();
await ctx.users.removeAsync(user);
Database Cascade: Let the database handle cascading (plugin-dependent)
// If your plugin supports cascade delete
await ctx.users.removeAsync(user); // Related data deleted automatically
Next Steps
- Data Manipulation - Learn about proxy-based updates and array/object manipulation
- Create Operations - Learn how to add new entities
- Read Operations - Learn how to query and retrieve data
- Update Operations - Learn how to modify existing entities
- Bulk Operations - Learn how to handle multiple entities efficiently