Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

CircleCICoverage Status

objection-soft-delete

A plugin that adds soft-delete functionality to Objection.js

Installation

NPM

npm i objection-soft-delete --save

Yarn

yarn add objection-soft-delete

Usage

Mixin the plugin on an object representing a table that uses a boolean column as a flag for soft delete

// Import objection model.constModel=require('objection').Model;// Import the pluginconstsoftDelete=require('objection-soft-delete');// Mixin the plugin and specify the column to to use. 'deleted' will be used if none is specified:classUserextendssoftDelete({columnName: 'deleted'})(Model){staticgettableName(){return'Users';}staticgetjsonSchema(){return{type: 'object',required: [],properties: {id: {type: 'integer'},// matches the columnName passed abovedeleted: {type: 'boolean'},// other columns},}}}

When .delete() or .del() is called for that model, the matching row(s) are flagged true instead of deleted

Delete a User:

awaitUser.query().where('id',1).delete();// db now has: { User id: 1, deleted: true, ... }

Or:

constuser=awaitUser.query().where('id',1).first();awaituser.$query().delete();// same

Deleted rows are still in the db:

constdeletedUser=awaitUser.query().where('id',1).first();// => { User id: 1, deleted: true, ... }

Filter out deleted rows without having to remember each model's "deleted" columnName:

constactiveUsers=awaitUser.query().whereNotDeleted();

Get only deleted rows:

constdeletedUsers=awaitUser.query().whereDeleted();

Restore row(s):

awaitUser.query().where('id',1).undelete();// db now has: { User id: 1, deleted: false, ... }

Permanently remove row(s) from the db:

awaitUser.query.where('id',1).hardDelete();// => row with id:1 is permanently deleted

Filtering out deleted/undeleted records in .eager() or .joinRelation()

Using the named filters

A notDeleted and a deleted filter will be added to the list of named filters for any model that mixes in the plugin. These filters use the .whereNotDeleted() and .whereDeleted() functions to filter records, and can be used without needing to remember the specific columnName for any model:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(notDeleted)');// => now group.users contains only records that are not deleted

Or:

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).first().eager('users(deleted)');// => now group.users contains only records that are deleted

With .joinRelation():

// some other Model with a relation to the `User` model:constgroup=awaitUserGroup.query().where('id',1).joinRelation('users(notDeleted)').where('users.firstName','like','a%');// => all groups that have an undeleted user whose first name starts with 'a';

Using a relationship filter

A filter can be applied directly to the relationship definition to ensure that deleted/undeleted rows never appear:

// some other class that has a FK to User:classUserGroupextendsModel{staticgettableName(){return'UserGroups';}
...
staticgetrelationMappings(){return{users: {relation: Model.ManyToManyRelation,modelClass: User,join: {from: 'UserGroups.id',through: {from: 'GroupUsers.groupId',to: 'GroupUsers.userId',},to: 'Users.id',},filter: (f)=>{f.whereNotDeleted();// or f.whereDeleted(), as needed.},},}}}

then:

constgroup=awaitUserGroup.query().where('id',1).first().eager('users');// => `User` rows are filtered out automatically without having to specify the filter here

Per-model columnName

If for some reason you have to deal with different column names for different models (legacy code/schemas can be a bear!), all functionality is fully supported:

classUserextendssoftDelete({columnName: 'deleted'})(Model){
...
}classUserGroupextendssoftDelete({columnName: 'inactive'})(Model){
...
}// everything will work as expected:awaitUser.query().whereNotDeleted();// => all undeleted usersawaitUserGroup.query().whereNotDeleted();// => all undeleted user groupsawaitUserGroup.query().whereNotDeleted().eager('users(notDeleted)');// => all undeleted user groups, with all related undeleted users eagerly loadedawaitUser.query().whereDeleted().eager('groups(deleted)');// => all deleted users, with all related deleted user groups eagerly loadedawaitUser.query().whereNotDeleted().joinRelation('groups(notDeleted)').where('groups.name','like','%local%').eager('groups(notDeleted)');// => all undeleted users that belong to undeleted user groups that have a name containing the string 'local', eagerly load all undeleted groups for said users.// and so on...

Using with .upsertGraph()

This plugin was actually born out of a need to have .upsertGraph() soft delete in some tables, and hard delete in others, so it plays nice with .upsertGraph():

// a model with soft deleteclassPhoneextendssoftDelete(Model){staticgettableName(){return'Phones';}}// a model without soft deleteclassEmailextendsModel{staticgettableName(){return'Emails';}}// assume a User model that relates to both, and the following existing data:User{id: 1,name: 'Johnny Cash',phones: [{id: 6,number: '+19195551234',},],emails: [{id: 3,address: 'mib@americanrecords.com',},]}// then:awaitUser.query().upsertGraph({id: 1,name: 'Johnny Cash',phones: [],emails: [],});// => phone id 6 will be flagged deleted (and will still be related to Johnny!), email id 3 will be removed from the database

Options

columnName: the name of the column to use as the soft delete flag on the model (Default: 'deleted'). The column must exist on the table for the model.

You can specify different column names per-model by using the options:

constsoftDelete=require('objection-soft-delete')({columnName: 'inactive',});

Tests

Tests can be run with:

npm test

or:

yarn test

Linting

The linter can be run with:

npm run lint

or:

yarn lint

Contributing

The usual spiel: fork, fix/improve, write tests, submit PR. I try to maintain a (mostly) consistent syntax, but am open to suggestions for improvement. Otherwise, the only two rules are: do good work, and no tests = no merge.

About

A plugin that adds soft-delete functionality to objection.js

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages