bookshelf-paranoia
Protect your database from data loss by soft deleting your rows.
Unmaintained
I don't use this package anymore so it's un-maintained. I still spend a little time managing small
fixes but do so at a fairly slow pace. If you're interested in maintaining this project, please reach out to me.
Installation
After installing bookshelf-paranoia
with npm i --save bookshelf-paranoia
,
all you need to do is add it as a bookshelf plugin and enable it on your models.
The default field used to soft delete your models is deleted_at
but you can override that.
let knex = require('knex')(require('./knexfile.js').development)
let bookshelf = require('bookshelf')(knex)
bookshelf.plugin(require('bookshelf-paranoia'))
let User = bookshelf.Model.extend({ tableName: 'users', softDelete: true })
Usage
You can call every method as usual and bookshelf-paranoia
will handle soft
deletes transparently for you.
yield User.forge({ id: 1000 }).destroy()
let user = yield User.forge({ id: 1000 }).fetch()
let user = yield User.forge({ id: 2000 }).fetch()
let blog = yield Blog.forge({ id: 2000 }).fetch({ withRelated: 'users' })
blog.related('users').findWhere({ id: 1000 })
let user = yield knex('users').select('*').where('id', 1000)
console.log(user[0].deleted_at)
Overrides
bookshelf-paranoia
provides a set of overrides so you can customize your
experience while using it.
bookshelf.plugin(require('bookshelf-paranoia'), { field: 'deletedAt' })
bookshelf.plugin(require('bookshelf-paranoia'), {
nullValue: '0000-00-00 00:00:00'
})
yield User.forge({ id: 1000 }).destroy({ hardDelete: true })
let user = yield User.forge({ id: 1000 }).fetch({ withDeleted: true })
bookshelf.plugin(require('bookshelf-paranoia'), { events: false })
bookshelf.plugin(require('bookshelf-paranoia'), {
events: { destroying: false }
})
bookshelf.plugin(require('bookshelf-paranoia'), {
events: {
saving: true,
updating: true,
saved: true,
updated: true
}
})
Sentinels/Uniqueness
Due to limitations with some DBMSes, constraining a soft-delete-enabled
model to "only one active instance" is difficult: any unique index will
capture both undeleted and deleted rows. There are ways around this,
e.g., scoped indexes (WHERE deleted_at IS NULL), but the most portable
method involves adding a so-called sentinel column: a field that is true/1
when the row is active and NULL when it has been deleted. Since unique
indexes do not consider null fields, this allows a compound unique index
to fulfill our needs: indexing ['email', 'active'] will ensure only one
unique active email at a time, for example.
To maintain backward compatibility, sentinel functionality is disabled
by default. It can be enabled globally by setting the sentinel
config
value to the name of the sentinel column, nominally "active". The
sentinel column should be added to all soft-deletable tables via
migration as a nullable boolean field.
bookshelf.plugin(require('bookshelf-paranoia'), { sentinel: 'active' })
let user = yield User.forge().save()
user.get('active')
yield user.destroy()
user.get('active')
Detecting soft deletes
By listening to the default events emitted by bookshelf when destroying a model
you're able to detect if that model is being soft deleted.
let model = new User({ id: 1000 })
model.on('destroying', (model, options) => {
if (options.softDelete) console.log(`User ${model.id} is being soft deleted!`)
})
model.on('destroyed', (model, options) => {
if (options.softDelete) console.log(`User ${model.id} has been soft deleted!`)
})
yield model.destroy()
Testing
git clone git@github.com:bsiddiqui/bookshelf-paranoia.git
cd bookshelf-paranoia && npm install && npm test