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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@haelue/evt-bus

v2.0.1

Published

Event bus with powerful features: type inferring, async emitter, propagation stop, handler sorting, group off, etc.

Downloads

137

Readme

language: English | 中文

Evt-Bus · npm

An event bus tool with powerful features:

  • 🔑 Type inferring: support typescript, you can use the event bus like calling functions with type inferring
  • 📦 Async emitter: define async handler, and emit using Promise OR async-await
  • 🧡 Propagation stop: stop event propagation in handler-loop
  • 🔌 Handlers sorting: sort the handlers with order-index
  • ⛰️ Handlers group off: subscribe events with group-id, and unsubscribe them by group
  • ⚙️ Subscribe repeating: optional "allow/avoid repeat" subscribe

Table of Contents

Quick Start

Step1: This project uses node and npm to install.

$ npm install --save @haelue/evt-bus

Step2: Insert types in Global-Declare-File: src/global.d.ts (create one if non-exist)

/** file: src/global.d.ts */

declare type EvtGroupName = import("@haelue/evt-bus").EvtGroupName;
declare type EvtOrder = import("@haelue/evt-bus").EvtOrder;
declare type EvtRepeatable = import("@haelue/evt-bus").EvtRepeatable;
declare type EvtMessage = import("@haelue/evt-bus").EvtMessage;

Step3: Create a Channel-File: src/events/index.ts (OR anywhere)

/** file: src/events/index.ts */

import {
  EvtChannel,
  EvtChannelName,
  EvtOnMethod,
  EvtOffMethod,
  EvtOffAllMethod,
  EvtOnCountMethod,
  EvtEmitMethod,
  EvtEmitAsyncMethod,
  EvtWithExceptionHandlerMethod,
  EvtChannelOptions,
  evtDefaultOptions,
} from "@haelue/evt-bus";

interface UserEvtChannel {
  /** Add event-listenning */
  on: EvtOnMethod & EvtOnOffDictionary;

  /** Remove event-listenning of same handler(if defined) & same group(if defined) & same order(if defined) */
  off: EvtOffMethod & EvtOnOffDictionary;

  /** Remove all event-listenning of same group(if defined) & same order(if defined) */
  offAll: EvtOffAllMethod;

  /** Emit event */
  emit: EvtEmitMethod & EvtEmitDictionary;

  /** Emit event (async) */
  emitAsync: EvtEmitAsyncMethod & EvtEmitAsyncDictionary;

  /** Count of event-listenning */
  onCount: EvtOnCountMethod & EvtOnCountDictionary;

  /** Set exception-handler once for next emit */
  withExceptionHandler: EvtWithExceptionHandlerMethod<
    EvtEmitDictionary,
    EvtEmitAsyncDictionary
  >;
}

export const channelCached: Record<EvtChannelName, any> = {};

/** Get an event-channel of options (caching). */
export default function evt(
  options?: Partial<EvtChannelOptions>,
): UserEvtChannel {
  const name = options?.name ?? evtDefaultOptions.name;
  return (channelCached[name] ??= new EvtChannel(options));
}

Tip: It can both use in Browser.js OR Node.js; With both esm, cjs OR umd.

Step4: Create an Event-Declare-File: src/events/evt.d.ts (OR anywhere, but ends with evt.d.ts / emit.d.ts)

/** file: src/events/evt.d.ts */

declare interface EvtEmitDictionary {
  /** example emitter with type infer */
  fooTrigger(bar: { id: string; score: number; time: Date }): boolean;
}

Tip: Return type must be: boolean.

Step5: Run command below: (recommand insert into package.json)

$ npx evt-autogen

Other declares will generate in your Event-Declare-File (Step4). (see what happens in src/events/evt.d.ts)

Step6: Use the event "fooTrigger":

/** file: path/to/your/code.ts */

import evt from "src/events";
const { emit, on, off } = evt();

on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received! ${JSON.stringify(bar)}`);
});

emit.fooTrigger({ id: "foo", score: 1, time: new Date() });

// console print:
// event received! {"id":"foo","score":1,"time":"2024-11-01T06:48:50.865Z"}

off.fooTrigger();

emit.fooTrigger({ id: "foo2", score: 2, time: new Date() });

// console print:
// [nothing as expected]

Tip: evt() provides the same cached instance in different file.

Usage

The usage mode of evt-bus can be very flexible.

You can create many Event-Declare-File (Step4) in different modules, evt-autogen scan path ./src recursive.

You can use -p to change scan path.

$ npx evt-autogen -P [path/to/declare/files/root]
# "-p -path -PATH" is also ok

You can use export interface instead of declare interface in Event-Declare-File (Step4), and make the filename ends with evt.ts / emit.ts (without ".d."), and import symbols in Channel-File (Step3).

The first "e" argument in on-handler is like: { message: "fooTrigger", cancel: false }, you can use e.cancel = true to stop event propagation. But if you don't need this feature, and hate the first "e" argument, directions below would help:

/** file: src/events/index.ts */

import {
--- EvtChannel,
+++ EvtChannelSimple as EvtChannel,
...
} from "@haelue/evt-bus"
$ npx evt-autogen -S
# "-s -simple -SIMPLE" is also ok

Features

Channel options

import evt from "src/events";
const { emit, on, off } = evt({
  // each option below is ommitable
  name: "channel-1", // default "#"
  defaultGroup: "group-1", // default "*"
  defaultOrder: -1, // default 0
  defaultRepeatable: true, // default false
  defaultExceptionHandler: console.log, // default console.error
});

Async emitter

import evt from "src/events";
const { emitAsync, on } = evt();

on.fooTrigger(async (e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received! ${JSON.stringify(bar)}`);
});

