GithubHelp home page GithubHelp logo

binier / tiny-typed-emitter Goto Github PK

View Code? Open in Web Editor NEW
110.0 4.0 11.0 24 KB

Fully type-checked NodeJS EventEmitter

License: MIT License

JavaScript 100.00%
nodejs typescript nodejs-library typescript-library typescript-boilerplate events event event-emitter event-emitters eventemitter

tiny-typed-emitter's Introduction

tiny-typed-emitter

Have your events and their listeners type-checked with no overhead.

npm version

Install

Simply add the dependency using npm:

$ npm i tiny-typed-emitter

or using yarn:

$ yarn add tiny-typed-emitter

Usage

  1. import tiny-typed-emitter library:
import { TypedEmitter } from 'tiny-typed-emitter';
  1. define events and their listener signatures (note: quotes around event names are not mandatory):
interface MyClassEvents {
  'added': (el: string, wasNew: boolean) => void;
  'deleted': (deletedCount: number) => void;
}
  1. on this step depending on your use case, you can:
  • define your custom class extending EventEmitter:
    class MyClass extends TypedEmitter<MyClassEvents> {
      constructor() {
        super();
      }
    }
  • create new event emitter instance:
    const emitter = new TypedEmitter<MyClassEvent>();

Generic events interface

To use with generic events interface:

interface MyClassEvents<T> {
  'added': (el: T, wasNew: boolean) => void;
}

class MyClass<T> extends TypedEmitter<MyClassEvents<T>> {

}

Compatible subclasses with different events

The type of eventNames() is a superset of the actual event names to make subclasses of a TypedEmitter that introduce different events type compatible. For example the following is possible:

class Animal<E extends ListenerSignature<E>=ListenerSignature<unknown>> extends TypedEmitter<{spawn: () => void} & E> {
  constructor() {
    super();
  }
}

class Frog<E extends ListenerSignature<E>> extends Animal<{jump: () => void} & E> {
}

class Bird<E extends ListenerSignature<E>> extends Animal<{fly: () => void} & E> {
}

const animals: Animal[] = [new Frog(), new Bird()];

No Overhead

Library adds no overhead. All it does is it simply reexports renamed EventEmitter with customized typings. You can check lib/index.js to see the exported code.

tiny-typed-emitter's People

Contributors

binier avatar gittenburg avatar omgimalexis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tiny-typed-emitter's Issues

[Question] Trying to expose specific methods only

Hi, I am trying to expose only the following methods to an API:

  • on, once, off

I want to emit stuff internally and expose only those 3 methods.

Here what I got as of now:

interface SocketEvents {
    'socket-connected': () => void
    'socket-disconnected': () => void
    'socket-reply': (event: string, ...params: any[]) => void
    'socket-error': (event: string, error: any) => void
}
class SocketEventEmitter extends TypedEmitter<SocketEvents> {
    constructor() { super() }
}
const eventEmitter = new SocketEventEmitter()

export function on(event: keyof SocketEvents, listener: SocketEvents[keyof SocketEvents]) {
    eventEmitter.on(event, listener)
}
export function once(event: keyof SocketEvents, listener: SocketEvents[keyof SocketEvents]) {
    eventEmitter.once(event, listener)
}
export function off(event: keyof SocketEvents, listener: SocketEvents[keyof SocketEvents]) {
    eventEmitter.off(event, listener)
}

The problem I'm facing with this, is the exported on, once, off doesn't automatically detect the correct listener to use.
I believe it might just be something to do with listener: SocketEvents[keyof SocketEvents].

I was wondering if anyone would have a solution for this.

Edit:
I tried this also

export const on = eventEmitter.on
export const once = eventEmitter.once
export const off = eventEmitter.off

But the return of the call expose the whole object

Keep default any event type

In my use case I am extending a library which has some static event names and some are dynamic generated.
So I would like to have some events typed but keep the default any emitter.
I adapted your library for this and wanted the share the code.
I think this is a general use case and would fit in this library.
So if you like it you could export it as an additional interface something like LooselyTypedEmitter<L>.
Note that this will prevent strict type checking, so it is not a replacement.

declare type DefaultFunction = (...args: any[]) => any;
declare type ListenerSignature<L> = {
    [E in keyof L]: DefaultFunction;
};

declare type DefaultListener = {
    [k: string]: DefaultFunction;
};

