Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1927d4d
chore: transform package in sequelize-historical
znarf Mar 25, 2019
ddc1b84
package: update mocha to version ^6.0.2
znarf Mar 27, 2019
28204d9
package: update chai to version 4.2.0
znarf Mar 27, 2019
de79d5d
package: update sequelize to version 5.2.1
znarf Mar 27, 2019
d16b0ac
package: update sqlite3 to version 4.0.6
znarf Mar 27, 2019
76d7f86
chore(package): update dependencies
greenkeeper[bot] Mar 27, 2019
cc329c1
chore: ignore package-lock.json
znarf Mar 27, 2019
5ece149
docs(readme): add Greenkeeper badge
greenkeeper[bot] Mar 27, 2019
208d5bb
package: downgrade lodash to version 3.10.1
znarf Mar 27, 2019
18997ae
Merge pull request #5 from opencollective/greenkeeper/initial
znarf Mar 27, 2019
6565f88
Added ModelSuffix
kurisutofu Apr 15, 2019
a9ea909
Merge pull request #1 from kurisutofu/develop
kurisutofu Apr 15, 2019
f2e4230
Fixed Read Me
kurisutofu Apr 15, 2019
f593c30
Merge pull request #2 from kurisutofu/develop
kurisutofu Apr 15, 2019
7c5c2d5
Fixed Read Me
kurisutofu Apr 15, 2019
51e6b93
Merge pull request #3 from kurisutofu/develop
kurisutofu Apr 15, 2019
55aaa1a
Added modelSuffix to ts file
kurisutofu Apr 15, 2019
92fe5fe
Merge pull request #4 from kurisutofu/develop
kurisutofu Apr 15, 2019
5d46282
Association
kurisutofu Apr 18, 2019
96bd1b4
Fix association
kurisutofu Apr 20, 2019
07581c7
Update association logic
kurisutofu Apr 20, 2019
2b85246
Fixed logic
kurisutofu Apr 21, 2019
3e22b02
Updated tests
kurisutofu Apr 21, 2019
4cefc56
Before Lodash Upgrade
kurisutofu Apr 22, 2019
9e5f15b
All test + upgrades
kurisutofu Apr 22, 2019
e7ba226
Updated MD file
kurisutofu Apr 23, 2019
c2f8cae
Renamed
kurisutofu Apr 29, 2019
38aee0b
Fixed test & finalize
kurisutofu Apr 30, 2019
e2cfdf5
Update readme
kurisutofu Apr 30, 2019
4aefbf8
Updating version
kurisutofu Apr 30, 2019
930350e
Merge pull request #5 from kurisutofu/develop
kurisutofu Apr 30, 2019
26eda4c
Adding Travis
kurisutofu Apr 30, 2019
d981171
Merge pull request #6 from kurisutofu/develop
kurisutofu Apr 30, 2019
8a507bf
Moved logic to use beforeBulkSync
kurisutofu May 8, 2019
c5dd2c0
Reverted to BeforeInsert
kurisutofu May 8, 2019
4485abc
Updating name back to sequelize-historical
kurisutofu May 8, 2019
78acc5c
Merge pull request #7 from kurisutofu/develop
kurisutofu May 8, 2019
343b8a1
Update names
kurisutofu May 8, 2019
6c8e398
Merge pull request #8 from kurisutofu/develop
kurisutofu May 8, 2019
d2e8925
Name update again
kurisutofu May 8, 2019
74d175f
Merge pull request #9 from kurisutofu/develop
kurisutofu May 8, 2019
7d5fe15
Renaming to temporal
kurisutofu May 30, 2019
823fa89
Merge pull request #10 from kurisutofu/develop
kurisutofu May 30, 2019
9e04d96
Fixed ReadMe
kurisutofu May 30, 2019
e88bcb2
Merge pull request #11 from kurisutofu/develop
kurisutofu May 30, 2019
283e625
Association onDelete/onUpdate actions
kurisutofu Jun 2, 2019
f0d0eca
Merge pull request #12 from kurisutofu/develop
kurisutofu Jun 2, 2019
1bb6c23
Removing associations
kurisutofu Jun 2, 2019
f8b94be
Merge pull request #13 from kurisutofu/develop
kurisutofu Jun 2, 2019
d7e9a68
AllowTransaction flag
kurisutofu Jun 11, 2019
c3a4e72
Merge pull request #14 from kurisutofu/develop
kurisutofu Jun 11, 2019
f611c01
Added AfterBulk hooks
kurisutofu Jun 11, 2019
0f44252
Merge pull request #15 from kurisutofu/develop
kurisutofu Jun 11, 2019
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 .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
.test.sqlite
node_modules
package-lock.json
server.js
.vscode
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.test.sqlite
node_modules
package-lock.json
server.js
.vscode
/test
.git
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
language: node_js
node_js:
- "9.0"
- "8.0"
- "7.0"
- "6.0"
- "11.14.0"
- "11.0"
77 changes: 73 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Temporal tables maintain __historical versions__ of data. Modifying operations (
- undo functionalities
- track interactions (customer support)

Under the hood a history table with the same structure, but without constraints is created.
Under the hood a history table with the same structure, but without constraints is created (unless option __addAssociation__ is set to __true__).

The normal singular/plural naming scheme in Sequelize is used:

Expand Down Expand Up @@ -75,12 +75,81 @@ whereas the options are listed here (with default value).
/* runs the insert within the sequelize hook chain, disable
for increased performance without warranties */
blocking: true,
/* By default sequelize-temporal persist only changes, and saves the previous state in the history table.
/* By default sequelize-temporal persist only changes, and saves the previous state in the history table.
The "full" option saves all transactions into the temporal database
(i.e. this includes the latest state.)
This allows to only query the hostory table to get the full history of an entity.
This allows to only query the history table to get the full history of an entity.
*/
full: false
full: false,
/*
By default sequelize-temporal will add 'History' to the history Model name and 'Histories' to the history table.
By updating the modelSuffix value, you can decide what the naming will be.
The value will be appended to the history Model name and its plural will be appended to the history tablename.

examples for table User:
modelSuffix: '_Hist' --> History Model Name: User_Hist --> History Table Name: User_Hists
modelSuffix: 'Memory' --> History Model Name: UserMemory --> History Table Name: UserMemories
modelSuffix: 'Pass' --> History Model Name: UserPass --> History Table Name: UserPasses
*/
modelSuffix: 'History',
/*
By default sequelize-temporal will create the history table without associations.
However, setting this flag to true, you can keep association between the history table and the table with the latest value (origin).

NOTE: THIS DOES NOT WORK IF YOU ARE USING A SEPARATE DB FOR THE HISTORICAL TABLES. IN THAT CASE, KEEP THE VALUE TO FALSE OR YOU WILL GET AN ERROR.

example for table User:
model: 'User'
history model: 'UserHistories'
--> This would add function User.getUserHistories() to return all history entries for that user entry.
--> This would add function UserHistories.getUser() to get the original user from an history.

If a model has associations, those would be mirrored to the history table.
Origin model can only get its own histories.
Even if a history table is associated to another origin table thought a foreign key field, the history table is not accessible from that origin table

Basically, what you can access in the origin table can be accessed from the history table.

example:
model: User
history model: UserHistories

model: Creation
history model: CreationHistories

User <-> Creation: 1 to many

User.getCreations() exists (1 to many)
Creation.getUser() exists (1 to 1)

User <-> UserHistories: 1 to many

User.getUserHistories() exists (1 to many)
UserHistories.getUser() exists (1 to 1)

Creation <-> CreationHistories: 1 to many

Creation.getCreationHistories() exists (1 to many)
CreationHistories.getCreation() exists (1 to 1)

CreationHistories -> User: many to 1

CreationHistories.getUser() exists (1 to 1) (same as Creation.getUser())
User.GetCreationHistories DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED.

UserHistories -> Creation: many to many

UserHistories.getCreations() exists (1 to many) (same as User.getCreations())
CreationHistories.getUser() DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED.

*/
addAssociations: false,
/*
By default, transactions are allowed but can be disabled with that flag for the historical tables (transactions on original tables should stay the same). It is useful in case you are using a separate DB than the one use by the original DB.

NOTE: IF YOU USE A SEPARATE DB FOR HISTORICAL TABLE, SET THE VALUE TO FALSE OR YOU WILL GET AN ERROR.
*/
allowTransactions: true
```

Details
Expand Down
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
declare module 'sequelize-temporal' {
interface Options {
blocking?:boolean,
full?:boolean
full?:boolean,
modelSuffix?:string,
addAssociations?:boolean,
}

function output<T>(define:T, sequelize:any, options?:Options): T
Expand Down
80 changes: 58 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ var temporalDefaultOptions = {
// runs the insert within the sequelize hook chain, disable
// for increased performance
blocking: true,
full: false
full: false,
modelSuffix: 'History',
addAssociations: false,
allowTransactions: true,
};

var excludeAttributes = function(obj, attrsToExclude){
// fancy way to exclude attributes
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;

var historyName = model.name + 'History';
//var historyName = model.getTableName() + 'History';
//var historyName = model.options.name.singular + 'History';
var historyName = model.name + temporalOptions.modelSuffix;

var historyOwnAttrs = {
hid: {
Expand All @@ -35,9 +31,9 @@ var Temporal = function(model, sequelize, temporalOptions){
}
};

var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"];
var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute","references","onDelete","onUpdate"];
var historyAttributes = _(model.rawAttributes).mapValues(function(v){
v = excludeAttributes(v, excludedAttributes);
v = _.omit(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"){
Expand All @@ -52,31 +48,34 @@ var Temporal = function(model, sequelize, temporalOptions){
timestamps: false
};
var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"];
var modelOptions = excludeAttributes(model.options, excludedNames);
var modelOptions = _.omit(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';});
historyOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';});
}

var modelHistory = sequelize.define(historyName, historyAttributes, historyOptions);
modelHistory.originModel = model;
modelHistory.addAssociations = temporalOptions.addAssociations;

// 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});
var insertHook = function(obj, options){
var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues;
var historyRecord = modelHistory.create(dataValues, {transaction: temporalOptions.allowTransactions? options.transaction: null});
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});
hits = _.map(hits, 'dataValues');
return modelHistory.bulkCreate(hits, {transaction: temporalOptions.allowTransactions? options.transaction: null});
}
});
if(temporalOptions.blocking){
Expand All @@ -85,12 +84,48 @@ var Temporal = function(model, sequelize, temporalOptions){
}
}

var beforeSync = function(options) {
const source = this.originModel;
const sourceHist = this;

if(source && !source.name.endsWith(temporalOptions.modelSuffix) && source.associations && temporalOptions.addAssociations == true && sourceHist) {
const pkfield = source.primaryKeyField;
//adding associations from history model to origin model's association
Object.keys(source.associations).forEach(assokey => {
const association = source.associations[assokey];
const associationOptions = _.cloneDeep(association.options);
const target = association.target;
const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1);
associationOptions.onDelete = 'NO ACTION';
associationOptions.onUpdate = 'NO ACTION';

//handle primary keys for belongsToMany
if(assocName == 'belongsToMany') {
sourceHist.primaryKeys = _.forEach(source.primaryKeys, (x) => x.autoIncrement = false);
sourceHist.primaryKeyField = Object.keys(sourceHist.primaryKeys)[0];
}

sourceHist[assocName].apply(sourceHist, [target, associationOptions]);
});

//adding associations between origin model and history
source.hasMany(sourceHist, { foreignKey: pkfield });
sourceHist.belongsTo(source, { foreignKey: pkfield });

sequelize.models[sourceHist.name] = sourceHist;
}

return Promise.resolve('Temporal associations established');
}

// use `after` to be nonBlocking
// all hooks just create a copy
if (temporalOptions.full) {
model.addHook('afterCreate', insertHook);
model.addHook('afterUpdate', insertHook);
model.addHook('afterDestroy', insertHook);
model.addHook('afterUpdate', insertHook);
model.addHook('afterBulkUpdate', insertBulkHook);
model.addHook('afterDestroy', insertHook);
model.addHook('afterBulkDestroy', insertBulkHook);
model.addHook('afterRestore', insertHook);
} else {
model.addHook('beforeUpdate', insertHook);
Expand All @@ -106,6 +141,7 @@ var Temporal = function(model, sequelize, temporalOptions){

modelHistory.addHook('beforeUpdate', readOnlyHook);
modelHistory.addHook('beforeDestroy', readOnlyHook);
modelHistory.addHook('beforeSync', 'HistoricalSyncHook', beforeSync);

return model;
};
Expand Down
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "sequelize-temporal",
"version": "1.0.6",
"name": "sequelize-temporal",
"version": "2.0.1",
"description": "Temporal tables for Sequelize",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha"
"test": "mocha"
},
"repository": {
"type": "git",
Expand All @@ -20,22 +20,22 @@
"sql11",
"undo",
"paranoid",
"historical"
"historical"
],
"author": "greenify <greenify@notsharingmy.info> (https://github.com/greenify)",
"license": "MIT",
"bugs": {
"url": "https://github.com/bonaval/sequelize-temporal/issues"
},
"homepage": "https://github.com/bonaval/sequelize-temporal#readme",
"homepage": "https://github.com/opencollective/sequelize-historical#readme",
"dependencies": {
"lodash": "^3.10.1"
"lodash": "^4.17.11"
},
"devDependencies": {
"chai": "^4.1.2",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"mocha": "^5.2.0",
"sequelize": "^4.38.0",
"sqlite3": "^4.0.1"
"mocha": "^6.1.4",
"sequelize": "^5.4.0",
"sqlite3": "^4.0.6"
}
}
Loading