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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@entity-history/mikroorm

v0.1.0

Published

Shadow-table entity history (audit trail) for MikroORM

Readme

@entity-history/mikroorm

Entity history for MikroORM (v7): every create/update/delete on a tracked entity writes a full snapshot to a per-entity shadow table (<table>_history), with user attribution, change reasons, time-travel queries, diffing, and revert.

Install

npm install @entity-history/mikroorm

Requires @mikro-orm/core >= 7 < 8 as a peer dependency.

Quickstart

import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';
import { MikroORM } from '@mikro-orm/postgresql';
import { Historized, historyEntities, HistorySubscriber } from '@entity-history/mikroorm';

@Entity()
@Historized()
export class User {
  @PrimaryKey() id!: number;
  @Property() email!: string;
}

const orm = await MikroORM.init({
  entities: [User, ...historyEntities()],
  subscribers: [new HistorySubscriber()],
});

historyEntities() reads MikroORM decorator metadata for every @Historized() entity and generates a matching user_history EntitySchema (same columns, plus history_id, history_type ('create' | 'update' | 'delete'), history_date, history_user_id, history_change_reason). Because the generated schema lives in the ORM config like any other entity, the schema generator and migrations cover it automatically.

The meta columns are identical to @entity-history/typeorm's — shadow tables are cross-ORM compatible, so switching ORMs keeps existing history.

@Historized options

@Historized({
  exclude: ['passwordHash'],    // properties omitted from the history table entirely
  tableName: 'user_history',    // default: <sourceTable>_history
  softDeleteField: 'deletedAt', // see Soft delete below
})

Soft delete

MikroORM has no native soft delete. If your entity models it with a nullable date/flag property, name it in softDeleteField: an update that sets the field (null → value) is recorded as 'delete', an update that clears it (value → null) as 'update' — the same mapping the TypeORM adapter uses for softRemove()/recover(). Without the option, only hard creates/updates/deletes are tracked.

Context: user attribution and change reasons

import { withHistoryContext } from '@entity-history/mikroorm';

await withHistoryContext({ userId: 'system', changeReason: 'nightly sync' }, async () => {
  await em.flush();
});

Missing context is not an error — history_user_id and history_change_reason are simply null. The NestJS wrapper (@entity-history/nestjs-mikroorm) sets this automatically per-request; for cron jobs, queue consumers, or scripts, call withHistoryContext explicitly.

Query API

import { historyRepo } from '@entity-history/mikroorm';

const history = historyRepo(em, User); // pass a contextual/forked EntityManager

await history.forEntity(userId).all();                       // every version, newest first
await history.forEntity(userId).all({ take: 20, skip: 0 });  // paginated
await history.forEntity(userId).asOf(date);                  // reconstructed User at `date`, or null
await history.asOf(date);                                    // table-wide snapshot at `date`
record.diffAgainst(olderRecord);                             // { changes: [{ field, old, new }] }
await history.forEntity(userId).revertTo(historyId);         // restore + record a new row, reason 'reverted'

Entities returned by asOf/toEntity are detached snapshots — they are never merged into the identity map, so reading history cannot mutate managed entities.

Relations

  • ManyToOne / owning OneToOne — automatic. The FK id column is part of the snapshot.
  • OneToMany / inverse OneToOne — put @Historized() on the child entity; reconstruct via asOf(date, { relations: ['posts'] }).
  • ManyToMany — not natively tracked. Model the junction as an explicit join entity and historize that instead.

Nested relation paths (posts.comments) are not supported in v1.

Bulk helpers

em.nativeUpdate(), em.nativeDelete(), and query-builder writes bypass MikroORM's event system, so they do not produce history. Use these instead:

import {
  bulkUpdateWithHistory,
  bulkDeleteWithHistory,
  bulkSoftDeleteWithHistory,
  bulkRestoreWithHistory,
} from '@entity-history/mikroorm';

await bulkUpdateWithHistory(em, Post, { status: 'draft' }, { status: 'archived' });
await bulkDeleteWithHistory(em, Post, { status: 'spam' });
await bulkSoftDeleteWithHistory(em, Post, { status: 'stale' }); // sets softDeleteField, records 'delete'
await bulkRestoreWithHistory(em, Post, { status: 'stale' });    // clears softDeleteField, records 'update'

Each runs in a single transaction and writes all history rows in one batched insert.

Limitations (v1)

  • MikroORM v7 only (peerDependencies: @mikro-orm/core >=7 <8).
  • Single-column primary keys only (no composite keys).
  • Default MikroORM naming strategy (or explicit fieldNames). A custom namingStrategy makes the runtime validator throw a configuration error rather than silently miswriting history.
  • em.nativeUpdate / nativeDelete / em.upsert / query-builder writes bypass history — use the bulk helpers above where one exists.
  • asOf relation reconstruction is one level deep; many-to-many needs an explicit join entity.
  • A history-row insert failure aborts the whole flush transaction, including the original write — history is never silently dropped.
  • Distributed as CommonJS only: @Historized() registers into a process-wide singleton shared with @entity-history/core; a dual CJS+ESM build would silently split that registry.