export class LooselyTypedEmitter<L extends ListenerSignature<L> = DefaultListener> {
    static defaultMaxListeners: number;
    addListener<U extends keyof L>(event: U, listener: L[U]): this;
    addListener(event: string, listener: DefaultFunction): this;
    prependListener<U extends keyof L>(event: U, listener: L[U]): this;
    prependListener(event: string, listener: DefaultFunction): this;
    prependOnceListener<U extends keyof L>(event: U, listener: L[U]): this;
    prependOnceListener(event: string, listener: DefaultFunction): this;
    removeListener<U extends keyof L>(event: U, listener: L[U]): this;
    removeListener(event: string, listener: DefaultFunction): this;
    removeAllListeners(event?: keyof L): this;
    removeAllListeners(event?: string): this;
    once<U extends keyof L>(event: U, listener: L[U]): this;
    once(event: string, listener: DefaultFunction): this;
    on<U extends keyof L>(event: U, listener: L[U]): this;
    on(event: string, listener: DefaultFunction): this;
    off<U extends keyof L>(event: U, listener: L[U]): this;
    off(event: string, listener: DefaultFunction): this;
    emit<U extends keyof L>(event: U, ...args: Parameters<L[U]>): boolean;
    emit(event: string, ...args: any[]): boolean;
    eventNames<U extends keyof L>(): U[];
    listenerCount(type: keyof L): number;
    listenerCount(type: string): number;
    listeners<U extends keyof L>(type: U): L[U][];
    listeners(type: string): DefaultFunction[];
    rawListeners<U extends keyof L>(type: U): L[U][];
    rawListeners(type: string): DefaultFunction[];
    getMaxListeners(): number;
    setMaxListeners(n: number): this;
}

Provide also raw interface definition instead of just class

First, thanks for this helpful package @binier ! I used to manually declare onX(…) events to enforce typed events until I found it.

One thing I miss is a plain interface definition which I can inherit.

export interface ITypedEmitter<L extends ListenerSignature<L> = DefaultListener> {
    addListener<U extends keyof L>( event: U, listener: L[U] ): this;
    prependListener<U extends keyof L>( event: U, listener: L[U] ): this;
    // etc.
}

export interface MyInterfaceWithEvents extends ITypedEmitter<MyEvents> {
    // …
}

so users of the interface can also rely on typed events, not only users of the instance.

Cannot find module 'tiny-typed-emitter' or its corresponding type declarations.ts(2307)

ButtonBehavior.ts:

import { ButtonBehaviorEvent } from './ButtonBehaviorEvent'
import { TypedEmitter } from 'tiny-typed-emitter';

export class ButtonBehavior extends TypedEmitter<ButtonBehaviorEvent> {

  constructor() {
    super();
    this.emit('buttonBehaviorEvent', 'over', true, true, false);
  }

}

ButtonBehaviorEvent.ts:

export interface ButtonBehaviorEvent {
  
  'buttonBehaviorEvent': (
                            $state: string, 
                            $selected: boolean, 
                            $enabled: boolean, 
                            $pressed: boolean,
                          ) => void;
}

package.json:

{
  "name": "drawing",
  "version": "1.0.0",
  "scripts": {
    "dev": "webpack --mode development",
  },
  "devDependencies": {
    "@types/fabric": "^3.6.8",
    "@types/jquery": "^3.5.1",
    "@types/node": "^14.6.4",
    "css-loader": "^4.2.2",
    "mini-css-extract-plugin": "^0.11.0",
    "sass": "^1.26.10",
    "sass-loader": "^10.0.2",
    "ts-loader": "^8.0.3",
    "typescript": "^4.0.2",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12"
  },
  "dependencies": {
    "tiny-typed-emitter": "^2.0.2"
  }
}

I get the errors:

From VScode:
Cannot find module 'tiny-typed-emitter' or its corresponding type declarations.ts(2307)

After compilation:
ButtonBehavior.ts - TS2315: Type 'EventEmitter' is not generic.
ButtonBehavior.ts - TS2339: Property 'emit' does not exist on type 'ButtonBehavior'.

Issue with subclasses

Hi, thanks for the great library 👍

I have a question about the subclasses with different events example that you provided in the readme:

The example works when calling the events from outside the class, but not from within the class itself. Specifically, if I alter the example to be as follows:

class Animal<E extends ListenerSignature<E> = ListenerSignature<unknown>> extends TypedEmitter<{ spawn: () => void } & E> {
	public constructor() {
		super();
	}

	private doSpawn() {
		this.emit('spawn');
	}
}

class Frog<E extends ListenerSignature<E>> extends Animal<{ jump: (testing: boolean) => void } & E> {}
class Bird<E extends ListenerSignature<E>> extends Animal<{ fly: () => void } & E> {}

Then the compiler fails at the this.emit('spawn'); line with the error: Argument of type '[]' is not assignable to parameter of type 'Parameters<({ spawn: () => void; } & E)["spawn"]>'.

I'm assuming this is because of some constraint that can't be applied, but I can't seem to wrap my head around it. Is there any way to solve this?

adding typed events to an extended class that extends EventEmitter?

hi, i'm creating a class that extends the Stream class, which extends EventEmitter. does this module support adding typed events to such classes?

interface MyEvents {
  'foo': () => void;
}

// how do I fit `TypedEmitter` here so that `MyClass` has the `foo` event and also extends `Stream`?
class MyClass extends Stream {
  constructor() {
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.