Skip to content

Events, Testing & API Reference

Events

Enable events and install @nestjs/event-emitter:

bash
npm install @nestjs/event-emitter
typescript
// app.module.ts
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot(),
    SoftDeleteModule.forRoot({
      softDeleteModels: ['User', 'Post'],
      enableEvents: true,
      prismaServiceToken: PrismaService,
    }),
  ],
})
export class AppModule {}

Listen to events with @OnEvent():

typescript
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { SoftDeletedEvent, RestoredEvent, PurgedEvent } from '@nestarc/soft-delete';

@Injectable()
export class AuditListener {
  @OnEvent(SoftDeletedEvent.EVENT_NAME)
  onDeleted(event: SoftDeletedEvent) {
    console.log(`${event.model} soft-deleted by ${event.actorId} at ${event.deletedAt}`);
  }

  @OnEvent(RestoredEvent.EVENT_NAME)
  onRestored(event: RestoredEvent) {
    console.log(`${event.model} restored by ${event.actorId}`);
  }

  @OnEvent(PurgedEvent.EVENT_NAME)
  onPurged(event: PurgedEvent) {
    console.log(`${event.count} ${event.model} records purged (older than ${event.olderThan})`);
  }
}
Event classEVENT_NAMEPayload fields
SoftDeletedEventsoft-delete.deletedmodel, where, deletedAt, actorId, count?
RestoredEventsoft-delete.restoredmodel, where, actorId, count?
PurgedEventsoft-delete.purgedmodel, count, olderThan

Version 0.5 populates the optional count for bulk delete and bulk restore operations. Single-row soft deletes report count: 1; single-row restores keep the field optional for backward compatibility.

typescript
@OnEvent(RestoredEvent.EVENT_NAME)
onRestored(event: RestoredEvent) {
  metrics.count('soft_delete.restored', event.count ?? 1, {
    model: event.model,
  });
}

Testing

Import TestSoftDeleteModule from @nestarc/soft-delete/testing in your unit or integration tests.

typescript
import { Test } from '@nestjs/testing';
import { TestSoftDeleteModule, expectSoftDeleted, expectNotSoftDeleted, expectCascadeSoftDeleted } from '@nestarc/soft-delete/testing';
import { SoftDeleteService } from '@nestarc/soft-delete';
import { createPrismaSoftDeleteExtension } from '@nestarc/soft-delete';
import { basePrisma } from './prisma';
import { prismaDmmf } from './prisma.dmmf';

describe('UsersService', () => {
  let softDelete: SoftDeleteService;
  let prisma: any; // your extended PrismaClient in tests

  beforeAll(async () => {
    prisma = basePrisma.$extends(
      createPrismaSoftDeleteExtension({
        softDeleteModels: ['User', 'Post'],
        cascade: { User: ['Post'] },
        dmmf: prismaDmmf,
      }),
    );

    const module = await Test.createTestingModule({
      imports: [
        TestSoftDeleteModule.register(
          {
            softDeleteModels: ['User', 'Post'],
            cascade: { User: ['Post'] },
            dmmf: prismaDmmf,
          },
          prisma,
        ),
      ],
    }).compile();

    softDelete = module.get(SoftDeleteService);
  });

  it('soft-deletes a user', async () => {
    await prisma.user.delete({ where: { id: 1 } });
    await expectSoftDeleted(prisma.user, { id: 1 });
  });

  it('restores a user', async () => {
    await softDelete.restore('User', { id: 1 });
    await expectNotSoftDeleted(prisma.user, { id: 1 });
  });

  it('cascades soft-delete to posts', async () => {
    await prisma.user.delete({ where: { id: 2 } });
    await expectCascadeSoftDeleted(prisma, 'User', { id: 2 }, ['Post']);
  });
});

basePrisma is your generated, adapter-backed Prisma 7 client. Because this test enables cascade, load prismaDmmf as shown in the DMMF setup.

Assertion helpers

HelperDescription
expectSoftDeleted(delegate, where, deletedAtField?)Asserts the record exists and deletedAt is non-null.
expectNotSoftDeleted(delegate, where, deletedAtField?)Asserts the record exists and deletedAt is null.
expectCascadeSoftDeleted(prisma, parentModel, where, childModels, deletedAtField?)Asserts the parent and all listed child models have soft-deleted records.

API Reference

@nestarc/soft-delete

ExportKindDescription
SoftDeleteModuleModuleNestJS dynamic module. Use .forRoot() or .forRootAsync().
SoftDeleteServiceServicerestore(), restoreMany(), forceDelete(), purge(), withDeleted(), onlyDeleted().
SoftDeleteContextServiceAsyncLocalStorage context for filter mode.
createPrismaSoftDeleteExtensionFunctionCreates a Prisma client extension for standalone use.
WithDeletedDecoratorInclude soft-deleted records in the route handler's queries.
OnlyDeletedDecoratorReturn only soft-deleted records in the route handler's queries.
SkipSoftDeleteDecoratorBypass soft-delete logic in the route handler.
WithDeletedRelationsDecoratorInclude deleted rows for exact to-many relation paths when relation filtering is enabled.
SoftDeleteFilterInterceptorInterceptorReads route metadata and sets the SoftDeleteContext. Auto-registered.
SoftDeletedEventClassEvent emitted after a soft-delete. EVENT_NAME = 'soft-delete.deleted'.
RestoredEventClassEvent emitted after a restore. EVENT_NAME = 'soft-delete.restored'.
PurgedEventClassEvent emitted after a purge. EVENT_NAME = 'soft-delete.purged'.
SoftDeleteEventEmitterServiceInternal emitter; exposed for advanced use.
SoftDeleteFieldMissingErrorErrorExported error reserved for missing-field validation; current runtime paths surface missing fields as Prisma errors.
CascadeRelationNotFoundErrorErrorThrown when a cascade relation cannot be resolved.
CascadeDmmfMissingErrorErrorThrown when cascade is configured without available DMMF metadata.
RelationDmmfMissingErrorErrorThrown when relation filters are enabled without available DMMF metadata.
SoftDeleteModuleOptionsInterfaceOptions for forRoot().
SoftDeleteModuleAsyncOptionsInterfaceOptions for forRootAsync().
SoftDeleteExtensionOptionsInterfaceOptions for createPrismaSoftDeleteExtension().
RelationFilterOptionsInterfaceEnables relation filtering and bounds traversal depth.
PrismaDmmfLikeInterfaceMinimal DMMF shape accepted by explicit configuration.

@nestarc/soft-delete/testing

ExportKindDescription
TestSoftDeleteModuleModuleLightweight test module. Use .register(options, prisma?).
expectSoftDeletedFunctionAssert a record is soft-deleted.
expectNotSoftDeletedFunctionAssert a record is not soft-deleted.
expectCascadeSoftDeletedFunctionAssert a parent and its children are all soft-deleted.

For complete signatures, see the generated @nestarc/soft-delete API and testing API.

Released under the MIT License.