Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "9.0"
- "8.0"
- "7.0"
- "6.0"
- '9.0'
- '8.0'
- '7.0'
- '6.0'
26 changes: 9 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
Historical tables for Sequelize
===============================
# Historical tables for Sequelize

Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5.

[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/)

## What is it?

What is it?
-----------

Historical tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to:
Historical tables maintain **historical versions** of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to:

- log changes (security/auditing)
- undo functionalities
Expand All @@ -22,15 +19,13 @@ The normal singular/plural naming scheme in Sequelize is used:
- model name: `modelName + History`
- table name: `modelName + Histories`

Installation
------------
## Installation

```
npm install sequelize-historical
```

How to use
----------
## How to use

### 1) Import `sequelize-historical`

Expand All @@ -48,7 +43,7 @@ var sequelize = new Sequelize('', '', '', {
});
```

### 2) Add the *historical* feature to your models
### 2) Add the _historical_ feature to your models

```
var User = Historical(sequelize.define('User'), sequelize);
Expand All @@ -62,8 +57,7 @@ var User = sequelize.define('User', {.types.}, {.options.}); // Sequelize Docu
Historical(User, sequelize);
```

Options
-------
## Options

The default syntax for `Historical` is:

Expand All @@ -84,8 +78,7 @@ whereas the options are listed here (with default value).
full: false
```

Details
--------
## Details

@See: https://wiki.postgresql.org/wiki/SQL2011Temporal

Expand All @@ -101,8 +94,7 @@ Triggers for storing old versions of rows to history table are inspired by refer

If you only use Postgres, you might want to have a look at the [Temporal Table](https://github.com/arkhipov/temporal_tables) extension.

License
-------
## License

The MIT License (MIT)

Expand Down
14 changes: 7 additions & 7 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
declare module 'sequelize-temporal' {
interface Options {
blocking?:boolean,
full?:boolean
}
declare module 'sequelize-temporal' {
interface Options {
blocking?: boolean;
full?: boolean;
}

function output<T>(define:T, sequelize:any, options?:Options): T
function output<T>(define: T, sequelize: any, options?: Options): T;

export = output;
export = output;
}
114 changes: 75 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ var temporalDefaultOptions = {
full: false
};

var excludeAttributes = function(obj, attrsToExclude){
var excludeAttributes = function(obj, attrsToExclude) {
// fancy way to exclude attributes
return _.omit(obj, _.partial(_.rearg(_.contains,0,2,1), attrsToExclude));
}
return _.omit(obj, _.partial(_.rearg(_.contains, 0, 2, 1), attrsToExclude));
};

var Temporal = function(model, sequelize, temporalOptions){
temporalOptions = _.extend({},temporalDefaultOptions, temporalOptions);
var Temporal = function(model, sequelize, temporalOptions) {
temporalOptions = _.extend({}, temporalDefaultOptions, temporalOptions);

var Sequelize = sequelize.Sequelize;

Expand All @@ -23,8 +23,8 @@ var Temporal = function(model, sequelize, temporalOptions){

var historyOwnAttrs = {
hid: {
type: Sequelize.BIGINT,
primaryKey: true,
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true,
unique: true
},
Expand All @@ -35,55 +35,89 @@ var Temporal = function(model, sequelize, temporalOptions){
}
};

var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"];
var historyAttributes = _(model.rawAttributes).mapValues(function(v){
v = excludeAttributes(v, excludedAttributes);
// remove the "NOW" defaultValue for the default timestamps
// we want to save them, but just a copy from our master record
if(v.fieldName == "createdAt" || v.fieldName == "updatedAt"){
v.type = Sequelize.DATE;
}
return v;
}).assign(historyOwnAttrs).value();
var excludedAttributes = [
'Model',
'unique',
'primaryKey',
'autoIncrement',
'set',
'get',
'_modelAttribute'
];
var historyAttributes = _(model.rawAttributes)
.mapValues(function(v) {
v = excludeAttributes(v, excludedAttributes);
// remove the "NOW" defaultValue for the default timestamps
// we want to save them, but just a copy from our master record
if (v.fieldName == 'createdAt' || v.fieldName == 'updatedAt') {
v.type = Sequelize.DATE;
}
return v;
})
.assign(historyOwnAttrs)
.value();
// If the order matters, use this:
//historyAttributes = _.assign({}, historyOwnAttrs, historyAttributes);

var historyOwnOptions = {
timestamps: false
};
var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"];
var excludedNames = [
'name',
'tableName',
'sequelize',
'uniqueKeys',
'hasPrimaryKey',
'hooks',
'scopes',
'instanceMethods',
'defaultScope'
];
var modelOptions = excludeAttributes(model.options, excludedNames);
var historyOptions = _.assign({}, modelOptions, historyOwnOptions);

// We want to delete indexes that have unique constraint
var indexes = historyOptions.indexes;
if(Array.isArray(indexes)){
historyOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';});
if (Array.isArray(indexes)) {
historyOptions.indexes = indexes.filter(function(index) {
return !index.unique && index.type != 'UNIQUE';
});
}

var modelHistory = sequelize.define(historyName, historyAttributes, historyOptions);
var modelHistory = sequelize.define(
historyName,
historyAttributes,
historyOptions
);

// we already get the updatedAt timestamp from our models
var insertHook = function(obj, options){
var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues;
var historyRecord = modelHistory.create(dataValues, {transaction: options.transaction});
if(temporalOptions.blocking){
var insertHook = function(obj, options) {
var dataValues =
(!temporalOptions.full && obj._previousDataValues) || obj.dataValues;
var historyRecord = modelHistory.create(dataValues, {
transaction: options.transaction
});
if (temporalOptions.blocking) {
return historyRecord;
}
}
var insertBulkHook = function(options){
if(!options.individualHooks){
var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){
if(hits){
hits = _.pluck(hits, 'dataValues');
return modelHistory.bulkCreate(hits, {transaction: options.transaction});
}
});
if(temporalOptions.blocking){
};
var insertBulkHook = function(options) {
if (!options.individualHooks) {
var queryAll = model
.findAll({ where: options.where, transaction: options.transaction })
.then(function(hits) {
if (hits) {
hits = _.pluck(hits, 'dataValues');
return modelHistory.bulkCreate(hits, {
transaction: options.transaction
});
}
});
if (temporalOptions.blocking) {
return queryAll;
}
}
}
};

// use `after` to be nonBlocking
// all hooks just create a copy
Expand All @@ -100,8 +134,10 @@ var Temporal = function(model, sequelize, temporalOptions){
model.addHook('beforeBulkUpdate', insertBulkHook);
model.addHook('beforeBulkDestroy', insertBulkHook);

var readOnlyHook = function(){
throw new Error("This is a read-only history database. You aren't allowed to modify it.");
var readOnlyHook = function() {
throw new Error(
"This is a read-only history database. You aren't allowed to modify it."
);
};

modelHistory.addHook('beforeUpdate', readOnlyHook);
Expand Down
20 changes: 19 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"test": "test"
},
"scripts": {
"test": "mocha"
"test": "mocha",
"pretest": "npm run prettier:check",
"prettier": "prettier \"**/*.@(js|json|md|yml|ts)\"",
"prettier:write": "npm run prettier -- --write",
"prettier:check": "npm run prettier -- --list-different"
},
"repository": {
"type": "git",
Expand All @@ -34,8 +38,22 @@
"devDependencies": {
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"mocha": "^6.0.2",
"prettier": "^1.16.4",
"sequelize": "^5.2.1",
"sqlite3": "^4.0.6"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,json,md,yml,ts}": [
"prettier --write",
"git add"
]
}
}
Loading