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

Routier’s delete operations feature:

  1. Individual entity removal - Remove specific entities by reference
  2. Batch deletion - Remove multiple entities efficiently
  3. Query-based removal - Remove entities matching specific criteria
  4. Automatic cleanup - Proper disposal of removed entities
  5. 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 blocked

Change 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); // false

Saving 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 deletion

Performance 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();

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