1
0
mirror of https://github.com/dzeiocom/libs.git synced 2025-04-22 10:52:11 +00:00

feat(listener): Add full support for ESM & Better typing
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 2m35s

Signed-off-by: Florian Bouillon <f.bouillon@aptatio.com>
This commit is contained in:
Florian Bouillon 2023-11-09 15:14:22 +01:00
parent f804e0f43c
commit 3d74438086
4 changed files with 2552 additions and 55 deletions

View File

@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.1.0] - 2023-11-09
### Added
- Full support for both CJS & ESM
### Changed
- Better typing support
## [1.0.3] - 2022-11-17 ## [1.0.3] - 2022-11-17
### Fixed ### Fixed

View File

@ -1,129 +1,155 @@
type ItemToArray<T> = { type RecordToArray<T> = {
[P in keyof T]?: Array<T[P]> [P in keyof T]?: Array<T[P]>
} }
type BuiltInEvents = { type EventList = Record<string, (...args: Array<any>) => void>
type BaseEvents<T extends EventList = {}> = {
newListener: (eventName: string, listener: (...args: Array<any>) => void) => void newListener: (eventName: string, listener: (...args: Array<any>) => void) => void
removeListener: (eventName: string, listener: (...args: Array<any>) => void) => void removeListener: (eventName: string, listener: (...args: Array<any>) => void) => void
all: (eventName: string, ...args: Array<any>) => void all: (eventName: string, ...args: Array<any>) => void
} } & T
export default abstract class Listener< /**
T extends Record<string, (...args: Array<any>) => void> = BuiltInEvents * Allows you to create a typed event handler system
*/
export default class Listener<
T extends EventList = BaseEvents
> { > {
private maxListeners = 10 private maxListeners = 10
private handlers: ItemToArray<T> = {} private handlers: RecordToArray<BaseEvents<T>> = {}
/** /**
* Add a listener to the event * Add a listener to the event
* @param event the event name * @param event the event name
* @param listener the listener * @param listener the listener
* @returns the class
*/ */
public addListener(event: keyof T, listener: T[typeof event]) { public addListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.on(event, listener) return this.on(event, listener)
} }
/** /**
* Add a listener to the event * Add a listener to the event
*
* @param event the event name * @param event the event name
* @param listener the listener * @param listener the listener
*
* @returns the class
*/ */
public on(event: keyof T, listener: T[typeof event]) { public on<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.internalAdd(true, event, listener) return this.internalAdd(true, event, listener)
} }
/** /**
* Add a one time trigger listener to the event * Add a one time trigger listener to the event
*
* @param event the event name * @param event the event name
* @param listener the listener * @param listener the listener
*
* @returns the class
*/ */
public once(event: keyof T, listener: T[typeof event]) { public once<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
const fn = (...args: Array<any>) => { const fn = (...args: Array<any>) => {
listener(...args) listener(...args)
this.off(event, fn as any) this.off(event, fn as BaseEvents<T>[Key])
} }
this.on(event, fn as any) return this.on(event, fn as BaseEvents<T>[Key])
return this
} }
/** /**
* remove a listener from the event * remove a listener from the event
*
* @param event the event name * @param event the event name
* @param listener the listener * @param listener the listener
*
* @returns the class
*/ */
public removeListener(event: keyof T, listener: T[typeof event]) { public removeListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.off(event, listener) return this.off(event, listener)
} }
/** /**
* remove a listener from the event * remove a listener from the event
*
* @param event the event name * @param event the event name
* @param listener the listener * @param listener the listener
*
*
* @returns the class
*/ */
public off(event: keyof T, listener: T[typeof event]) { public off<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
const listeners = this.listeners(event) return this.internalRemove(event, listener)
const index = listeners.indexOf(listener)
if (index !== -1) {
(this.handlers[event] as Array<T[typeof event]>).splice(index, 1)
}
// @ts-expect-error Meta Listener
this.emit('removeListener', event, listener)
return this
} }
/** /**
* remove every listeners from the event * remove every listeners from the event
*
* @param event the event name * @param event the event name
*
* @returns the class
*/ */
public removeAllListeners(event: keyof T) { public removeAllListeners(event: keyof BaseEvents<T>): this {
const listeners = this.listeners(event) return this.internalRemove(event)
listeners.forEach((ev) => this.off(event, ev)) }
/**
* set a maximum number of listeners before sending a warning
*
* @param count the number of listeners before sending the warning
*
* @returns the class
*/
public setMaxListeners(count: number): this {
this.maxListeners = count
return this return this
} }
/**
* set a maximum numbre of listeners before sending a warning
* @param n the number of listeners before sending the warning
*/
public setMaxListeners(n: number) {
this.maxListeners = n
}
/** /**
* Return the current max number of listeners * Return the current max number of listeners
*
* @returns the maximum number of listeners before it send warnings
*/ */
public getMaxListeners() { public getMaxListeners(): number {
return this.maxListeners return this.maxListeners
} }
/** /**
* return every listeners from the event * return every listeners from the event
*
* @param event the event name * @param event the event name
*
* @returns the list of events
*/ */
public listeners(event: keyof T) { public listeners<Key extends keyof BaseEvents<T>>(event: Key): Array<BaseEvents<T>[Key]> {
const item = this.handlers[event] as Array<T[typeof event]> return this.handlers[event] ?? []
return item ?? []
} }
/** /**
* return every listeners from the event * return every listeners from the event
*
* @param event the event name * @param event the event name
*
* @returns the list of listenersattached to the event
*/ */
public rawListeners(event: keyof T) { public rawListeners<Key extends keyof BaseEvents<T>>(event: Key): Array<BaseEvents<T>[Key]> {
return this.listenerCount(event) return this.listeners(event)
} }
/** /**
* Emit the event with the selected variables * Emit the event with the selected variables
*
* @param event the event to emit * @param event the event to emit
* @param ev the variables to send * @param ev the variables to send
*
* @return if the evenet was emitted to any listeners or not
*/ */
public emit(event: keyof T, ...ev: Parameters<T[typeof event]>) { public emit<Key extends keyof BaseEvents<T>>(event: Key, ...ev: Parameters<BaseEvents<T>[Key]>): boolean {
if (event !== 'all') { if (event !== 'all') {
// @ts-expect-error Meta Listener // @ts-expect-error the all event is sent each time a new event is emitted
this.emit('all', event, ...ev) this.emit('all', event, ...ev)
} }
const listeners = this.listeners(event) const listeners = this.listeners(event)
@ -133,27 +159,36 @@ export default abstract class Listener<
/** /**
* return the number of events in the current event * return the number of events in the current event
*
* @param event the event * @param event the event
*
* @returns the number of listeners attached to the event
*/ */
public listenerCount(event: keyof T) { public listenerCount(event: keyof BaseEvents<T>): number {
return this.listeners(event).length return this.listeners(event).length
} }
/** /**
* prepend the listener to the event list * prepend the listener to the event list
*
* @param event the event * @param event the event
* @param listener the listener to attach * @param listener the listener to attach
*
* @returns the class
*/ */
public prependListener(event: keyof T, listener: T[typeof event]) { public prependListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.internalAdd(false, event, listener) return this.internalAdd(false, event, listener)
} }
/** /**
* prepend the listener to the event list to triggered once * prepend the listener to the event list to triggered once
*
* @param event the event * @param event the event
* @param listener the listener to attach * @param listener the listener to attach
*
* @returns the class
*/ */
public prependOnceListener(event: keyof T, listener: T[typeof event]) { public prependOnceListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
const fn = (...args: Parameters<typeof listener>) => { const fn = (...args: Parameters<typeof listener>) => {
listener(...args) listener(...args)
this.off(event, fn as any) this.off(event, fn as any)
@ -164,22 +199,39 @@ export default abstract class Listener<
/** /**
* get every event names with at least one event * get every event names with at least one event
*
* @returns the list of events keys
*/ */
public eventNames() { public eventNames(): Array<keyof BaseEvents<T>> {
return Object.keys(this.handlers) return Object.keys(this.handlers)
} }
// Browser Listeners /**
public addEventListener(event: keyof T, listener: T[typeof event]) { * Add a listener to the event
*
* @param event the event name
* @param listener the listener
*
* @returns the class
*/
public addEventListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.on(event, listener) return this.on(event, listener)
} }
public removeEventListener(event: keyof T, listener: T[typeof event]) { /**
* remove a listener from the event
*
* @param event the event name
* @param listener the listener
*
* @returns the class
*/
public removeEventListener<Key extends keyof BaseEvents<T>>(event: Key, listener: BaseEvents<T>[Key]): this {
return this.off(event, listener) return this.off(event, listener)
} }
private internalAdd(push: boolean, event: keyof T, listener: T[typeof event]) { private internalAdd<Key extends keyof BaseEvents<T>>(push: boolean, event: Key, listener: BaseEvents<T>[Key]): this {
// @ts-expect-error Meta Listener // @ts-expect-error Meta Listener that emit when a new event is addrd
this.emit('newListener', event, listener) this.emit('newListener', event, listener)
const item = this.handlers[event] const item = this.handlers[event]
if (!item) { if (!item) {
@ -193,8 +245,25 @@ export default abstract class Listener<
} }
const listenerCount = this.listenerCount(event) const listenerCount = this.listenerCount(event)
if (listenerCount > this.getMaxListeners()) { if (listenerCount > this.getMaxListeners()) {
console.warn(`MaxListenersExceededWarning: Possible EventEmitter memory leak detected. ${this.getMaxListeners()} listeners recommended while there is ${listenerCount} listeners. Use emitter.setMaxListeners() to increase limit`) console.warn(`MaxListenersExceededWarning: Possible Listener memory leak detected. ${this.getMaxListeners()} listeners recommended while there is ${listenerCount} listeners. Use setMaxListeners() to increase the limit`)
} }
return this return this
} }
private internalRemove<Key extends keyof BaseEvents<T>>(event: Key, listener?: BaseEvents<T>[Key]): this {
if (!listener) {
for (const listener of (this.handlers[event] ?? [])) {
this.internalRemove(event, listener)
}
return this
}
const listeners = this.listeners(event)
const idx = listeners.indexOf(listener)
if (idx >= 0) {
this.handlers[event]?.splice(idx, 1)
}
// @ts-expect-error Meta Listener that emit when a event is removed
this.emit('removeListener', event, listener)
return this
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@dzeio/listener", "name": "@dzeio/listener",
"version": "1.0.3", "version": "1.1.0",
"description": "A NodeJS Listener implementation", "description": "A NodeJS Listener implementation",
"repository": { "repository": {
"type": "git", "type": "git",
@ -10,9 +10,28 @@
"author": "Aviortheking", "author": "Aviortheking",
"license": "MIT", "license": "MIT",
"main": "./dist/Listener.js", "main": "./dist/Listener.js",
"module": "./dist/Listener.mjs",
"types": "./dist/Listener.d.ts", "types": "./dist/Listener.d.ts",
"browser": "./dist/Listener.global.js",
"exports": {
".": {
"require": {
"types": "./dist/Listener.d.ts",
"default": "./dist/Listener.js"
},
"import": {
"types": "./dist/Listener.d.mts",
"default": "./dist/Listener.mjs"
}
}
},
"files": ["dist"],
"scripts": { "scripts": {
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
"build": "tsc" "build": "tsup ./Listener.ts --format cjs,esm --dts --clean && tsup ./Listener.ts --format iife --global-name Listener --minify --sourcemap"
},
"devDependencies": {
"tsup": "^7.2.0",
"typescript": "^5.2.2"
} }
} }