NestboltNestbolt

@nestbolt/soft-delete

Introduction

Soft delete for NestJS TypeORM entities -- mark rows as deleted instead of removing them, with restore, force delete, and trashed-query helpers.

@nestbolt/soft-delete adds soft-delete semantics to NestJS TypeORM entities. Decorate any entity with @SoftDeletable() and a deletedAt column, then call softDelete(), restore(), or forceDelete() through a typed service or directly on the entity instance via the mixin.

Key Features

  • Non-destructive by default -- softDelete() writes deletedAt = now() instead of issuing a DELETE. Rows stay queryable.
  • Restore and force delete -- restore() clears deletedAt, forceDelete() issues a real DELETE for when you actually need the row gone.
  • Per-entity column override -- default deleted_at column is configurable globally via forRoot() or per entity via @SoftDeletable({ columnName }).
  • Query helpers -- withTrashed() returns a SelectQueryBuilder that includes deleted rows; onlyTrashed() returns one that selects only deleted rows.
  • Entity mixin -- SoftDeletableMixin adds softDelete(), restore(), forceDelete(), isDeleted(), isTrashed(), and getDeletedAt() directly on entity instances.
  • repo.remove() interception -- a TypeORM subscriber best-effort intercepts repository.remove() calls and turns them into soft deletes for decorated entities.
  • Event system -- emits soft-delete.deleted, soft-delete.restored, and soft-delete.force-deleted via @nestjs/event-emitter (optional).

Architecture

The package is built around several core components:

SoftDeleteModule registers all providers globally. You call SoftDeleteModule.forRoot() or SoftDeleteModule.forRootAsync() once in your root module, and SoftDeleteService becomes available everywhere via dependency injection.

SoftDeleteService is the primary API surface. It provides softDelete(), restore(), forceDelete(), withTrashed(), onlyTrashed(), and isSoftDeletable(). It also resolves the deleted-at column name from module options + entity-level overrides, and emits events when the optional @nestjs/event-emitter is registered.

@SoftDeletable() decorator marks an entity class as soft-deletable and optionally overrides the column name for that entity.

SoftDeletableMixin is an optional mixin that adds convenience methods directly to your entity instances so you can call post.softDelete() instead of service.softDelete(Post, post.id).

SoftDeleteSubscriber registers itself on the TypeORM DataSource and intercepts repository.remove() calls -- if the entity is decorated and not already soft-deleted, it sets the deleted-at column instead of letting the row be physically removed.

Basic Usage

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { SoftDeletable, SoftDeletableMixin } from "@nestbolt/soft-delete";

@SoftDeletable()
@Entity("posts")
export class Post extends SoftDeletableMixin(class { id!: string }) {
  @PrimaryGeneratedColumn("uuid")
  declare id: string;

  @Column()
  title!: string;

  @Column({ name: "deleted_at", type: "datetime", nullable: true, default: null })
  deletedAt!: Date | null;
}

// Soft delete, restore, force delete
await post.softDelete();
await post.restore();
await post.forceDelete();

// Inspect state
post.isDeleted();      // true after softDelete()
post.getDeletedAt();   // Date | null

Next Steps

  • Installation -- install the package and its peer dependencies.
  • Quick Start -- set up the module, decorate an entity, and soft-delete your first record in minutes.
  • Configuration -- forRoot, forRootAsync, and the full SoftDeleteService API.
  • Decorator -- @SoftDeletable() and @IncludeDeleted() options reference.
  • Mixin -- add softDelete(), restore(), forceDelete(), and friends directly to your entities.
  • Subscriber -- how repository.remove() is intercepted and when to prefer the explicit API.
  • Events -- listen to soft-delete.deleted, soft-delete.restored, and soft-delete.force-deleted.