@glazk0/lucid-soft-deletes
v1.0.2
Published
Laravel-style soft-delete mixin for AdonisJS Lucid v22
Maintainers
Readme
@glazk0/lucid-soft-deletes
Laravel-style soft-delete mixin for AdonisJS Lucid v22 on AdonisJS v7.
Adds a deletedAt column, a global WHERE deleted_at IS NULL scope, a trashed getter, restore() and forceDelete() instance methods, and withTrashed() / onlyTrashed() / bulk restore() query-builder macros.
This package is a fork of
lookinlab/adonis-lucid-soft-deletes, which is no longer actively maintained. The fork brings the mixin forward to AdonisJS v7 + Lucid v22, updates the typings, and ships an Ace command for scaffolding the migration.
Install
node ace add @glazk0/lucid-soft-deletesThis installs the package with your project's package manager and runs the configure hook, which registers the provider in adonisrc.ts, registers the make:soft-delete-migration Ace command, and drops a reference doc under docs/soft_deletes_example.md.
Usage
Apply the mixin
import { DateTime } from 'luxon'
import { compose } from '@adonisjs/core/helpers'
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { SoftDeletes } from '@glazk0/lucid-soft-deletes'
export default class User extends compose(BaseModel, SoftDeletes) {
@column({ isPrimary: true })
declare id: number
@column()
declare email: string
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null
}Add the deleted_at column
node ace make:soft-delete-migration users
node ace migration:runThe generated migration adds a nullable, indexed deleted_at column. The index is important — the global scope appends WHERE deleted_at IS NULL to every query against the table.
Soft-delete, restore, force-delete
await user.delete() // soft delete (sets deletedAt)
user.trashed // true
await user.restore() // un-delete
await user.forceDelete() // permanent delete (bypasses soft-delete)Query helpers
await User.query().withTrashed() // includes soft-deleted rows
await User.query().onlyTrashed() // returns only soft-deleted rows
await User.query().onlyTrashed().restore() // bulk restore matching rowsOverride the column name
Re-declare deletedAt on your model:
export default class User extends compose(BaseModel, SoftDeletes) {
@column.dateTime({ columnName: 'removed_at' })
declare deletedAt: DateTime | null
}Gotchas
- Raw queries bypass the scope.
db.from('users')is not filtered — same behavior as Laravel. firstOrCreate/updateOrCreateskip soft-deleted rows by default and can create duplicates. Use.withTrashed()lookups for match-or-restore flows.compose()order matters. PutSoftDeletesfirst afterBaseModelso its hooks register before any other mixin's hooks..clone()drops the scope flag. Explicitquery.clone()afterwithTrashed()loses the opt-out — parity with Laravel.- Serialization.
deletedAtis serialized by default. To hide it, override with@column.dateTime({ columnName: 'deleted_at', serializeAs: null }). - Bulk
restore()is gated. Callingrestore()on a query builder for a model that lacks the mixin throwsModelNotSoftDeletableException.
License
MIT
