npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nestjs-changelog

v1.0.19

Published

[![CircleCI](https://circleci.com/gh/Shift3/nestjs-changelog/tree/master.svg?style=svg)](https://circleci.com/gh/Shift3/nestjs-changelog/tree/master)

Downloads

25

Readme

NestJS ChangeLog

CircleCI

NestJS ChangeLog is a change tracker for NestJS. It is similar to PaperTrail in that it hooks into entity events and saves the changes. It stores a ChangeLog that you can view or even revert to previous versions of a tracked entity.

Getting Started

First install the package.

npm install --save nestjs-changelog

Wrap your typeorm ConnectionOptions with our helper function that adds the necessary entity and subscriber.

import { addChangeDetectionToConnection } from 'nestjs-changelog';
const TypeOrmConfig = addChangeDetectionToConnection({
    type: 'postgres',
    // ...
    // the rest of your regular typeorm config
    // ...
});

Add the Change entity to typeorm's ormconfig.json so that you can generate a migration for the new entity.

{
    "entities": [
        "src/database/models/**/*.entity.ts",
        "node_modules/nestjs-changelog/dist/change.entity.js"
    ],
}

Generate the new migration

# generate it
npx ts-node -r tsconfig-paths/register node_modules/.bin/typeorm migration:generate -n add_nestjs_changelog
# run it
npx ts-node -r tsconfig-paths/register node_modules/.bin/typeorm migration:run

Finally, add the module to your AppModule

import { ChangeModule } from 'nestjs-changelog';

@Module({
    imports: [
        TypeOrmModule.forRoot(TypeOrmConfig),
        ChangeModule.register({
            // this tells the change logger how to turn a user into a display name
            userToDisplayName: (user: any) => `${user.firstName} ${user.lastName}`
        }),
        // your other modules imports...
    ]
})

Tracking Changes

Now all you need to do to start tracking changes is pick an entity to track changes on and decorate it with the @TrackChanges decorator, for example:

@Entity()
@TrackChanges()
export class MyTrackedEntity extends BaseEntity {
    // ...
}

Any change that goes through typeorm's lifecycle hooks in subscribers will now be tracked. Each change will create a Change record in the database.

There are some options you can pass to @TrackChanges

/* only the attributes listed will trigger the creation of a change record,
   by default all attributes are watched */
only?: string[],

/* the attributes listed in `except` will not trigger the creation of a
   change record if only they are changed */
except?: string[],

/* allows you to choose whether to ignore timestamps like created at,
   updated at, and deleted at. By default, timestamps are ignored, set this
   to false to track changes to timestamps */
ignoresTimestamps?: boolean,

Retrieving the ChangeLog

You can retrieve change records for a tracked entity by using the ChangeRepository's changeLogQuery method. We return a SelectQueryBuilder<Change> from this method so that you can chain any additional methods you would like onto it, for example pagination.

const trackedEntity = await this.myTrackedEntityRepository.findOne(id);
const allChanges = await this.changeRepository
	.changeLogQuery(trackedEntity)
	.getMany();

Reverting a Change

You can revert any change like so:

const change = await this.changeRepository.findOne(changeId);
await this.changeRepository.revert(change);

Working with Change

You can preview what kind of entity reverting a change will generate. This would be useful for displaying to the user what reverting will do, before asking the user to confirm.

const entity = await this.changeRepository.retreiveEntityBeforeChange(change)

You can traverse the ChangeLog

// get the most recent change
const lastChange = await this.changeRepository.lastChange(entity); 

// get the oldest change.
const firstChange = await this.changeRepository.firstChange(entity); 

// get the next change (if any)
const nextChange = await this.changeRepository.next(change);

// get the previous change (if any)
const previousChange = await this.changeRepository.previous(change);

Caveats and Limitations

Associations

Currently the tracking of associations is limited to tracking @ManyToOne changes. We cannot track changes to @ManyToMany or @OneToMany without a more complicated procedure. This may be in the works later, depending on need.

Database Decisions

This package was made with the focus of keeping a valid changelog regardless of anything that happens. As a result of this a few design choices have been made. The users identifying name is denormalized in the database (we do not store a foreign key to the user) to the whoDisplay column. This is to ensure that even if a user is deleted from the database, we are still able to an identifying display name, as well as store their old id.

We also store the actual changes as a json blob as well. This is to ensure that the changes at the time are captured, regardless of how the database schema looks.

TypeORM API

TypeORM has a few eccentricities around which events can be monitored and which can't, that users must keep in mind. Since nestjs-changelog is based around detecting when an entity has changed, there are a few things to be aware of. Typeorm has methods of inserting, updating and deleting that talk directly with the database. These methods are .delete, .update, and insert on repository and entity manager. Below is a demonstration of what works and doesn't work in terms of automatic creation of Change entries.

let entity = new SomeEntity();
await entity.save(); // works
await repository.save(entity); // works
await repository.insert({name: 'name'}); // does NOT cause a change to be created

await repository.save(entity); // works
await connection.manager.save(entity); // works
await entity.save(); // works
await repository.update(entity.id, {name: 'name'}); // does NOT cause a change to be created

await repository.remove(entity); // works
await connection.manager.remove(entity); // works
await entity.remove(); // works
await repository.delete(entity); // does NOT cause a change to be created

// using the query builder creates a direct sql query that does NOT trigger typeorm
await repository
  .createQueryBuilder()
  .anything()

As a workaround, if you still want to create change entries while utilizing the query builder or direct database calls, you can use the changeRepository.createDatabaseEntry method.

// example of manually creating a change record because we are working with the direct db methods
// .insert
const insertResult = await connection.manager.insert(SomeEntity, { name: 'name' });
const entity = await connection.manager.findOne(SomeEntity, insertResult.identifiers[0].id);
// manually create an entry
await changeRepository.createChangeEntry(entity, ChangeAction.CREATE);

// .update
const entityBefore = await connection.manager.findOne(SomeEntity, 1);
await connection.manager.update(SomeEntity, { id: entityBefore.id }, { name: 'name' });
const entityAfter = await connection.manager.findOne(SomeEntity, entityBefore.id);
// manually create an entry
await changeRepository.createChangeEntry(entityAfter, ChangeAction.UPDATE, entityBefore);

// .delete
const entity = await connection.manager.findOne(SomeEntity, 1);
await connection.manager.delete(SomeEntity, { id: 1 });
// manually create an entry
await changeRepository.createChangeEntry(entity, ChangeAction.DELETE);