Plug and play TypeORM entity history tracking.
npm install typeorm-simple-history
If you use Typescript enable experimentalDecorators
flag inside your tsconfig file, otherwise for babel use one of the following plugins babel-plugin-transform-decorators-legacy or @babel/plugin-proposal-decorators.
As early as possible in your application, import and setup history module.
import { getRepository } from 'typeorm';
import { setup } from 'typeorm-simple-history';
// Run setup as early as possible
setup({ getRepository });
Register entity for tracking either by using @TrackHistory
decorator or by calling trackHistory
method.
Now every time decorated entity updates library will automatically save model differences in the database.
import { Entity, PrimaryGeneratedColumn, Column, BeforeUpdate } from 'typeorm';
import { TrackHistory, trackHistory } from 'typeorm-simple-history';
@TrackHistory()
@Entity()
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column()
firstName!: string;
@Column()
lastName!: string;
}
// Or `trackEntityHistory` method instead of @TrackHistory decorator
trackHistory(User);
Register history entities and subscribers in TypeORM connection:
import { ConnectionOptions } from 'typeorm';
import { getEntities, getSubscribers } from 'typeorm-simple-history';
export const options: ConnectionOptions = {
// ...
entities: [Foo, Bar, ...getEntities()],
subscribers: [FooSubscriber, ...getSubscribers()],
};
When accessing historyRepository
use one of following methods:
import { getRepository } from 'typeorm';
import { getHistoryRepository, History } from 'typeorm-simple-history';
// Get history repository by using `getHistoryRepository`
const historyRepository = getHistoryRepository(User); // or
const historyRepository = getHistoryRepository<User>('user'); // or
const historyRepository = getHistoryRepository('user'); // Without TS support
// or with TypeORM's `getRepository`
const historyRepository = getRepository<History<User>>('user_history'); // or
const historyRepository = getRepository('user_history'); // Without TS support
import { getRepository } from 'typeorm';
import { getHistoryRepository } from 'typeorm-simple-history';
try {
const historyRepository = getHistoryRepository(User);
const userHistory = await historyRepository.find({ where: { origin: req.params.id } });
return userHistory;
} catch (err) {
// ...
}
Response:
[
{
"id": 1,
"diff": {
"firstName": "Peter"
},
"historyDetails": null,
"createdAt": "2022-04-29T14:35:05.645Z"
},
{
"id": 2,
"diff": {
"firstName": "John",
"lastName": "Stanbridge"
},
"historyDetails": null,
"createdAt": "2022-04-29T14:35:19.633Z"
},
{
"id": 3,
"diff": {
"lastName": "Wong"
},
"historyDetails": null,
"createdAt": "2022-04-29T14:35:28.282Z"
}
]
import { getRepository } from 'typeorm';
import { getHistoryRepository, hydrate } from 'typeorm-simple-history';
try {
const repository = getRepository(User);
const historyRepository = getHistoryRepository(User);
const user = await repository.findOne(req.params.id);
const userHistory = await historyRepository.find({ where: { origin: user } });
const response = hydrate(user, userHistory);
return response;
} catch (err) {
// ...
}
Response:
[
{
"originId": 1,
"id": 1,
"firstName": "Peter",
"lastName": "Virtue",
"historyDetails": null,
"createdAt": "2022-04-29T14:35:05.645Z"
},
{
"originId": 1,
"id": 2,
"firstName": "John",
"lastName": "Stanbridge",
"historyDetails": null,
"createdAt": "2022-04-29T14:35:19.633Z"
},
{
"originId": 1,
"id": 33,
"firstName": "John",
"lastName": "Wong",
"historyDetails": null,
"createdAt": "2022-04-29T14:35:28.282Z"
}
]
import { getRepository } from 'typeorm';
import { cloneDeep, omit } from 'lodash';
// Library also supports additional JSON property `historyDetails` inside history entry.
// It can be used for storing interesting things like editor info.
// In order to store data to this property pass additional `historyDetails` object inside `data` parameter.
try {
const repository = getRepository(User);
const user = await this.repository.findOne(req.params.id);
const data = omit<User, 'id'>(req.body, 'id');
const newEntity = Object.assign(cloneDeep(user, data);
await this.repository.save(newEntity, { data: { historyDetails: { editor: 'John Doe' } } });
return true;
} catch (err) {
// ...
}
Hydrated response would look like this:
[
{
"originId": 1,
"id": 1,
"firstName": "Peter",
"lastName": "Virtue",
"historyDetails": {
"editor": "John Doe"
},
"createdAt": "2022-04-29T14:35:05.645Z"
}
// ...
]
Library is currently only supporting relational databases.