New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

eventify

Package Overview
Dependencies
Maintainers
5
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eventify

Tiny event emitter with strict TypeScript typings and optional schema validation.

latest
npmnpm
Version
3.1.0
Version published
Weekly downloads
0
Maintainers
5
Weekly downloads
 
Created
Source

Eventify

Tiny event emitter with strict TypeScript types, wildcard namespaces, and optional schema validation.

  • ESM only, tree-shakeable
  • Node 20+, Bun, modern browsers
  • Backbone-style semantics (all, listener snapshots, predictable order)

Install

npm install eventify

Quickstart

import { createEmitter } from "eventify";

const emitter = createEmitter();

emitter.on("alert", (message) => {
  console.log(message);
});

emitter.trigger("alert", "hello");

Usage

Mixins

import { decorateWithEvents } from "eventify";

const target = decorateWithEvents({ name: "service" });

Typed Events

type Events = {
  ready: void;
  "change:title": string;
  data: [string, number];
};

const emitter = createEmitter<Events>();

emitter.on("data", (name, count) => {
  console.log(name, count);
});

emitter.trigger("data", "hello", 42);

Event Maps + Space-Delimited Names

emitter.on({
  "change:title": () => console.log("title"),
  "change:author": () => console.log("author"),
});

emitter.on("open close", () => console.log("toggled"));

Namespaces + Wildcards

Event names are split by namespaceDelimiter (default /). The wildcard token (default *) matches segments.

  • * in the middle matches one segment
  • trailing * matches remaining segments
  • * alone matches any event (use "all" if you need the event name)
  • leading/trailing delimiters create empty segments that must match exactly
const emitter = createEmitter({
  namespaceDelimiter: "/",
  wildcard: "*",
});

emitter.on("/product/foo/org/123/user/56/*", () => {
  console.log("any account for that user");
});

emitter.on("/product/foo/org/123/*", () => {
  console.log("any user in org 123");
});

emitter.on("/product/foo/*", () => {
  console.log("any org in product foo");
});

emitter.trigger("/product/foo/org/123/user/56/account/abcd");

Middle-segment wildcards:

emitter.on("/product/foo/org/*/tracked-object/*/assesment", () => {
  console.log("any tracked-object assesment within an org");
});

Colon namespaces:

const emitter = createEmitter({
  namespaceDelimiter: ":",
  wildcard: "*",
});

emitter.on("namespace:foo:*", () => {
  console.log("any sub-event in namespace:foo");
});

Schemas (Zod v4 Compatible)

Any schema with parse or safeParse works. Zod is supported via DI.

import { z } from "zod";
import { createEmitter, setDefaultSchemaValidator } from "eventify";

const schemas = {
  data: z.tuple([z.string(), z.number()]),
  ready: z.undefined(),
};

const emitter = createEmitter({
  schemas,
  validate: setDefaultSchemaValidator,
});

emitter.on("data", (name, count) => {
  console.log(name, count);
});

emitter.trigger("data", "hello", 1);

Validation runs on trigger/emit/produce. If validation fails, the call throws and no listeners run.

Error Handling

Listener failures never crash by default. Errors are routed to onError.

const emitter = createEmitter({
  onError: (error, meta) => {
    console.error(meta.event, error);
  },
});

emitter.on("boom", () => {
  throw new Error("nope");
});

emitter.trigger("boom");

EventTarget Interop

createEmitter and decorateWithEvents expose addEventListener, removeEventListener, and dispatchEvent.

trigger/emit/produce dispatch a CustomEvent with the payload in event.detail. dispatchEvent only uses the EventTarget path (and matching on listeners). It does not run schemas, patterns, or "all". detail is the single payload for 1-arg events, an array for multi-arg events, and undefined for no-arg events.

Async Iteration

const emitter = createEmitter();
const iterator = emitter.iterate("data");

emitter.trigger("data", "a", 1);

const { value } = await iterator.next();
// value -> ["a", 1]

Iterate all events:

const all = emitter.iterate("all");
emitter.trigger("ready");
const { value } = await all.next();
// value -> ["ready"]

