asljs-eventful 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +37 -31
- package/dist/eventful.d.ts +2 -0
- package/dist/eventful.js +148 -0
- package/dist/guards.d.ts +5 -0
- package/dist/guards.js +18 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +10 -0
- package/eventful.d.ts +14 -105
- package/eventful.js +3 -407
- package/package.json +22 -4
- package/tests/eventful.test.js +0 -305
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ const obj =
|
|
|
32
32
|
eventful(
|
|
33
33
|
{ },
|
|
34
34
|
{ trace:
|
|
35
|
-
(
|
|
35
|
+
(action, payload) => {
|
|
36
36
|
console.log(
|
|
37
37
|
`Action: ${action}`,
|
|
38
38
|
payload);
|
|
@@ -40,10 +40,10 @@ const obj =
|
|
|
40
40
|
|
|
41
41
|
// Tracing actions include:
|
|
42
42
|
// - 'new' on creation: payload { object }
|
|
43
|
-
// - 'on' when subscribing: payload { event, listener }
|
|
44
|
-
// - 'off' when unsubscribing: payload { event, listener }
|
|
45
|
-
// - 'emit' for sync emit: payload { event, args, listeners }
|
|
46
|
-
// - 'emitAsync' for async emit: payload { event, args, listeners }
|
|
43
|
+
// - 'on' when subscribing: payload { object, event, listener }
|
|
44
|
+
// - 'off' when unsubscribing: payload { object, event, listener }
|
|
45
|
+
// - 'emit' for sync emit: payload { object, event, args, listeners }
|
|
46
|
+
// - 'emitAsync' for async emit: payload { object, event, args, listeners }
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Custom error handler for listener errors:
|
|
@@ -53,10 +53,10 @@ const obj =
|
|
|
53
53
|
eventful(
|
|
54
54
|
{ },
|
|
55
55
|
{ error:
|
|
56
|
-
(
|
|
56
|
+
({ error, object, event, listener }) => {
|
|
57
57
|
console.error(
|
|
58
58
|
`Error in listener for event "${event}"`,
|
|
59
|
-
|
|
59
|
+
error);
|
|
60
60
|
} });
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -69,22 +69,32 @@ const obj =
|
|
|
69
69
|
{ strict: true });
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
### Global Events
|
|
73
|
+
|
|
74
|
+
`eventful` is also a global emitter. When you create an enhanced object via `eventful(target, options)`, its lifecycle and actions are traced via the per-instance `trace` hook and also emitted as global events on `eventful`.
|
|
73
75
|
|
|
74
76
|
```js
|
|
75
|
-
|
|
76
|
-
(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
const offNew =
|
|
78
|
+
eventful.on(
|
|
79
|
+
'new',
|
|
80
|
+
({ object }) => {
|
|
81
|
+
console.log('created', object);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const offError =
|
|
85
|
+
eventful.on(
|
|
86
|
+
'error',
|
|
87
|
+
({ error, object, event }) => {
|
|
88
|
+
console.error('listener error', event, error);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Later
|
|
92
|
+
offNew();
|
|
93
|
+
offError();
|
|
86
94
|
```
|
|
87
95
|
|
|
96
|
+
Note: if a **global** `eventful.on('error', ...)` listener throws, `eventful` throws a `ListenerError` (an `Error` subclass with fields `{ error, object, event, listener }`) to avoid an infinite error loop.
|
|
97
|
+
|
|
88
98
|
## API
|
|
89
99
|
|
|
90
100
|
### eventful([target], [options])
|
|
@@ -94,12 +104,8 @@ a new empty object is created.
|
|
|
94
104
|
|
|
95
105
|
- `target` (Object): The object to be enhanced with event capabilities.
|
|
96
106
|
- `options` (Object): Configuration options.
|
|
97
|
-
- `error` (Function | null):
|
|
98
|
-
|
|
99
|
-
provided.
|
|
100
|
-
- `trace` (Function | null): Custom trace hook `(object, action, payload)`
|
|
101
|
-
for `new`, `on`, `off`, `emit`, `emitAsync`. Defaults to `null`. Only
|
|
102
|
-
called when provided.
|
|
107
|
+
- `error` (Function | null): Optional error hook called with `{ error, object, event, listener }`.
|
|
108
|
+
- `trace` (Function | null): Optional trace hook called with `(action, payload)`.
|
|
103
109
|
- `strict` (Boolean): If true, propagates listener errors; otherwise they
|
|
104
110
|
are isolated. Defaults to false.
|
|
105
111
|
|
|
@@ -107,7 +113,7 @@ a new empty object is created.
|
|
|
107
113
|
|
|
108
114
|
Registers a listener for the specified event.
|
|
109
115
|
|
|
110
|
-
- `event` (String): The event name.
|
|
116
|
+
- `event` (String | Symbol): The event name.
|
|
111
117
|
- `listener` (Function): The callback function to be invoked when the event is
|
|
112
118
|
emitted.
|
|
113
119
|
|
|
@@ -118,7 +124,7 @@ Returns a function to remove the listener.
|
|
|
118
124
|
Registers a one-time listener for the specified event. The listener is removed
|
|
119
125
|
after its first invocation.
|
|
120
126
|
|
|
121
|
-
- `event` (String): The event name.
|
|
127
|
+
- `event` (String | Symbol): The event name.
|
|
122
128
|
- `listener` (Function): The callback function to be invoked when the event is
|
|
123
129
|
emitted.
|
|
124
130
|
|
|
@@ -139,7 +145,7 @@ obj.emit('tick', 2); // no-op; already unsubscribed
|
|
|
139
145
|
|
|
140
146
|
Removes a listener for the specified event.
|
|
141
147
|
|
|
142
|
-
- `event` (String): The event name.
|
|
148
|
+
- `event` (String | Symbol): The event name.
|
|
143
149
|
- `listener` (Function): The callback function to be removed.
|
|
144
150
|
|
|
145
151
|
### emit(event, ...args)
|
|
@@ -147,7 +153,7 @@ Removes a listener for the specified event.
|
|
|
147
153
|
Emits the specified event, invoking all registered listeners with the provided
|
|
148
154
|
arguments.
|
|
149
155
|
|
|
150
|
-
- `event` (String): The event name.
|
|
156
|
+
- `event` (String | Symbol): The event name.
|
|
151
157
|
- `...args` (Any): Arguments to pass to the listeners.
|
|
152
158
|
|
|
153
159
|
### emitAsync(event, ...args)
|
|
@@ -156,7 +162,7 @@ Emits the specified event asynchronously, running listeners in parallel.
|
|
|
156
162
|
In non-strict mode, all listeners run and rejections are isolated; in strict
|
|
157
163
|
mode, the first rejection causes the returned promise to reject.
|
|
158
164
|
|
|
159
|
-
- `event` (String): The event name.
|
|
165
|
+
- `event` (String | Symbol): The event name.
|
|
160
166
|
- `...args` (Any): Arguments to pass to the listeners.
|
|
161
167
|
|
|
162
168
|
Returns a Promise that resolves when all listeners have been invoked.
|
|
@@ -165,7 +171,7 @@ Returns a Promise that resolves when all listeners have been invoked.
|
|
|
165
171
|
|
|
166
172
|
Checks if there are any listeners registered for the specified event.
|
|
167
173
|
|
|
168
|
-
- `event` (String): The event name.
|
|
174
|
+
- `event` (String | Symbol): The event name.
|
|
169
175
|
|
|
170
176
|
Returns `true` if there are listeners, otherwise `false`.
|
|
171
177
|
|
package/dist/eventful.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ListenerError } from './types.js';
|
|
2
|
+
import { eventTypeGuard, functionTypeGuard, isFunction, isObject, } from './guards.js';
|
|
3
|
+
const eventfulImpl = (object = Object.create(null), options = {}) => {
|
|
4
|
+
if (!isObject(object)
|
|
5
|
+
&& !isFunction(object)) {
|
|
6
|
+
throw new TypeError('Expect an object or a function.');
|
|
7
|
+
}
|
|
8
|
+
for (const method of ['on', 'once', 'off', 'emit', 'emitAsync', 'has']) {
|
|
9
|
+
if (object[method] !== undefined) {
|
|
10
|
+
throw new Error(`Method "${method}" already exists.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const { strict = false, trace = null, error = null } = options;
|
|
14
|
+
const traceHook = typeof trace === 'function'
|
|
15
|
+
? trace
|
|
16
|
+
: null;
|
|
17
|
+
const errorHook = typeof error === 'function'
|
|
18
|
+
? error
|
|
19
|
+
: null;
|
|
20
|
+
const enhanced = object !== eventful;
|
|
21
|
+
const traceFn = (action, payload) => {
|
|
22
|
+
traceHook?.(action, payload);
|
|
23
|
+
if (enhanced) {
|
|
24
|
+
eventful.emit(action, payload);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
traceFn('new', { object });
|
|
28
|
+
const emptySet = new Set();
|
|
29
|
+
const map = new Map();
|
|
30
|
+
const properties = { enumerable: false,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true };
|
|
33
|
+
Object.defineProperties(object, { on: Object.assign({ value: on }, properties),
|
|
34
|
+
once: Object.assign({ value: once }, properties),
|
|
35
|
+
off: Object.assign({ value: off }, properties),
|
|
36
|
+
emit: Object.assign({ value: emit }, properties),
|
|
37
|
+
emitAsync: Object.assign({ value: emitAsync }, properties),
|
|
38
|
+
has: Object.assign({ value: has }, properties) });
|
|
39
|
+
return object;
|
|
40
|
+
function add(event, listener) {
|
|
41
|
+
let listeners = map.get(event);
|
|
42
|
+
if (!listeners) {
|
|
43
|
+
map.set(event, listeners = new Set());
|
|
44
|
+
}
|
|
45
|
+
listeners.add(listener);
|
|
46
|
+
}
|
|
47
|
+
function remove(event, listener) {
|
|
48
|
+
const listeners = map.get(event);
|
|
49
|
+
if (!listeners)
|
|
50
|
+
return false;
|
|
51
|
+
const deleted = listeners.delete(listener);
|
|
52
|
+
if (listeners.size === 0)
|
|
53
|
+
map.delete(event);
|
|
54
|
+
return deleted;
|
|
55
|
+
}
|
|
56
|
+
function reportListenerError(event, listener, err) {
|
|
57
|
+
const errorArgs = { error: err,
|
|
58
|
+
object: object,
|
|
59
|
+
event,
|
|
60
|
+
listener };
|
|
61
|
+
errorHook?.(errorArgs);
|
|
62
|
+
if (object === eventful
|
|
63
|
+
&& event === 'error') {
|
|
64
|
+
throw new ListenerError('Error in a global error listener.', err, object, event, listener);
|
|
65
|
+
}
|
|
66
|
+
eventful.emit('error', errorArgs);
|
|
67
|
+
}
|
|
68
|
+
function on(event, listener) {
|
|
69
|
+
eventTypeGuard(event);
|
|
70
|
+
functionTypeGuard(listener);
|
|
71
|
+
traceFn('on', { object,
|
|
72
|
+
event,
|
|
73
|
+
listener });
|
|
74
|
+
add(event, listener);
|
|
75
|
+
let active = true;
|
|
76
|
+
return () => active
|
|
77
|
+
? ((active = false), remove(event, listener))
|
|
78
|
+
: false;
|
|
79
|
+
}
|
|
80
|
+
function once(event, listener) {
|
|
81
|
+
eventTypeGuard(event);
|
|
82
|
+
functionTypeGuard(listener);
|
|
83
|
+
const off = on(event, (...args) => {
|
|
84
|
+
off();
|
|
85
|
+
listener(...args);
|
|
86
|
+
});
|
|
87
|
+
return off;
|
|
88
|
+
}
|
|
89
|
+
function off(event, listener) {
|
|
90
|
+
eventTypeGuard(event);
|
|
91
|
+
functionTypeGuard(listener);
|
|
92
|
+
traceFn('off', { object,
|
|
93
|
+
event,
|
|
94
|
+
listener });
|
|
95
|
+
return remove(event, listener);
|
|
96
|
+
}
|
|
97
|
+
function has(event) {
|
|
98
|
+
eventTypeGuard(event);
|
|
99
|
+
return (map.get(event)?.size ?? 0) > 0;
|
|
100
|
+
}
|
|
101
|
+
function emit(event, ...args) {
|
|
102
|
+
eventTypeGuard(event);
|
|
103
|
+
const listeners = map.get(event)
|
|
104
|
+
|| emptySet;
|
|
105
|
+
traceFn('emit', { object,
|
|
106
|
+
listeners: [...listeners],
|
|
107
|
+
event,
|
|
108
|
+
args });
|
|
109
|
+
if (listeners.size === 0)
|
|
110
|
+
return;
|
|
111
|
+
for (const listener of listeners) {
|
|
112
|
+
try {
|
|
113
|
+
listener(...args);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
reportListenerError(event, listener, err);
|
|
117
|
+
if (strict)
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function emitAsync(event, ...args) {
|
|
123
|
+
eventTypeGuard(event);
|
|
124
|
+
const listeners = map.get(event)
|
|
125
|
+
|| emptySet;
|
|
126
|
+
traceFn('emitAsync', { object,
|
|
127
|
+
listeners: [...listeners],
|
|
128
|
+
event,
|
|
129
|
+
args });
|
|
130
|
+
if (listeners.size === 0)
|
|
131
|
+
return;
|
|
132
|
+
const calls = [...listeners].map(async (listener) => {
|
|
133
|
+
try {
|
|
134
|
+
await listener(...args);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
reportListenerError(event, listener, err);
|
|
138
|
+
if (strict)
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
await (strict
|
|
143
|
+
? Promise.all(calls)
|
|
144
|
+
: Promise.allSettled(calls));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
export const eventful = eventfulImpl;
|
|
148
|
+
eventful(eventful);
|
package/dist/guards.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EventName } from './types.js';
|
|
2
|
+
export declare function eventTypeGuard(value: any): asserts value is EventName;
|
|
3
|
+
export declare function isFunction(value: any): value is Function;
|
|
4
|
+
export declare function isObject(value: any): value is object;
|
|
5
|
+
export declare function functionTypeGuard(value: any): asserts value is Function;
|
package/dist/guards.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function eventTypeGuard(value) {
|
|
2
|
+
if (typeof value !== 'string'
|
|
3
|
+
&& typeof value !== 'symbol') {
|
|
4
|
+
throw new TypeError('Expect event to be a string or symbol.');
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function isFunction(value) {
|
|
8
|
+
return typeof value === 'function';
|
|
9
|
+
}
|
|
10
|
+
export function isObject(value) {
|
|
11
|
+
return typeof value === 'object'
|
|
12
|
+
&& value !== null;
|
|
13
|
+
}
|
|
14
|
+
export function functionTypeGuard(value) {
|
|
15
|
+
if (!isFunction(value)) {
|
|
16
|
+
throw new TypeError('Expect a function.');
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export type EventName = string | symbol;
|
|
2
|
+
export type EventMap = Record<EventName, any[]>;
|
|
3
|
+
export type Listener<Args extends any[] = any[]> = (...args: Args) => any;
|
|
4
|
+
export interface ListenerErrorArgs {
|
|
5
|
+
error: any;
|
|
6
|
+
object: object | Function;
|
|
7
|
+
event: EventName;
|
|
8
|
+
listener: Function;
|
|
9
|
+
}
|
|
10
|
+
export declare class ListenerError extends Error implements ListenerErrorArgs {
|
|
11
|
+
error: any;
|
|
12
|
+
object: object | Function;
|
|
13
|
+
event: EventName;
|
|
14
|
+
listener: Function;
|
|
15
|
+
constructor(message: string, error: any, object: object | Function, event: EventName, listener: Function);
|
|
16
|
+
}
|
|
17
|
+
type TraceAction = 'new' | 'on' | 'off' | 'emit' | 'emitAsync';
|
|
18
|
+
type TracePayloadByAction = {
|
|
19
|
+
new: {
|
|
20
|
+
object: object | Function;
|
|
21
|
+
};
|
|
22
|
+
on: {
|
|
23
|
+
object: object | Function;
|
|
24
|
+
event: EventName;
|
|
25
|
+
listener: Function;
|
|
26
|
+
};
|
|
27
|
+
off: {
|
|
28
|
+
object: object | Function;
|
|
29
|
+
event: EventName;
|
|
30
|
+
listener: Function;
|
|
31
|
+
};
|
|
32
|
+
emit: {
|
|
33
|
+
object: object | Function;
|
|
34
|
+
listeners: Function[];
|
|
35
|
+
event: EventName;
|
|
36
|
+
args: any[];
|
|
37
|
+
};
|
|
38
|
+
emitAsync: {
|
|
39
|
+
object: object | Function;
|
|
40
|
+
listeners: Function[];
|
|
41
|
+
event: EventName;
|
|
42
|
+
args: any[];
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type TraceFn = <A extends TraceAction>(action: A, args: TracePayloadByAction[A]) => void;
|
|
46
|
+
export type EventfulFn = EventfulFactory & Eventful;
|
|
47
|
+
export interface EventfulFactory {
|
|
48
|
+
<T extends object | Function | undefined, E extends EventMap = EventMap>(object?: T, options?: EventfulOptions): (T extends undefined ? {} : T) & Eventful<E>;
|
|
49
|
+
}
|
|
50
|
+
export interface EventfulOptions {
|
|
51
|
+
/**
|
|
52
|
+
* If true, exceptions from listeners are propagated (fail fast).
|
|
53
|
+
* When false, errors are isolated (ignored) after calling `error` hook.
|
|
54
|
+
*/
|
|
55
|
+
strict?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Optional tracing hook. Receives action name and a safe payload.
|
|
58
|
+
* Actions include: 'new', 'on', 'off', 'emit', 'emitAsync'.
|
|
59
|
+
* Use to integrate with your logger without exposing internals.
|
|
60
|
+
*/
|
|
61
|
+
trace?: TraceFn | null;
|
|
62
|
+
/**
|
|
63
|
+
* Optional error hook. Receives structured context of listener failures
|
|
64
|
+
* (error, object, event, listener). Called for sync and async errors.
|
|
65
|
+
*/
|
|
66
|
+
error?: ((error: ListenerErrorArgs) => void) | null;
|
|
67
|
+
}
|
|
68
|
+
export interface Eventful<E extends EventMap = EventMap> {
|
|
69
|
+
/**
|
|
70
|
+
* Subscribe to an event. Returns an unsubscribe function.
|
|
71
|
+
*/
|
|
72
|
+
on(event: keyof E & EventName, listener: Listener<E[keyof E & EventName]>): () => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe once to an event. Returns an unsubscribe function
|
|
75
|
+
* (called automatically).
|
|
76
|
+
*/
|
|
77
|
+
once(event: keyof E & EventName, listener: Listener<E[keyof E & EventName]>): () => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Unsubscribe a previously registered listener. Returns true if removed.
|
|
80
|
+
*/
|
|
81
|
+
off(event: keyof E & EventName, listener: Listener<E[keyof E & EventName]>): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Emit an event synchronously. All listeners run in order.
|
|
84
|
+
* Errors are isolated (ignored) unless `strict` is true.
|
|
85
|
+
*/
|
|
86
|
+
emit(event: keyof E & EventName, ...args: E[keyof E & EventName]): void;
|
|
87
|
+
/**
|
|
88
|
+
* Emit an event and wait for all listeners (run in parallel).
|
|
89
|
+
* Errors are isolated (ignored) unless `strict` is true.
|
|
90
|
+
*/
|
|
91
|
+
emitAsync(event: keyof E & EventName, ...args: E[keyof E & EventName]): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Returns true if there is at least one listener for the event.
|
|
94
|
+
*/
|
|
95
|
+
has(event: keyof E & EventName): boolean;
|
|
96
|
+
}
|
|
97
|
+
export {};
|
package/dist/types.js
ADDED
package/eventful.d.ts
CHANGED
|
@@ -1,105 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/** Subscribe once to an event. Returns an unsubscribe function (called automatically). */
|
|
17
|
-
once<K extends keyof E>(
|
|
18
|
-
event: K,
|
|
19
|
-
listener: Listener<E[K]>
|
|
20
|
-
): () => boolean;
|
|
21
|
-
|
|
22
|
-
/** Unsubscribe a previously registered listener. */
|
|
23
|
-
off<K extends keyof E>(
|
|
24
|
-
event: K,
|
|
25
|
-
listener: Listener<E[K]>
|
|
26
|
-
): boolean;
|
|
27
|
-
|
|
28
|
-
/** Emit an event synchronously. */
|
|
29
|
-
emit<K extends keyof E>(
|
|
30
|
-
event: K,
|
|
31
|
-
...args: E[K]
|
|
32
|
-
): void;
|
|
33
|
-
|
|
34
|
-
/** Emit an event and wait for all listeners (run in parallel, errors isolated unless strict). */
|
|
35
|
-
emitAsync<K extends keyof E>(
|
|
36
|
-
event: K,
|
|
37
|
-
...args: E[K]
|
|
38
|
-
): Promise<void>;
|
|
39
|
-
|
|
40
|
-
/** Returns true if there is at least one listener for the event. */
|
|
41
|
-
has<K extends keyof E>(
|
|
42
|
-
event: K
|
|
43
|
-
): boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export interface ErrorContext {
|
|
48
|
-
object: object | Function;
|
|
49
|
-
event: string | symbol;
|
|
50
|
-
listener: Function;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type TracePayload =
|
|
54
|
-
| {
|
|
55
|
-
event: string | symbol;
|
|
56
|
-
args: any[];
|
|
57
|
-
listeners?: Function[];
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
object: object | Function;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export interface EventfulOptions {
|
|
64
|
-
/** If true, exceptions from listeners are propagated. Otherwise they are swallowed after calling `error`. */
|
|
65
|
-
strict?: boolean;
|
|
66
|
-
|
|
67
|
-
/** Optional tracing hook; defaults to `eventful.trace`. */
|
|
68
|
-
trace?: ((
|
|
69
|
-
object: object | Function,
|
|
70
|
-
action: string,
|
|
71
|
-
payload?: TracePayload
|
|
72
|
-
) => void) | null;
|
|
73
|
-
|
|
74
|
-
/** Optional error hook; defaults to `eventful.error`. */
|
|
75
|
-
error?: ((
|
|
76
|
-
err: unknown,
|
|
77
|
-
context: ErrorContext
|
|
78
|
-
) => void) | null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface EventfulFactory {
|
|
82
|
-
<T extends object | Function | undefined,
|
|
83
|
-
E extends EventMap = Record<string | symbol, any[]>>(
|
|
84
|
-
object?: T,
|
|
85
|
-
options?: EventfulOptions
|
|
86
|
-
): (T extends undefined ? {} : T) & Eventful<E>;
|
|
87
|
-
|
|
88
|
-
/** Global hooks you can override. */
|
|
89
|
-
options: {
|
|
90
|
-
/** Default tracer. Replace to integrate with your logger. */
|
|
91
|
-
trace: (
|
|
92
|
-
object: object | Function,
|
|
93
|
-
action: string,
|
|
94
|
-
payload?: TracePayload
|
|
95
|
-
) => void | null;
|
|
96
|
-
|
|
97
|
-
/** Default error handler. Replace to integrate with your logger. */
|
|
98
|
-
error: (
|
|
99
|
-
err: unknown,
|
|
100
|
-
context: ErrorContext
|
|
101
|
-
) => void | null;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export const eventful: EventfulFactory;
|
|
1
|
+
export {
|
|
2
|
+
eventful
|
|
3
|
+
} from './dist/eventful.js';
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
EventName,
|
|
7
|
+
EventMap,
|
|
8
|
+
Eventful,
|
|
9
|
+
EventfulFactory,
|
|
10
|
+
EventfulOptions,
|
|
11
|
+
Listener,
|
|
12
|
+
ListenerError,
|
|
13
|
+
TraceFn
|
|
14
|
+
} from './dist/types.js';
|
package/eventful.js
CHANGED
|
@@ -1,407 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* @property {string|symbol} event The event name.
|
|
5
|
-
* @property {Function} listener The listener function that caused the error.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {Object} EventfulOptions
|
|
10
|
-
* @property {boolean} [strict] If true, exceptions from listeners are propagated.
|
|
11
|
-
* @property {(object: Object|Function, action: string, payload: any) => void} [trace] Optional tracing hook.
|
|
12
|
-
* @property {(err: any, context: ErrorContext) => void} [error] Optional error hook.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A mixin that adds event emitter capabilities to an object.
|
|
17
|
-
*
|
|
18
|
-
* The method returns the original object enhanced with the event
|
|
19
|
-
* subscribing and emitting methods.
|
|
20
|
-
*
|
|
21
|
-
* If no object is provided, a new empty object is created and enhanced.
|
|
22
|
-
*
|
|
23
|
-
* The options can configure the behavior of the event emitter.
|
|
24
|
-
*
|
|
25
|
-
* @param {Object|Function} object The object to enhance.
|
|
26
|
-
* @param {EventfulOptions} options The options to configure the event emitter.
|
|
27
|
-
* @returns {Object|Function} The enhanced object.
|
|
28
|
-
*
|
|
29
|
-
* @throws {TypeError} If the first argument is not an object or function.
|
|
30
|
-
*/
|
|
31
|
-
const eventful =
|
|
32
|
-
(object = Object.create(null),
|
|
33
|
-
options = { }) =>
|
|
34
|
-
{
|
|
35
|
-
if ('object' !== typeof object
|
|
36
|
-
&& 'function' !== typeof object)
|
|
37
|
-
{
|
|
38
|
-
throw new TypeError(
|
|
39
|
-
'Expect an object or a function.');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const emptySet =
|
|
43
|
-
new Set();
|
|
44
|
-
|
|
45
|
-
const { strict = false,
|
|
46
|
-
trace = null,
|
|
47
|
-
error = null } =
|
|
48
|
-
options;
|
|
49
|
-
|
|
50
|
-
const globalOptions =
|
|
51
|
-
eventful.options;
|
|
52
|
-
|
|
53
|
-
const traceFn =
|
|
54
|
-
trace
|
|
55
|
-
|| globalOptions.trace;
|
|
56
|
-
|
|
57
|
-
if (isFunction(traceFn)) {
|
|
58
|
-
traceFn(
|
|
59
|
-
object,
|
|
60
|
-
'new');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
for (const method of
|
|
64
|
-
[ 'on',
|
|
65
|
-
'once',
|
|
66
|
-
'off',
|
|
67
|
-
'emit',
|
|
68
|
-
'emitAsync',
|
|
69
|
-
'has' ])
|
|
70
|
-
{
|
|
71
|
-
if (method in object) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Method "${method}" already exists.`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** @type {Map<string|symbol, Set<Function>>} */
|
|
78
|
-
const map =
|
|
79
|
-
new Map();
|
|
80
|
-
|
|
81
|
-
const add =
|
|
82
|
-
(event, listener) => {
|
|
83
|
-
eventTypeGuard(event);
|
|
84
|
-
functionTypeGuard(listener);
|
|
85
|
-
|
|
86
|
-
let listeners =
|
|
87
|
-
map.get(event);
|
|
88
|
-
|
|
89
|
-
if (!listeners) {
|
|
90
|
-
listeners = new Set();
|
|
91
|
-
map.set(event, listeners);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
listeners.add(listener);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const remove =
|
|
98
|
-
(event, listener) => {
|
|
99
|
-
eventTypeGuard(event);
|
|
100
|
-
functionTypeGuard(listener);
|
|
101
|
-
|
|
102
|
-
const listeners =
|
|
103
|
-
map.get(event);
|
|
104
|
-
|
|
105
|
-
if (!listeners)
|
|
106
|
-
return false;
|
|
107
|
-
|
|
108
|
-
const deleted =
|
|
109
|
-
listeners.delete(listener);
|
|
110
|
-
|
|
111
|
-
if (listeners.size === 0)
|
|
112
|
-
map.delete(event);
|
|
113
|
-
|
|
114
|
-
return deleted;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const getListeners =
|
|
118
|
-
event => {
|
|
119
|
-
eventTypeGuard(event);
|
|
120
|
-
|
|
121
|
-
return map.get(event)
|
|
122
|
-
|| emptySet;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Subscribe to an event.
|
|
127
|
-
*
|
|
128
|
-
* @type {(event: string|symbol, listener: Function) => () => boolean}
|
|
129
|
-
*/
|
|
130
|
-
const on =
|
|
131
|
-
(event, listener) => {
|
|
132
|
-
eventTypeGuard(event);
|
|
133
|
-
functionTypeGuard(listener);
|
|
134
|
-
|
|
135
|
-
const traceFn =
|
|
136
|
-
trace
|
|
137
|
-
|| globalOptions.trace;
|
|
138
|
-
|
|
139
|
-
if (isFunction(traceFn)) {
|
|
140
|
-
traceFn(
|
|
141
|
-
object,
|
|
142
|
-
'on',
|
|
143
|
-
{ event,
|
|
144
|
-
listener });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
add(
|
|
148
|
-
event,
|
|
149
|
-
listener);
|
|
150
|
-
|
|
151
|
-
let active = true;
|
|
152
|
-
|
|
153
|
-
return () => {
|
|
154
|
-
if (!active)
|
|
155
|
-
return false;
|
|
156
|
-
|
|
157
|
-
active = false;
|
|
158
|
-
|
|
159
|
-
return remove(
|
|
160
|
-
event,
|
|
161
|
-
listener);
|
|
162
|
-
};
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Subscribe to an event for a single occurrence.
|
|
167
|
-
*
|
|
168
|
-
* @type {(event: string|symbol, listener: Function) => () => boolean}
|
|
169
|
-
*/
|
|
170
|
-
const once =
|
|
171
|
-
(event, listener) => {
|
|
172
|
-
eventTypeGuard(event);
|
|
173
|
-
functionTypeGuard(listener);
|
|
174
|
-
|
|
175
|
-
const off =
|
|
176
|
-
on(
|
|
177
|
-
event,
|
|
178
|
-
(...args) => {
|
|
179
|
-
off();
|
|
180
|
-
listener(...args);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
return off;
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
/** @type {(event: string|symbol) => boolean} */
|
|
188
|
-
const has =
|
|
189
|
-
event => {
|
|
190
|
-
eventTypeGuard(event);
|
|
191
|
-
|
|
192
|
-
return map.get(event)?.size > 0
|
|
193
|
-
|| false;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Emit an event synchronously.
|
|
198
|
-
* All listeners run in order.
|
|
199
|
-
* Errors are isolated (ignored) unless `strict` is true.
|
|
200
|
-
*
|
|
201
|
-
* @param {string|symbol} event
|
|
202
|
-
* @param {...any} args
|
|
203
|
-
*/
|
|
204
|
-
const emit =
|
|
205
|
-
(event, ...args) => {
|
|
206
|
-
eventTypeGuard(event);
|
|
207
|
-
|
|
208
|
-
const listeners =
|
|
209
|
-
getListeners(event);
|
|
210
|
-
|
|
211
|
-
const traceFn =
|
|
212
|
-
trace
|
|
213
|
-
|| globalOptions.trace;
|
|
214
|
-
|
|
215
|
-
if (isFunction(traceFn)) {
|
|
216
|
-
traceFn(
|
|
217
|
-
object,
|
|
218
|
-
'emit',
|
|
219
|
-
{ listeners: [...listeners],
|
|
220
|
-
event,
|
|
221
|
-
args });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (listeners.size === 0)
|
|
225
|
-
return;
|
|
226
|
-
|
|
227
|
-
for (const listener of listeners) {
|
|
228
|
-
try {
|
|
229
|
-
listener(...args);
|
|
230
|
-
} catch (err) {
|
|
231
|
-
const errorFn =
|
|
232
|
-
error
|
|
233
|
-
|| globalOptions.error;
|
|
234
|
-
|
|
235
|
-
if (isFunction(errorFn)) {
|
|
236
|
-
const context =
|
|
237
|
-
{ object,
|
|
238
|
-
event,
|
|
239
|
-
listener };
|
|
240
|
-
|
|
241
|
-
errorFn(
|
|
242
|
-
err,
|
|
243
|
-
context);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (strict)
|
|
247
|
-
throw err;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Emit an event and wait for all listeners (run in parallel).
|
|
254
|
-
* Errors are isolated (ignored) so all listeners run.
|
|
255
|
-
*
|
|
256
|
-
* @param {string|symbol} event
|
|
257
|
-
* @param {...any} args
|
|
258
|
-
* @returns {Promise<void>}
|
|
259
|
-
*/
|
|
260
|
-
const emitAsync =
|
|
261
|
-
async (event, ...args) => {
|
|
262
|
-
eventTypeGuard(event);
|
|
263
|
-
|
|
264
|
-
const listeners =
|
|
265
|
-
getListeners(event);
|
|
266
|
-
|
|
267
|
-
const traceFn =
|
|
268
|
-
trace
|
|
269
|
-
|| globalOptions.trace;
|
|
270
|
-
|
|
271
|
-
if (isFunction(traceFn)) {
|
|
272
|
-
traceFn(
|
|
273
|
-
object,
|
|
274
|
-
'emitAsync',
|
|
275
|
-
{ listeners: [...listeners],
|
|
276
|
-
event,
|
|
277
|
-
args });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (listeners.size === 0)
|
|
281
|
-
return;
|
|
282
|
-
|
|
283
|
-
const calls =
|
|
284
|
-
Array.from(listeners)
|
|
285
|
-
.map(listener =>
|
|
286
|
-
new Promise(
|
|
287
|
-
(resolve, reject) => {
|
|
288
|
-
try {
|
|
289
|
-
Promise.resolve(
|
|
290
|
-
listener(...args))
|
|
291
|
-
.then(
|
|
292
|
-
resolve,
|
|
293
|
-
err => {
|
|
294
|
-
const errorFn =
|
|
295
|
-
error
|
|
296
|
-
|| globalOptions.error;
|
|
297
|
-
|
|
298
|
-
if (isFunction(errorFn)) {
|
|
299
|
-
const context =
|
|
300
|
-
{ object,
|
|
301
|
-
event,
|
|
302
|
-
listener };
|
|
303
|
-
|
|
304
|
-
errorFn(
|
|
305
|
-
err,
|
|
306
|
-
context);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
reject(err);
|
|
310
|
-
});
|
|
311
|
-
} catch (err) {
|
|
312
|
-
const errorFn =
|
|
313
|
-
error
|
|
314
|
-
|| globalOptions.error;
|
|
315
|
-
|
|
316
|
-
if (isFunction(errorFn)) {
|
|
317
|
-
const context =
|
|
318
|
-
{ object,
|
|
319
|
-
event,
|
|
320
|
-
listener };
|
|
321
|
-
|
|
322
|
-
errorFn(
|
|
323
|
-
err,
|
|
324
|
-
context);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
reject(err);
|
|
328
|
-
}
|
|
329
|
-
}));
|
|
330
|
-
|
|
331
|
-
if (strict)
|
|
332
|
-
await Promise.all(calls);
|
|
333
|
-
else
|
|
334
|
-
await Promise.allSettled(calls);
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Unsubscribe from an event.
|
|
339
|
-
*
|
|
340
|
-
* @param {string} event The event name.
|
|
341
|
-
* @param {Function} listener The handling function.
|
|
342
|
-
* @returns {boolean} True if unsubscribed, false if not found.
|
|
343
|
-
*/
|
|
344
|
-
const off =
|
|
345
|
-
(event, listener) => {
|
|
346
|
-
eventTypeGuard(event);
|
|
347
|
-
functionTypeGuard(listener);
|
|
348
|
-
|
|
349
|
-
const traceFn =
|
|
350
|
-
trace
|
|
351
|
-
|| globalOptions.trace;
|
|
352
|
-
|
|
353
|
-
if (isFunction(traceFn)) {
|
|
354
|
-
traceFn(
|
|
355
|
-
object,
|
|
356
|
-
'off',
|
|
357
|
-
{ event,
|
|
358
|
-
listener });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return remove(
|
|
362
|
-
event,
|
|
363
|
-
listener);
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const attributes =
|
|
367
|
-
{ enumerable: false,
|
|
368
|
-
configurable: true,
|
|
369
|
-
writable: true };
|
|
370
|
-
|
|
371
|
-
Object.defineProperties(
|
|
372
|
-
object,
|
|
373
|
-
{ on: { value: on, ...attributes },
|
|
374
|
-
once: { value: once, ...attributes },
|
|
375
|
-
off: { value: off, ...attributes },
|
|
376
|
-
emit: { value: emit, ...attributes },
|
|
377
|
-
emitAsync: { value: emitAsync, ...attributes },
|
|
378
|
-
has: { value: has, ...attributes } });
|
|
379
|
-
|
|
380
|
-
return object;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
eventful.options =
|
|
384
|
-
{ trace: null,
|
|
385
|
-
error: null };
|
|
386
|
-
|
|
387
|
-
function eventTypeGuard(value) {
|
|
388
|
-
if ('string' !== typeof value
|
|
389
|
-
&& 'symbol' !== typeof value)
|
|
390
|
-
{
|
|
391
|
-
throw new TypeError(
|
|
392
|
-
'Expect event to be a string or symbol.');
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function isFunction(value) {
|
|
397
|
-
return 'function' === typeof value;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function functionTypeGuard(value) {
|
|
401
|
-
if (!isFunction(value)) {
|
|
402
|
-
throw new TypeError(
|
|
403
|
-
'Expect a function.');
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
export { eventful };
|
|
1
|
+
export {
|
|
2
|
+
eventful
|
|
3
|
+
} from './dist/eventful.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asljs-eventful",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Lightweight event helper adding on/off/emit to any object.",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist/**",
|
|
7
|
+
"eventful.js",
|
|
8
|
+
"eventful.d.ts",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE.md"
|
|
11
|
+
],
|
|
5
12
|
"keywords": [
|
|
6
13
|
"events",
|
|
7
14
|
"javascript",
|
|
@@ -20,12 +27,23 @@
|
|
|
20
27
|
"type": "module",
|
|
21
28
|
"main": "eventful.js",
|
|
22
29
|
"types": "eventful.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./eventful.d.ts",
|
|
33
|
+
"default": "./eventful.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
23
36
|
"directories": {
|
|
24
37
|
"doc": "docs"
|
|
25
38
|
},
|
|
26
39
|
"scripts": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
40
|
+
"build": "tsc -p .",
|
|
41
|
+
"build:tests": "tsc -p tsconfig.tests.json",
|
|
42
|
+
"lint": "eslint .",
|
|
43
|
+
"lint:fix": "eslint . --fix",
|
|
44
|
+
"prepack": "npm run build",
|
|
45
|
+
"test": "npm run build && npm run build:tests && node --test .tests/*.test.js",
|
|
46
|
+
"test:watch": "npm run build && npm run build:tests && node --watch --test .tests/*.test.js",
|
|
47
|
+
"coverage": "npm run build && npm run build:tests && NODE_V8_COVERAGE=.coverage node --test .tests/*.test.js && node -e \"console.log('Coverage in .coverage (use c8/istanbul if you want reports)')\""
|
|
30
48
|
}
|
|
31
49
|
}
|
package/tests/eventful.test.js
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { eventful } from '../eventful.js';
|
|
4
|
-
|
|
5
|
-
test(
|
|
6
|
-
'eventful does not create a new object',
|
|
7
|
-
() => {
|
|
8
|
-
const original = { };
|
|
9
|
-
|
|
10
|
-
const enhanced =
|
|
11
|
-
eventful(original);
|
|
12
|
-
|
|
13
|
-
assert.equal(
|
|
14
|
-
enhanced,
|
|
15
|
-
original);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test(
|
|
19
|
-
'eventful extends a function',
|
|
20
|
-
() => {
|
|
21
|
-
const original =
|
|
22
|
-
() => { };
|
|
23
|
-
|
|
24
|
-
const enhanced =
|
|
25
|
-
eventful(original);
|
|
26
|
-
|
|
27
|
-
assert.equal(
|
|
28
|
-
enhanced,
|
|
29
|
-
original);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test(
|
|
33
|
-
'trace is called on creation with action "new"',
|
|
34
|
-
() => {
|
|
35
|
-
const tracer =
|
|
36
|
-
createTracer();
|
|
37
|
-
|
|
38
|
-
const object =
|
|
39
|
-
eventful(
|
|
40
|
-
{ },
|
|
41
|
-
{ trace: tracer.trace });
|
|
42
|
-
|
|
43
|
-
const creation =
|
|
44
|
-
tracer.getFirstTraceByAction('new');
|
|
45
|
-
|
|
46
|
-
assert.ok(creation);
|
|
47
|
-
|
|
48
|
-
assert.equal(
|
|
49
|
-
creation.object,
|
|
50
|
-
object);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test(
|
|
54
|
-
'eventful creates an empty event emitter object',
|
|
55
|
-
() => {
|
|
56
|
-
const obj =
|
|
57
|
-
eventful();
|
|
58
|
-
|
|
59
|
-
assert.ok(obj);
|
|
60
|
-
assert.equal(typeof obj.on, 'function');
|
|
61
|
-
assert.equal(typeof obj.off, 'function');
|
|
62
|
-
assert.equal(typeof obj.emit, 'function');
|
|
63
|
-
assert.equal(typeof obj.emitAsync, 'function');
|
|
64
|
-
assert.equal(typeof obj.has, 'function');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test(
|
|
68
|
-
'eventful throws when an object has one of the event emitter methods',
|
|
69
|
-
() => {
|
|
70
|
-
for (const method of
|
|
71
|
-
[ 'on',
|
|
72
|
-
'once',
|
|
73
|
-
'off',
|
|
74
|
-
'emit',
|
|
75
|
-
'emitAsync',
|
|
76
|
-
'has'])
|
|
77
|
-
{
|
|
78
|
-
assert.throws(
|
|
79
|
-
() => eventful({ [method]: () => { } }));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test(
|
|
84
|
-
'exceptions in listeners are suppressed by default',
|
|
85
|
-
async () => {
|
|
86
|
-
const obj =
|
|
87
|
-
eventful();
|
|
88
|
-
|
|
89
|
-
obj.on(
|
|
90
|
-
'test',
|
|
91
|
-
() => { throw new Error('test error'); });
|
|
92
|
-
|
|
93
|
-
await assert.doesNotReject(
|
|
94
|
-
() => obj.emitAsync('test'));
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test(
|
|
98
|
-
'strict mode propagates exceptions in listeners',
|
|
99
|
-
async () => {
|
|
100
|
-
const obj =
|
|
101
|
-
eventful(
|
|
102
|
-
{ },
|
|
103
|
-
{ error: () => { },
|
|
104
|
-
strict: true });
|
|
105
|
-
|
|
106
|
-
obj.on(
|
|
107
|
-
'test',
|
|
108
|
-
() => { throw new Error('test error'); });
|
|
109
|
-
|
|
110
|
-
await assert.rejects(
|
|
111
|
-
() => obj.emitAsync('test'));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test(
|
|
115
|
-
'async listener rejection is suppressed by default',
|
|
116
|
-
async () => {
|
|
117
|
-
const obj =
|
|
118
|
-
eventful(
|
|
119
|
-
{ },
|
|
120
|
-
{ error: () => {} });
|
|
121
|
-
|
|
122
|
-
obj.on(
|
|
123
|
-
'test',
|
|
124
|
-
async () => { throw new Error('async fail'); });
|
|
125
|
-
|
|
126
|
-
await assert.doesNotReject(
|
|
127
|
-
() => obj.emitAsync('test'));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test(
|
|
131
|
-
'non-strict runs other listeners even if one fails',
|
|
132
|
-
async () => {
|
|
133
|
-
const obj =
|
|
134
|
-
eventful(
|
|
135
|
-
{ },
|
|
136
|
-
{ error: () => {} });
|
|
137
|
-
|
|
138
|
-
let ran = 0;
|
|
139
|
-
|
|
140
|
-
obj.on(
|
|
141
|
-
'test',
|
|
142
|
-
() => { ran += 1; });
|
|
143
|
-
|
|
144
|
-
obj.on(
|
|
145
|
-
'test',
|
|
146
|
-
() => { throw new Error('boom'); });
|
|
147
|
-
|
|
148
|
-
obj.on(
|
|
149
|
-
'test',
|
|
150
|
-
() => { ran += 1; });
|
|
151
|
-
|
|
152
|
-
await assert.doesNotReject(
|
|
153
|
-
() => obj.emitAsync('test'));
|
|
154
|
-
|
|
155
|
-
assert.equal(ran, 2);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test(
|
|
159
|
-
'trace receives safe payload and action names',
|
|
160
|
-
async () => {
|
|
161
|
-
const tracer =
|
|
162
|
-
createTracer();
|
|
163
|
-
|
|
164
|
-
const obj =
|
|
165
|
-
eventful(
|
|
166
|
-
{ },
|
|
167
|
-
{ trace: tracer.trace });
|
|
168
|
-
|
|
169
|
-
const off = obj.on('e', () => {});
|
|
170
|
-
obj.emit('e', 1, 2);
|
|
171
|
-
await obj.emitAsync('e', 3, 4);
|
|
172
|
-
off();
|
|
173
|
-
|
|
174
|
-
// Expect actions at least 'on', 'emit', 'emitAsync'
|
|
175
|
-
assert.ok(tracer.getFirstTraceByAction('on'));
|
|
176
|
-
assert.ok(tracer.getFirstTraceByAction('emit'));
|
|
177
|
-
assert.ok(tracer.getFirstTraceByAction('emitAsync'));
|
|
178
|
-
|
|
179
|
-
const emitTrace =
|
|
180
|
-
tracer.getFirstTraceByAction('emit');
|
|
181
|
-
|
|
182
|
-
assert.ok(Array.isArray(emitTrace.payload.listeners));
|
|
183
|
-
assert.equal(emitTrace.payload.event, 'e');
|
|
184
|
-
assert.deepEqual(emitTrace.payload.args, [1, 2]);
|
|
185
|
-
|
|
186
|
-
const emitAsyncTrace =
|
|
187
|
-
tracer.getFirstTraceByAction('emitAsync');
|
|
188
|
-
|
|
189
|
-
assert.ok(Array.isArray(emitAsyncTrace.payload.listeners));
|
|
190
|
-
assert.equal(emitAsyncTrace.payload.event, 'e');
|
|
191
|
-
assert.deepEqual(emitAsyncTrace.payload.args, [3, 4]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test(
|
|
195
|
-
'error hook runs for async rejection (non-strict)',
|
|
196
|
-
async () => {
|
|
197
|
-
let errors = 0;
|
|
198
|
-
const obj =
|
|
199
|
-
eventful(
|
|
200
|
-
{ },
|
|
201
|
-
{ error: () => { errors += 1; } });
|
|
202
|
-
|
|
203
|
-
obj.on('e', async () => { throw new Error('reject'); });
|
|
204
|
-
|
|
205
|
-
await assert.doesNotReject(() => obj.emitAsync('e'));
|
|
206
|
-
assert.equal(errors, 1);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test(
|
|
210
|
-
'has reflects subscribe and unsubscribe',
|
|
211
|
-
() => {
|
|
212
|
-
const obj =
|
|
213
|
-
eventful();
|
|
214
|
-
|
|
215
|
-
assert.equal(
|
|
216
|
-
obj.has('x'),
|
|
217
|
-
false);
|
|
218
|
-
|
|
219
|
-
const off =
|
|
220
|
-
obj.on(
|
|
221
|
-
'x',
|
|
222
|
-
() => { });
|
|
223
|
-
|
|
224
|
-
assert.equal(
|
|
225
|
-
obj.has('x'),
|
|
226
|
-
true);
|
|
227
|
-
|
|
228
|
-
off();
|
|
229
|
-
|
|
230
|
-
assert.equal(
|
|
231
|
-
obj.has('x'),
|
|
232
|
-
false);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test(
|
|
236
|
-
'strict mode propagates async rejections',
|
|
237
|
-
async () => {
|
|
238
|
-
const obj =
|
|
239
|
-
eventful(
|
|
240
|
-
{ },
|
|
241
|
-
{ error: () => { },
|
|
242
|
-
strict: true });
|
|
243
|
-
|
|
244
|
-
obj.on(
|
|
245
|
-
'e',
|
|
246
|
-
async () => { throw new Error('nope'); });
|
|
247
|
-
|
|
248
|
-
await assert.rejects(() => obj.emitAsync('e'));
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test(
|
|
252
|
-
'emit ignores errors when no error hook (non-strict)',
|
|
253
|
-
() => {
|
|
254
|
-
const obj =
|
|
255
|
-
eventful();
|
|
256
|
-
|
|
257
|
-
obj.on(
|
|
258
|
-
'x',
|
|
259
|
-
() => { throw new Error('boom'); });
|
|
260
|
-
|
|
261
|
-
assert.doesNotThrow(
|
|
262
|
-
() => obj.emit('x'));
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test(
|
|
266
|
-
'event must be string or symbol',
|
|
267
|
-
() => {
|
|
268
|
-
const obj =
|
|
269
|
-
eventful();
|
|
270
|
-
|
|
271
|
-
assert.throws(
|
|
272
|
-
() => obj.on(123, () => {}),
|
|
273
|
-
TypeError);
|
|
274
|
-
|
|
275
|
-
assert.throws(
|
|
276
|
-
() => obj.emit(123),
|
|
277
|
-
TypeError);
|
|
278
|
-
|
|
279
|
-
const s =
|
|
280
|
-
Symbol('e');
|
|
281
|
-
|
|
282
|
-
assert.doesNotThrow(
|
|
283
|
-
() => obj.on(s, () => {}));
|
|
284
|
-
|
|
285
|
-
assert.doesNotThrow(
|
|
286
|
-
() => obj.emit(s));
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
function createTracer() {
|
|
290
|
-
const traces = [];
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
trace:
|
|
294
|
-
(object, action, payload) =>
|
|
295
|
-
traces.push({ object, action, payload }),
|
|
296
|
-
getTraces:
|
|
297
|
-
() => traces,
|
|
298
|
-
getTracesByAction:
|
|
299
|
-
action =>
|
|
300
|
-
traces.filter(t => t.action === action),
|
|
301
|
-
getFirstTraceByAction:
|
|
302
|
-
action =>
|
|
303
|
-
traces.find(t => t.action === action)
|
|
304
|
-
};
|
|
305
|
-
}
|