Fox Events Fox Events

Getting started

Install Fox Events, create a typed channel, and start emitting and listening in a few lines.

Install

bash
npm install fox-events
bash
yarn add fox-events

Quick example

Create a channel with a payload type, then use emit, on, and once as you need:

ts
import { Fox } from "fox-events";

const userLogin = Fox.channel<{ userId: string }>("user:login");

userLogin.on((payload) => {
  console.log("Login:", payload.userId);
});

userLogin.emit({ userId: "u-1" });
const first = await userLogin.once();
console.log("First login:", first.userId);

For the mental model (who publishes, who listens, channel, on vs once, timeout), see Publishers and listeners.

Instance API (channel)

Use Fox.channel<T>(name) when you want a long-lived channel with typed payloads. You get emit, emitAsync, on, once, and trail.

ts
const channel = Fox.channel<{ userId: string }>("user:login");

channel.emit({ userId: "u-1" });
await channel.emitAsync({ userId: "u-2" });

const off = channel.on((payload) => {
  console.log(payload.userId);
});
off();

const payload = await channel.once();
const payloadWithTimeout = await channel.once({ timeout: 5000 });

const history = channel.trail();
console.log(history.published, history.received, history.unsubscribed);
channel.clearTrail();

For trail options (maxHistory), types, and toText(), see Trail (History).

Sync and async

  • emit(name, payload) — Fires immediately and returns void. Listeners run synchronously; the caller does not wait. Use when you don’t need to wait for middleware or persistence (e.g. UI events, navigation).
  • emitAsync(name, payload) — Returns a Promise that resolves after all middleware (and any persistence) have run. Use when the event goes through middleware (e.g. storage, Bridge) and you need to wait for it to be processed before continuing (e.g. “save then redirect”).
ts
// Fire-and-forget: UI or in-memory only
channel.emit("user:login", { userId: "u-1" });

// Wait for persistence / middleware (e.g. IndexedDB, Bridge to RN)
await channel.emitAsync("user:login", { userId: "u-1" });
// Now safe to navigate or read from storage

Without middleware, emit and emitAsync behave the same for listeners; the only difference is that emitAsync returns a Promise you can await. With middleware (e.g. storage), emitAsync waits until the middleware has finished.

Static API

When you don’t need a channel instance, use Fox.emit, Fox.on, and Fox.once with the event name:

ts
Fox.emit("app:ready", { version: "1.0" });
await Fox.emitAsync("app:ready", { version: "1.0" });

const off = Fox.on("app:ready", (payload) => {});
off();

const payload = await Fox.once("app:ready");
const payloadWithTimeout = await Fox.once("app:ready", { timeout: 3000 });

Custom EventTarget

You can inject your own EventTarget (e.g. for testing or scoping):

ts
const customTarget = new EventTarget();
const channel = Fox.channel("user:action", {
  deps: { eventTarget: customTarget },
});