seaorm-soft-delete 0.1.0

Soft-delete support for SeaORM entities
Documentation
# seaorm-soft-delete

Soft-delete support for [SeaORM](https://www.sea-ql.org/SeaORM/) entities.

Instead of `DELETE`, soft-deleted records have `deleted_at` set to the current timestamp.
Queries exclude soft-deleted records by default.

## Installation

```toml
[dependencies]
seaorm-soft-delete = "0.1"
```

If you don't need the migration helper, disable the default feature:

```toml
seaorm-soft-delete = { version = "0.1", default-features = false }
```

## Setup

### 1. Add `deleted_at` column via migration

```rust
use seaorm_soft_delete::SoftDeleteMigration;

// inside your MigrationTrait::up()
SoftDeleteMigration::add_column(&manager, Users::Table).await?;
```

> **Note:** Uses `ADD COLUMN IF NOT EXISTS` — idempotent on **PostgreSQL only**.
> On MySQL/SQLite, guard the migration yourself.

### 2. Implement `SoftDeleteEntity` on your entity

```rust
use seaorm_soft_delete::SoftDeleteEntity;

impl SoftDeleteEntity for users::Entity {
    fn deleted_at_column() -> users::Column {
        users::Column::DeletedAt
    }
}
```

### 3. Implement `SoftDeleteActiveModel` on your active model

```rust
use seaorm_soft_delete::SoftDeleteActiveModel;
use sea_orm::Set;

impl SoftDeleteActiveModel for users::ActiveModel {
    fn set_deleted_at(&mut self, value: Option<chrono::DateTime<chrono::Utc>>) {
        self.deleted_at = Set(value.map(|v| v.into()));
    }
}
```

### 4. Implement `SoftDeleteModel` on your model (optional)

Adds `is_deleted()` directly on model instances.

```rust
use seaorm_soft_delete::SoftDeleteModel;

impl SoftDeleteModel for users::Model {
    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
        self.deleted_at.map(|v| v.into())
    }
}
```

## Querying

```rust
// Active records only (WHERE deleted_at IS NULL) — use by default
users::Entity::find_active().all(&db).await?;
users::Entity::find_active_by_id(id).one(&db).await?;

// Soft-deleted records only (WHERE deleted_at IS NOT NULL)
users::Entity::find_deleted().all(&db).await?;
users::Entity::find_deleted_by_id(id).one(&db).await?;

// All records including soft-deleted
users::Entity::find_with_deleted().all(&db).await?;
```

All return `Select<Entity>` — chain `.filter()`, `.order_by()`, `.paginate()` etc. normally.

## Write operations

```rust
let model: ActiveModel = user.into();

// Soft-delete: sets deleted_at = NOW()
model.soft_delete(&db).await?;

// Restore: sets deleted_at = NULL
model.restore(&db).await?;

// Hard delete: permanently removes the row
model.hard_delete(&db).await?;
```

## Checking deleted state

```rust
// Requires SoftDeleteModel impl (see Setup step 4)
if user.is_deleted() {
    println!("deleted at {:?}", user.deleted_at());
}
```

## Performance tip

For large tables, add a partial index so active-record queries stay fast:

```sql
CREATE INDEX idx_users_active ON users (id) WHERE deleted_at IS NULL;
```

## MSRV

Rust 1.85+

## Out of scope (v1)

- Cascading soft deletes
- Custom column names (always `deleted_at`)
- MySQL/SQLite idempotent migration

## License

MIT