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

@nestjs-transactional/outbox-typeorm

v2.0.0

Published

TypeORM persistence backend for @nestjs-transactional/outbox — event_publication table, FOR UPDATE SKIP LOCKED worker safety

Downloads

246

Readme

@nestjs-transactional/outbox-typeorm

npm version License: MIT

TypeORM storage for @nestjs-transactional/outbox.

outbox defines the registry and the worker but ships no production storage. This package provides it: the event_publication table, its archive, a repository implementation, and a migration to create both.

Publication rows are written through the ambient transaction, so they commit with your business data or not at all — that is the guarantee the whole pattern rests on, and it comes from the transparent repository support in @nestjs-transactional/typeorm.

Install

pnpm add @nestjs-transactional/outbox-typeorm \
         @nestjs-transactional/outbox \
         @nestjs-transactional/typeorm \
         @nestjs-transactional/core

Module format

This package ships ESM only, matching NestJS 12, which is ESM-only across its own packages. There is no CommonJS build.

A CommonJS application still works: Node loads ESM from require() since 22.12.0, which is why engines.node is >=22.13.0. What does not follow Node here is tooling with its own module loader — Jest above all, which needs NODE_OPTIONS=--experimental-vm-modules and a few config settings. The 19 example applications in the repository all run their suites that way and can be copied from.

Reasoning and measurements: ADR-022.

Quick start

Register the two entities on your DataSource, then wire four modules in this order:

import { Module } from '@nestjs/common';
import { TransactionalModule } from '@nestjs-transactional/core';
import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
import { OutboxModule, OutboxProcessingModule } from '@nestjs-transactional/outbox';
import {
  EventPublicationArchiveEntity,
  EventPublicationEntity,
  OutboxTypeOrmModule,
  typeOrmEventPublicationRepositoryProvider,
} from '@nestjs-transactional/outbox-typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      entities: [EventPublicationEntity, EventPublicationArchiveEntity, Order],
    }),

    TransactionalModule.forRoot({ isGlobal: true }),
    TypeOrmTransactionalModule.forRoot({ isDefault: true }),

    // Auto-create the tables in development only — production runs the
    // migration instead. See Schema below.
    OutboxTypeOrmModule.forRoot({
      schemaInitialization: { enabled: process.env.NODE_ENV !== 'production' },
    }),

    OutboxModule.forRoot({
      // Required. Without it `outbox` keeps its in-memory default and
      // nothing is persisted — see the warning below.
      repository: typeOrmEventPublicationRepositoryProvider(),
      republishOnStartup: true,
    }),

    OutboxModule.forFeature([OrderPlacedEvent]),

    OutboxProcessingModule, // worker processes only
  ],
})
export class AppModule {}

repository is not optional in practice. OutboxModule.forRoot() installs InMemoryEventPublicationRepository when the option is omitted, and that provider wins. The application works — publishes, handles, completes — and every publication is lost on restart, with nothing in the database and no error to notice. typeOrmEventPublicationRepositoryProvider() aliases the outbox's token to the TypeORM implementation this module registers.

Schema

Two tables. event_publication is the hot queue; event_publication_archive is the cold audit trail used by the ARCHIVE completion mode. Four indexes cover the worker, operator and cleanup paths — (status, publicationDate), (status, listenerId), (eventType) and (completionDate). status is varchar(32) rather than an enum, so a new lifecycle state never forces a type migration.

In production, run the shipped migration:

import { CreateEventPublication1700000000000 } from '@nestjs-transactional/outbox-typeorm';

// In your DataSource config:
migrations: [CreateEventPublication1700000000000];

In development, schemaInitialization: { enabled: true } creates both tables at bootstrap instead. It is idempotent and checks for existing tables first, but it is still schema DDL at application startup: keep it out of production, where a migration gives you a reviewable, ordered, reversible change.

Concurrency

tryClaim is one conditional UPDATE (WHERE id = :id AND status IN (PUBLISHED, RESUBMITTED)) that reports whether the row actually transitioned. That is where the safety lives, which is why findReadyForProcessing deliberately does not lock rows.

SELECT ... FOR UPDATE SKIP LOCKED was tried and dropped: a pessimistic lock has to be held by a transaction wide enough to span the listener invocation, which is unsafe when listeners are slow. Concurrent workers may therefore fetch the same row; only one wins the claim, and the others move on without invoking anything. The cost is a wasted SELECT at typical worker counts (DD-025).

Multiple dataSources

One forRoot per dataSource, mirroring the other packages:

OutboxTypeOrmModule.forRoot(),                          // 'default'
OutboxTypeOrmModule.forRoot({ dataSource: 'billing' }), // 'billing'

Each registers its repository under a per-dataSource token; pass the matching name to typeOrmEventPublicationRepositoryProvider('billing') when wiring that dataSource's OutboxModule (ADR-019). forRootAsync is available for config resolved at runtime.

Compatibility

| Peer | Supported range | | --- | --- | | Node.js | >=22.13.0 | | typeorm | ^0.3.0 \|\| ^1.0.0 | | @nestjs/typeorm | ^10.0.0 \|\| ^11.0.0 \|\| ^12.0.0 | | @nestjs/common / @nestjs/core | ^10.0.0 \|\| ^11.0.0 \|\| ^12.0.0 | | reflect-metadata | ^0.1.13 \|\| ^0.2.0 | | rxjs | ^7.0.0 |

CI exercises the repository against a real Postgres via testcontainers at three points of the TypeORM range: 0.3.31, 1.0.0 and 1.1.0.

Documentation

License

MIT