Skip to content

Commit 0627d68

Browse files
committed
feat: add back chained-calls via chain utility
1 parent f2fc56c commit 0627d68

File tree

5 files changed

+290
-137
lines changed

5 files changed

+290
-137
lines changed

src/chain.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { LiteEmit } from "./emitter";
2+
import type { EventMap, Listener, WildcardListener } from "./types";
3+
4+
export interface ChainedLiteEmit<EM extends EventMap> {
5+
on: ((event: "*", listener: WildcardListener) => this) &
6+
(<K extends keyof EM>(event: K, listener: Listener<EM[K]>) => this);
7+
8+
once: ((event: "*", listener: WildcardListener) => this) &
9+
(<K extends keyof EM>(event: K, listener: Listener<EM[K]>) => this);
10+
11+
emit: <K extends keyof EM>(event: K, ...args: EM[K]) => this;
12+
13+
off: (() => this) &
14+
((event: "*", listener?: WildcardListener) => this) &
15+
(<K extends keyof EM>(event: K, listener?: Listener<EM[K]>) => this);
16+
17+
unwrap: () => LiteEmit<EM>;
18+
}
19+
20+
export function chain<EM extends EventMap>(
21+
emitter: LiteEmit<EM>,
22+
): ChainedLiteEmit<EM> {
23+
const self: ChainedLiteEmit<EM> = {
24+
on(event: any, listener: any): any {
25+
emitter.on(event, listener);
26+
27+
return self;
28+
},
29+
once(event: any, listener: any): any {
30+
emitter.once(event, listener);
31+
32+
return self;
33+
},
34+
emit(event: any, ...args: any[]): any {
35+
(emitter.emit as any)(event, ...args);
36+
37+
return self;
38+
},
39+
off(event?: any, listener?: any): any {
40+
(emitter.off as any)(event, listener);
41+
42+
return self;
43+
},
44+
unwrap() {
45+
return emitter;
46+
},
47+
};
48+
49+
return self;
50+
}