await emitAsync.fooTrigger({ id: "foo", score: 1, time: new Date() });

Propagation stop & return

import evt from "src/events";
const { emit, on } = evt();

let changeFlag = false;
on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  e.cancel = true;
});
on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  changeFlag = true;
});

const result = emit.fooTrigger({ id: "foo", score: 1, time: new Date() });
console.log(`result: ${result}, flag changed: ${changeFlag}`);

// console print:
// result: false, flag changed: false

Handlers sorting

The default sort-order is 0, you can change it in channel-building, OR in on-method.

import evt from "src/events";
const { emit, on } = evt({ defaultOrder: 2 });

on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received: Order 2 (default)}`);
});
on.fooTrigger(
  (e, bar: { id: string; score: number; time: Date }) => {
    console.log(`event received: Order 3}`);
  },
  undefined,
  3,
);
on.fooTrigger(
  (e, bar: { id: string; score: number; time: Date }) => {
    console.log(`event received: Order 1`);
  },
  undefined,
  1,
);

emit.fooTrigger({ id: "foo", score: 1, time: new Date() });

// console print:
// event received: Order 3
// event received: Order 2 (default)
// event received: Order 1

Handlers group off

The default group name is *, you can change it in channel-building, OR in on-method.

import evt from "src/events";
const { on, offAll } = evt({ defaultGroup: "group-1" });

on.fooTrigger(
  (e, bar: { id: string; score: number; time: Date }) => {},
  "group-2",
);
on.eventBar((e, bar: string, tick: number) => {}, "group-2");

offAll("group-2");

Avoid/allow repeat subscribe

The default setting is avoid-repeat, you can change to allow-repeat in channel-building, OR in on-method.

import evt from "src/events";
const { emit, on } = evt({ defaultRepeatable: true });

const handler1 = (e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received in handler1`);
};
on.fooTrigger(handler1);
on.fooTrigger(handler1);
on.fooTrigger(handler1);

const handler2 = (e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received in handler2`);
};
on.fooTrigger(handler2, undefined, undefined, false);
on.fooTrigger(handler2, undefined, undefined, false);
on.fooTrigger(handler2, undefined, undefined, false);

emit.fooTrigger({ id: "foo", score: 1, time: new Date() });

// console print:
// event received in handler1
// event received in handler1
// event received in handler1
// event received in handler2

Tip: Each arrow-function creates different instance, which is not repeat

Handle exception

The default handler is console.error, you can change it in channel-building, OR using withExceptionHandler-method.

import evt from "src/events";
const { withExceptionHandler, emit, on } = evt({
  defaultExceptionHandler: () => console.log("Handle error throws"),
});

on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  throw new Error("Specified error");
});

emit.fooTrigger({ id: "foo", score: 1, time: new Date() });

// console print:
// Handle error throws

const avoidErrorLog = () => {};
withExceptionHandler(avoidErrorLog).emit.fooTrigger({
  id: "foo",
  score: 1,
  time: new Date(),
});

// console print:
// [nothing as expected]

Tip: If event-handler is async, use async emitter.

String message

String-message is also supported like a typical event bus.

import evt from "src/events";
const { emit, on, off } = evt();

on("fooTrigger", (e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received! ${JSON.stringify(bar)}`);
});

emit("fooTrigger", { id: "foo", score: 1, time: new Date() });

off("fooTrigger");

Tip: And it is also compatible to declare-type usage.

Advanced details

Advanced details that you may not use, but needs to be mentioned.

Count listeners

Use onCount-method to count subscribe listeners.

import evt from "src/events";
const { on, off, onCount } = evt();

on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received! ${JSON.stringify(bar)}`);
});
on.fooTrigger((e, bar: { id: string; score: number; time: Date }) => {
  console.log(`event received! ${JSON.stringify(bar)}`);
});

console.log("fooTrigger event subscribe count: ", onCount.fooTrigger());

// console print:
// fooTrigger event subscribe count: 2

off.fooTrigger();

console.log("fooTrigger event subscribe count: ", onCount.fooTrigger());

// console print:
// fooTrigger event subscribe count: 0

Methods: on, off, offAll, onCount

See the declares of methods: on, off, offAll, onCount:

interface onMethods {
  fooTrigger(handler, groupId?, sortOrder?, repeatable?): void;
}
interface offMethods {
  fooTrigger(handler?, groupId?, sortOrder?): void;
}
interface offAllMethod {
  (groupId?, sortOrder?): void;
}
interface onCountMethods {
  fooTrigger(handler?, groupId?, sortOrder?): number;
}

In on-method, if you ommit any parameter OR put undefined, the parameter will get default value.

But in off / offAll / onCount -method, if you ommit any parameter OR put undefined, it means this "Parameter-Match-Condition" will be ignored while searching listeners.

Debug tons of events

In a badly structed project, tons of events may interweaving here and there. A debug tool is designed to analyze them. Example of vue3 web project as belows:

/** file: main.ts */

import { nextTick } from "vue";
import { loadEvtDebug } from "@haelue/evt-bus";
loadEvtDebug(false, nextTick, ["mouseMoving", "keyboardPressing"]);

Run the web project, open the browser console, input evtDebug() and see what outputs.