Abort with an AbortSignal:

const controller = new AbortController();
const iterator = emitter.iterate("data", { signal: controller.signal });
controller.abort();

for await with abort:

const controller = new AbortController();

(async () => {
  for await (const value of emitter.iterate("data", {
    signal: controller.signal,
  })) {
    console.log(value);
    controller.abort();
  }
})();

Failure Modes + Constraints

  • Validation failure: trigger/emit/produce throws and no listeners run (including wildcard and all).
  • Listener errors: thrown errors or rejected promises are routed to onError and do not stop other listeners.
  • Invalid callbacks: invoking a non-function throws a TypeError that is routed to onError.
  • onError failures: errors thrown by the handler are swallowed to avoid crashing producers.
  • iterate backpressure: if producers emit faster than you consume, the iterator queue grows. Use AbortSignal, return(), or stop iteration.
  • listenTo/listenToOnce: the target must be an Eventify emitter (or another object with compatible on/once/off).

Constraint tools:

  • Payload: schemas + validate enforce data shape at emit time.
  • Cardinality: once / listenToOnce.
  • Lifetime: off / stopListening / iterate + AbortSignal.
  • Namespace scope: namespaceDelimiter + wildcard.
  • Errors: onError centralizes listener failures.

Semantics

  • Dispatch order: event listeners, then matching patterns, then "all".
  • Listener lists are snapshotted at emit time; mutations during dispatch do not affect the current cycle.
  • The context defaults to the emitter.
  • Duplicate registrations are allowed.
  • "all" is a compatibility feature (Backbone/Eventify style); it is not a standard EventTarget concept.

API

Preferred Named Exports

createEmitter([options]);
decorateWithEvents([target], [options]);
setDefaultSchemaValidator(schema, payload, meta);

createEmitter returns a standalone emitter. decorateWithEvents mixes Eventify methods into an existing object. setDefaultSchemaValidator is the default validator function (no global mutation).

Default Export (Compat)

Eventify.create([options]);
Eventify.enable([target], [options]);
Eventify.defaultSchemaValidator;
Eventify.version;
Eventify.proto;

The Eventify default export remains for compatibility.

Options

type EventifyOptions = {
  schemas?: Record<string, SchemaLike>;
  validate?: SchemaValidator;
  onError?: (
    error: unknown,
    meta: {
      event: string;
      args: unknown[];
      listener?: (...args: unknown[]) => unknown;
      emitter: object;
    },
  ) => void;
  namespaceDelimiter?: string; // default "/"
  wildcard?: string; // default "*"
};

Events

on(event, callback, [context])
on({ event: callback, ... }, [context])
on("a b c", callback, [context])
once(event, callback, [context])
once({ event: callback, ... }, [context])
once("a b c", callback, [context])
off()
off(event, [callback], [context])
off({ event: callback, ... }, [context])
trigger(event, ...args);
emit(event, ...args);
produce(event, ...args);

emit and produce are aliases of trigger.

EventTarget Interop

addEventListener(type, listener, [options]);
removeEventListener(type, listener, [options]);
dispatchEvent(event);

Cross-Emitter Listening

listenTo(other, event, callback)
listenTo(other, { event: callback, ... })
listenToOnce(other, event, callback)
listenToOnce(other, { event: callback, ... })
stopListening([other], [event], [callback]);

Async Iteration

iterate(event, [options]);

For "all", each value is [eventName, ...args]. For other events, a single argument is yielded as a value; multiple arguments are yielded as an array.

Type Exports

EventMap;
EventName;
EventHandler;
EventHandlerMap;
SchemaLike;
SchemaMap;
SchemaValidator;
EventsFromSchemas;

Benchmarks + Changelog

  • BENCHMARKS.md
  • CHANGELOG.md

Development + Release

bun install
bun run format
bun test --coverage
bun run build:all
bunx playwright install --with-deps chromium
bun run test:browser
bun run test:all
bun run ci:local
bun run publish

ci:local requires act installed locally.

License

MIT

Keywords

event

FAQs

Package last updated on 09 Feb 2026

Related posts