src/emitter.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import type {
2+
ErrorHandler,
3+
EventMap,
4+
Listener,
5+
OffFunction,
6+
Options,
7+
WildcardListener,
8+
} from "./types";
9+
10+
export class LiteEmit<EM extends EventMap = EventMap> {
11+
#listenerMap = new Map<keyof EM, Listener<EM[keyof EM]>[]>();
12+
#wildcardListeners: WildcardListener[] = [];
13+
#errorHandler: ErrorHandler | undefined;
14+
15+
constructor(options?: Options) {
16+
this.#errorHandler = options?.errorHandler;
17+
}
18+
19+
public on(event: "*", listener: WildcardListener): OffFunction;
20+
public on<K extends keyof EM>(
21+
event: K,
22+
listener: Listener<EM[K]>,
23+
): OffFunction;
24+
public on<K extends keyof EM>(
25+
event: K | "*",
26+
listener: Listener<EM[K]> | WildcardListener,
27+
): OffFunction {
28+
if (event === "*") {
29+
if (!this.#wildcardListeners.includes(listener as any)) {
30+
this.#wildcardListeners.push(listener as any);
31+
}
32+
33+
return () => this.off("*", listener as any);
34+
}
35+
36+
if (!this.#listenerMap.has(event)) {
37+
this.#listenerMap.set(event, []);
38+
}
39+
const listeners = this.#listenerMap.get(event)!;
40+
if (!listeners.includes(listener as any)) {
41+
listeners.push(listener as any);
42+
}
43+
44+
return () => this.off(event, listener as any);
45+
}
46+
47+
public once(event: "*", listener: WildcardListener): OffFunction;
48+
public once<K extends keyof EM>(
49+
event: K,
50+
listener: Listener<EM[K]>,
51+
): OffFunction;
52+
public once<K extends keyof EM>(
53+
event: K | "*",
54+
listener: Listener<EM[K]> | WildcardListener,
55+
): OffFunction {
56+
const onceListener = (...args: any[]) => {
57+
this.off(event, onceListener);
58+
listener(...args);
59+
};
60+
61+
return this.on(event, onceListener);
62+
}
63+
64+
#callListenerWithErrorHandler(listener: Listener<any>, args: any[]): void {
65+
try {
66+
const result = listener(...args);
67+
if (result instanceof Promise) {
68+
result.catch((e) => {
69+
this.#errorHandler?.(e);
70+
});
71+
}
72+
} catch (e: unknown) {
73+
this.#errorHandler?.(e);
74+
}
75+
}
76+
77+
public emit<K extends keyof EM>(event: K, ...args: EM[K]): void {
78+
if (this.#wildcardListeners.length > 0) {
79+
for (const listener of this.#wildcardListeners) {
80+
this.#callListenerWithErrorHandler(listener, [event, ...args]);
81+
}
82+
}
83+
const listeners = this.#listenerMap.get(event);
84+
if (listeners) {
85+
for (const listener of listeners) {
86+
this.#callListenerWithErrorHandler(listener, args);
87+
}
88+
}
89+
}
90+
91+
public off(): void;
92+
public off(event: "*", listener?: WildcardListener): void;
93+
public off<K extends keyof EM>(event: K, listener?: Listener<EM[K]>): void;
94+
public off<K extends keyof EM>(
95+
event?: K | "*",
96+
listener?: Listener<EM[K]> | WildcardListener,
97+
): void {
98+
if (event === undefined) {
99+
this.#listenerMap.clear();
100+
this.#wildcardListeners.length = 0;
101+
102+
return;
103+
// Event param is given
104+
} else if (event === "*") {
105+
// Remove the specified listener
106+
if (listener) {
107+
const index = this.#wildcardListeners.indexOf(listener as any);
108+
if (index !== -1) {
109+
this.#wildcardListeners.splice(index, 1);
110+
}
111+
// Clear all wildcard listners
112+
} else {
113+
this.#wildcardListeners.length = 0;
114+
}
115+
116+
return;
117+
}
118+
// The event param is defined and not a wildcard symbol
119+
if (listener) {
120+
const listeners = this.#listenerMap.get(event);
121+
if (listeners) {
122+
const index = listeners.indexOf(listener as any);
123+
if (index !== -1) {
124+
listeners.splice(index, 1);
125+
}
126+
}
127+
} else {
128+
this.#listenerMap.delete(event);
129+
}
130+
}
131+
}

src/index.ts

