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

@znight/events

v0.6.1

Published

Znight events: in-process application events with listeners wired at build time, inline or detached

Readme

@znight/events

In-process application events for Znight: publish an instance of a class, and the provider methods marked @OnEvent() whose parameter is typed with that class (or one of its base classes) run. Listeners are found and checked by the compiler, not registered at runtime (RFC 0011).

npm install @znight/events

Usage

// src/users/user-created.ts: an event is an instance of any class
export class UserCreated {
  constructor(
    readonly userId: string,
    readonly email: string,
  ) {}
}
// src/users/create-user.ts
import { Injectable } from '@znight/core'
import { EventPublisher } from '@znight/events'
import { UserCreated } from './user-created.ts'

@Injectable()
export class CreateUser {
  constructor(private readonly events: EventPublisher) {}

  async execute(email: string): Promise<string> {
    const id = crypto.randomUUID()
    // ... save the user
    await this.events.publish(new UserCreated(id, email))
    return id
  }
}
// src/mail/welcome-mail.ts: a class with @OnEvent methods becomes a singleton provider
import { OnEvent } from '@znight/events'
import { UserCreated } from '../users/user-created.ts'

export class WelcomeMail {
  @OnEvent({ detached: true })
  async send(event: UserCreated): Promise<void> {
    // runs after publish() resolved, in its own scope; errors go to onError
  }
}
// src/main.ts
import { Application } from '@znight/core'
import { EventsPlugin } from '@znight/events'
import { __ZNIGHT_METADATA__ } from './.znight/container.generated.ts'

const app = await Application.create({
  metadata: __ZNIGHT_METADATA__,
  // before HttpPlugin, MessagingPlugin and SchedulePlugin: it stops after them
  plugins: [EventsPlugin.configure()],
})
await app.start()

Listeners

@OnEvent(options?) goes on a public instance method with exactly one parameter, the event. Its type annotation is the subscription:

| Parameter type | Receives | | ----------------------------- | ---------------------------------------------- | | event: UserCreated | instances of UserCreated and its subclasses | | event: UserEvent (a base) | every subclass instance (a "wildcard") | | event: Started \| Cancelled | instances of either class (one entry per type) |

An interface, a type alias, a primitive, object, unknown, a class with type arguments, an optional or nullable parameter and a string argument (@OnEvent('user.created'), the @nestjs/event-emitter form) are build errors (ZNIGHT070).

| Option | Default | Effect | | ---------- | ------- | -------------------------------------------------------------------------------------------- | | order | 0 | Integer; lower runs first. Ties: provider token, then method name | | detached | false | Runs after the publication in a fresh scope; errors are reported (onError), never rethrown |

  • Inline listeners (the default) run in order, each awaited, in the publisher's async context: a request-scoped host is the publisher's instance (one unit of work). The first error rejects publish() with that error, unchanged, and skips the remaining inline and every detached listener.
  • Detached listeners start on a macrotask once the inline phase succeeded, concurrently, each in its own scope. A detached listener whose host depends on a request-scoped external (HttpContext, InboundMessage, JobContext) is a build error: pass what it needs in the event.
  • Listeners declared on an abstract class run on each concrete provider extending it (extends, not implements), which is then never reported unused nor pruned. A subclass that redeclares the method replaces the inherited listener: its own @OnEvent (with its options) applies, or none.
  • Method interceptors (@Logged, @Traced, your own @Intercept) wrap listeners like any method.

Publisher

| Method | Behaviour | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | publish(event) | Runs inline listeners, then schedules detached ones; resolves when the inline phase is done | | emit(event) | Fire and forget: the whole publication runs on a macrotask in a fresh scope, every error goes to onError |

Both throw a TypeError for a value that is not a class instance, and reject with ZNIGHT021 before EventsPlugin is bootstrapped (in a constructor) and once it is disposed.

Plugin options

import { EventsPlugin } from '@znight/events'

EventsPlugin.configure({
  onError: (error, info) => console.error(`${info.listener} failed on ${info.eventName}`, error),
  drainTimeoutMs: 10_000,
})

| Option | Default | Effect | | ---------------- | ------------------- | ----------------------------------------------------------------------------- | | onError | logger.error(...) | Receives errors of detached listeners and of emit(); may be async | | drainTimeoutMs | 10000 | How long onStop waits for detached listeners in flight (Infinity: no cap) |

Testing

EventPublisher is an abstract class: TestBed auto-mocks it. In application tests, listener hosts are instantiated by TestApp, and recordEvents() records publications and failures:

import { recordEvents } from '@znight/events/testing'

const events = recordEvents(test) // a TestApp or an Application
// ... exercise the application
await events.idle() // detached listeners settled
events.of(UserCreated) // the published UserCreated instances
events.failures() // listeners that threw

Observability

Two core hooks, produced only when observed: events:publish (one publication with listeners, around its inline phase) and events:listener (one listener invocation, inline or detached).

Compiler contract

  • Manifest: compiler: '@znight/events/compiler', provides: EventPublisher → EventsPlugin (singleton).

  • The extension owns @znight/events#OnEvent (target method, one static argument), reports ZNIGHT005 and ZNIGHT070, adds each concrete host as a root and generates metadata.extensions.events (absent without listener):

    {
      listeners: [{
        event: UserCreated,
        eventName: 'UserCreated',
        token: 'src/mail/welcome-mail.ts#WelcomeMail',
        method: 'send',
        order: 0,
        detached: true,
        invoke: (c: WelcomeMail, e: UserCreated) => c.send(e),
      }],
    }

    Entries are sorted by (order, token, method, event name). The generated code calls no helper of this package.

  • Specification: §9.8 of docs/internals/architecture.md.

Limitations

  • In-process only, no persistence and no retries: a listener that needs them is a message consumer (relay the event to MessageBroker from a detached listener).
  • No transaction phases (@TransactionalEventListener): publish after the commit, or buffer events until it (see the application events guide).
  • No concurrency limit for detached listeners.

License

MIT