Lines changed: 4 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,4 @@
1-
export type EventMap = Record<PropertyKey, any[]>;
2-
3-
export type Listener<A extends any[]> = (...args: A) => void | Promise<void>;
4-
5-
export type WildcardListener = Listener<[event: string, ...args: string[]]>;
6-
7-
export type ErrorHandler = (e: unknown) => void;
8-
9-
export interface Options {
10-
errorHandler?: ErrorHandler;
11-
}
12-
13-
type OffFunction = () => void;
14-
15-
export class LiteEmit<EM extends EventMap = EventMap> {
16-
#listenerMap = new Map<keyof EM, Listener<EM[keyof EM]>[]>();
17-
#wildcardListeners: WildcardListener[] = [];
18-
#errorHandler: ErrorHandler | undefined;
19-
20-
constructor(options?: Options) {
21-
this.#errorHandler = options?.errorHandler;
22-
}
23-
24-
public on(event: "*", listener: WildcardListener): OffFunction;
25-
public on<K extends keyof EM>(
26-
event: K,
27-
listener: Listener<EM[K]>,
28-
): OffFunction;
29-
public on<K extends keyof EM>(
30-
event: K | "*",
31-
listener: Listener<EM[K]> | WildcardListener,
32-
): OffFunction {
33-
if (event === "*") {
34-
if (!this.#wildcardListeners.includes(listener as any)) {
35-
this.#wildcardListeners.push(listener as any);
36-
}
37-
38-
return () => this.off("*", listener as any);
39-
}
40-
41-
if (!this.#listenerMap.has(event)) {
42-
this.#listenerMap.set(event, []);
43-
}
44-
const listeners = this.#listenerMap.get(event)!;
45-
if (!listeners.includes(listener as any)) {
46-
listeners.push(listener as any);
47-
}
48-
49-
return () => this.off(event, listener as any);
50-
}
51-
52-
public once(event: "*", listener: WildcardListener): OffFunction;
53-
public once<K extends keyof EM>(
54-
event: K,
55-
listener: Listener<EM[K]>,
56-
): OffFunction;
57-
public once<K extends keyof EM>(
58-
event: K | "*",
59-
listener: Listener<EM[K]> | WildcardListener,
60-
): OffFunction {
61-
const onceListener = (...args: any[]) => {
62-
this.off(event, onceListener);
63-
listener(...args);
64-
};
65-
66-
return this.on(event, onceListener);
67-
}
68-
69-
#callListenerWithErrorHandler(listener: Listener<any>, args: any[]): void {
70-
try {
71-
const result = listener(...args);
72-
if (result instanceof Promise) {
73-
result.catch((e) => {
74-
this.#errorHandler?.(e);
75-
});
76-
}
77-
} catch (e: unknown) {
78-
this.#errorHandler?.(e);
79-
}
80-
}
81-
82-
public emit<K extends keyof EM>(event: K, ...args: EM[K]): void {
83-
if (this.#wildcardListeners.length > 0) {
84-
for (const listener of this.#wildcardListeners) {
85-
this.#callListenerWithErrorHandler(listener, [event, ...args]);
86-
}
87-
}
88-
const listeners = this.#listenerMap.get(event);
89-
if (listeners) {
90-
for (const listener of listeners) {
91-
this.#callListenerWithErrorHandler(listener, args);
92-
}
93-
}
94-
}
95-
96-
public off(): void;
97-
public off(event: "*", listener?: WildcardListener): void;
98-
public off<K extends keyof EM>(event: K, listener?: Listener<EM[K]>): void;
99-
public off<K extends keyof EM>(
100-
event?: K | "*",
101-
listener?: Listener<EM[K]> | WildcardListener,
102-
): void {
103-
if (event === undefined) {
104-
this.#listenerMap.clear();
105-
this.#wildcardListeners.length = 0;
106-
107-
return;
108-
// Event param is given
109-
} else if (event === "*") {
110-
// Remove the specified listener
111-
if (listener) {
112-
const index = this.#wildcardListeners.indexOf(listener as any);
113-
if (index !== -1) {
114-
this.#wildcardListeners.splice(index, 1);
115-
}
116-
// Clear all wildcard listners
117-
} else {
118-
this.#wildcardListeners.length = 0;
119-
}
120-
121-
return;
122-
}
123-
// The event param is defined and not a wildcard symbol
124-
if (listener) {
125-
const listeners = this.#listenerMap.get(event);
126-
if (listeners) {
127-
const index = listeners.indexOf(listener as any);
128-
if (index !== -1) {
129-
listeners.splice(index, 1);
130-
}
131-
}
132-
} else {
133-
this.#listenerMap.delete(event);
134-
}
135-
}
136-
}
1+
export type { ChainedLiteEmit } from "./chain";
2+
export { chain } from "./chain";
3+
export { LiteEmit } from "./emitter";
4+
export type * from "./types";

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export type EventMap = Record<PropertyKey, any[]>;
2+
export type Listener<A extends any[]> = (...args: A) => void | Promise<void>;
3+
export type WildcardListener = Listener<[event: string, ...args: string[]]>;
4+
export type ErrorHandler = (e: unknown) => void;
5+
export interface Options {
6+
errorHandler?: ErrorHandler;
7+
}
8+
export type OffFunction = () => void;

0 commit comments

Comments
 (0)